diff --git a/crates/puffin-cli/src/commands/pip_compile.rs b/crates/puffin-cli/src/commands/pip_compile.rs index 3ebb7c1665ba..6966ccbcf2c0 100644 --- a/crates/puffin-cli/src/commands/pip_compile.rs +++ b/crates/puffin-cli/src/commands/pip_compile.rs @@ -36,6 +36,7 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); pub(crate) async fn pip_compile( requirements: &[RequirementsSource], constraints: &[RequirementsSource], + overrides: &[RequirementsSource], extras: ExtrasSpecification<'_>, output_file: Option<&Path>, resolution_mode: ResolutionMode, @@ -76,8 +77,9 @@ pub(crate) async fn pip_compile( project, requirements, constraints, + overrides, extras: used_extras, - } = RequirementsSpecification::from_sources(requirements, constraints, &extras)?; + } = RequirementsSpecification::from_sources(requirements, constraints, overrides, &extras)?; // Check that all provided extras are used if let ExtrasSpecification::Some(extras) = extras { @@ -108,7 +110,7 @@ pub(crate) async fn pip_compile( .unwrap_or_default(); // Create a manifest of the requirements. - let manifest = Manifest::new(requirements, constraints, preferences, project); + let manifest = Manifest::new(requirements, constraints, overrides, preferences, project); let options = ResolutionOptions::new(resolution_mode, prerelease_mode, exclude_newer); // Detect the current Python interpreter. diff --git a/crates/puffin-cli/src/commands/pip_install.rs b/crates/puffin-cli/src/commands/pip_install.rs index c8af0a6de6de..58d78c4f4f7a 100644 --- a/crates/puffin-cli/src/commands/pip_install.rs +++ b/crates/puffin-cli/src/commands/pip_install.rs @@ -33,6 +33,7 @@ use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsS pub(crate) async fn pip_install( requirements: &[RequirementsSource], constraints: &[RequirementsSource], + overrides: &[RequirementsSource], extras: &ExtrasSpecification<'_>, resolution_mode: ResolutionMode, prerelease_mode: PreReleaseMode, @@ -57,7 +58,7 @@ pub(crate) async fn pip_install( let start = std::time::Instant::now(); // Determine the requirements. - let spec = specification(requirements, constraints, extras)?; + let spec = specification(requirements, constraints, overrides, extras)?; // Detect the current Python interpreter. let platform = Platform::current()?; @@ -123,6 +124,7 @@ pub(crate) async fn pip_install( fn specification( requirements: &[RequirementsSource], constraints: &[RequirementsSource], + overrides: &[RequirementsSource], extras: &ExtrasSpecification<'_>, ) -> Result { // If the user requests `extras` but does not provide a pyproject toml source @@ -137,7 +139,8 @@ fn specification( } // Read all requirements from the provided sources. - let spec = RequirementsSpecification::from_sources(requirements, constraints, extras)?; + let spec = + RequirementsSpecification::from_sources(requirements, constraints, overrides, extras)?; // Check that all provided extras are used if let ExtrasSpecification::Some(extras) = extras { @@ -185,6 +188,7 @@ async fn resolve( project, requirements, constraints, + overrides, extras: _, } = spec; @@ -200,7 +204,7 @@ async fn resolve( .collect(), }; - let manifest = Manifest::new(requirements, constraints, preferences, project); + let manifest = Manifest::new(requirements, constraints, overrides, preferences, project); let options = ResolutionOptions::new(resolution_mode, prerelease_mode, exclude_newer); debug!( diff --git a/crates/puffin-cli/src/main.rs b/crates/puffin-cli/src/main.rs index 00b6557d1090..44cdfac32e26 100644 --- a/crates/puffin-cli/src/main.rs +++ b/crates/puffin-cli/src/main.rs @@ -107,10 +107,28 @@ struct PipCompileArgs { #[clap(required(true))] src_file: Vec, - /// Constrain versions using the given constraints files. + /// Constrain versions using the given requirements files. + /// + /// Constraints files are `requirements.txt`-like files that only control the _version_ of a + /// requirement that's installed. However, including a package in a constraints file will _not_ + /// trigger the installation of that package. + /// + /// This is equivalent to pip's `--constraint` option. #[clap(short, long)] constraint: Vec, + /// Override versions using the given requirements files. + /// + /// Overrides files are `requirements.txt`-like files that force a specific version of a + /// requirement to be installed, regardless of the requirements declared by any constituent + /// package, and regardless of whether this would be considered an invalid resolution. + /// + /// While constraints are _additive_, in that they're combined with the requirements of the + /// constituent packages, overrides are _absolute_, in that they completely replace the + /// requirements of the constituent packages. + #[clap(long)] + r#override: Vec, + /// Include optional dependencies in the given extra group name; may be provided more than once. #[clap(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] extra: Vec, @@ -228,10 +246,28 @@ struct PipInstallArgs { #[clap(short, long, group = "sources")] requirement: Vec, - /// Constrain versions using the given constraints files. + /// Constrain versions using the given requirements files. + /// + /// Constraints files are `requirements.txt`-like files that only control the _version_ of a + /// requirement that's installed. However, including a package in a constraints file will _not_ + /// trigger the installation of that package. + /// + /// This is equivalent to pip's `--constraint` option. #[clap(short, long)] constraint: Vec, + /// Override versions using the given requirements files. + /// + /// Overrides files are `requirements.txt`-like files that force a specific version of a + /// requirement to be installed, regardless of the requirements declared by any constituent + /// package, and regardless of whether this would be considered an invalid resolution. + /// + /// While constraints are _additive_, in that they're combined with the requirements of the + /// constituent packages, overrides are _absolute_, in that they completely replace the + /// requirements of the constituent packages. + #[clap(long)] + r#override: Vec, + /// Include optional dependencies in the given extra group name; may be provided more than once. #[clap(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] extra: Vec, @@ -383,6 +419,11 @@ async fn inner() -> Result { .into_iter() .map(RequirementsSource::from) .collect::>(); + let overrides = args + .r#override + .into_iter() + .map(RequirementsSource::from) + .collect::>(); let index_urls = IndexUrls::from_args(args.index_url, args.extra_index_url, args.no_index); let extras = if args.all_extras { @@ -395,6 +436,7 @@ async fn inner() -> Result { commands::pip_compile( &requirements, &constraints, + &overrides, extras, args.output_file.as_deref(), args.resolution.unwrap_or_default(), @@ -441,6 +483,11 @@ async fn inner() -> Result { .into_iter() .map(RequirementsSource::from) .collect::>(); + let overrides = args + .r#override + .into_iter() + .map(RequirementsSource::from) + .collect::>(); let index_urls = IndexUrls::from_args(args.index_url, args.extra_index_url, args.no_index); let extras = if args.all_extras { @@ -454,6 +501,7 @@ async fn inner() -> Result { commands::pip_install( &requirements, &constraints, + &overrides, &extras, args.resolution.unwrap_or_default(), args.prerelease.unwrap_or_default(), diff --git a/crates/puffin-cli/src/requirements.rs b/crates/puffin-cli/src/requirements.rs index 9465c2e1365a..b4e61a939c3d 100644 --- a/crates/puffin-cli/src/requirements.rs +++ b/crates/puffin-cli/src/requirements.rs @@ -64,6 +64,8 @@ pub(crate) struct RequirementsSpecification { pub(crate) requirements: Vec, /// The constraints for the project. pub(crate) constraints: Vec, + /// The overrides for the project. + pub(crate) overrides: Vec, /// The extras used to collect requirements. pub(crate) extras: FxHashSet, } @@ -82,6 +84,7 @@ impl RequirementsSpecification { project: None, requirements: vec![requirement], constraints: vec![], + overrides: vec![], extras: FxHashSet::default(), } } @@ -95,6 +98,7 @@ impl RequirementsSpecification { .map(|entry| entry.requirement) .collect(), constraints: requirements_txt.constraints.into_iter().collect(), + overrides: vec![], extras: FxHashSet::default(), } } @@ -131,6 +135,7 @@ impl RequirementsSpecification { project: project_name, requirements, constraints: vec![], + overrides: vec![], extras: used_extras, } } @@ -141,6 +146,7 @@ impl RequirementsSpecification { pub(crate) fn from_sources( requirements: &[RequirementsSource], constraints: &[RequirementsSource], + overrides: &[RequirementsSource], extras: &ExtrasSpecification, ) -> Result { let mut spec = Self::default(); @@ -152,6 +158,7 @@ impl RequirementsSpecification { let source = Self::from_source(source, extras)?; spec.requirements.extend(source.requirements); spec.constraints.extend(source.constraints); + spec.overrides.extend(source.overrides); spec.extras.extend(source.extras); // Use the first project name discovered @@ -160,11 +167,20 @@ impl RequirementsSpecification { } } - // Read all constraints, treating both requirements _and_ constraints as constraints. + // Read all constraints, treating _everything_ as a constraint. for source in constraints { let source = Self::from_source(source, extras)?; spec.constraints.extend(source.requirements); spec.constraints.extend(source.constraints); + spec.constraints.extend(source.overrides); + } + + // Read all overrides, treating both requirements _and_ constraints as overrides. + for source in overrides { + let source = Self::from_source(source, extras)?; + spec.overrides.extend(source.requirements); + spec.overrides.extend(source.constraints); + spec.overrides.extend(source.overrides); } Ok(spec) @@ -172,6 +188,6 @@ impl RequirementsSpecification { /// Read the requirements from a set of sources. pub(crate) fn requirements(requirements: &[RequirementsSource]) -> Result> { - Ok(Self::from_sources(requirements, &[], &ExtrasSpecification::None)?.requirements) + Ok(Self::from_sources(requirements, &[], &[], &ExtrasSpecification::None)?.requirements) } } diff --git a/crates/puffin-cli/tests/pip_compile.rs b/crates/puffin-cli/tests/pip_compile.rs index 9d36dd4dd6b0..f1ff99a3e5fa 100644 --- a/crates/puffin-cli/tests/pip_compile.rs +++ b/crates/puffin-cli/tests/pip_compile.rs @@ -1606,3 +1606,117 @@ fn compile_yanked_version_indirect() -> Result<()> { Ok(()) } + +/// Flask==3.0.0 depends on Werkzeug>=3.0.0. Demonstrate that we can override this +/// requirement with an incompatible version. +#[test] +fn override_dependency() -> Result<()> { + let temp_dir = TempDir::new()?; + let cache_dir = TempDir::new()?; + let venv = create_venv_py312(&temp_dir, &cache_dir); + + let requirements_in = temp_dir.child("requirements.in"); + requirements_in.write_str("flask==3.0.0")?; + + let overrides_txt = temp_dir.child("overrides.txt"); + overrides_txt.write_str("werkzeug==2.3.0")?; + + insta::with_settings!({ + filters => INSTA_FILTERS.to_vec() + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("requirements.in") + .arg("--override") + .arg("overrides.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .arg("--exclude-newer") + .arg(EXCLUDE_NEWER) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by Puffin v0.0.1 via the following command: + # puffin pip-compile requirements.in --override overrides.txt --cache-dir [CACHE_DIR] + blinker==1.7.0 + # via flask + click==8.1.7 + # via flask + flask==3.0.0 + itsdangerous==2.1.2 + # via flask + jinja2==3.1.2 + # via flask + markupsafe==2.1.3 + # via + # jinja2 + # werkzeug + werkzeug==2.3.0 + # via flask + + ----- stderr ----- + Resolved 7 packages in [TIME] + "###); + }); + + Ok(()) +} + +/// Black==23.10.1 depends on tomli>=1.1.0 for Python versions below 3.11. Demonstrate that we can +/// override it with a multi-line override. +#[test] +fn override_multi_dependency() -> Result<()> { + let temp_dir = TempDir::new()?; + let cache_dir = TempDir::new()?; + let venv = create_venv_py312(&temp_dir, &cache_dir); + + let requirements_in = temp_dir.child("requirements.in"); + requirements_in.write_str("black==23.10.1")?; + + let overrides_txt = temp_dir.child("overrides.txt"); + overrides_txt.write_str( + "tomli>=1.1.0; python_version >= '3.11'\ntomli<1.0.0; python_version < '3.11'", + )?; + + insta::with_settings!({ + filters => INSTA_FILTERS.to_vec() + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("requirements.in") + .arg("--override") + .arg("overrides.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .arg("--exclude-newer") + .arg(EXCLUDE_NEWER) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by Puffin v0.0.1 via the following command: + # puffin pip-compile requirements.in --override overrides.txt --cache-dir [CACHE_DIR] + black==23.10.1 + click==8.1.7 + # via black + mypy-extensions==1.0.0 + # via black + packaging==23.2 + # via black + pathspec==0.11.2 + # via black + platformdirs==4.0.0 + # via black + tomli==2.0.1 + # via black + + ----- stderr ----- + Resolved 7 packages in [TIME] + "###); + }); + + Ok(()) +} diff --git a/crates/puffin-dev/src/resolve_cli.rs b/crates/puffin-dev/src/resolve_cli.rs index 20462c6dc91d..88f068764db5 100644 --- a/crates/puffin-dev/src/resolve_cli.rs +++ b/crates/puffin-dev/src/resolve_cli.rs @@ -60,12 +60,7 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> { // Copied from `BuildDispatch` let tags = Tags::from_interpreter(venv.interpreter())?; let resolver = Resolver::new( - Manifest::new( - args.requirements.clone(), - Vec::default(), - Vec::default(), - None, - ), + Manifest::simple(args.requirements.clone()), ResolutionOptions::default(), venv.interpreter().markers(), &tags, diff --git a/crates/puffin-dispatch/src/lib.rs b/crates/puffin-dispatch/src/lib.rs index 69ff825c8fba..b2ccc3ed1d55 100644 --- a/crates/puffin-dispatch/src/lib.rs +++ b/crates/puffin-dispatch/src/lib.rs @@ -92,7 +92,7 @@ impl BuildContext for BuildDispatch { Box::pin(async { let tags = Tags::from_interpreter(&self.interpreter)?; let resolver = Resolver::new( - Manifest::new(requirements.to_vec(), Vec::default(), Vec::default(), None), + Manifest::simple(requirements.to_vec()), self.options, self.interpreter.markers(), &tags, diff --git a/crates/puffin-resolver/src/lib.rs b/crates/puffin-resolver/src/lib.rs index af3845384047..8bd3096b1c41 100644 --- a/crates/puffin-resolver/src/lib.rs +++ b/crates/puffin-resolver/src/lib.rs @@ -15,6 +15,7 @@ mod error; mod file; mod finder; mod manifest; +mod overrides; mod pins; mod prerelease_mode; mod pubgrub; diff --git a/crates/puffin-resolver/src/manifest.rs b/crates/puffin-resolver/src/manifest.rs index 6767d08cd9d5..08cdc6969d1c 100644 --- a/crates/puffin-resolver/src/manifest.rs +++ b/crates/puffin-resolver/src/manifest.rs @@ -6,6 +6,7 @@ use puffin_normalize::PackageName; pub struct Manifest { pub(crate) requirements: Vec, pub(crate) constraints: Vec, + pub(crate) overrides: Vec, pub(crate) preferences: Vec, pub(crate) project: Option, } @@ -14,14 +15,26 @@ impl Manifest { pub fn new( requirements: Vec, constraints: Vec, + overrides: Vec, preferences: Vec, project: Option, ) -> Self { Self { requirements, constraints, + overrides, preferences, project, } } + + pub fn simple(requirements: Vec) -> Self { + Self { + requirements, + constraints: Vec::new(), + overrides: Vec::new(), + preferences: Vec::new(), + project: None, + } + } } diff --git a/crates/puffin-resolver/src/overrides.rs b/crates/puffin-resolver/src/overrides.rs new file mode 100644 index 000000000000..885bec91f646 --- /dev/null +++ b/crates/puffin-resolver/src/overrides.rs @@ -0,0 +1,45 @@ +use itertools::Either; +use std::hash::BuildHasherDefault; + +use rustc_hash::FxHashMap; + +use pep508_rs::Requirement; +use puffin_normalize::PackageName; + +/// A set of overrides for a set of requirements. +#[derive(Debug, Default, Clone)] +pub(crate) struct Overrides(FxHashMap>); + +impl Overrides { + /// Create a new set of overrides from a set of requirements. + pub(crate) fn from_requirements(requirements: Vec) -> Self { + let mut overrides: FxHashMap> = + FxHashMap::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default()); + for requirement in requirements { + overrides + .entry(requirement.name.clone()) + .or_default() + .push(requirement); + } + Self(overrides) + } + + /// Get the overrides for a package. + pub(crate) fn get(&self, name: &PackageName) -> Option<&Vec> { + self.0.get(name) + } + + /// Apply the overrides to a set of requirements. + pub(crate) fn apply<'a>( + &'a self, + requirements: &'a [Requirement], + ) -> impl Iterator { + requirements.iter().flat_map(|requirement| { + if let Some(overrides) = self.get(&requirement.name) { + Either::Left(overrides.iter()) + } else { + Either::Right(std::iter::once(requirement)) + } + }) + } +} diff --git a/crates/puffin-resolver/src/pubgrub/dependencies.rs b/crates/puffin-resolver/src/pubgrub/dependencies.rs index e56e98ba1172..201142046d94 100644 --- a/crates/puffin-resolver/src/pubgrub/dependencies.rs +++ b/crates/puffin-resolver/src/pubgrub/dependencies.rs @@ -7,6 +7,7 @@ use pep508_rs::{MarkerEnvironment, Requirement, VersionOrUrl}; use puffin_cache::CanonicalUrl; use puffin_normalize::{ExtraName, PackageName}; +use crate::overrides::Overrides; use crate::pubgrub::specifier::PubGrubSpecifier; use crate::pubgrub::{PubGrubPackage, PubGrubVersion}; use crate::ResolveError; @@ -19,6 +20,7 @@ impl PubGrubDependencies { pub(crate) fn from_requirements( requirements: &[Requirement], constraints: &[Requirement], + overrides: &Overrides, extra: Option<&ExtraName>, source: Option<&PackageName>, env: &MarkerEnvironment, @@ -27,7 +29,7 @@ impl PubGrubDependencies { DependencyConstraints::>::default(); // Iterate over all declared requirements. - for requirement in requirements { + for requirement in overrides.apply(requirements) { // Avoid self-dependencies. if source.is_some_and(|source| source == &requirement.name) { warn!("{} has a dependency on itself", requirement.name); @@ -75,6 +77,11 @@ impl PubGrubDependencies { // If any requirements were further constrained by the user, add those constraints. for constraint in constraints { + // If a requirement was overridden, skip it. + if overrides.get(&constraint.name).is_some() { + continue; + } + // Avoid self-dependencies. if source.is_some_and(|source| source == &constraint.name) { warn!("{} has a dependency on itself", constraint.name); diff --git a/crates/puffin-resolver/src/resolver.rs b/crates/puffin-resolver/src/resolver.rs index b1d99b4e0ae2..cdac1e712b29 100644 --- a/crates/puffin-resolver/src/resolver.rs +++ b/crates/puffin-resolver/src/resolver.rs @@ -31,6 +31,7 @@ use pypi_types::{IndexUrl, Metadata21}; use crate::candidate_selector::CandidateSelector; use crate::error::ResolveError; use crate::manifest::Manifest; +use crate::overrides::Overrides; use crate::pins::FilePins; use crate::pubgrub::{ PubGrubDependencies, PubGrubPackage, PubGrubPriorities, PubGrubVersion, MIN_VERSION, @@ -147,6 +148,7 @@ pub struct Resolver<'a, Provider: ResolverProvider> { project: Option, requirements: Vec, constraints: Vec, + overrides: Overrides, allowed_urls: AllowedUrls, markers: &'a MarkerEnvironment, selector: CandidateSelector, @@ -197,6 +199,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> { .requirements .iter() .chain(manifest.constraints.iter()) + .chain(manifest.overrides.iter()) .filter_map(|req| { if let Some(pep508_rs::VersionOrUrl::Url(url)) = &req.version_or_url { Some(url) @@ -208,6 +211,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> { project: manifest.project, requirements: manifest.requirements, constraints: manifest.constraints, + overrides: Overrides::from_requirements(manifest.overrides), markers, reporter: None, provider, @@ -586,6 +590,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> { let constraints = PubGrubDependencies::from_requirements( &self.requirements, &self.constraints, + &self.overrides, None, None, self.markers, @@ -621,6 +626,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> { let mut constraints = PubGrubDependencies::from_requirements( &metadata.requires_dist, &self.constraints, + &self.overrides, extra.as_ref(), Some(package_name), self.markers, diff --git a/crates/puffin-resolver/tests/resolver.rs b/crates/puffin-resolver/tests/resolver.rs index 096d96d887ca..fd830824a1ed 100644 --- a/crates/puffin-resolver/tests/resolver.rs +++ b/crates/puffin-resolver/tests/resolver.rs @@ -121,6 +121,7 @@ async fn black() -> Result<()> { vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -144,6 +145,7 @@ async fn black_colorama() -> Result<()> { vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -167,6 +169,7 @@ async fn black_python_310() -> Result<()> { vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -192,6 +195,7 @@ async fn black_mypy_extensions() -> Result<()> { vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![Requirement::from_str("mypy-extensions<0.4.4").unwrap()], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -217,6 +221,7 @@ async fn black_mypy_extensions_extra() -> Result<()> { vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![Requirement::from_str("mypy-extensions[extra]<0.4.4").unwrap()], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -242,6 +247,7 @@ async fn black_flake8() -> Result<()> { vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![Requirement::from_str("flake8<1").unwrap()], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -265,6 +271,7 @@ async fn black_lowest() -> Result<()> { vec![Requirement::from_str("black>21").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -288,6 +295,7 @@ async fn black_lowest_direct() -> Result<()> { vec![Requirement::from_str("black>21").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -310,6 +318,7 @@ async fn black_respect_preference() -> Result<()> { let manifest = Manifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![], + vec![], vec![Requirement::from_str("black==23.9.0").unwrap()], None, ); @@ -333,6 +342,7 @@ async fn black_ignore_preference() -> Result<()> { let manifest = Manifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![], + vec![], vec![Requirement::from_str("black==23.9.2").unwrap()], None, ); @@ -357,6 +367,7 @@ async fn black_disallow_prerelease() -> Result<()> { vec![Requirement::from_str("black<=20.0").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -382,6 +393,7 @@ async fn black_allow_prerelease_if_necessary() -> Result<()> { vec![Requirement::from_str("black<=20.0").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -407,6 +419,7 @@ async fn pylint_disallow_prerelease() -> Result<()> { vec![Requirement::from_str("pylint==2.3.0").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -430,6 +443,7 @@ async fn pylint_allow_prerelease() -> Result<()> { vec![Requirement::from_str("pylint==2.3.0").unwrap()], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -456,6 +470,7 @@ async fn pylint_allow_explicit_prerelease_without_marker() -> Result<()> { ], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new( @@ -482,6 +497,7 @@ async fn pylint_allow_explicit_prerelease_with_marker() -> Result<()> { ], vec![], vec![], + vec![], None, ); let options = ResolutionOptions::new(