From 68f33e8fefcebf8a6e84b5f88467802623764cb6 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 19 Aug 2025 15:32:56 +0100 Subject: [PATCH] Add `--no-install-*` arguments to `uv add` (#15375) ## Summary Closes https://github.com/astral-sh/uv/issues/15369. --- crates/uv-cli/src/lib.rs | 20 ++++++ crates/uv/src/commands/project/add.rs | 8 ++- crates/uv/src/lib.rs | 2 + crates/uv/src/settings.rs | 6 ++ crates/uv/tests/it/edit.rs | 99 +++++++++++++++++++++++++++ docs/reference/cli.md | 4 ++ 6 files changed, 138 insertions(+), 1 deletion(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index c3b849885..18af2f7df 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3812,6 +3812,26 @@ pub struct AddArgs { /// as direct path dependency instead. #[arg(long, overrides_with = "workspace")] pub no_workspace: bool, + + /// Do not install the current project. + /// + /// By default, the current project is installed into the environment with all of its + /// dependencies. The `--no-install-project` option allows the project to be excluded, but all of + /// its dependencies are still installed. This is particularly useful in situations like building + /// Docker images where installing the project separately from its dependencies allows optimal + /// layer caching. + #[arg(long, conflicts_with = "frozen", conflicts_with = "no_sync")] + pub no_install_project: bool, + + /// Do not install any workspace members, including the current project. + /// + /// By default, all of the workspace members and their dependencies are installed into the + /// environment. The `--no-install-workspace` option allows exclusion of all the workspace + /// members while retaining their dependencies. This is particularly useful in situations like + /// building Docker images where installing the workspace separately from its dependencies + /// allows optimal layer caching. + #[arg(long, conflicts_with = "frozen", conflicts_with = "no_sync")] + pub no_install_workspace: bool, } #[derive(Args)] diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 34d927d54..df986d54d 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -69,6 +69,8 @@ pub(crate) async fn add( frozen: bool, active: Option, no_sync: bool, + no_install_project: bool, + no_install_workspace: bool, requirements: Vec, constraints: Vec, marker: Option, @@ -735,6 +737,8 @@ pub(crate) async fn add( lock_state, sync_state, locked, + no_install_project, + no_install_workspace, &defaulted_extras, &defaulted_groups, raw, @@ -963,6 +967,8 @@ async fn lock_and_sync( lock_state: UniversalState, sync_state: PlatformState, locked: bool, + no_install_project: bool, + no_install_workspace: bool, extras: &ExtrasSpecificationWithDefaults, groups: &DependencyGroupsWithDefaults, raw: bool, @@ -1149,7 +1155,7 @@ async fn lock_and_sync( extras, groups, EditableMode::Editable, - InstallOptions::default(), + InstallOptions::new(no_install_project, no_install_workspace, vec![]), Modifications::Sufficient, None, settings.into(), diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 5f2399219..c2bda3b35 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1979,6 +1979,8 @@ async fn run_project( args.frozen, args.active, args.no_sync, + args.no_install_project, + args.no_install_workspace, requirements, constraints, args.marker, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 2f1646b76..5f366f3eb 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1370,6 +1370,8 @@ pub(crate) struct AddSettings { pub(crate) script: Option, pub(crate) python: Option, pub(crate) workspace: Option, + pub(crate) no_install_project: bool, + pub(crate) no_install_workspace: bool, pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, pub(crate) indexes: Vec, @@ -1409,6 +1411,8 @@ impl AddSettings { python, workspace, no_workspace, + no_install_project, + no_install_workspace, } = args; let dependency_type = if let Some(extra) = optional { @@ -1510,6 +1514,8 @@ impl AddSettings { script, python: python.and_then(Maybe::into_option), workspace: flag(workspace, no_workspace, "workspace"), + no_install_project, + no_install_workspace, editable: flag(editable, no_editable, "editable"), extras: extra.unwrap_or_default(), refresh: Refresh::from(refresh), diff --git a/crates/uv/tests/it/edit.rs b/crates/uv/tests/it/edit.rs index 78f2dd602..f627a5961 100644 --- a/crates/uv/tests/it/edit.rs +++ b/crates/uv/tests/it/edit.rs @@ -13650,3 +13650,102 @@ fn add_multiline_indentation() -> Result<()> { Ok(()) } + +/// Add a requirement without installing the project. +#[test] +fn add_no_install_project() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "#})?; + context + .temp_dir + .child("project") + .child("src") + .child("project") + .child("__init__.py") + .touch()?; + + uv_snapshot!(context.filters(), context.add().arg("iniconfig").arg("--no-install-project"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "); + + let pyproject_toml = context.read("pyproject.toml"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + pyproject_toml, @r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "iniconfig>=2.0.0", + ] + + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + "# + ); + }); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 3 + requires-python = ">=3.12" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { editable = "." } + dependencies = [ + { name = "iniconfig" }, + ] + + [package.metadata] + requires-dist = [{ name = "iniconfig", specifier = ">=2.0.0" }] + "# + ); + }); + + Ok(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 8832197a1..6b054e93f 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -539,6 +539,10 @@ uv add [OPTIONS] >

May also be set with the UV_NO_CACHE environment variable.

--no-config

Avoid discovering configuration files (pyproject.toml, uv.toml).

Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.

May also be set with the UV_NO_CONFIG environment variable.

--no-index

Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via --find-links

+
--no-install-project

Do not install the current project.

+

By default, the current project is installed into the environment with all of its dependencies. The --no-install-project option allows the project to be excluded, but all of its dependencies are still installed. This is particularly useful in situations like building Docker images where installing the project separately from its dependencies allows optimal layer caching.

+
--no-install-workspace

Do not install any workspace members, including the current project.

+

By default, all of the workspace members and their dependencies are installed into the environment. The --no-install-workspace option allows exclusion of all the workspace members while retaining their dependencies. This is particularly useful in situations like building Docker images where installing the workspace separately from its dependencies allows optimal layer caching.

--no-managed-python

Disable use of uv-managed Python versions.

Instead, uv will search for a suitable Python version on the system.

May also be set with the UV_NO_MANAGED_PYTHON environment variable.

--no-progress

Hide all progress outputs.