diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index ddb866e90d421..5a8c5cc183ac7 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3565,13 +3565,13 @@ pub struct RunArgs { #[arg(long, conflicts_with_all = ["only_group", "only_dev"], value_hint = ValueHint::Other)] pub group: Vec, - /// Disable the specified dependency group. + /// Disable the specified dependency group [env: `UV_NO_GROUP`=] /// /// This option always takes precedence over default groups, /// `--all-groups`, and `--group`. /// /// May be provided multiple times. - #[arg(long, env = EnvVars::UV_NO_GROUP, value_delimiter = ' ', value_hint = ValueHint::Other)] + #[arg(long, value_delimiter = ' ', value_hint = ValueHint::Other)] pub no_group: Vec, /// Ignore the default dependency groups. @@ -3909,13 +3909,13 @@ pub struct SyncArgs { #[arg(long, conflicts_with_all = ["only_group", "only_dev"], value_hint = ValueHint::Other)] pub group: Vec, - /// Disable the specified dependency group. + /// Disable the specified dependency group [env: `UV_NO_GROUP`=] /// /// This option always takes precedence over default groups, /// `--all-groups`, and `--group`. /// /// May be provided multiple times. - #[arg(long, env = EnvVars::UV_NO_GROUP, value_delimiter = ' ', value_hint = ValueHint::Other)] + #[arg(long, value_delimiter = ' ', value_hint = ValueHint::Other)] pub no_group: Vec, /// Ignore the default dependency groups. @@ -4724,13 +4724,13 @@ pub struct TreeArgs { #[arg(long, conflicts_with_all = ["only_group", "only_dev"])] pub group: Vec, - /// Disable the specified dependency group. + /// Disable the specified dependency group [env: `UV_NO_GROUP`=] /// /// This option always takes precedence over default groups, /// `--all-groups`, and `--group`. /// /// May be provided multiple times. - #[arg(long, env = EnvVars::UV_NO_GROUP, value_delimiter = ' ')] + #[arg(long, value_delimiter = ' ')] pub no_group: Vec, /// Ignore the default dependency groups. @@ -4900,13 +4900,13 @@ pub struct ExportArgs { #[arg(long, conflicts_with_all = ["only_group", "only_dev"])] pub group: Vec, - /// Disable the specified dependency group. + /// Disable the specified dependency group [env: `UV_NO_GROUP`=] /// /// This option always takes precedence over default groups, /// `--all-groups`, and `--group`. /// /// May be provided multiple times. - #[arg(long, env = EnvVars::UV_NO_GROUP, value_delimiter = ' ')] + #[arg(long, value_delimiter = ' ')] pub no_group: Vec, /// Ignore the default dependency groups. @@ -5189,10 +5189,10 @@ pub struct AuditArgs { #[arg(long, value_parser = clap::builder::BoolishValueParser::new())] pub no_dev: bool, - /// Don't audit the specified dependency group. + /// Don't audit the specified dependency group [env: `UV_NO_GROUP`=] /// /// May be provided multiple times. - #[arg(long, env = EnvVars::UV_NO_GROUP, value_delimiter = ' ', value_hint = ValueHint::Other)] + #[arg(long, value_delimiter = ' ', value_hint = ValueHint::Other)] pub no_group: Vec, /// Don't audit the default dependency groups. @@ -6187,8 +6187,8 @@ pub struct ToolUpgradeArgs { )] pub no_sources: bool, - /// Don't use sources from the `tool.uv.sources` table for the specified packages. - #[arg(long, help_heading = "Resolver options", env = EnvVars::UV_NO_SOURCES_PACKAGE, value_delimiter = ' ')] + /// Don't use sources from the `tool.uv.sources` table for the specified packages [env: `UV_NO_SOURCES_PACKAGE`=] + #[arg(long, help_heading = "Resolver options", value_delimiter = ' ')] pub no_sources_package: Vec, #[command(flatten)] @@ -7033,11 +7033,10 @@ pub struct BuildOptionsArgs { )] pub build: bool, - /// Don't build source distributions for a specific package. + /// Don't build source distributions for a specific package [env: `UV_NO_BUILD_PACKAGE`=] #[arg( long, help_heading = "Build options", - env = EnvVars::UV_NO_BUILD_PACKAGE, value_delimiter = ' ', value_hint = ValueHint::Other, )] @@ -7064,11 +7063,10 @@ pub struct BuildOptionsArgs { )] pub binary: bool, - /// Don't install pre-built wheels for a specific package. + /// Don't install pre-built wheels for a specific package [env: `UV_NO_BINARY_PACKAGE`=] #[arg( long, help_heading = "Build options", - env = EnvVars::UV_NO_BINARY_PACKAGE, value_delimiter = ' ', value_hint = ValueHint::Other, )] @@ -7260,8 +7258,8 @@ pub struct InstallerArgs { )] pub no_sources: bool, - /// Don't use sources from the `tool.uv.sources` table for the specified packages. - #[arg(long, help_heading = "Resolver options", env = EnvVars::UV_NO_SOURCES_PACKAGE, value_delimiter = ' ')] + /// Don't use sources from the `tool.uv.sources` table for the specified packages [env: `UV_NO_SOURCES_PACKAGE`=] + #[arg(long, help_heading = "Resolver options", value_delimiter = ' ')] pub no_sources_package: Vec, } @@ -7480,8 +7478,8 @@ pub struct ResolverArgs { )] pub no_sources: bool, - /// Don't use sources from the `tool.uv.sources` table for the specified packages. - #[arg(long, help_heading = "Resolver options", env = EnvVars::UV_NO_SOURCES_PACKAGE, value_delimiter = ' ')] + /// Don't use sources from the `tool.uv.sources` table for the specified packages [env: `UV_NO_SOURCES_PACKAGE`=] + #[arg(long, help_heading = "Resolver options", value_delimiter = ' ')] pub no_sources_package: Vec, } @@ -7757,8 +7755,8 @@ pub struct ResolverInstallerArgs { )] pub no_sources: bool, - /// Don't use sources from the `tool.uv.sources` table for the specified packages. - #[arg(long, help_heading = "Resolver options", env = EnvVars::UV_NO_SOURCES_PACKAGE, value_delimiter = ' ')] + /// Don't use sources from the `tool.uv.sources` table for the specified packages [env: `UV_NO_SOURCES_PACKAGE`=] + #[arg(long, help_heading = "Resolver options", value_delimiter = ' ')] pub no_sources_package: Vec, } diff --git a/crates/uv-cli/src/options.rs b/crates/uv-cli/src/options.rs index 0bcf9735744e4..527ab29562a2c 100644 --- a/crates/uv-cli/src/options.rs +++ b/crates/uv-cli/src/options.rs @@ -293,7 +293,11 @@ impl From for PipOptions { exclude_newer_package: exclude_newer_package.map(ExcludeNewerPackage::from_iter), link_mode, no_sources: if no_sources { Some(true) } else { None }, - no_sources_package: Some(no_sources_package), + no_sources_package: if no_sources_package.is_empty() { + None + } else { + Some(no_sources_package) + }, ..Self::from(index_args) } } @@ -339,7 +343,11 @@ impl From for PipOptions { link_mode, compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"), no_sources: if no_sources { Some(true) } else { None }, - no_sources_package: Some(no_sources_package), + no_sources_package: if no_sources_package.is_empty() { + None + } else { + Some(no_sources_package) + }, ..Self::from(index_args) } } @@ -414,7 +422,11 @@ impl From for PipOptions { link_mode, compile_bytecode: flag(compile_bytecode, no_compile_bytecode, "compile-bytecode"), no_sources: if no_sources { Some(true) } else { None }, - no_sources_package: Some(no_sources_package), + no_sources_package: if no_sources_package.is_empty() { + None + } else { + Some(no_sources_package) + }, ..Self::from(index_args) } } @@ -582,11 +594,23 @@ pub fn resolver_options( link_mode, torch_backend: None, no_build: flag(no_build, build, "build"), - no_build_package: Some(no_build_package), + no_build_package: if no_build_package.is_empty() { + None + } else { + Some(no_build_package) + }, no_binary: flag(no_binary, binary, "binary"), - no_binary_package: Some(no_binary_package), + no_binary_package: if no_binary_package.is_empty() { + None + } else { + Some(no_binary_package) + }, no_sources: if no_sources { Some(true) } else { None }, - no_sources_package: Some(no_sources_package), + no_sources_package: if no_sources_package.is_empty() { + None + } else { + Some(no_sources_package) + }, } } diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index d5dc9047f0d27..478636bcfdc96 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -10,6 +10,7 @@ use uv_dirs::{system_config_file, user_config_dir}; use uv_distribution_types::Origin; use uv_flags::EnvironmentFlags; use uv_fs::Simplified; +use uv_normalize::{GroupName, PackageName}; use uv_pep440::Version; use uv_static::{EnvVars, InvalidEnvironmentVariable, parse_boolish_environment_variable}; use uv_warnings::warn_user; @@ -751,6 +752,10 @@ pub struct EnvironmentOptions { pub show_resolution: EnvFlag, pub no_editable: EnvFlag, pub no_env_file: EnvFlag, + pub no_group: Option>, + pub no_binary_package: Option>, + pub no_build_package: Option>, + pub no_sources_package: Option>, pub venv_seed: EnvFlag, pub venv_clear: EnvFlag, pub venv_relocatable: EnvFlag, @@ -847,6 +852,12 @@ impl EnvironmentOptions { show_resolution: EnvFlag::new(EnvVars::UV_SHOW_RESOLUTION)?, no_editable: EnvFlag::new(EnvVars::UV_NO_EDITABLE)?, 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)?, + no_build_package: parse_name_list_environment_variable(EnvVars::UV_NO_BUILD_PACKAGE)?, + no_sources_package: parse_name_list_environment_variable( + EnvVars::UV_NO_SOURCES_PACKAGE, + )?, venv_seed: EnvFlag::new(EnvVars::UV_VENV_SEED)?, venv_clear: EnvFlag::new(EnvVars::UV_VENV_CLEAR)?, venv_relocatable: EnvFlag::new(EnvVars::UV_VENV_RELOCATABLE)?, @@ -878,6 +889,36 @@ fn parse_string_environment_variable(name: &'static str) -> Result(name: &'static str) -> Result>, Error> +where + T: FromStr, + ::Err: std::fmt::Display, +{ + let Some(value) = parse_string_environment_variable(name)? else { + return Ok(None); + }; + + let names = value + .split_whitespace() + .map(|entry| { + entry.parse::().map_err(|err| { + Error::InvalidEnvironmentVariable(InvalidEnvironmentVariable { + name: name.to_string(), + value: value.clone(), + err: err.to_string(), + }) + }) + }) + .collect::, _>>()?; + + if names.is_empty() { + Ok(None) + } else { + Ok(Some(names)) + } +} + fn parse_integer_environment_variable( name: &'static str, help: Option<&str>, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 2cded84c97720..0bde929106e7c 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -712,7 +712,11 @@ impl RunSettings { no_dev.into(), only_dev, group, - no_group, + if no_group.is_empty() { + environment.no_group.clone().unwrap_or_default() + } else { + no_group + }, no_default_groups, only_group, all_groups, @@ -748,6 +752,7 @@ impl RunSettings { settings: ResolverInstallerSettings::combine( resolver_installer_options(installer, build), filesystem, + &environment, ), env_file: EnvFile::from_args(env_file, no_env_file), install_mirrors: environment @@ -841,13 +846,16 @@ impl ToolRunSettings { let filesystem_options = filesystem.map(FilesystemOptions::into_options); - let options = - resolver_installer_options(installer, build).combine(ResolverInstallerOptions::from( - filesystem_options - .as_ref() - .map(|options| options.top_level.clone()) - .unwrap_or_default(), - )); + let options = resolver_installer_options_with_environment( + resolver_installer_options(installer, build), + &environment, + ) + .combine(ResolverInstallerOptions::from( + filesystem_options + .as_ref() + .map(|options| options.top_level.clone()) + .unwrap_or_default(), + )); let filesystem_install_mirrors = filesystem_options .map(|options| options.install_mirrors.clone()) @@ -963,13 +971,16 @@ impl ToolInstallSettings { let filesystem_options = filesystem.map(FilesystemOptions::into_options); - let options = - resolver_installer_options(installer, build).combine(ResolverInstallerOptions::from( - filesystem_options - .as_ref() - .map(|options| options.top_level.clone()) - .unwrap_or_default(), - )); + let options = resolver_installer_options_with_environment( + resolver_installer_options(installer, build), + &environment, + ) + .combine(ResolverInstallerOptions::from( + filesystem_options + .as_ref() + .map(|options| options.top_level.clone()) + .unwrap_or_default(), + )); let filesystem_install_mirrors = filesystem_options .map(|options| options.install_mirrors.clone()) @@ -1118,7 +1129,10 @@ impl ToolUpgradeSettings { no_sources_package, }; - let args = resolver_installer_options(installer, build); + let args = resolver_installer_options_with_environment( + resolver_installer_options(installer, build), + environment, + ); let filesystem = filesystem.map(FilesystemOptions::into_options); let filesystem_install_mirrors = filesystem .clone() @@ -1733,6 +1747,7 @@ impl SyncSettings { let settings = ResolverInstallerSettings::combine( resolver_installer_options(installer, build), filesystem, + &environment, ); let check = flag(check, no_check, "check").unwrap_or_default(); @@ -1787,7 +1802,11 @@ impl SyncSettings { no_dev.into(), only_dev, group, - no_group, + if no_group.is_empty() { + environment.no_group.clone().unwrap_or_default() + } else { + no_group + }, no_default_groups, only_group, all_groups, @@ -1878,7 +1897,11 @@ impl LockSettings { script, python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), - settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), + settings: ResolverSettings::combine( + resolver_options(resolver, build), + filesystem, + &environment, + ), install_mirrors: environment .install_mirrors .combine(filesystem_install_mirrors), @@ -1932,7 +1955,11 @@ impl MetadataSettings { dry_run: DryRun::from_args(dry_run), python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), - settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), + settings: ResolverSettings::combine( + resolver_options(resolver, build), + filesystem, + &environment, + ), install_mirrors: environment .install_mirrors .combine(filesystem_install_mirrors), @@ -2164,6 +2191,7 @@ impl AddSettings { settings: ResolverInstallerSettings::combine( resolver_installer_options(installer, build), filesystem, + &environment, ), install_mirrors: environment .install_mirrors @@ -2263,6 +2291,7 @@ impl RemoveSettings { settings: ResolverInstallerSettings::combine( resolver_installer_options(installer, build), filesystem, + &environment, ), install_mirrors: environment .install_mirrors @@ -2347,6 +2376,7 @@ impl VersionSettings { settings: ResolverInstallerSettings::combine( resolver_installer_options(installer, build), filesystem, + &environment, ), install_mirrors: environment .install_mirrors @@ -2433,7 +2463,11 @@ impl TreeSettings { no_dev.into(), only_dev, group, - no_group, + if no_group.is_empty() { + environment.no_group.clone().unwrap_or_default() + } else { + no_group + }, no_default_groups, only_group, all_groups, @@ -2452,7 +2486,11 @@ impl TreeSettings { python_version, python_platform, python: python.and_then(Maybe::into_option), - resolver: ResolverSettings::combine(resolver_options(resolver, build), filesystem), + resolver: ResolverSettings::combine( + resolver_options(resolver, build), + filesystem, + &environment, + ), install_mirrors: environment .install_mirrors .combine(filesystem_install_mirrors), @@ -2582,7 +2620,11 @@ impl ExportSettings { no_dev.into(), only_dev, group, - no_group, + if no_group.is_empty() { + environment.no_group.clone().unwrap_or_default() + } else { + no_group + }, no_default_groups, only_group, all_groups, @@ -2607,7 +2649,11 @@ impl ExportSettings { script, python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), - settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), + settings: ResolverSettings::combine( + resolver_options(resolver, build), + filesystem, + &environment, + ), install_mirrors: environment .install_mirrors .combine(filesystem_install_mirrors), @@ -2732,7 +2778,11 @@ impl AuditSettings { no_dev, only_dev, vec![], - no_group, + if no_group.is_empty() { + environment.no_group.clone().unwrap_or_default() + } else { + no_group + }, no_default_groups, only_group.clone(), only_group.is_empty() && !only_dev, @@ -2741,10 +2791,14 @@ impl AuditSettings { frozen: resolve_frozen(frozen), python_version, python_platform, + settings: ResolverSettings::combine( + resolver_options(resolver, build), + filesystem, + &environment, + ), install_mirrors: environment .install_mirrors .combine(filesystem_install_mirrors), - settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), output_format, service_format, service_url, @@ -3677,7 +3731,11 @@ impl BuildSettings { ), python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), - settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), + settings: ResolverSettings::combine( + resolver_options(resolver, build), + filesystem, + &environment, + ), install_mirrors: environment .install_mirrors .combine(filesystem_install_mirrors), @@ -3834,7 +3892,21 @@ pub(crate) struct ResolverSettings { impl ResolverSettings { /// Resolve the [`ResolverSettings`] from the CLI and filesystem configuration. - pub(crate) fn combine(args: ResolverOptions, filesystem: Option) -> Self { + pub(crate) fn combine( + mut args: ResolverOptions, + filesystem: Option, + environment: &EnvironmentOptions, + ) -> Self { + args.no_binary_package = args + .no_binary_package + .or(environment.no_binary_package.clone()); + args.no_build_package = args + .no_build_package + .or(environment.no_build_package.clone()); + args.no_sources_package = args + .no_sources_package + .or(environment.no_sources_package.clone()); + // The problem is that for `upgrade`... we want to combine the two `Upgrade` structs, // not the individual fields. let options = args.combine(ResolverOptions::from( @@ -3914,18 +3986,37 @@ impl ResolverInstallerSettings { pub(crate) fn combine( args: ResolverInstallerOptions, filesystem: Option, + environment: &EnvironmentOptions, ) -> Self { - let options = args.combine(ResolverInstallerOptions::from( - filesystem - .map(FilesystemOptions::into_options) - .map(|options| options.top_level) - .unwrap_or_default(), - )); + let options = resolver_installer_options_with_environment(args, environment).combine( + ResolverInstallerOptions::from( + filesystem + .map(FilesystemOptions::into_options) + .map(|options| options.top_level) + .unwrap_or_default(), + ), + ); Self::from(options) } } +fn resolver_installer_options_with_environment( + mut options: ResolverInstallerOptions, + environment: &EnvironmentOptions, +) -> ResolverInstallerOptions { + options.no_binary_package = options + .no_binary_package + .or(environment.no_binary_package.clone()); + options.no_build_package = options + .no_build_package + .or(environment.no_build_package.clone()); + options.no_sources_package = options + .no_sources_package + .or(environment.no_sources_package.clone()); + options +} + impl From for ResolverInstallerSettings { fn from(value: ResolverInstallerOptions) -> Self { let index_locations = IndexLocations::new( @@ -4205,6 +4296,9 @@ impl PipSettings { let reinstall = reinstall.combine(top_level_reinstall); let reinstall_package = reinstall_package.combine(top_level_reinstall_package); let torch_backend = torch_backend.combine(top_level_torch_backend); + let args_no_sources_package = args + .no_sources_package + .or(environment.no_sources_package.clone()); Self { index_locations: IndexLocations::new( @@ -4360,7 +4454,7 @@ impl PipSettings { .unwrap_or_default(), sources: NoSources::from_args( args.no_sources.combine(no_sources), - args.no_sources_package + args_no_sources_package .combine(no_sources_package) .unwrap_or_default(), ), diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 6cf1d9c17aed3..2d6068ccf6f56 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -8032,6 +8032,38 @@ fn no_binary() -> Result<()> { Ok(()) } +#[test] +fn no_binary_package_empty_environment_variable() -> Result<()> { + let context = uv_test::test_context!("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 = ["iniconfig"] + "#, + )?; + + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_NO_BINARY_PACKAGE, ""), @" + 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 + "); + + Ok(()) +} + #[test] fn no_binary_error() -> Result<()> { let context = uv_test::test_context!("3.12"); diff --git a/crates/uv/tests/it/tool_install.rs b/crates/uv/tests/it/tool_install.rs index 7a2cad9152040..b03c8cc65d841 100644 --- a/crates/uv/tests/it/tool_install.rs +++ b/crates/uv/tests/it/tool_install.rs @@ -2739,6 +2739,43 @@ fn tool_install_no_entrypoints() { .assert(predicate::path::missing()); } +#[test] +fn tool_install_no_binary_package_env_var() { + let context = uv_test::test_context!("3.12").with_filtered_exe_suffix(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + uv_snapshot!(context.filters(), context.tool_install() + .arg("pytest") + .env(EnvVars::UV_NO_BINARY_PACKAGE, "iniconfig") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 4 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + iniconfig==2.0.0 + + packaging==24.0 + + pluggy==1.4.0 + + pytest==8.1.1 + Installed 2 executables: py.test, pytest + "); + + let receipt: toml::Value = toml::from_str( + &fs_err::read_to_string(tool_dir.join("pytest").join("uv-receipt.toml")).unwrap(), + ) + .unwrap(); + assert_snapshot!( + receipt["tool"]["options"]["no-binary-package"].to_string(), + @r#"["iniconfig"]"# + ); +} + /// Test installing a package that can't be installed. #[test] fn tool_install_uninstallable() { diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index 5a49ab847656e..a8121830910eb 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -161,6 +161,43 @@ fn tool_run_at_version() { "); } +#[test] +fn tool_run_no_binary_package_env_var() { + let context = uv_test::test_context!("3.12").with_filtered_exe_suffix(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + context + .tool_install() + .arg("pytest") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()) + .assert() + .success(); + + uv_snapshot!(context.filters(), context.tool_run() + .arg("pytest") + .arg("--version") + .env(EnvVars::UV_NO_BINARY_PACKAGE, "iniconfig") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + pytest 8.1.1 + + ----- stderr ----- + Resolved 4 packages in [TIME] + Prepared 1 package in [TIME] + Installed 4 packages in [TIME] + + iniconfig==2.0.0 + + packaging==24.0 + + pluggy==1.4.0 + + pytest==8.1.1 + "); +} + #[test] fn tool_run_from_version() { let context = uv_test::test_context!("3.12"); diff --git a/crates/uv/tests/it/tool_upgrade.rs b/crates/uv/tests/it/tool_upgrade.rs index cc0ae17c47845..054882d066deb 100644 --- a/crates/uv/tests/it/tool_upgrade.rs +++ b/crates/uv/tests/it/tool_upgrade.rs @@ -1056,6 +1056,63 @@ fn tool_upgrade_settings() { "); } +#[test] +fn tool_upgrade_no_binary_package_env_var() { + let context = uv_test::test_context!("3.12").with_filtered_exe_suffix(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + uv_snapshot!(context.filters(), context.tool_install() + .arg("black>=23") + .arg("--resolution=lowest-direct") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 6 packages in [TIME] + Prepared 6 packages in [TIME] + Installed 6 packages in [TIME] + + black==23.1.0 + + click==8.1.7 + + mypy-extensions==1.0.0 + + packaging==24.0 + + pathspec==0.12.1 + + platformdirs==4.2.0 + Installed 2 executables: black, blackd + "); + + uv_snapshot!(context.filters(), context.tool_upgrade() + .arg("black") + .arg("--resolution=highest") + .env(EnvVars::UV_NO_BINARY_PACKAGE, "iniconfig") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()) + .env(EnvVars::PATH, bin_dir.as_os_str()), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Updated black v23.1.0 -> v24.3.0 + - black==23.1.0 + + black==24.3.0 + Installed 2 executables: black, blackd + "); + + let receipt: toml::Value = toml::from_str( + &fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), + ) + .unwrap(); + assert_snapshot!( + receipt["tool"]["options"]["no-binary-package"].to_string(), + @r#"["iniconfig"]"# + ); +} + #[test] fn tool_upgrade_respect_constraints() { let context = uv_test::test_context!("3.12")