From e66361d4cc433c3054a708f8fb04d5ef9395d8a7 Mon Sep 17 00:00:00 2001 From: Di-Is Date: Sat, 20 Jul 2024 13:30:22 +0000 Subject: [PATCH 1/4] add: use constraint dependency from pyproject.toml with the `uv lock` --- crates/uv-settings/src/settings.rs | 1 + crates/uv-workspace/src/pyproject.rs | 1 + crates/uv-workspace/src/workspace.rs | 32 +++++++++++++++++ crates/uv/src/commands/project/lock.rs | 2 +- crates/uv/tests/lock.rs | 49 ++++++++++++++++++++++++++ uv.schema.json | 9 +++++ 6 files changed, 93 insertions(+), 1 deletion(-) diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 36981e49b258..6190cf48e302 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -48,6 +48,7 @@ pub struct Options { ) )] pub override_dependencies: Option>>, + pub constraint_dependencies: Option>>, } /// Global settings, relevant to all invocations. diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index 974e12e0ca12..2fbe27b842be 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -104,6 +104,7 @@ pub struct ToolUv { ) )] pub override_dependencies: Option>>, + pub constraint_dependencies: Option>>, } #[derive(Serialize, Deserialize, OptionsMetadata, Default, Debug, Clone, PartialEq, Eq)] diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 6029d66ce17c..edeb36a4e490 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -254,6 +254,38 @@ impl Workspace { .collect() } + /// Returns the set of constraints for the workspace. + pub fn constraints(&self) -> Vec { + let Some(workspace_package) = self + .packages + .values() + .find(|workspace_package| workspace_package.root() == self.install_path()) + else { + return vec![]; + }; + + let Some(constraints) = workspace_package + .pyproject_toml() + .tool + .as_ref() + .and_then(|tool| tool.uv.as_ref()) + .and_then(|uv| uv.constraint_dependencies.as_ref()) + else { + return vec![]; + }; + + constraints + .iter() + .map(|requirement| { + Requirement::from( + requirement + .clone() + .with_origin(RequirementOrigin::Workspace), + ) + }) + .collect() + } + /// The path to the workspace root, the directory containing the top level `pyproject.toml` with /// the `uv.tool.workspace`, or the `pyproject.toml` in an implicit single workspace project. pub fn install_path(&self) -> &PathBuf { diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 8cafc99494d5..a2269386157e 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -211,7 +211,7 @@ pub(super) async fn do_lock( .into_iter() .map(UnresolvedRequirementSpecification::from) .collect::>(); - let constraints = vec![]; + let constraints = workspace.constraints().into_iter().collect::>(); let dev = vec![DEV_DEPENDENCIES.clone()]; let source_trees = vec![]; diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index 3d99fb6fd68c..ed38362b0ed2 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -644,6 +644,55 @@ fn lock_project_with_overrides() -> Result<()> { Ok(()) } + +/// Lock a project with a uv.tool.constraint-dependencies. +#[test] +fn lock_project_with_constraints() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + + [tool.uv] + constraint-dependencies = ["idna<3.4"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning + Resolved 4 packages in [TIME] + "###); + + // Install the base dependencies from the lockfile. + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `uv sync` is experimental and may change without warning + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==3.7.0 + + idna==3.3 + + project==0.1.0 (from file://[TEMP_DIR]/) + + sniffio==1.3.1 + "###); + + Ok(()) +} + /// Lock a project with a dependency that has an extra. #[test] fn lock_dependency_extra() -> Result<()> { diff --git a/uv.schema.json b/uv.schema.json index aa81a48c1c6d..51e80b60ea81 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -29,6 +29,15 @@ } ] }, + "constraint-dependencies": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Requirement" + } + }, "dev-dependencies": { "description": "PEP 508-style requirements, e.g., `ruff==0.5.0`, or `ruff @ https://...`.", "type": [ From 362ace27d18f11c658817aeb2472dff615e491a0 Mon Sep 17 00:00:00 2001 From: Di-Is Date: Sat, 20 Jul 2024 14:56:16 +0000 Subject: [PATCH 2/4] refactor: fix redundant implementation --- crates/uv/src/commands/project/lock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index a2269386157e..1a291e0f4ffe 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -211,7 +211,7 @@ pub(super) async fn do_lock( .into_iter() .map(UnresolvedRequirementSpecification::from) .collect::>(); - let constraints = workspace.constraints().into_iter().collect::>(); + let constraints = workspace.constraints(); let dev = vec![DEV_DEPENDENCIES.clone()]; let source_trees = vec![]; From e012e69ba44728c786571d503d4f5e0cdb98a58b Mon Sep 17 00:00:00 2001 From: Di-Is Date: Sun, 21 Jul 2024 02:23:16 +0000 Subject: [PATCH 3/4] add: use constraint dependency from pyproject.toml with the `uv pip compile/install` --- crates/uv/src/commands/pip/compile.rs | 7 +++++ crates/uv/src/commands/pip/install.rs | 7 +++++ crates/uv/src/lib.rs | 2 ++ crates/uv/src/settings.rs | 32 +++++++++++++++++++ crates/uv/tests/pip_compile.rs | 44 +++++++++++++++++++++++++++ crates/uv/tests/pip_install.rs | 40 ++++++++++++++++++++++++ crates/uv/tests/show_settings.rs | 18 +++++++++++ 7 files changed, 150 insertions(+) diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index d820f7ec166e..c87d36f82f97 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -48,6 +48,7 @@ pub(crate) async fn pip_compile( requirements: &[RequirementsSource], constraints: &[RequirementsSource], overrides: &[RequirementsSource], + constraints_from_workspace: Vec, overrides_from_workspace: Vec, extras: ExtrasSpecification, output_file: Option<&Path>, @@ -126,6 +127,12 @@ pub(crate) async fn pip_compile( ) .await?; + let constraints = constraints + .iter() + .cloned() + .chain(constraints_from_workspace.into_iter()) + .collect(); + let overrides: Vec = overrides .iter() .cloned() diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 7777c0f1cfc2..d25c4f7cd27d 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -40,6 +40,7 @@ pub(crate) async fn pip_install( requirements: &[RequirementsSource], constraints: &[RequirementsSource], overrides: &[RequirementsSource], + constraints_from_workspace: Vec, overrides_from_workspace: Vec, extras: &ExtrasSpecification, resolution_mode: ResolutionMode, @@ -104,6 +105,12 @@ pub(crate) async fn pip_install( ) .await?; + let constraints: Vec = constraints + .iter() + .cloned() + .chain(constraints_from_workspace.into_iter()) + .collect(); + let overrides: Vec = overrides .iter() .cloned() diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ca679fdaea9f..3b3a3f31d0e9 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -200,6 +200,7 @@ async fn run(cli: Cli) -> Result { &requirements, &constraints, &overrides, + args.constraints_from_workspace, args.overrides_from_workspace, args.settings.extras, args.settings.output_file.as_deref(), @@ -349,6 +350,7 @@ async fn run(cli: Cli) -> Result { &requirements, &constraints, &overrides, + args.constraints_from_workspace, args.overrides_from_workspace, &args.settings.extras, args.settings.resolution, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 3d25120f03bb..bc725f27d9f2 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -760,6 +760,7 @@ pub(crate) struct PipCompileSettings { pub(crate) src_file: Vec, pub(crate) constraint: Vec, pub(crate) r#override: Vec, + pub(crate) constraints_from_workspace: Vec, pub(crate) overrides_from_workspace: Vec, pub(crate) refresh: Refresh, pub(crate) settings: PipSettings, @@ -821,6 +822,20 @@ impl PipCompileSettings { compat_args: _, } = args; + let constraints_from_workspace = if let Some(configuration) = &filesystem { + configuration + .constraint_dependencies + .clone() + .unwrap_or_default() + .into_iter() + .map(|requirement| { + Requirement::from(requirement.with_origin(RequirementOrigin::Workspace)) + }) + .collect() + } else { + Vec::new() + }; + let overrides_from_workspace = if let Some(configuration) = &filesystem { configuration .override_dependencies @@ -845,6 +860,7 @@ impl PipCompileSettings { .into_iter() .filter_map(Maybe::into_option) .collect(), + constraints_from_workspace, overrides_from_workspace, refresh: Refresh::from(refresh), settings: PipSettings::combine( @@ -985,6 +1001,7 @@ pub(crate) struct PipInstallSettings { pub(crate) constraint: Vec, pub(crate) r#override: Vec, pub(crate) dry_run: bool, + pub(crate) constraints_from_workspace: Vec, pub(crate) overrides_from_workspace: Vec, pub(crate) refresh: Refresh, pub(crate) settings: PipSettings, @@ -1033,6 +1050,20 @@ impl PipInstallSettings { compat_args: _, } = args; + let constraints_from_workspace = if let Some(configuration) = &filesystem { + configuration + .constraint_dependencies + .clone() + .unwrap_or_default() + .into_iter() + .map(|requirement| { + Requirement::from(requirement.with_origin(RequirementOrigin::Workspace)) + }) + .collect() + } else { + Vec::new() + }; + let overrides_from_workspace = if let Some(configuration) = &filesystem { configuration .override_dependencies @@ -1060,6 +1091,7 @@ impl PipInstallSettings { .filter_map(Maybe::into_option) .collect(), dry_run, + constraints_from_workspace, overrides_from_workspace, refresh: Refresh::from(refresh), settings: PipSettings::combine( diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 14a5b4f6f1c0..e01b25481601 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -3043,6 +3043,50 @@ fn override_dependency_from_pyproject() -> Result<()> { Ok(()) } +/// Check that `tool.uv.constraint-dependencies` in `pyproject.toml` is respected. +#[test] +fn constraint_dependency_from_pyproject() -> Result<()> { + let context = TestContext::new("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#"[project] + name = "example" + version = "0.0.0" + dependencies = [ + "anyio==3.7.0" + ] + + [tool.uv] + constraint-dependencies = [ + "idna<3.4" + ] + "#, + )?; + + uv_snapshot!(context.pip_compile() + .arg("pyproject.toml"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] pyproject.toml + anyio==3.7.0 + # via example (pyproject.toml) + idna==3.3 + # via + # -c (workspace) + # anyio + sniffio==1.3.1 + # via anyio + + ----- stderr ----- + Resolved 3 packages in [TIME] + "### + ); + + Ok(()) +} + /// Check that `override-dependencies` in `uv.toml` is respected. #[test] fn override_dependency_from_specific_uv_toml() -> Result<()> { diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index b4c9eda15a95..7142f20110e7 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -2429,6 +2429,46 @@ fn install_constraints_txt() -> Result<()> { Ok(()) } +/// Check that `tool.uv.constraint-dependencies` in `pyproject.toml` is respected. +#[test] +fn install_constraints_from_pyproject() -> Result<()> { + let context = TestContext::new("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#"[project] + name = "example" + version = "0.0.0" + dependencies = [ + "anyio==3.7.0" + ] + + [tool.uv] + constraint-dependencies = [ + "idna<3.4" + ] + "#, + )?; + + uv_snapshot!(context.pip_install() + .arg("-r") + .arg("pyproject.toml"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==3.7.0 + + idna==3.3 + + sniffio==1.3.1 + "### + ); + + Ok(()) +} + /// Install a package from a `requirements.txt` file, with an inline constraint. #[test] fn install_constraints_inline() -> Result<()> { diff --git a/crates/uv/tests/show_settings.rs b/crates/uv/tests/show_settings.rs index bb9b5d488f36..3c498da84a30 100644 --- a/crates/uv/tests/show_settings.rs +++ b/crates/uv/tests/show_settings.rs @@ -73,6 +73,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -206,6 +207,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -340,6 +342,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -506,6 +509,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -641,6 +645,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -762,6 +767,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -920,6 +926,7 @@ fn resolve_index_url() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -1078,6 +1085,7 @@ fn resolve_index_url() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -1281,6 +1289,7 @@ fn resolve_find_links() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -1438,6 +1447,7 @@ fn resolve_top_level() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -1565,6 +1575,7 @@ fn resolve_top_level() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -1720,6 +1731,7 @@ fn resolve_top_level() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -1899,6 +1911,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -2016,6 +2029,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -2133,6 +2147,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -2252,6 +2267,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -2396,6 +2412,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( @@ -2541,6 +2558,7 @@ fn resolve_both() -> anyhow::Result<()> { ], constraint: [], override: [], + constraints_from_workspace: [], overrides_from_workspace: [], refresh: None( Timestamp( From 475a506a69f9cf3a810c618ea4c9e930237c6d71 Mon Sep 17 00:00:00 2001 From: Di-Is Date: Sun, 21 Jul 2024 02:24:50 +0000 Subject: [PATCH 4/4] refactor: format code --- crates/uv/tests/pip_compile.rs | 2 +- crates/uv/tests/pip_install.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index e01b25481601..2cccc7c0c009 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -3062,7 +3062,7 @@ fn constraint_dependency_from_pyproject() -> Result<()> { ] "#, )?; - + uv_snapshot!(context.pip_compile() .arg("pyproject.toml"), @r###" success: true diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 7142f20110e7..593b98f4d81b 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -2448,7 +2448,7 @@ fn install_constraints_from_pyproject() -> Result<()> { ] "#, )?; - + uv_snapshot!(context.pip_install() .arg("-r") .arg("pyproject.toml"), @r###"