From 8b8338576322202c229a96e71a1a4f5faf9190c3 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 26 Oct 2023 17:41:02 -0700 Subject: [PATCH] Support constraints in `requirements.in` files (#212) Closes #172. --- crates/puffin-cli/src/commands/pip_compile.rs | 24 +- crates/puffin-cli/src/commands/pip_sync.rs | 11 +- .../puffin-cli/src/commands/pip_uninstall.rs | 13 +- crates/puffin-cli/src/requirements.rs | 109 +++++---- crates/puffin-cli/tests/pip_compile.rs | 229 ++++++++++++++++++ ...p_compile__compile_constraints_inline.snap | 21 ++ .../pip_compile__compile_constraints_txt.snap | 28 +++ .../pip_compile__compile_pyproject_toml.snap | 26 ++ .../pip_compile__compile_requirements_in.snap | 26 ++ .../pip_compile__missing_requirements_in.snap | 18 ++ .../snapshots/pip_compile__missing_venv.snap | 20 ++ crates/puffin-dispatch/src/lib.rs | 4 +- crates/puffin-package/src/requirements_txt.rs | 4 +- crates/puffin-resolver/src/lib.rs | 2 +- crates/puffin-resolver/src/resolver.rs | 6 +- crates/puffin-resolver/tests/resolver.rs | 24 +- 16 files changed, 476 insertions(+), 89 deletions(-) create mode 100644 crates/puffin-cli/tests/pip_compile.rs create mode 100644 crates/puffin-cli/tests/snapshots/pip_compile__compile_constraints_inline.snap create mode 100644 crates/puffin-cli/tests/snapshots/pip_compile__compile_constraints_txt.snap create mode 100644 crates/puffin-cli/tests/snapshots/pip_compile__compile_pyproject_toml.snap create mode 100644 crates/puffin-cli/tests/snapshots/pip_compile__compile_requirements_in.snap create mode 100644 crates/puffin-cli/tests/snapshots/pip_compile__missing_requirements_in.snap create mode 100644 crates/puffin-cli/tests/snapshots/pip_compile__missing_venv.snap diff --git a/crates/puffin-cli/src/commands/pip_compile.rs b/crates/puffin-cli/src/commands/pip_compile.rs index 415ebf39a..ed4d3c96c 100644 --- a/crates/puffin-cli/src/commands/pip_compile.rs +++ b/crates/puffin-cli/src/commands/pip_compile.rs @@ -16,12 +16,12 @@ use platform_tags::Tags; use puffin_client::RegistryClientBuilder; use puffin_dispatch::BuildDispatch; use puffin_interpreter::Virtualenv; -use puffin_resolver::{Manifest, ResolutionMode}; +use puffin_resolver::{ResolutionManifest, ResolutionMode}; use crate::commands::{elapsed, ExitStatus}; use crate::index_urls::IndexUrls; use crate::printer::Printer; -use crate::requirements::RequirementsSource; +use crate::requirements::{RequirementsSource, RequirementsSpecification}; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -40,29 +40,23 @@ pub(crate) async fn pip_compile( let start = std::time::Instant::now(); // Read all requirements from the provided sources. - let requirements = requirements - .iter() - .map(RequirementsSource::requirements) - .flatten_ok() - .collect::>>()?; - let constraints = constraints - .iter() - .map(RequirementsSource::requirements) - .flatten_ok() - .collect::>>()?; + let RequirementsSpecification { + requirements, + constraints, + } = RequirementsSpecification::try_from_sources(requirements, constraints)?; let preferences: Vec = output_file .filter(|_| upgrade_mode.is_prefer_pinned()) .filter(|output_file| output_file.exists()) .map(Path::to_path_buf) .map(RequirementsSource::from) .as_ref() - .map(RequirementsSource::requirements) + .map(RequirementsSpecification::try_from_source) .transpose()? - .map(Iterator::collect) + .map(|spec| spec.requirements) .unwrap_or_default(); // Create a manifest of the requirements. - let manifest = Manifest::new(requirements, constraints, preferences, resolution_mode); + let manifest = ResolutionManifest::new(requirements, constraints, preferences, resolution_mode); // Detect the current Python interpreter. let platform = Platform::current()?; diff --git a/crates/puffin-cli/src/commands/pip_sync.rs b/crates/puffin-cli/src/commands/pip_sync.rs index 40bf14d1c..acdbe6ecf 100644 --- a/crates/puffin-cli/src/commands/pip_sync.rs +++ b/crates/puffin-cli/src/commands/pip_sync.rs @@ -20,7 +20,7 @@ use crate::commands::reporters::{ use crate::commands::{elapsed, ExitStatus}; use crate::index_urls::IndexUrls; use crate::printer::Printer; -use crate::requirements::RequirementsSource; +use crate::requirements::{RequirementsSource, RequirementsSpecification}; /// Install a set of locked requirements into the current Python environment. pub(crate) async fn pip_sync( @@ -31,11 +31,10 @@ pub(crate) async fn pip_sync( mut printer: Printer, ) -> Result { // Read all requirements from the provided sources. - let requirements = sources - .iter() - .map(RequirementsSource::requirements) - .flatten_ok() - .collect::>>()?; + let RequirementsSpecification { + requirements, + constraints: _, + } = RequirementsSpecification::try_from_sources(sources, &[])?; if requirements.is_empty() { writeln!(printer, "No requirements found")?; diff --git a/crates/puffin-cli/src/commands/pip_uninstall.rs b/crates/puffin-cli/src/commands/pip_uninstall.rs index 677c01fb6..a605d0bdc 100644 --- a/crates/puffin-cli/src/commands/pip_uninstall.rs +++ b/crates/puffin-cli/src/commands/pip_uninstall.rs @@ -3,17 +3,15 @@ use std::path::Path; use anyhow::Result; use colored::Colorize; -use itertools::Itertools; use tracing::debug; -use pep508_rs::Requirement; use platform_host::Platform; use puffin_interpreter::Virtualenv; use puffin_package::package_name::PackageName; use crate::commands::{elapsed, ExitStatus}; use crate::printer::Printer; -use crate::requirements::RequirementsSource; +use crate::requirements::{RequirementsSource, RequirementsSpecification}; /// Uninstall packages from the current environment. pub(crate) async fn pip_uninstall( @@ -24,11 +22,10 @@ pub(crate) async fn pip_uninstall( let start = std::time::Instant::now(); // Read all requirements from the provided sources. - let requirements = sources - .iter() - .map(RequirementsSource::requirements) - .flatten_ok() - .collect::>>()?; + let RequirementsSpecification { + requirements, + constraints: _, + } = RequirementsSpecification::try_from_sources(sources, &[])?; // Detect the current Python interpreter. let platform = Platform::current()?; diff --git a/crates/puffin-cli/src/requirements.rs b/crates/puffin-cli/src/requirements.rs index 8a5a06ea2..bfe013acc 100644 --- a/crates/puffin-cli/src/requirements.rs +++ b/crates/puffin-cli/src/requirements.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use std::str::FromStr; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use fs_err as fs; use pep508_rs::Requirement; @@ -35,50 +35,77 @@ impl From for RequirementsSource { } } -impl RequirementsSource { - /// Return an iterator over the requirements in this source. - pub(crate) fn requirements(&self) -> Result> { - let iter_name = if let Self::Name(name) = self { - let requirement = - Requirement::from_str(name).with_context(|| format!("Failed to parse `{name}`"))?; - Some(std::iter::once(requirement)) - } else { - None - }; +#[derive(Debug, Default)] +pub(crate) struct RequirementsSpecification { + /// The requirements for the project. + pub(crate) requirements: Vec, + /// The constraints for the project. + pub(crate) constraints: Vec, +} - let iter_requirements_txt = if let Self::RequirementsTxt(path) = self { - let requirements_txt = RequirementsTxt::parse(path, std::env::current_dir()?)?; - if !requirements_txt.constraints.is_empty() { - bail!("Constraints in requirements files are not supported"); +impl RequirementsSpecification { + /// Read the requirements and constraints from a source. + pub(crate) fn try_from_source(source: &RequirementsSource) -> Result { + Ok(match source { + RequirementsSource::Name(name) => { + let requirement = Requirement::from_str(name) + .with_context(|| format!("Failed to parse `{name}`"))?; + Self { + requirements: vec![requirement], + constraints: vec![], + } } - Some( - requirements_txt - .requirements - .into_iter() - .map(|entry| entry.requirement), - ) - } else { - None - }; - - let iter_pyproject_toml = if let Self::PyprojectToml(path) = self { - let contents = fs::read_to_string(path)?; - let pyproject_toml = toml::from_str::(&contents) - .with_context(|| format!("Failed to read `{}`", path.display()))?; - Some( - pyproject_toml + RequirementsSource::RequirementsTxt(path) => { + let requirements_txt = RequirementsTxt::parse(path, std::env::current_dir()?)?; + Self { + requirements: requirements_txt + .requirements + .into_iter() + .map(|entry| entry.requirement) + .collect(), + constraints: requirements_txt.constraints.into_iter().collect(), + } + } + RequirementsSource::PyprojectToml(path) => { + let contents = fs::read_to_string(path)?; + let pyproject_toml = toml::from_str::(&contents) + .with_context(|| format!("Failed to read `{}`", path.display()))?; + let requirements = pyproject_toml .project .into_iter() - .flat_map(|project| project.dependencies.into_iter().flatten()), - ) - } else { - None - }; + .flat_map(|project| project.dependencies.into_iter().flatten()) + .collect(); + Self { + requirements, + constraints: vec![], + } + } + }) + } - Ok(iter_name - .into_iter() - .flatten() - .chain(iter_requirements_txt.into_iter().flatten()) - .chain(iter_pyproject_toml.into_iter().flatten())) + /// Read the combined requirements and constraints from a set of sources. + pub(crate) fn try_from_sources( + requirements: &[RequirementsSource], + constraints: &[RequirementsSource], + ) -> Result { + let mut spec = Self::default(); + + // Read all requirements, and keep track of all requirements _and_ constraints. + // A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading + // a requirements file can also add constraints. + for source in requirements { + let source = Self::try_from_source(source)?; + spec.requirements.extend(source.requirements); + spec.constraints.extend(source.constraints); + } + + // Read all constraints, treating both requirements _and_ constraints as constraints. + for source in constraints { + let source = Self::try_from_source(source)?; + spec.constraints.extend(source.requirements); + spec.constraints.extend(source.constraints); + } + + Ok(spec) } } diff --git a/crates/puffin-cli/tests/pip_compile.rs b/crates/puffin-cli/tests/pip_compile.rs new file mode 100644 index 000000000..e7da7943e --- /dev/null +++ b/crates/puffin-cli/tests/pip_compile.rs @@ -0,0 +1,229 @@ +#![cfg(all(feature = "python", feature = "pypi"))] + +use std::process::Command; + +use anyhow::Result; +use assert_cmd::prelude::*; +use assert_fs::prelude::*; +use insta_cmd::_macro_support::insta; +use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; + +const BIN_NAME: &str = "puffin"; + +#[test] +fn missing_requirements_in() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let requirements_in = temp_dir.child("requirements.in"); + + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("requirements.in") + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir)); + + requirements_in.assert(predicates::path::missing()); + + Ok(()) +} + +#[test] +fn missing_venv() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("requirements.in") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + + venv.assert(predicates::path::missing()); + + Ok(()) +} + +/// Resolve a specific version of Django from a `requirements.in` file. +#[test] +fn compile_requirements_in() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_in = temp_dir.child("requirements.in"); + requirements_in.touch()?; + requirements_in.write_str("django==5.0b1")?; + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + (r"# .* pip-compile", "# [BIN_PATH] pip-compile"), + (r"--cache-dir .*", "--cache-dir [CACHE_DIR]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("requirements.in") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Ok(()) +} + +/// Resolve a specific version of Django from a `pyproject.toml` file. +#[test] +fn compile_pyproject_toml() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let pyproject_toml = temp_dir.child("pyproject.toml"); + pyproject_toml.touch()?; + pyproject_toml.write_str( + r#"[build-system] +requires = ["setuptools", "wheel"] + +[project] +name = "project" +dependencies = [ + "django==5.0b1", +] +"#, + )?; + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + (r"# .* pip-compile", "# [BIN_PATH] pip-compile"), + (r"--cache-dir .*", "--cache-dir [CACHE_DIR]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("pyproject.toml") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Ok(()) +} + +/// Resolve a package from a `requirements.in` file, with a `constraints.txt` file. +#[test] +fn compile_constraints_txt() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_in = temp_dir.child("requirements.in"); + requirements_in.touch()?; + requirements_in.write_str("django==5.0b1")?; + + let constraints_txt = temp_dir.child("constraints.txt"); + constraints_txt.touch()?; + constraints_txt.write_str("sqlparse<0.4.4")?; + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + (r"# .* pip-compile", "# [BIN_PATH] pip-compile"), + (r"--cache-dir .*", "--cache-dir [CACHE_DIR]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("requirements.in") + .arg("--constraint") + .arg("constraints.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Ok(()) +} + +/// Resolve a package from a `requirements.in` file, with an inline constraint. +#[test] +fn compile_constraints_inline() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_in = temp_dir.child("requirements.in"); + requirements_in.touch()?; + requirements_in.write_str("django==5.0b1")?; + requirements_in.write_str("-c constraints.txt")?; + + let constraints_txt = temp_dir.child("constraints.txt"); + constraints_txt.touch()?; + constraints_txt.write_str("sqlparse<0.4.4")?; + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + (r"# .* pip-compile", "# [BIN_PATH] pip-compile"), + (r"--cache-dir .*", "--cache-dir [CACHE_DIR]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-compile") + .arg("requirements.in") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Ok(()) +} diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__compile_constraints_inline.snap b/crates/puffin-cli/tests/snapshots/pip_compile__compile_constraints_inline.snap new file mode 100644 index 000000000..7b91e6730 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_compile__compile_constraints_inline.snap @@ -0,0 +1,21 @@ +--- +source: crates/puffin-cli/tests/pip_compile.rs +info: + program: puffin + args: + - pip-compile + - requirements.in + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp5R6H5M + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpIdL1TH/.venv +--- +success: true +exit_code: 0 +----- stdout ----- +# This file was autogenerated by Puffin v0.0.1 via the following command: +# [BIN_PATH] pip-compile requirements.in --cache-dir [CACHE_DIR] + +----- stderr ----- +Resolved 0 packages in [TIME] + diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__compile_constraints_txt.snap b/crates/puffin-cli/tests/snapshots/pip_compile__compile_constraints_txt.snap new file mode 100644 index 000000000..e2b3adf72 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_compile__compile_constraints_txt.snap @@ -0,0 +1,28 @@ +--- +source: crates/puffin-cli/tests/pip_compile.rs +info: + program: puffin + args: + - pip-compile + - requirements.in + - "--constraint" + - constraints.txt + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpvjdHOb + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpSAaWi3/.venv +--- +success: true +exit_code: 0 +----- stdout ----- +# This file was autogenerated by Puffin v0.0.1 via the following command: +# [BIN_PATH] pip-compile requirements.in --constraint constraints.txt --cache-dir [CACHE_DIR] +asgiref==3.7.2 + # via django +django==5.0b1 +sqlparse==0.4.3 + # via django + +----- stderr ----- +Resolved 3 packages in [TIME] + diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__compile_pyproject_toml.snap b/crates/puffin-cli/tests/snapshots/pip_compile__compile_pyproject_toml.snap new file mode 100644 index 000000000..1ace0bbb3 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_compile__compile_pyproject_toml.snap @@ -0,0 +1,26 @@ +--- +source: crates/puffin-cli/tests/pip_compile.rs +info: + program: puffin + args: + - pip-compile + - pyproject.toml + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpOOTFwj + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpU0VXyY/.venv +--- +success: true +exit_code: 0 +----- stdout ----- +# This file was autogenerated by Puffin v0.0.1 via the following command: +# [BIN_PATH] pip-compile pyproject.toml --cache-dir [CACHE_DIR] +asgiref==3.7.2 + # via django +django==5.0b1 +sqlparse==0.4.4 + # via django + +----- stderr ----- +Resolved 3 packages in [TIME] + diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__compile_requirements_in.snap b/crates/puffin-cli/tests/snapshots/pip_compile__compile_requirements_in.snap new file mode 100644 index 000000000..04474bfc9 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_compile__compile_requirements_in.snap @@ -0,0 +1,26 @@ +--- +source: crates/puffin-cli/tests/pip_compile.rs +info: + program: puffin + args: + - pip-compile + - requirements.in + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpxF1zFY + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp33QZdv/.venv +--- +success: true +exit_code: 0 +----- stdout ----- +# This file was autogenerated by Puffin v0.0.1 via the following command: +# [BIN_PATH] pip-compile requirements.in --cache-dir [CACHE_DIR] +asgiref==3.7.2 + # via django +django==5.0b1 +sqlparse==0.4.4 + # via django + +----- stderr ----- +Resolved 3 packages in [TIME] + diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__missing_requirements_in.snap b/crates/puffin-cli/tests/snapshots/pip_compile__missing_requirements_in.snap new file mode 100644 index 000000000..167e2fe5e --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_compile__missing_requirements_in.snap @@ -0,0 +1,18 @@ +--- +source: crates/puffin-cli/tests/pip_compile.rs +info: + program: puffin + args: + - pip-compile + - requirements.in + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpQVD5K4 +--- +success: false +exit_code: 2 +----- stdout ----- + +----- stderr ----- +error: failed to open file `requirements.in` + Caused by: No such file or directory (os error 2) + diff --git a/crates/puffin-cli/tests/snapshots/pip_compile__missing_venv.snap b/crates/puffin-cli/tests/snapshots/pip_compile__missing_venv.snap new file mode 100644 index 000000000..f88a921f6 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_compile__missing_venv.snap @@ -0,0 +1,20 @@ +--- +source: crates/puffin-cli/tests/pip_compile.rs +info: + program: puffin + args: + - pip-compile + - requirements.in + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmptw6jFz + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpDn1YGM/.venv +--- +success: false +exit_code: 2 +----- stdout ----- + +----- stderr ----- +error: failed to open file `requirements.in` + Caused by: No such file or directory (os error 2) + diff --git a/crates/puffin-dispatch/src/lib.rs b/crates/puffin-dispatch/src/lib.rs index 84dbb3876..afcd7410b 100644 --- a/crates/puffin-dispatch/src/lib.rs +++ b/crates/puffin-dispatch/src/lib.rs @@ -19,7 +19,7 @@ use puffin_installer::{ Downloader, Installer, PartitionedRequirements, RemoteDistribution, Unzipper, }; use puffin_interpreter::{InterpreterInfo, Virtualenv}; -use puffin_resolver::{Manifest, ResolutionMode, Resolver, WheelFinder}; +use puffin_resolver::{ResolutionManifest, ResolutionMode, Resolver, WheelFinder}; use puffin_traits::BuildContext; /// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`] @@ -71,7 +71,7 @@ impl BuildContext for BuildDispatch { self.interpreter_info.simple_version(), )?; let resolver = Resolver::new( - Manifest::new( + ResolutionManifest::new( requirements.to_vec(), Vec::default(), Vec::default(), diff --git a/crates/puffin-package/src/requirements_txt.rs b/crates/puffin-package/src/requirements_txt.rs index 87c233b8a..a45cd5c12 100644 --- a/crates/puffin-package/src/requirements_txt.rs +++ b/crates/puffin-package/src/requirements_txt.rs @@ -168,7 +168,9 @@ impl RequirementsTxt { end, } })?; - // Here we add both to constraints + // Treat any nested requirements or constraints as constraints. This differs + // from `pip`, which seems to treat `-r` requirements in constraints files as + // _requirements_, but we don't want to support that. data.constraints.extend( sub_constraints .requirements diff --git a/crates/puffin-resolver/src/lib.rs b/crates/puffin-resolver/src/lib.rs index 41e8b31b8..b105d0236 100644 --- a/crates/puffin-resolver/src/lib.rs +++ b/crates/puffin-resolver/src/lib.rs @@ -1,6 +1,6 @@ pub use error::ResolveError; pub use resolution::{Graph, PinnedPackage}; -pub use resolver::{Manifest, Resolver}; +pub use resolver::{ResolutionManifest, Resolver}; pub use selector::ResolutionMode; pub use source_distribution::BuiltSourceDistributionCache; pub use wheel_finder::{Reporter, WheelFinder}; diff --git a/crates/puffin-resolver/src/resolver.rs b/crates/puffin-resolver/src/resolver.rs index 522fc2125..94fba34d4 100644 --- a/crates/puffin-resolver/src/resolver.rs +++ b/crates/puffin-resolver/src/resolver.rs @@ -41,14 +41,14 @@ use crate::BuiltSourceDistributionCache; /// A manifest of requirements, constraints, and preferences. #[derive(Debug)] -pub struct Manifest { +pub struct ResolutionManifest { requirements: Vec, constraints: Vec, preferences: Vec, mode: ResolutionMode, } -impl Manifest { +impl ResolutionManifest { pub fn new( requirements: Vec, constraints: Vec, @@ -78,7 +78,7 @@ pub struct Resolver<'a, Context: BuildContext + Sync> { impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> { /// Initialize a new resolver. pub fn new( - manifest: Manifest, + manifest: ResolutionManifest, markers: &'a MarkerEnvironment, tags: &'a Tags, client: &'a RegistryClient, diff --git a/crates/puffin-resolver/tests/resolver.rs b/crates/puffin-resolver/tests/resolver.rs index d3bdd3bd0..d7738edc5 100644 --- a/crates/puffin-resolver/tests/resolver.rs +++ b/crates/puffin-resolver/tests/resolver.rs @@ -16,7 +16,7 @@ use platform_host::{Arch, Os, Platform}; use platform_tags::Tags; use puffin_client::RegistryClientBuilder; use puffin_interpreter::{InterpreterInfo, Virtualenv}; -use puffin_resolver::{Manifest, ResolutionMode, Resolver}; +use puffin_resolver::{ResolutionManifest, ResolutionMode, Resolver}; use puffin_traits::BuildContext; struct DummyContext; @@ -64,7 +64,7 @@ async fn pylint() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("pylint==2.3.0").unwrap()], vec![], vec![], @@ -85,7 +85,7 @@ async fn black() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![], vec![], @@ -106,7 +106,7 @@ async fn black_colorama() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()], vec![], vec![], @@ -127,7 +127,7 @@ async fn black_python_310() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![], vec![], @@ -150,7 +150,7 @@ async fn black_mypy_extensions() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![Requirement::from_str("mypy-extensions<1").unwrap()], vec![], @@ -173,7 +173,7 @@ async fn black_mypy_extensions_extra() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![Requirement::from_str("mypy-extensions[extra]<1").unwrap()], vec![], @@ -196,7 +196,7 @@ async fn black_flake8() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![Requirement::from_str("flake8<1").unwrap()], vec![], @@ -217,7 +217,7 @@ async fn black_lowest() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black>21").unwrap()], vec![], vec![], @@ -238,7 +238,7 @@ async fn black_lowest_direct() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black>21").unwrap()], vec![], vec![], @@ -259,7 +259,7 @@ async fn black_respect_preference() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![], vec![Requirement::from_str("black==23.9.0").unwrap()], @@ -280,7 +280,7 @@ async fn black_ignore_preference() -> Result<()> { let client = RegistryClientBuilder::default().build(); - let manifest = Manifest::new( + let manifest = ResolutionManifest::new( vec![Requirement::from_str("black<=23.9.1").unwrap()], vec![], vec![Requirement::from_str("black==23.9.2").unwrap()],