mirror of
https://github.com/astral-sh/uv
synced 2026-06-21 13:47:25 +00:00
Recognize UV_NO_INSTALL_PROJECT, UV_NO_INSTALL_WORKSPACE, UV_NO_INSTALL_LOCAL (#19323)
## Summary Adds environment variable support for `--no-install-project`, `--no-install-workspace`, and `--no-install-local` so the flags can be set when uv is invoked through a wrapper that doesn't expose flag pass-through (Pulumi's `pulumi install` is the example in the issue). The new env vars (`UV_NO_INSTALL_PROJECT`, `UV_NO_INSTALL_WORKSPACE`, `UV_NO_INSTALL_LOCAL`) are wired into both `uv sync` and `uv add`, and apply alongside the CLI flag. If the corresponding `--only-install-*` is passed on the CLI, the env var is ignored to keep the existing CLI conflict semantics intact. Closes #19315. ## Test Plan Added integration tests for each of the three flags in `crates/uv/tests/it/sync.rs`, including a check that `--only-install-project` overrides `UV_NO_INSTALL_PROJECT=1`. Also verified manually: ``` $ UV_NO_INSTALL_PROJECT=1 uv sync Installed 1 package in 4ms + packaging==26.2 $ uv sync Installed 2 packages in 5ms + demo==0.1.0 (from file:///tmp/demo) + packaging==26.2 ```
This commit is contained in:
@@ -22,11 +22,17 @@ doc-valid-idents = [
|
||||
"UV_NO_DEV",
|
||||
"UV_NO_EDITABLE",
|
||||
"UV_NO_ENV_FILE",
|
||||
"UV_NO_INSTALL_LOCAL",
|
||||
"UV_NO_INSTALL_PROJECT",
|
||||
"UV_NO_INSTALL_WORKSPACE",
|
||||
"UV_NO_INSTALLER_METADATA",
|
||||
"UV_NO_MANAGED_PYTHON",
|
||||
"UV_NO_PROGRESS",
|
||||
"UV_NO_SYNC",
|
||||
"UV_OFFLINE",
|
||||
"UV_ONLY_INSTALL_LOCAL",
|
||||
"UV_ONLY_INSTALL_PROJECT",
|
||||
"UV_ONLY_INSTALL_WORKSPACE",
|
||||
"UV_PREVIEW",
|
||||
"UV_SHOW_RESOLUTION",
|
||||
"UV_VENV_CLEAR",
|
||||
|
||||
@@ -3999,7 +3999,7 @@ pub struct SyncArgs {
|
||||
#[arg(long, overrides_with = "active", hide = true)]
|
||||
pub no_active: bool,
|
||||
|
||||
/// Do not install the current project.
|
||||
/// Do not install the current project [env: UV_NO_INSTALL_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
|
||||
@@ -4016,7 +4016,7 @@ pub struct SyncArgs {
|
||||
#[arg(long, conflicts_with = "no_install_project", hide = true)]
|
||||
pub only_install_project: bool,
|
||||
|
||||
/// Do not install any workspace members, including the root project.
|
||||
/// Do not install any workspace members, including the root project [env: UV_NO_INSTALL_WORKSPACE=]
|
||||
///
|
||||
/// By default, all workspace members and their dependencies are installed into the
|
||||
/// environment. The `--no-install-workspace` option allows exclusion of all the workspace
|
||||
@@ -4033,7 +4033,7 @@ pub struct SyncArgs {
|
||||
#[arg(long, conflicts_with = "no_install_workspace", hide = true)]
|
||||
pub only_install_workspace: bool,
|
||||
|
||||
/// Do not install local path dependencies
|
||||
/// Do not install local path dependencies [env: UV_NO_INSTALL_LOCAL=]
|
||||
///
|
||||
/// Skips the current project, workspace members, and any other local (path or editable)
|
||||
/// packages. Only remote/indexed dependencies are installed. Useful in Docker builds to cache
|
||||
@@ -4501,7 +4501,7 @@ pub struct AddArgs {
|
||||
#[arg(long, overrides_with = "workspace")]
|
||||
pub no_workspace: bool,
|
||||
|
||||
/// Do not install the current project.
|
||||
/// Do not install the current project [env: UV_NO_INSTALL_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
|
||||
@@ -4529,7 +4529,7 @@ pub struct AddArgs {
|
||||
)]
|
||||
pub only_install_project: bool,
|
||||
|
||||
/// Do not install any workspace members, including the current project.
|
||||
/// Do not install any workspace members, including the current project [env: UV_NO_INSTALL_WORKSPACE=]
|
||||
///
|
||||
/// By default, all workspace members and their dependencies are installed into the
|
||||
/// environment. The `--no-install-workspace` option allows exclusion of all the workspace
|
||||
@@ -4557,7 +4557,7 @@ pub struct AddArgs {
|
||||
)]
|
||||
pub only_install_workspace: bool,
|
||||
|
||||
/// Do not install local path dependencies
|
||||
/// Do not install local path dependencies [env: UV_NO_INSTALL_LOCAL=]
|
||||
///
|
||||
/// Skips the current project, workspace members, and any other local (path or editable)
|
||||
/// packages. Only remote/indexed dependencies are installed. Useful in Docker builds to cache
|
||||
|
||||
@@ -749,6 +749,12 @@ pub struct EnvironmentOptions {
|
||||
pub no_dev: EnvFlag,
|
||||
pub show_resolution: EnvFlag,
|
||||
pub no_editable: EnvFlag,
|
||||
pub no_install_project: EnvFlag,
|
||||
pub no_install_workspace: EnvFlag,
|
||||
pub no_install_local: EnvFlag,
|
||||
pub only_install_project: EnvFlag,
|
||||
pub only_install_workspace: EnvFlag,
|
||||
pub only_install_local: EnvFlag,
|
||||
pub no_env_file: EnvFlag,
|
||||
pub no_group: Option<Vec<GroupName>>,
|
||||
pub no_binary_package: Option<Vec<PackageName>>,
|
||||
@@ -859,6 +865,12 @@ impl EnvironmentOptions {
|
||||
no_dev: EnvFlag::new(EnvVars::UV_NO_DEV)?,
|
||||
show_resolution: EnvFlag::new(EnvVars::UV_SHOW_RESOLUTION)?,
|
||||
no_editable: EnvFlag::new(EnvVars::UV_NO_EDITABLE)?,
|
||||
no_install_project: EnvFlag::new(EnvVars::UV_NO_INSTALL_PROJECT)?,
|
||||
no_install_workspace: EnvFlag::new(EnvVars::UV_NO_INSTALL_WORKSPACE)?,
|
||||
no_install_local: EnvFlag::new(EnvVars::UV_NO_INSTALL_LOCAL)?,
|
||||
only_install_project: EnvFlag::new(EnvVars::UV_ONLY_INSTALL_PROJECT)?,
|
||||
only_install_workspace: EnvFlag::new(EnvVars::UV_ONLY_INSTALL_WORKSPACE)?,
|
||||
only_install_local: EnvFlag::new(EnvVars::UV_ONLY_INSTALL_LOCAL)?,
|
||||
no_env_file: EnvFlag::new(EnvVars::UV_NO_ENV_FILE)?,
|
||||
no_group: parse_name_list_environment_variable(EnvVars::UV_NO_GROUP)?,
|
||||
no_binary_package: parse_name_list_environment_variable(EnvVars::UV_NO_BINARY_PACKAGE)?,
|
||||
|
||||
@@ -260,6 +260,38 @@ impl EnvVars {
|
||||
#[attr_added_in("0.9.9")]
|
||||
pub const UV_NO_DEFAULT_GROUPS: &'static str = "UV_NO_DEFAULT_GROUPS";
|
||||
|
||||
/// Equivalent to the `--no-install-project` command-line argument. If set, uv will
|
||||
/// install the project's dependencies but not the project itself.
|
||||
#[attr_added_in("next release")]
|
||||
pub const UV_NO_INSTALL_PROJECT: &'static str = "UV_NO_INSTALL_PROJECT";
|
||||
|
||||
/// Equivalent to the `--no-install-workspace` command-line argument. If set, uv will
|
||||
/// install workspace dependencies but not workspace members (including the current
|
||||
/// project).
|
||||
#[attr_added_in("next release")]
|
||||
pub const UV_NO_INSTALL_WORKSPACE: &'static str = "UV_NO_INSTALL_WORKSPACE";
|
||||
|
||||
/// Equivalent to the `--no-install-local` command-line argument. If set, uv will skip
|
||||
/// the current project, workspace members, and any other local (path or editable)
|
||||
/// packages, installing only remote dependencies.
|
||||
#[attr_added_in("next release")]
|
||||
pub const UV_NO_INSTALL_LOCAL: &'static str = "UV_NO_INSTALL_LOCAL";
|
||||
|
||||
/// Equivalent to the hidden `--only-install-project` command-line argument.
|
||||
#[attr_hidden]
|
||||
#[attr_added_in("next release")]
|
||||
pub const UV_ONLY_INSTALL_PROJECT: &'static str = "UV_ONLY_INSTALL_PROJECT";
|
||||
|
||||
/// Equivalent to the hidden `--only-install-workspace` command-line argument.
|
||||
#[attr_hidden]
|
||||
#[attr_added_in("next release")]
|
||||
pub const UV_ONLY_INSTALL_WORKSPACE: &'static str = "UV_ONLY_INSTALL_WORKSPACE";
|
||||
|
||||
/// Equivalent to the hidden `--only-install-local` command-line argument.
|
||||
#[attr_hidden]
|
||||
#[attr_added_in("next release")]
|
||||
pub const UV_ONLY_INSTALL_LOCAL: &'static str = "UV_ONLY_INSTALL_LOCAL";
|
||||
|
||||
/// Equivalent to the `--no-binary` command-line argument. If set, uv will install
|
||||
/// all packages from source. The resolver will still use pre-built wheels to
|
||||
/// extract package metadata, if available.
|
||||
|
||||
@@ -1794,6 +1794,46 @@ impl SyncSettings {
|
||||
Some(environment.no_editable),
|
||||
);
|
||||
|
||||
let (no_install_project, only_install_project) = resolve_flag_pair(
|
||||
no_install_project,
|
||||
only_install_project,
|
||||
"no-install-project",
|
||||
"only-install-project",
|
||||
Some(environment.no_install_project),
|
||||
Some(environment.only_install_project),
|
||||
);
|
||||
let (no_install_workspace, only_install_workspace) = resolve_flag_pair(
|
||||
no_install_workspace,
|
||||
only_install_workspace,
|
||||
"no-install-workspace",
|
||||
"only-install-workspace",
|
||||
Some(environment.no_install_workspace),
|
||||
Some(environment.only_install_workspace),
|
||||
);
|
||||
let (no_install_local, only_install_local) = resolve_flag_pair(
|
||||
no_install_local,
|
||||
only_install_local,
|
||||
"no-install-local",
|
||||
"only-install-local",
|
||||
Some(environment.no_install_local),
|
||||
Some(environment.only_install_local),
|
||||
);
|
||||
check_conflicts(no_install_project, only_install_project);
|
||||
check_conflicts(no_install_workspace, only_install_workspace);
|
||||
check_conflicts(no_install_local, only_install_local);
|
||||
if script.is_some() {
|
||||
let script = Flag::from_cli("script");
|
||||
check_conflicts(no_install_project, script);
|
||||
check_conflicts(no_install_workspace, script);
|
||||
check_conflicts(no_install_local, script);
|
||||
}
|
||||
let no_install_project = no_install_project.is_enabled();
|
||||
let only_install_project = only_install_project.is_enabled();
|
||||
let no_install_workspace = no_install_workspace.is_enabled();
|
||||
let only_install_workspace = only_install_workspace.is_enabled();
|
||||
let no_install_local = no_install_local.is_enabled();
|
||||
let only_install_local = only_install_local.is_enabled();
|
||||
|
||||
let malware_settings = MalwareCheckSettings::from(&environment);
|
||||
|
||||
Self {
|
||||
@@ -2092,6 +2132,34 @@ impl AddSettings {
|
||||
Some(environment.no_editable),
|
||||
);
|
||||
|
||||
let (no_install_project, only_install_project) = resolve_flag_pair(
|
||||
no_install_project,
|
||||
only_install_project,
|
||||
"no-install-project",
|
||||
"only-install-project",
|
||||
Some(environment.no_install_project),
|
||||
Some(environment.only_install_project),
|
||||
);
|
||||
let (no_install_workspace, only_install_workspace) = resolve_flag_pair(
|
||||
no_install_workspace,
|
||||
only_install_workspace,
|
||||
"no-install-workspace",
|
||||
"only-install-workspace",
|
||||
Some(environment.no_install_workspace),
|
||||
Some(environment.only_install_workspace),
|
||||
);
|
||||
let (no_install_local, only_install_local) = resolve_flag_pair(
|
||||
no_install_local,
|
||||
only_install_local,
|
||||
"no-install-local",
|
||||
"only-install-local",
|
||||
Some(environment.no_install_local),
|
||||
Some(environment.only_install_local),
|
||||
);
|
||||
check_conflicts(no_install_project, only_install_project);
|
||||
check_conflicts(no_install_workspace, only_install_workspace);
|
||||
check_conflicts(no_install_local, only_install_local);
|
||||
|
||||
let dependency_type = if let Some(extra) = optional {
|
||||
DependencyType::Optional(extra)
|
||||
} else if let Some(group) = group {
|
||||
@@ -2169,6 +2237,38 @@ impl AddSettings {
|
||||
// Check for conflicts between no_sync and frozen.
|
||||
check_conflicts(no_sync, frozen);
|
||||
|
||||
let no_install_package_flag = if no_install_package.is_empty() {
|
||||
Flag::disabled()
|
||||
} else {
|
||||
Flag::from_cli("no-install-package")
|
||||
};
|
||||
let only_install_package_flag = if only_install_package.is_empty() {
|
||||
Flag::disabled()
|
||||
} else {
|
||||
Flag::from_cli("only-install-package")
|
||||
};
|
||||
|
||||
for install_flag in [
|
||||
no_install_project,
|
||||
no_install_workspace,
|
||||
no_install_local,
|
||||
only_install_project,
|
||||
only_install_workspace,
|
||||
only_install_local,
|
||||
no_install_package_flag,
|
||||
only_install_package_flag,
|
||||
] {
|
||||
check_conflicts(install_flag, frozen);
|
||||
check_conflicts(install_flag, no_sync);
|
||||
}
|
||||
|
||||
let no_install_project = no_install_project.is_enabled();
|
||||
let only_install_project = only_install_project.is_enabled();
|
||||
let no_install_workspace = no_install_workspace.is_enabled();
|
||||
let only_install_workspace = only_install_workspace.is_enabled();
|
||||
let no_install_local = no_install_local.is_enabled();
|
||||
let only_install_local = only_install_local.is_enabled();
|
||||
|
||||
let malware_settings = MalwareCheckSettings::from(&environment);
|
||||
|
||||
Self {
|
||||
|
||||
@@ -15491,6 +15491,36 @@ fn add_no_install_project() -> Result<()> {
|
||||
);
|
||||
});
|
||||
|
||||
uv_snapshot!(context.filters(), context.add().arg("typing-extensions").env(EnvVars::UV_NO_INSTALL_PROJECT, "1"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.add().arg("typing-extensions").arg("--frozen").env(EnvVars::UV_NO_INSTALL_PROJECT, "1"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: the argument `UV_NO_INSTALL_PROJECT` (environment variable) cannot be used with `--frozen`
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.add().arg("typing-extensions").arg("--frozen").env(EnvVars::UV_ONLY_INSTALL_PROJECT, "1"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: the argument `UV_ONLY_INSTALL_PROJECT` (environment variable) cannot be used with `--frozen`
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -5849,6 +5849,39 @@ fn no_install_project() -> Result<()> {
|
||||
+ sniffio==1.3.1
|
||||
");
|
||||
|
||||
fs_err::remove_dir_all(&context.venv)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_NO_INSTALL_PROJECT, "1"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: .venv
|
||||
Resolved 4 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ idna==3.6
|
||||
+ sniffio==1.3.1
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_ONLY_INSTALL_PROJECT, "1"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 4 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 3 packages in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
- anyio==3.7.0
|
||||
- idna==3.6
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
- sniffio==1.3.1
|
||||
");
|
||||
|
||||
// However, we do require the `pyproject.toml`.
|
||||
fs_err::remove_file(pyproject_toml)?;
|
||||
|
||||
@@ -5941,6 +5974,23 @@ fn no_install_workspace() -> Result<()> {
|
||||
// Remove the virtual environment.
|
||||
fs_err::remove_dir_all(&context.venv)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").env(EnvVars::UV_NO_INSTALL_WORKSPACE, "1"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: .venv
|
||||
Installed 4 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ idna==3.6
|
||||
+ iniconfig==2.0.0
|
||||
+ sniffio==1.3.1
|
||||
");
|
||||
|
||||
fs_err::remove_dir_all(&context.venv)?;
|
||||
|
||||
// We don't require the `pyproject.toml` for non-root members, if `--frozen` is provided.
|
||||
fs_err::remove_file(child.join("pyproject.toml"))?;
|
||||
|
||||
@@ -6097,6 +6147,56 @@ fn no_install_local() -> Result<()> {
|
||||
+ sniffio==1.3.1
|
||||
");
|
||||
|
||||
fs_err::remove_dir_all(&context.venv)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").env(EnvVars::UV_NO_INSTALL_LOCAL, "1"), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Creating virtual environment at: .venv
|
||||
Installed 3 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ idna==3.6
|
||||
+ sniffio==1.3.1
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `UV_NO_INSTALL_PROJECT=1` should conflict with `--script`, just like
|
||||
/// `--no-install-project`.
|
||||
#[test]
|
||||
fn no_install_env_var_conflicts() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
let script = context.temp_dir.child("script.py");
|
||||
script.write_str(indoc! {r#"
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
# ///
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::UV_NO_INSTALL_PROJECT, "1"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: the argument `UV_NO_INSTALL_PROJECT` (environment variable) cannot be used with `--script`
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::UV_NO_INSTALL_PROJECT, "1").env(EnvVars::UV_ONLY_INSTALL_PROJECT, "1"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: the argument `UV_NO_INSTALL_PROJECT` (environment variable) cannot be used with `UV_ONLY_INSTALL_PROJECT` (environment variable)
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user