From d44e65e9ed04ece32da064e05e5d3e70365550d8 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 4 Feb 2026 08:30:34 -0600 Subject: [PATCH] Use relocatable virtual environments by default (#17770) Under the `relocatable-envs-default` preview feature See #13994 --- crates/uv-cli/src/lib.rs | 9 +++++- crates/uv-preview/src/lib.rs | 7 +++++ crates/uv/src/lib.rs | 6 +++- crates/uv/src/settings.rs | 3 ++ crates/uv/tests/it/show_settings.rs | 2 ++ crates/uv/tests/it/venv.rs | 49 +++++++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 2 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index e324af375..26c2d3cd8 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3170,9 +3170,16 @@ pub struct VenvArgs { /// absolute paths), the entrypoints and scripts themselves will _not_ be relocatable. In other /// words, copying those entrypoints and scripts to a location outside the environment will not /// work, as they reference paths relative to the environment itself. - #[arg(long)] + #[arg(long, overrides_with("no_relocatable"))] pub relocatable: bool, + /// Don't make the virtual environment relocatable. + /// + /// Disables the default relocatable behavior when the `relocatable-envs-default` preview + /// feature is enabled. + #[arg(long, overrides_with("relocatable"), hide = true)] + pub no_relocatable: bool, + #[command(flatten)] pub index_args: IndexArgs, diff --git a/crates/uv-preview/src/lib.rs b/crates/uv-preview/src/lib.rs index 9104f0dc6..df716023c 100644 --- a/crates/uv-preview/src/lib.rs +++ b/crates/uv-preview/src/lib.rs @@ -36,6 +36,7 @@ pub enum PreviewFeature { GcsEndpoint = 1 << 21, AdjustUlimit = 1 << 22, SpecialCondaEnvNames = 1 << 23, + RelocatableEnvsDefault = 1 << 24, } impl PreviewFeature { @@ -66,6 +67,7 @@ impl PreviewFeature { Self::GcsEndpoint => "gcs-endpoint", Self::AdjustUlimit => "adjust-ulimit", Self::SpecialCondaEnvNames => "special-conda-env-names", + Self::RelocatableEnvsDefault => "relocatable-envs-default", } } } @@ -109,6 +111,7 @@ impl FromStr for PreviewFeature { "metadata-json" => Self::MetadataJson, "adjust-ulimit" => Self::AdjustUlimit, "special-conda-env-names" => Self::SpecialCondaEnvNames, + "relocatable-envs-default" => Self::RelocatableEnvsDefault, _ => return Err(PreviewFeatureParseError), }) } @@ -336,5 +339,9 @@ mod tests { PreviewFeature::SpecialCondaEnvNames.as_str(), "special-conda-env-names" ); + assert_eq!( + PreviewFeature::RelocatableEnvsDefault.as_str(), + "relocatable-envs-default" + ); } } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index cdd8b3f1b..f5310d858 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1247,7 +1247,11 @@ async fn run(mut cli: Cli) -> Result { args.no_project, &cache, printer, - args.relocatable, + args.relocatable + || (globals + .preview + .is_enabled(PreviewFeature::RelocatableEnvsDefault) + && !args.no_relocatable), globals.preview, ) .await diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 68cc35368..b6c5540d9 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -3347,6 +3347,7 @@ pub(crate) struct VenvSettings { pub(crate) prompt: Option, pub(crate) system_site_packages: bool, pub(crate) relocatable: bool, + pub(crate) no_relocatable: bool, pub(crate) no_project: bool, pub(crate) refresh: Refresh, pub(crate) settings: PipSettings, @@ -3371,6 +3372,7 @@ impl VenvSettings { prompt, system_site_packages, relocatable, + no_relocatable, index_args, index_strategy, keyring_provider, @@ -3396,6 +3398,7 @@ impl VenvSettings { system_site_packages, no_project, relocatable, + no_relocatable, refresh: Refresh::from(refresh), settings: PipSettings::combine( PipOptions { diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index b244a174b..3ed9594e3 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -7931,6 +7931,7 @@ fn preview_features() { GcsEndpoint, AdjustUlimit, SpecialCondaEnvNames, + RelocatableEnvsDefault, ], }, python_preference: Managed, @@ -8190,6 +8191,7 @@ fn preview_features() { GcsEndpoint, AdjustUlimit, SpecialCondaEnvNames, + RelocatableEnvsDefault, ], }, python_preference: Managed, diff --git a/crates/uv/tests/it/venv.rs b/crates/uv/tests/it/venv.rs index 928166d1e..131cb421a 100644 --- a/crates/uv/tests/it/venv.rs +++ b/crates/uv/tests/it/venv.rs @@ -1304,6 +1304,55 @@ fn verify_pyvenv_cfg_relocatable() { )); } +/// With `relocatable-envs-default` preview feature, venvs are relocatable by default. +#[test] +fn relocatable_envs_default_preview() { + let context = TestContext::new("3.12"); + + // Create a virtual environment with the preview feature enabled. + context + .venv() + .arg(context.venv.as_os_str()) + .arg("--clear") + .arg("--python") + .arg("3.12") + .arg("--preview-features") + .arg("relocatable-envs-default") + .assert() + .success(); + + let pyvenv_cfg = context.venv.child("pyvenv.cfg"); + pyvenv_cfg.assert(predicates::path::is_file()); + + // Relocatable flag is set by default under preview. + pyvenv_cfg.assert(predicates::str::contains("relocatable = true")); +} + +/// With `relocatable-envs-default` preview feature, `--no-relocatable` opts out. +#[test] +fn relocatable_envs_default_no_relocatable() { + let context = TestContext::new("3.12"); + + // Create a virtual environment with the preview feature but opt out. + context + .venv() + .arg(context.venv.as_os_str()) + .arg("--clear") + .arg("--python") + .arg("3.12") + .arg("--preview-features") + .arg("relocatable-envs-default") + .arg("--no-relocatable") + .assert() + .success(); + + let pyvenv_cfg = context.venv.child("pyvenv.cfg"); + pyvenv_cfg.assert(predicates::path::is_file()); + + // Relocatable flag is NOT set because of --no-relocatable. + pyvenv_cfg.assert(predicates::str::contains("relocatable").not()); +} + /// Ensure that a nested virtual environment uses the same `home` directory as the parent. #[test] fn verify_nested_pyvenv_cfg() -> Result<()> {