diff --git a/crates/requirements-txt/src/lib.rs b/crates/requirements-txt/src/lib.rs index 3ca2a5a6a..1aa9e3fa2 100644 --- a/crates/requirements-txt/src/lib.rs +++ b/crates/requirements-txt/src/lib.rs @@ -594,7 +594,7 @@ fn parse_entry( end: s.cursor(), } })?; - RequirementsTxtStatement::NoBinary(NoBinary::from_arg(specifier)) + RequirementsTxtStatement::NoBinary(NoBinary::from_pip_arg(specifier)) } else if s.eat_if("--only-binary") { let given = parse_value(content, s, |c: char| !['\n', '\r', '#'].contains(&c))?; let specifier = PackageNameSpecifier::from_str(given).map_err(|err| { @@ -605,7 +605,7 @@ fn parse_entry( end: s.cursor(), } })?; - RequirementsTxtStatement::OnlyBinary(NoBuild::from_arg(specifier)) + RequirementsTxtStatement::OnlyBinary(NoBuild::from_pip_arg(specifier)) } else if s.at(char::is_ascii_alphanumeric) || s.at(|char| matches!(char, '.' | '/' | '$')) { let source = if requirements_txt == Path::new("-") { None diff --git a/crates/uv-configuration/src/build_options.rs b/crates/uv-configuration/src/build_options.rs index 8f974d3b3..0079440da 100644 --- a/crates/uv-configuration/src/build_options.rs +++ b/crates/uv-configuration/src/build_options.rs @@ -46,6 +46,14 @@ impl BuildOptions { } } + #[must_use] + pub fn combine(self, no_binary: NoBinary, no_build: NoBuild) -> Self { + Self { + no_binary: self.no_binary.combine(no_binary), + no_build: self.no_build.combine(no_build), + } + } + pub fn no_binary_package(&self, package_name: &PackageName) -> bool { match &self.no_binary { NoBinary::None => false, @@ -108,7 +116,22 @@ pub enum NoBinary { impl NoBinary { /// Determine the binary installation strategy to use for the given arguments. - pub fn from_args(no_binary: Vec) -> Self { + pub fn from_args(no_binary: Option, no_binary_package: Vec) -> Self { + match no_binary { + Some(true) => Self::All, + Some(false) => Self::None, + None => { + if no_binary_package.is_empty() { + Self::None + } else { + Self::Packages(no_binary_package) + } + } + } + } + + /// Determine the binary installation strategy to use for the given arguments from the pip CLI. + pub fn from_pip_args(no_binary: Vec) -> Self { let combined = PackageNameSpecifiers::from_iter(no_binary.into_iter()); match combined { PackageNameSpecifiers::All => Self::All, @@ -117,9 +140,9 @@ impl NoBinary { } } - /// Determine the binary installation strategy to use for the given argument. - pub fn from_arg(no_binary: PackageNameSpecifier) -> Self { - Self::from_args(vec![no_binary]) + /// Determine the binary installation strategy to use for the given argument from the pip CLI. + pub fn from_pip_arg(no_binary: PackageNameSpecifier) -> Self { + Self::from_pip_args(vec![no_binary]) } /// Combine a set of [`NoBinary`] values. @@ -188,7 +211,22 @@ pub enum NoBuild { impl NoBuild { /// Determine the build strategy to use for the given arguments. - pub fn from_args(only_binary: Vec, no_build: bool) -> Self { + pub fn from_args(no_build: Option, no_build_package: Vec) -> Self { + match no_build { + Some(true) => Self::All, + Some(false) => Self::None, + None => { + if no_build_package.is_empty() { + Self::None + } else { + Self::Packages(no_build_package) + } + } + } + } + + /// Determine the build strategy to use for the given arguments from the pip CLI. + pub fn from_pip_args(only_binary: Vec, no_build: bool) -> Self { if no_build { Self::All } else { @@ -201,9 +239,9 @@ impl NoBuild { } } - /// Determine the build strategy to use for the given argument. - pub fn from_arg(no_build: PackageNameSpecifier) -> Self { - Self::from_args(vec![no_build], false) + /// Determine the build strategy to use for the given argument from the pip CLI. + pub fn from_pip_arg(no_build: PackageNameSpecifier) -> Self { + Self::from_pip_args(vec![no_build], false) } /// Combine a set of [`NoBuild`] values. @@ -310,23 +348,23 @@ mod tests { #[test] fn no_build_from_args() -> Result<(), Error> { assert_eq!( - NoBuild::from_args(vec![PackageNameSpecifier::from_str(":all:")?], false), + NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":all:")?], false), NoBuild::All, ); assert_eq!( - NoBuild::from_args(vec![PackageNameSpecifier::from_str(":all:")?], true), + NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":all:")?], true), NoBuild::All, ); assert_eq!( - NoBuild::from_args(vec![PackageNameSpecifier::from_str(":none:")?], true), + NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":none:")?], true), NoBuild::All, ); assert_eq!( - NoBuild::from_args(vec![PackageNameSpecifier::from_str(":none:")?], false), + NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":none:")?], false), NoBuild::None, ); assert_eq!( - NoBuild::from_args( + NoBuild::from_pip_args( vec![ PackageNameSpecifier::from_str("foo")?, PackageNameSpecifier::from_str("bar")? @@ -339,7 +377,7 @@ mod tests { ]), ); assert_eq!( - NoBuild::from_args( + NoBuild::from_pip_args( vec![ PackageNameSpecifier::from_str("test")?, PackageNameSpecifier::All @@ -349,7 +387,7 @@ mod tests { NoBuild::All, ); assert_eq!( - NoBuild::from_args( + NoBuild::from_pip_args( vec![ PackageNameSpecifier::from_str("foo")?, PackageNameSpecifier::from_str(":none:")?, diff --git a/crates/uv-configuration/src/name_specifiers.rs b/crates/uv-configuration/src/name_specifiers.rs index a554e2560..25b766188 100644 --- a/crates/uv-configuration/src/name_specifiers.rs +++ b/crates/uv-configuration/src/name_specifiers.rs @@ -2,6 +2,9 @@ use std::str::FromStr; use pep508_rs::PackageName; +/// A specifier used for (e.g.) pip's `--no-binary` flag. +/// +/// This is a superset of the package name format, allowing for special values `:all:` and `:none:`. #[derive(Debug, Clone)] pub enum PackageNameSpecifier { All, @@ -85,10 +88,9 @@ impl schemars::JsonSchema for PackageNameSpecifier { } } -/// Package name specification. +/// A repeated specifier used for (e.g.) pip's `--no-binary` flag. /// -/// Consumes both package names and selection directives for compatibility with pip flags -/// such as `--no-binary`. +/// This is a superset of the package name format, allowing for special values `:all:` and `:none:`. #[derive(Debug, Clone)] pub enum PackageNameSpecifiers { All, diff --git a/crates/uv-settings/src/combine.rs b/crates/uv-settings/src/combine.rs index bb944e63f..35593b49b 100644 --- a/crates/uv-settings/src/combine.rs +++ b/crates/uv-settings/src/combine.rs @@ -81,6 +81,10 @@ impl Combine for ResolverInstallerOptions { upgrade_package: self.upgrade_package.combine(other.upgrade_package), reinstall: self.reinstall.combine(other.reinstall), reinstall_package: self.reinstall_package.combine(other.reinstall_package), + no_build: self.no_build.combine(other.no_build), + no_build_package: self.no_build_package.combine(other.no_build_package), + no_binary: self.no_binary.combine(other.no_binary), + no_binary_package: self.no_binary_package.combine(other.no_binary_package), } } } diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 5b7c99238..2956a8999 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -77,6 +77,10 @@ pub struct InstallerOptions { pub compile_bytecode: Option, pub reinstall: Option, pub reinstall_package: Option>, + pub no_build: Option, + pub no_build_package: Option>, + pub no_binary: Option, + pub no_binary_package: Option>, } /// Settings relevant to all resolver operations. @@ -98,6 +102,10 @@ pub struct ResolverOptions { pub link_mode: Option, pub upgrade: Option, pub upgrade_package: Option>, + pub no_build: Option, + pub no_build_package: Option>, + pub no_binary: Option, + pub no_binary_package: Option>, } /// Shared settings, relevant to all operations that must resolve and install dependencies. The @@ -123,6 +131,10 @@ pub struct ResolverInstallerOptions { pub upgrade_package: Option>, pub reinstall: Option, pub reinstall_package: Option>, + pub no_build: Option, + pub no_build_package: Option>, + pub no_binary: Option, + pub no_binary_package: Option>, } /// A `[tool.uv.pip]` section. @@ -180,132 +192,3 @@ pub struct PipOptions { pub concurrent_builds: Option, pub concurrent_installs: Option, } - -impl Options { - /// Return the `pip` section, with any top-level options merged in. If options are repeated - /// between the top-level and the `pip` section, the `pip` options are preferred. - /// - /// For example, prefers `tool.uv.pip.index-url` over `tool.uv.index-url`. - pub fn pip(self) -> PipOptions { - let PipOptions { - python, - system, - break_system_packages, - target, - prefix, - index_url, - extra_index_url, - no_index, - find_links, - index_strategy, - keyring_provider, - no_build, - no_binary, - only_binary, - no_build_isolation, - strict, - extra, - all_extras, - no_deps, - resolution, - prerelease, - output_file, - no_strip_extras, - no_annotate, - no_header, - custom_compile_command, - generate_hashes, - legacy_setup_py, - config_settings, - python_version, - python_platform, - exclude_newer, - no_emit_package, - emit_index_url, - emit_find_links, - emit_marker_expression, - emit_index_annotation, - annotation_style, - link_mode, - compile_bytecode, - require_hashes, - upgrade, - upgrade_package, - reinstall, - reinstall_package, - concurrent_builds, - concurrent_downloads, - concurrent_installs, - } = self.pip.unwrap_or_default(); - - let ResolverInstallerOptions { - index_url: top_level_index_url, - extra_index_url: top_level_extra_index_url, - no_index: top_level_no_index, - find_links: top_level_find_links, - index_strategy: top_level_index_strategy, - keyring_provider: top_level_keyring_provider, - resolution: top_level_resolution, - prerelease: top_level_prerelease, - config_settings: top_level_config_settings, - exclude_newer: top_level_exclude_newer, - link_mode: top_level_link_mode, - compile_bytecode: top_level_compile_bytecode, - upgrade: top_level_upgrade, - upgrade_package: top_level_upgrade_package, - reinstall: top_level_reinstall, - reinstall_package: top_level_reinstall_package, - } = self.top_level; - - PipOptions { - python, - system, - break_system_packages, - target, - prefix, - index_url: index_url.or(top_level_index_url), - extra_index_url: extra_index_url.or(top_level_extra_index_url), - no_index: no_index.or(top_level_no_index), - find_links: find_links.or(top_level_find_links), - index_strategy: index_strategy.or(top_level_index_strategy), - keyring_provider: keyring_provider.or(top_level_keyring_provider), - no_build, - no_binary, - only_binary, - no_build_isolation, - strict, - extra, - all_extras, - no_deps, - resolution: resolution.or(top_level_resolution), - prerelease: prerelease.or(top_level_prerelease), - output_file, - no_strip_extras, - no_annotate, - no_header, - custom_compile_command, - generate_hashes, - legacy_setup_py, - config_settings: config_settings.or(top_level_config_settings), - python_version, - python_platform, - exclude_newer: exclude_newer.or(top_level_exclude_newer), - no_emit_package, - emit_index_url, - emit_find_links, - emit_marker_expression, - emit_index_annotation, - annotation_style, - link_mode: link_mode.or(top_level_link_mode), - compile_bytecode: compile_bytecode.or(top_level_compile_bytecode), - require_hashes, - upgrade: upgrade.or(top_level_upgrade), - upgrade_package: upgrade_package.or(top_level_upgrade_package), - reinstall: reinstall.or(top_level_reinstall), - reinstall_package: reinstall_package.or(top_level_reinstall_package), - concurrent_builds, - concurrent_downloads, - concurrent_installs, - } - } -} diff --git a/crates/uv/src/cli.rs b/crates/uv/src/cli.rs index 1aafd9214..0c6ebc454 100644 --- a/crates/uv/src/cli.rs +++ b/crates/uv/src/cli.rs @@ -1495,6 +1495,9 @@ pub(crate) struct RunArgs { #[command(flatten)] pub(crate) installer: ResolverInstallerArgs, + #[command(flatten)] + pub(crate) build: BuildArgs, + #[command(flatten)] pub(crate) refresh: RefreshArgs, @@ -1544,6 +1547,9 @@ pub(crate) struct SyncArgs { #[command(flatten)] pub(crate) installer: InstallerArgs, + #[command(flatten)] + pub(crate) build: BuildArgs, + #[command(flatten)] pub(crate) refresh: RefreshArgs, @@ -1568,6 +1574,9 @@ pub(crate) struct LockArgs { #[command(flatten)] pub(crate) resolver: ResolverArgs, + #[command(flatten)] + pub(crate) build: BuildArgs, + #[command(flatten)] pub(crate) refresh: RefreshArgs, @@ -1668,6 +1677,9 @@ pub(crate) struct ToolRunArgs { #[command(flatten)] pub(crate) installer: ResolverInstallerArgs, + #[command(flatten)] + pub(crate) build: BuildArgs, + #[command(flatten)] pub(crate) refresh: RefreshArgs, @@ -1790,6 +1802,39 @@ pub(crate) struct RefreshArgs { pub(crate) refresh_package: Vec, } +#[derive(Args)] +#[allow(clippy::struct_excessive_bools)] +pub(crate) struct BuildArgs { + /// Don't build source distributions. + /// + /// When enabled, resolving will not run arbitrary code. The cached wheels of already-built + /// source distributions will be reused, but operations that require building distributions will + /// exit with an error. + #[arg(long, overrides_with("build"))] + pub(crate) no_build: bool, + + #[arg(long, overrides_with("no_build"), hide = true)] + pub(crate) build: bool, + + /// Don't build source distributions for a specific package. + #[arg(long)] + pub(crate) no_build_package: Vec, + + /// Don't install pre-built wheels. + /// + /// The given packages will be installed from a source distribution. The resolver + /// will still use pre-built wheels for metadata. + #[arg(long, overrides_with("binary"))] + pub(crate) no_binary: bool, + + #[arg(long, overrides_with("no_binary"), hide = true)] + pub(crate) binary: bool, + + /// Don't install pre-built wheels for a specific package. + #[arg(long)] + pub(crate) no_binary_package: Vec, +} + /// Arguments that are used by commands that need to install (but not resolve) packages. #[derive(Args)] #[allow(clippy::struct_excessive_bools)] diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index a8d56da04..9a1f4e0c0 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -22,7 +22,7 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, IndexStrategy, - NoBinary, NoBuild, Overrides, PreviewMode, SetupPyStrategy, Upgrade, + Overrides, PreviewMode, SetupPyStrategy, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::BuildDispatch; @@ -80,8 +80,7 @@ pub(crate) async fn pip_compile( config_settings: ConfigSettings, connectivity: Connectivity, no_build_isolation: bool, - no_build: NoBuild, - no_binary: NoBinary, + build_options: BuildOptions, python_version: Option, python_platform: Option, exclude_newer: Option, @@ -123,8 +122,8 @@ pub(crate) async fn pip_compile( extra_index_urls, no_index, find_links, - no_binary: specified_no_binary, - no_build: specified_no_build, + no_binary, + no_build, } = RequirementsSpecification::from_sources( requirements, constraints, @@ -254,10 +253,8 @@ pub(crate) async fn pip_compile( let preferences = read_requirements_txt(output_file, &upgrade).await?; let git = GitResolver::default(); - // Combine the `--no-binary` and `--no-build` flags. - let no_binary = no_binary.combine(specified_no_binary); - let no_build = no_build.combine(specified_no_build); - let build_options = BuildOptions::new(no_binary, no_build); + // Combine the `--no-binary` and `--no-build` flags from the requirements files. + let build_options = build_options.combine(no_binary, no_build); // Resolve the flat indexes from `--find-links`. let flat_index = { diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index 3d7b23eba..768b4a114 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -12,8 +12,8 @@ use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, IndexStrategy, NoBinary, - NoBuild, PreviewMode, Reinstall, SetupPyStrategy, Upgrade, + BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, IndexStrategy, PreviewMode, + Reinstall, SetupPyStrategy, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::BuildDispatch; @@ -56,8 +56,7 @@ pub(crate) async fn pip_install( connectivity: Connectivity, config_settings: &ConfigSettings, no_build_isolation: bool, - no_build: NoBuild, - no_binary: NoBinary, + build_options: BuildOptions, python_version: Option, python_platform: Option, strict: bool, @@ -92,8 +91,8 @@ pub(crate) async fn pip_install( extra_index_urls, no_index, find_links, - no_binary: specified_no_binary, - no_build: specified_no_build, + no_binary, + no_build, extras: _, } = operations::read_requirements( requirements, @@ -263,10 +262,8 @@ pub(crate) async fn pip_install( .platform(interpreter.platform()) .build(); - // Combine the `--no-binary` and `--no-build` flags. - let no_binary = no_binary.combine(specified_no_binary); - let no_build = no_build.combine(specified_no_build); - let build_options = BuildOptions::new(no_binary, no_build); + // Combine the `--no-binary` and `--no-build` flags from the requirements files. + let build_options = build_options.combine(no_binary, no_build); // Resolve the flat indexes from `--find-links`. let flat_index = { diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index eb80f9bf5..9968e87a8 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -11,8 +11,8 @@ use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, IndexStrategy, NoBinary, - NoBuild, PreviewMode, Reinstall, SetupPyStrategy, Upgrade, + BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, IndexStrategy, PreviewMode, + Reinstall, SetupPyStrategy, Upgrade, }; use uv_configuration::{KeyringProviderType, TargetTriple}; use uv_dispatch::BuildDispatch; @@ -48,8 +48,7 @@ pub(crate) async fn pip_sync( connectivity: Connectivity, config_settings: &ConfigSettings, no_build_isolation: bool, - no_build: NoBuild, - no_binary: NoBinary, + build_options: BuildOptions, python_version: Option, python_platform: Option, strict: bool, @@ -90,8 +89,8 @@ pub(crate) async fn pip_sync( extra_index_urls, no_index, find_links, - no_binary: specified_no_binary, - no_build: specified_no_build, + no_binary, + no_build, extras: _, } = operations::read_requirements( requirements, @@ -206,10 +205,8 @@ pub(crate) async fn pip_sync( .platform(interpreter.platform()) .build(); - // Combine the `--no-binary` and `--no-build` flags. - let no_binary = no_binary.combine(specified_no_binary); - let no_build = no_build.combine(specified_no_build); - let build_options = BuildOptions::new(no_binary, no_build); + // Combine the `--no-binary` and `--no-build` flags from the requirements files. + let build_options = build_options.combine(no_binary, no_build); // Resolve the flat indexes from `--find-links`. let flat_index = { diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 1894feeee..224af3f86 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -64,6 +64,7 @@ pub(crate) async fn add( &settings.config_setting, settings.exclude_newer.as_ref(), &settings.link_mode, + &settings.build_options, preview, connectivity, concurrency, @@ -93,6 +94,7 @@ pub(crate) async fn add( &settings.config_setting, &settings.link_mode, &settings.compile_bytecode, + &settings.build_options, preview, connectivity, concurrency, diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index f6ddda3e8..5aadc789a 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -62,6 +62,7 @@ pub(crate) async fn lock( &settings.config_setting, settings.exclude_newer.as_ref(), &settings.link_mode, + &settings.build_options, preview, connectivity, concurrency, @@ -98,6 +99,7 @@ pub(super) async fn do_lock( config_setting: &ConfigSettings, exclude_newer: Option<&ExcludeNewer>, link_mode: &LinkMode, + build_options: &BuildOptions, preview: PreviewMode, connectivity: Connectivity, concurrency: Concurrency, @@ -161,7 +163,6 @@ pub(super) async fn do_lock( // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. let build_isolation = BuildIsolation::default(); - let build_options = BuildOptions::default(); let extras = ExtrasSpecification::default(); let setup_py = SetupPyStrategy::default(); @@ -169,7 +170,7 @@ pub(super) async fn do_lock( let flat_index = { let client = FlatIndexClient::new(&client, cache); let entries = client.fetch(index_locations.flat_index()).await?; - FlatIndex::from_entries(entries, None, &hasher, &build_options) + FlatIndex::from_entries(entries, None, &hasher, build_options) }; // If an existing lockfile exists, build up a set of preferences. @@ -192,7 +193,7 @@ pub(super) async fn do_lock( config_setting, build_isolation, *link_mode, - &build_options, + build_options, concurrency, preview, ); diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index a611bbece..52014eed2 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -9,9 +9,7 @@ use distribution_types::Resolution; use pep440_rs::Version; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; -use uv_configuration::{ - BuildOptions, Concurrency, ExtrasSpecification, PreviewMode, SetupPyStrategy, -}; +use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode, SetupPyStrategy}; use uv_dispatch::BuildDispatch; use uv_distribution::Workspace; use uv_fs::Simplified; @@ -280,6 +278,7 @@ pub(crate) async fn update_environment( compile_bytecode, upgrade, reinstall, + build_options, } = settings; let client_builder = BaseClientBuilder::new() @@ -348,7 +347,6 @@ pub(crate) async fn update_environment( // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. let build_isolation = BuildIsolation::default(); - let build_options = BuildOptions::default(); let dev = Vec::default(); let dry_run = false; let extras = ExtrasSpecification::default(); @@ -360,7 +358,7 @@ pub(crate) async fn update_environment( let flat_index = { let client = FlatIndexClient::new(&client, cache); let entries = client.fetch(index_locations.flat_index()).await?; - FlatIndex::from_entries(entries, Some(tags), &hasher, &build_options) + FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; // Create a build dispatch. @@ -377,7 +375,7 @@ pub(crate) async fn update_environment( config_setting, build_isolation, *link_mode, - &build_options, + build_options, concurrency, preview, ); @@ -436,7 +434,7 @@ pub(crate) async fn update_environment( config_setting, build_isolation, *link_mode, - &build_options, + build_options, concurrency, preview, ) @@ -448,7 +446,7 @@ pub(crate) async fn update_environment( site_packages, pip::operations::Modifications::Sufficient, reinstall, - &build_options, + build_options, *link_mode, *compile_bytecode, index_locations, diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 707d2a686..03051bbb5 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -66,6 +66,7 @@ pub(crate) async fn remove( &settings.config_setting, settings.exclude_newer.as_ref(), &settings.link_mode, + &settings.build_options, preview, connectivity, concurrency, @@ -95,6 +96,7 @@ pub(crate) async fn remove( &settings.config_setting, &settings.link_mode, &settings.compile_bytecode, + &settings.build_options, preview, connectivity, concurrency, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index e24c2abfb..744727636 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -75,6 +75,7 @@ pub(crate) async fn run( &settings.config_setting, settings.exclude_newer.as_ref(), &settings.link_mode, + &settings.build_options, preview, connectivity, concurrency, @@ -97,6 +98,7 @@ pub(crate) async fn run( &settings.config_setting, &settings.link_mode, &settings.compile_bytecode, + &settings.build_options, preview, connectivity, concurrency, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 4171a8ce1..5fbae7a24 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -72,6 +72,7 @@ pub(crate) async fn sync( &settings.config_setting, &settings.link_mode, &settings.compile_bytecode, + &settings.build_options, preview, connectivity, concurrency, @@ -100,6 +101,7 @@ pub(super) async fn do_sync( config_setting: &ConfigSettings, link_mode: &LinkMode, compile_bytecode: &bool, + build_options: &BuildOptions, preview: PreviewMode, connectivity: Connectivity, concurrency: Concurrency, @@ -150,7 +152,6 @@ pub(super) async fn do_sync( // TODO(charlie): These are all default values. We should consider whether we want to make them // optional on the downstream APIs. let build_isolation = BuildIsolation::default(); - let build_options = BuildOptions::default(); let dry_run = false; let hasher = HashStrategy::default(); let setup_py = SetupPyStrategy::default(); @@ -159,7 +160,7 @@ pub(super) async fn do_sync( let flat_index = { let client = FlatIndexClient::new(&client, cache); let entries = client.fetch(index_locations.flat_index()).await?; - FlatIndex::from_entries(entries, Some(tags), &hasher, &build_options) + FlatIndex::from_entries(entries, Some(tags), &hasher, build_options) }; // Create a build dispatch. @@ -176,7 +177,7 @@ pub(super) async fn do_sync( config_setting, build_isolation, *link_mode, - &build_options, + build_options, concurrency, preview, ); @@ -189,7 +190,7 @@ pub(super) async fn do_sync( site_packages, Modifications::Sufficient, reinstall, - &build_options, + build_options, *link_mode, *compile_bytecode, index_locations, diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 1896c23ed..a69fd47ef 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -272,8 +272,7 @@ async fn run() -> Result { args.settings.config_setting, globals.connectivity, args.settings.no_build_isolation, - args.settings.no_build, - args.settings.no_binary, + args.settings.build_options, args.settings.python_version, args.settings.python_platform, args.settings.exclude_newer, @@ -332,8 +331,7 @@ async fn run() -> Result { globals.connectivity, &args.settings.config_setting, args.settings.no_build_isolation, - args.settings.no_build, - args.settings.no_binary, + args.settings.build_options, args.settings.python_version, args.settings.python_platform, args.settings.strict, @@ -411,8 +409,7 @@ async fn run() -> Result { globals.connectivity, &args.settings.config_setting, args.settings.no_build_isolation, - args.settings.no_build, - args.settings.no_binary, + args.settings.build_options, args.settings.python_version, args.settings.python_platform, args.settings.strict, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index d303da1a2..1a432c10b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -12,8 +12,9 @@ use pypi_types::Requirement; use uv_cache::{CacheArgs, Refresh}; use uv_client::Connectivity; use uv_configuration::{ - Concurrency, ConfigSettings, ExtrasSpecification, IndexStrategy, KeyringProviderType, NoBinary, - NoBuild, PreviewMode, Reinstall, SetupPyStrategy, TargetTriple, Upgrade, + BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, IndexStrategy, + KeyringProviderType, NoBinary, NoBuild, PreviewMode, Reinstall, SetupPyStrategy, TargetTriple, + Upgrade, }; use uv_normalize::PackageName; use uv_resolver::{AnnotationStyle, DependencyMode, ExcludeNewer, PreReleaseMode, ResolutionMode}; @@ -24,10 +25,10 @@ use uv_settings::{ use uv_toolchain::{Prefix, PythonVersion, Target}; use crate::cli::{ - AddArgs, ColorChoice, GlobalArgs, IndexArgs, InstallerArgs, LockArgs, Maybe, PipCheckArgs, - PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, - PipUninstallArgs, RefreshArgs, RemoveArgs, ResolverArgs, ResolverInstallerArgs, RunArgs, - SyncArgs, ToolRunArgs, ToolchainInstallArgs, ToolchainListArgs, VenvArgs, + AddArgs, BuildArgs, ColorChoice, GlobalArgs, IndexArgs, InstallerArgs, LockArgs, Maybe, + PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, + PipSyncArgs, PipUninstallArgs, RefreshArgs, RemoveArgs, ResolverArgs, ResolverInstallerArgs, + RunArgs, SyncArgs, ToolRunArgs, ToolchainInstallArgs, ToolchainListArgs, VenvArgs, }; use crate::commands::ListFormat; @@ -142,6 +143,7 @@ impl RunSettings { args, with, installer, + build, refresh, python, package, @@ -160,7 +162,7 @@ impl RunSettings { package, refresh: Refresh::from(refresh), settings: ResolverInstallerSettings::combine( - ResolverInstallerOptions::from(installer), + resolver_installer_options(installer, build), filesystem, ), } @@ -190,6 +192,7 @@ impl ToolRunSettings { from, with, installer, + build, refresh, python, } = args; @@ -202,7 +205,7 @@ impl ToolRunSettings { python, refresh: Refresh::from(refresh), settings: ResolverInstallerSettings::combine( - ResolverInstallerOptions::from(installer), + resolver_installer_options(installer, build), filesystem, ), } @@ -292,6 +295,7 @@ impl SyncSettings { dev, no_dev, installer, + build, refresh, python, } = args; @@ -304,7 +308,7 @@ impl SyncSettings { dev: flag(dev, no_dev).unwrap_or(true), python, refresh: Refresh::from(refresh), - settings: InstallerSettings::combine(InstallerOptions::from(installer), filesystem), + settings: InstallerSettings::combine(installer_options(installer, build), filesystem), } } } @@ -324,6 +328,7 @@ impl LockSettings { pub(crate) fn resolve(args: LockArgs, filesystem: Option) -> Self { let LockArgs { resolver, + build, refresh, python, } = args; @@ -331,7 +336,7 @@ impl LockSettings { Self { python, refresh: Refresh::from(refresh), - settings: ResolverSettings::combine(ResolverOptions::from(resolver), filesystem), + settings: ResolverSettings::combine(resolver_options(resolver, build), filesystem), } } } @@ -955,6 +960,7 @@ pub(crate) struct InstallerSettings { pub(crate) link_mode: LinkMode, pub(crate) compile_bytecode: bool, pub(crate) reinstall: Reinstall, + pub(crate) build_options: BuildOptions, } impl InstallerSettings { @@ -977,6 +983,10 @@ impl InstallerSettings { upgrade_package: _, reinstall, reinstall_package, + no_build, + no_build_package, + no_binary, + no_binary_package, } = filesystem .map(FilesystemOptions::into_options) .map(|options| options.top_level) @@ -1009,11 +1019,25 @@ impl InstallerSettings { .combine(compile_bytecode) .unwrap_or_default(), reinstall: Reinstall::from_args( - args.reinstall.or(reinstall), + args.reinstall.combine(reinstall), args.reinstall_package - .or(reinstall_package) + .combine(reinstall_package) .unwrap_or_default(), ), + build_options: BuildOptions::new( + NoBinary::from_args( + args.no_binary.combine(no_binary), + args.no_binary_package + .combine(no_binary_package) + .unwrap_or_default(), + ), + NoBuild::from_args( + args.no_build.combine(no_build), + args.no_build_package + .combine(no_build_package) + .unwrap_or_default(), + ), + ), } } } @@ -1034,6 +1058,7 @@ pub(crate) struct ResolverSettings { pub(crate) exclude_newer: Option, pub(crate) link_mode: LinkMode, pub(crate) upgrade: Upgrade, + pub(crate) build_options: BuildOptions, } impl ResolverSettings { @@ -1056,6 +1081,10 @@ impl ResolverSettings { upgrade_package, reinstall: _, reinstall_package: _, + no_build, + no_build_package, + no_binary, + no_binary_package, } = filesystem .map(FilesystemOptions::into_options) .map(|options| options.top_level) @@ -1087,8 +1116,24 @@ impl ResolverSettings { exclude_newer: args.exclude_newer.combine(exclude_newer), link_mode: args.link_mode.combine(link_mode).unwrap_or_default(), upgrade: Upgrade::from_args( - args.upgrade.or(upgrade), - args.upgrade_package.or(upgrade_package).unwrap_or_default(), + args.upgrade.combine(upgrade), + args.upgrade_package + .combine(upgrade_package) + .unwrap_or_default(), + ), + build_options: BuildOptions::new( + NoBinary::from_args( + args.no_binary.combine(no_binary), + args.no_binary_package + .combine(no_binary_package) + .unwrap_or_default(), + ), + NoBuild::from_args( + args.no_build.combine(no_build), + args.no_build_package + .combine(no_build_package) + .unwrap_or_default(), + ), ), } } @@ -1113,6 +1158,7 @@ pub(crate) struct ResolverInstallerSettings { pub(crate) compile_bytecode: bool, pub(crate) upgrade: Upgrade, pub(crate) reinstall: Reinstall, + pub(crate) build_options: BuildOptions, } impl ResolverInstallerSettings { @@ -1138,6 +1184,10 @@ impl ResolverInstallerSettings { upgrade_package, reinstall, reinstall_package, + no_build, + no_build_package, + no_binary, + no_binary_package, } = filesystem .map(FilesystemOptions::into_options) .map(|options| options.top_level) @@ -1173,15 +1223,31 @@ impl ResolverInstallerSettings { .combine(compile_bytecode) .unwrap_or_default(), upgrade: Upgrade::from_args( - args.upgrade.or(upgrade), - args.upgrade_package.or(upgrade_package).unwrap_or_default(), + args.upgrade.combine(upgrade), + args.upgrade_package + .combine(upgrade_package) + .unwrap_or_default(), ), reinstall: Reinstall::from_args( - args.reinstall.or(reinstall), + args.reinstall.combine(reinstall), args.reinstall_package - .or(reinstall_package) + .combine(reinstall_package) .unwrap_or_default(), ), + build_options: BuildOptions::new( + NoBinary::from_args( + args.no_binary.combine(no_binary), + args.no_binary_package + .combine(no_binary_package) + .unwrap_or_default(), + ), + NoBuild::from_args( + args.no_build.combine(no_build), + args.no_build_package + .combine(no_build_package) + .unwrap_or_default(), + ), + ), } } } @@ -1202,9 +1268,8 @@ pub(crate) struct PipSettings { pub(crate) prefix: Option, pub(crate) index_strategy: IndexStrategy, pub(crate) keyring_provider: KeyringProviderType, - pub(crate) no_binary: NoBinary, - pub(crate) no_build: NoBuild, pub(crate) no_build_isolation: bool, + pub(crate) build_options: BuildOptions, pub(crate) strict: bool, pub(crate) dependency_mode: DependencyMode, pub(crate) resolution: ResolutionMode, @@ -1237,6 +1302,10 @@ pub(crate) struct PipSettings { impl PipSettings { /// Resolve the [`PipSettings`] from the CLI and filesystem configuration. pub(crate) fn combine(args: PipOptions, filesystem: Option) -> Self { + let Options { top_level, pip, .. } = filesystem + .map(FilesystemOptions::into_options) + .unwrap_or_default(); + let PipOptions { python, system, @@ -1286,10 +1355,51 @@ impl PipSettings { concurrent_builds, concurrent_downloads, concurrent_installs, - } = filesystem - .map(FilesystemOptions::into_options) - .map(Options::pip) - .unwrap_or_default(); + } = pip.unwrap_or_default(); + + let ResolverInstallerOptions { + index_url: top_level_index_url, + extra_index_url: top_level_extra_index_url, + no_index: top_level_no_index, + find_links: top_level_find_links, + index_strategy: top_level_index_strategy, + keyring_provider: top_level_keyring_provider, + resolution: top_level_resolution, + prerelease: top_level_prerelease, + config_settings: top_level_config_settings, + exclude_newer: top_level_exclude_newer, + link_mode: top_level_link_mode, + compile_bytecode: top_level_compile_bytecode, + upgrade: top_level_upgrade, + upgrade_package: top_level_upgrade_package, + reinstall: top_level_reinstall, + reinstall_package: top_level_reinstall_package, + no_build: top_level_no_build, + no_build_package: top_level_no_build_package, + no_binary: top_level_no_binary, + no_binary_package: top_level_no_binary_package, + } = top_level; + + // Merge the top-level options (`tool.uv`) with the pip-specific options (`tool.uv.pip`), + // preferring the latter. + // + // For example, prefer `tool.uv.pip.index-url` over `tool.uv.index-url`. + let index_url = index_url.combine(top_level_index_url); + let extra_index_url = extra_index_url.combine(top_level_extra_index_url); + let no_index = no_index.combine(top_level_no_index); + let find_links = find_links.combine(top_level_find_links); + let index_strategy = index_strategy.combine(top_level_index_strategy); + let keyring_provider = keyring_provider.combine(top_level_keyring_provider); + let resolution = resolution.combine(top_level_resolution); + let prerelease = prerelease.combine(top_level_prerelease); + let config_settings = config_settings.combine(top_level_config_settings); + let exclude_newer = exclude_newer.combine(top_level_exclude_newer); + let link_mode = link_mode.combine(top_level_link_mode); + let compile_bytecode = compile_bytecode.combine(top_level_compile_bytecode); + let upgrade = upgrade.combine(top_level_upgrade); + let upgrade_package = upgrade_package.combine(top_level_upgrade_package); + let reinstall = reinstall.combine(top_level_reinstall); + let reinstall_package = reinstall_package.combine(top_level_reinstall_package); Self { index_locations: IndexLocations::new( @@ -1348,10 +1458,6 @@ impl PipSettings { .no_build_isolation .combine(no_build_isolation) .unwrap_or_default(), - no_build: NoBuild::from_args( - args.only_binary.combine(only_binary).unwrap_or_default(), - args.no_build.combine(no_build).unwrap_or_default(), - ), config_setting: args .config_settings .combine(config_settings) @@ -1392,20 +1498,21 @@ impl PipSettings { .unwrap_or_default(), target: args.target.combine(target).map(Target::from), prefix: args.prefix.combine(prefix).map(Prefix::from), - no_binary: NoBinary::from_args(args.no_binary.combine(no_binary).unwrap_or_default()), compile_bytecode: args .compile_bytecode .combine(compile_bytecode) .unwrap_or_default(), strict: args.strict.combine(strict).unwrap_or_default(), upgrade: Upgrade::from_args( - args.upgrade.or(upgrade), - args.upgrade_package.or(upgrade_package).unwrap_or_default(), + args.upgrade.combine(upgrade), + args.upgrade_package + .combine(upgrade_package) + .unwrap_or_default(), ), reinstall: Reinstall::from_args( - args.reinstall.or(reinstall), + args.reinstall.combine(reinstall), args.reinstall_package - .or(reinstall_package) + .combine(reinstall_package) .unwrap_or_default(), ), concurrency: Concurrency { @@ -1425,6 +1532,21 @@ impl PipSettings { .map(NonZeroUsize::get) .unwrap_or_else(Concurrency::threads), }, + build_options: BuildOptions::new( + NoBinary::from_pip_args(args.no_binary.combine(no_binary).unwrap_or_default()) + .combine(NoBinary::from_args( + top_level_no_binary, + top_level_no_binary_package.unwrap_or_default(), + )), + NoBuild::from_pip_args( + args.only_binary.combine(only_binary).unwrap_or_default(), + args.no_build.combine(no_build).unwrap_or_default(), + ) + .combine(NoBuild::from_args( + top_level_no_build, + top_level_no_build_package.unwrap_or_default(), + )), + ), } } } @@ -1507,15 +1629,6 @@ impl From for PipOptions { } = args; Self { - index_url: index_args.index_url.and_then(Maybe::into_option), - extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { - extra_index_urls - .into_iter() - .filter_map(Maybe::into_option) - .collect() - }), - no_index: Some(index_args.no_index), - find_links: index_args.find_links, upgrade: flag(upgrade, no_upgrade), upgrade_package: Some(upgrade_package), index_strategy, @@ -1530,7 +1643,7 @@ impl From for PipOptions { .map(|config_settings| config_settings.into_iter().collect::()), exclude_newer, link_mode, - ..PipOptions::default() + ..PipOptions::from(index_args) } } } @@ -1551,15 +1664,6 @@ impl From for PipOptions { } = args; Self { - index_url: index_args.index_url.and_then(Maybe::into_option), - extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { - extra_index_urls - .into_iter() - .filter_map(Maybe::into_option) - .collect() - }), - no_index: Some(index_args.no_index), - find_links: index_args.find_links, reinstall: flag(reinstall, no_reinstall), reinstall_package: Some(reinstall_package), index_strategy, @@ -1568,7 +1672,7 @@ impl From for PipOptions { .map(|config_settings| config_settings.into_iter().collect::()), link_mode, compile_bytecode: flag(compile_bytecode, no_compile_bytecode), - ..PipOptions::default() + ..PipOptions::from(index_args) } } } @@ -1596,151 +1700,6 @@ impl From for PipOptions { } = args; Self { - index_url: index_args.index_url.and_then(Maybe::into_option), - extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { - extra_index_urls - .into_iter() - .filter_map(Maybe::into_option) - .collect() - }), - no_index: Some(index_args.no_index), - find_links: index_args.find_links, - upgrade: flag(upgrade, no_upgrade), - upgrade_package: Some(upgrade_package), - reinstall: flag(reinstall, no_reinstall), - reinstall_package: Some(reinstall_package), - index_strategy, - keyring_provider, - resolution, - prerelease: if pre { - Some(PreReleaseMode::Allow) - } else { - prerelease - }, - config_settings: config_setting - .map(|config_settings| config_settings.into_iter().collect::()), - exclude_newer, - link_mode, - compile_bytecode: flag(compile_bytecode, no_compile_bytecode), - ..PipOptions::default() - } - } -} - -impl From for InstallerOptions { - fn from(args: InstallerArgs) -> Self { - let InstallerArgs { - index_args, - reinstall, - no_reinstall, - reinstall_package, - index_strategy, - keyring_provider, - config_setting, - link_mode, - compile_bytecode, - no_compile_bytecode, - } = args; - - Self { - index_url: index_args.index_url.and_then(Maybe::into_option), - extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { - extra_index_urls - .into_iter() - .filter_map(Maybe::into_option) - .collect() - }), - no_index: Some(index_args.no_index), - find_links: index_args.find_links, - reinstall: flag(reinstall, no_reinstall), - reinstall_package: Some(reinstall_package), - index_strategy, - keyring_provider, - config_settings: config_setting - .map(|config_settings| config_settings.into_iter().collect::()), - link_mode, - compile_bytecode: flag(compile_bytecode, no_compile_bytecode), - } - } -} - -impl From for ResolverOptions { - fn from(args: ResolverArgs) -> Self { - let ResolverArgs { - index_args, - upgrade, - no_upgrade, - upgrade_package, - index_strategy, - keyring_provider, - resolution, - prerelease, - pre, - config_setting, - exclude_newer, - link_mode, - } = args; - - Self { - index_url: index_args.index_url.and_then(Maybe::into_option), - extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { - extra_index_urls - .into_iter() - .filter_map(Maybe::into_option) - .collect() - }), - no_index: Some(index_args.no_index), - find_links: index_args.find_links, - upgrade: flag(upgrade, no_upgrade), - upgrade_package: Some(upgrade_package), - index_strategy, - keyring_provider, - resolution, - prerelease: if pre { - Some(PreReleaseMode::Allow) - } else { - prerelease - }, - config_settings: config_setting - .map(|config_settings| config_settings.into_iter().collect::()), - exclude_newer, - link_mode, - } - } -} - -impl From for ResolverInstallerOptions { - fn from(args: ResolverInstallerArgs) -> Self { - let ResolverInstallerArgs { - index_args, - upgrade, - no_upgrade, - upgrade_package, - reinstall, - no_reinstall, - reinstall_package, - index_strategy, - keyring_provider, - resolution, - prerelease, - pre, - config_setting, - exclude_newer, - link_mode, - compile_bytecode, - no_compile_bytecode, - } = args; - - Self { - index_url: index_args.index_url.and_then(Maybe::into_option), - extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { - extra_index_urls - .into_iter() - .filter_map(Maybe::into_option) - .collect() - }), - no_index: Some(index_args.no_index), - find_links: index_args.find_links, upgrade: flag(upgrade, no_upgrade), upgrade_package: Some(upgrade_package), reinstall: flag(reinstall, no_reinstall), @@ -1758,6 +1717,7 @@ impl From for ResolverInstallerOptions { exclude_newer, link_mode, compile_bytecode: flag(compile_bytecode, no_compile_bytecode), + ..PipOptions::from(index_args) } } } @@ -1779,9 +1739,195 @@ impl From for PipOptions { .filter_map(Maybe::into_option) .collect() }), - no_index: Some(no_index), + no_index: if no_index { Some(true) } else { None }, find_links, ..PipOptions::default() } } } + +/// Construct the [`InstallerOptions`] from the [`InstallerArgs`] and [`BuildArgs`]. +fn installer_options(installer_args: InstallerArgs, build_args: BuildArgs) -> InstallerOptions { + let InstallerArgs { + index_args, + reinstall, + no_reinstall, + reinstall_package, + index_strategy, + keyring_provider, + config_setting, + link_mode, + compile_bytecode, + no_compile_bytecode, + } = installer_args; + + let BuildArgs { + no_build, + build, + no_build_package, + no_binary, + binary, + no_binary_package, + } = build_args; + + InstallerOptions { + index_url: index_args.index_url.and_then(Maybe::into_option), + extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { + extra_index_urls + .into_iter() + .filter_map(Maybe::into_option) + .collect() + }), + no_index: if index_args.no_index { + Some(true) + } else { + None + }, + find_links: index_args.find_links, + reinstall: flag(reinstall, no_reinstall), + reinstall_package: Some(reinstall_package), + index_strategy, + keyring_provider, + config_settings: config_setting + .map(|config_settings| config_settings.into_iter().collect::()), + link_mode, + compile_bytecode: flag(compile_bytecode, no_compile_bytecode), + no_build: flag(no_build, build), + no_build_package: Some(no_build_package), + no_binary: flag(no_binary, binary), + no_binary_package: Some(no_binary_package), + } +} + +/// Construct the [`ResolverOptions`] from the [`ResolverArgs`] and [`BuildArgs`]. +fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> ResolverOptions { + let ResolverArgs { + index_args, + upgrade, + no_upgrade, + upgrade_package, + index_strategy, + keyring_provider, + resolution, + prerelease, + pre, + config_setting, + exclude_newer, + link_mode, + } = resolver_args; + + let BuildArgs { + no_build, + build, + no_build_package, + no_binary, + binary, + no_binary_package, + } = build_args; + + ResolverOptions { + index_url: index_args.index_url.and_then(Maybe::into_option), + extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { + extra_index_urls + .into_iter() + .filter_map(Maybe::into_option) + .collect() + }), + no_index: if index_args.no_index { + Some(true) + } else { + None + }, + find_links: index_args.find_links, + upgrade: flag(upgrade, no_upgrade), + upgrade_package: Some(upgrade_package), + index_strategy, + keyring_provider, + resolution, + prerelease: if pre { + Some(PreReleaseMode::Allow) + } else { + prerelease + }, + config_settings: config_setting + .map(|config_settings| config_settings.into_iter().collect::()), + exclude_newer, + link_mode, + no_build: flag(no_build, build), + no_build_package: Some(no_build_package), + no_binary: flag(no_binary, binary), + no_binary_package: Some(no_binary_package), + } +} + +/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildArgs`]. +fn resolver_installer_options( + resolver_installer_args: ResolverInstallerArgs, + build_args: BuildArgs, +) -> ResolverInstallerOptions { + let ResolverInstallerArgs { + index_args, + upgrade, + no_upgrade, + upgrade_package, + reinstall, + no_reinstall, + reinstall_package, + index_strategy, + keyring_provider, + resolution, + prerelease, + pre, + config_setting, + exclude_newer, + link_mode, + compile_bytecode, + no_compile_bytecode, + } = resolver_installer_args; + + let BuildArgs { + no_build, + build, + no_build_package, + no_binary, + binary, + no_binary_package, + } = build_args; + + ResolverInstallerOptions { + index_url: index_args.index_url.and_then(Maybe::into_option), + extra_index_url: index_args.extra_index_url.map(|extra_index_urls| { + extra_index_urls + .into_iter() + .filter_map(Maybe::into_option) + .collect() + }), + no_index: if index_args.no_index { + Some(true) + } else { + None + }, + find_links: index_args.find_links, + upgrade: flag(upgrade, no_upgrade), + upgrade_package: Some(upgrade_package), + reinstall: flag(reinstall, no_reinstall), + reinstall_package: Some(reinstall_package), + index_strategy, + keyring_provider, + resolution, + prerelease: if pre { + Some(PreReleaseMode::Allow) + } else { + prerelease + }, + config_settings: config_setting + .map(|config_settings| config_settings.into_iter().collect::()), + exclude_newer, + link_mode, + compile_bytecode: flag(compile_bytecode, no_compile_bytecode), + no_build: flag(no_build, build), + no_build_package: Some(no_build_package), + no_binary: flag(no_binary, binary), + no_binary_package: Some(no_binary_package), + } +} diff --git a/crates/uv/tests/show_settings.rs b/crates/uv/tests/show_settings.rs index 716dede6f..8e20e3e29 100644 --- a/crates/uv/tests/show_settings.rs +++ b/crates/uv/tests/show_settings.rs @@ -117,9 +117,11 @@ fn resolve_uv_toml() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: LowestDirect, @@ -241,9 +243,11 @@ fn resolve_uv_toml() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: Highest, @@ -366,9 +370,11 @@ fn resolve_uv_toml() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: Highest, @@ -523,9 +529,11 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: LowestDirect, @@ -626,9 +634,11 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: Highest, @@ -761,9 +771,11 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: LowestDirect, @@ -933,9 +945,11 @@ fn resolve_index_url() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: Highest, @@ -1104,9 +1118,11 @@ fn resolve_index_url() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: Highest, @@ -1238,7 +1254,7 @@ fn resolve_find_links() -> anyhow::Result<()> { }, ), ], - no_index: false, + no_index: true, }, python: None, system: false, @@ -1248,9 +1264,11 @@ fn resolve_find_links() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: Highest, @@ -1373,9 +1391,11 @@ fn resolve_top_level() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: LowestDirect, @@ -1421,7 +1441,7 @@ fn resolve_top_level() -> anyhow::Result<()> { ); // Write out to both the top-level (`tool.uv`) and the pip section (`tool.uv.pip`). The - // `tool.uv.pip` section should take precedence. + // `tool.uv.pip` section should take precedence when combining. pyproject.write_str(indoc::indoc! {r#" [project] name = "example" @@ -1429,9 +1449,11 @@ fn resolve_top_level() -> anyhow::Result<()> { [tool.uv] resolution = "lowest-direct" + extra-index-url = ["https://test.pypi.org/simple"] [tool.uv.pip] resolution = "highest" + extra-index-url = ["https://download.pytorch.org/whl"] "#})?; let requirements_in = context.temp_dir.child("requirements.in"); @@ -1477,7 +1499,52 @@ fn resolve_top_level() -> anyhow::Result<()> { settings: PipSettings { index_locations: IndexLocations { index: None, - extra_index: [], + extra_index: [ + Url( + VerbatimUrl { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "download.pytorch.org", + ), + ), + port: None, + path: "/whl", + query: None, + fragment: None, + }, + given: Some( + "https://download.pytorch.org/whl", + ), + }, + ), + Url( + VerbatimUrl { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "test.pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + given: Some( + "https://test.pypi.org/simple", + ), + }, + ), + ], flat_index: [], no_index: false, }, @@ -1489,9 +1556,11 @@ fn resolve_top_level() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: Highest, @@ -1578,7 +1647,52 @@ fn resolve_top_level() -> anyhow::Result<()> { settings: PipSettings { index_locations: IndexLocations { index: None, - extra_index: [], + extra_index: [ + Url( + VerbatimUrl { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "download.pytorch.org", + ), + ), + port: None, + path: "/whl", + query: None, + fragment: None, + }, + given: Some( + "https://download.pytorch.org/whl", + ), + }, + ), + Url( + VerbatimUrl { + url: Url { + scheme: "https", + cannot_be_a_base: false, + username: "", + password: None, + host: Some( + Domain( + "test.pypi.org", + ), + ), + port: None, + path: "/simple", + query: None, + fragment: None, + }, + given: Some( + "https://test.pypi.org/simple", + ), + }, + ), + ], flat_index: [], no_index: false, }, @@ -1590,9 +1704,11 @@ fn resolve_top_level() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: LowestDirect, @@ -1715,9 +1831,11 @@ fn resolve_user_configuration() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: LowestDirect, @@ -1823,9 +1941,11 @@ fn resolve_user_configuration() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: LowestDirect, @@ -1931,9 +2051,11 @@ fn resolve_user_configuration() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: Highest, @@ -2041,9 +2163,11 @@ fn resolve_user_configuration() -> anyhow::Result<()> { prefix: None, index_strategy: FirstIndex, keyring_provider: Disabled, - no_binary: None, - no_build: None, no_build_isolation: false, + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, strict: false, dependency_mode: Transitive, resolution: LowestDirect, diff --git a/uv.schema.json b/uv.schema.json index 43231d47c..ccce817fe 100644 --- a/uv.schema.json +++ b/uv.schema.json @@ -110,6 +110,36 @@ "null" ] }, + "no-binary": { + "type": [ + "boolean", + "null" + ] + }, + "no-binary-package": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, + "no-build": { + "type": [ + "boolean", + "null" + ] + }, + "no-build-package": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageName" + } + }, "no-cache": { "type": [ "boolean",