Add a setting

This commit is contained in:
Zanie Blue 2025-07-28 15:45:51 -05:00
parent 1480f81712
commit bae72a8c10
15 changed files with 175 additions and 59 deletions

View File

@ -354,6 +354,7 @@ pub fn resolver_options(
no_binary: flag(no_binary, binary, "binary"), no_binary: flag(no_binary, binary, "binary"),
no_binary_package: Some(no_binary_package), no_binary_package: Some(no_binary_package),
no_sources: if no_sources { Some(true) } else { None }, no_sources: if no_sources { Some(true) } else { None },
build_dependency_strategy: None,
} }
} }
@ -480,5 +481,6 @@ pub fn resolver_installer_options(
Some(no_binary_package) Some(no_binary_package)
}, },
no_sources: if no_sources { Some(true) } else { None }, no_sources: if no_sources { Some(true) } else { None },
build_dependency_strategy: None,
} }
} }

View File

@ -1,4 +1,5 @@
pub use authentication::*; pub use authentication::*;
pub use build_dependency_strategy::*;
pub use build_options::*; pub use build_options::*;
pub use concurrency::*; pub use concurrency::*;
pub use config_settings::*; pub use config_settings::*;
@ -24,6 +25,7 @@ pub use trusted_publishing::*;
pub use vcs::*; pub use vcs::*;
mod authentication; mod authentication;
mod build_dependency_strategy;
mod build_options; mod build_options;
mod concurrency; mod concurrency;
mod config_settings; mod config_settings;

View File

@ -4,8 +4,8 @@ use std::path::PathBuf;
use url::Url; use url::Url;
use uv_configuration::{ use uv_configuration::{
ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType, PackageConfigSettings, BuildDependencyStrategy, ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType,
RequiredVersion, TargetTriple, TrustedPublishing, PackageConfigSettings, RequiredVersion, TargetTriple, TrustedPublishing,
}; };
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex}; use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
use uv_install_wheel::LinkMode; use uv_install_wheel::LinkMode;
@ -77,6 +77,7 @@ macro_rules! impl_combine_or {
impl_combine_or!(AddBoundsKind); impl_combine_or!(AddBoundsKind);
impl_combine_or!(AnnotationStyle); impl_combine_or!(AnnotationStyle);
impl_combine_or!(BuildDependencyStrategy);
impl_combine_or!(ExcludeNewer); impl_combine_or!(ExcludeNewer);
impl_combine_or!(ExportFormat); impl_combine_or!(ExportFormat);
impl_combine_or!(ForkStrategy); impl_combine_or!(ForkStrategy);

View File

@ -329,6 +329,7 @@ fn warn_uv_toml_masked_fields(options: &Options) {
no_build_package, no_build_package,
no_binary, no_binary,
no_binary_package, no_binary_package,
build_dependency_strategy: _,
}, },
install_mirrors: install_mirrors:
PythonInstallMirrors { PythonInstallMirrors {

View File

@ -4,8 +4,9 @@ use serde::{Deserialize, Serialize};
use uv_cache_info::CacheKey; use uv_cache_info::CacheKey;
use uv_configuration::{ use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, PackageConfigSettings, BuildDependencyStrategy, ConfigSettings, IndexStrategy, KeyringProviderType,
PackageNameSpecifier, RequiredVersion, TargetTriple, TrustedHost, TrustedPublishing, PackageConfigSettings, PackageNameSpecifier, RequiredVersion, TargetTriple, TrustedHost,
TrustedPublishing,
}; };
use uv_distribution_types::{ use uv_distribution_types::{
Index, IndexUrl, IndexUrlError, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata, Index, IndexUrl, IndexUrlError, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata,
@ -373,6 +374,7 @@ pub struct ResolverOptions {
pub no_build_isolation: Option<bool>, pub no_build_isolation: Option<bool>,
pub no_build_isolation_package: Option<Vec<PackageName>>, pub no_build_isolation_package: Option<Vec<PackageName>>,
pub no_sources: Option<bool>, pub no_sources: Option<bool>,
pub build_dependency_strategy: Option<BuildDependencyStrategy>,
} }
/// Shared settings, relevant to all operations that must resolve and install dependencies. The /// Shared settings, relevant to all operations that must resolve and install dependencies. The
@ -509,6 +511,23 @@ pub struct ResolverInstallerOptions {
"# "#
)] )]
pub keyring_provider: Option<KeyringProviderType>, pub keyring_provider: Option<KeyringProviderType>,
/// The strategy to use when resolving build dependencies for source distributions.
///
/// - `latest`: Use the latest compatible version of each build dependency.
/// - `prefer-locked`: Prefer the versions pinned in the lockfile, if available.
///
/// When set to `prefer-locked`, uv will use the locked versions of packages specified in the
/// lockfile as preferences when resolving build dependencies during source builds. This helps
/// ensure that build environments are consistent with the project's resolved dependencies.
#[option(
default = "\"latest\"",
value_type = "str",
example = r#"
build-dependency-strategy = "prefer-locked"
"#,
possible_values = true
)]
pub build_dependency_strategy: Option<BuildDependencyStrategy>,
/// The strategy to use when selecting between the different compatible versions for a given /// The strategy to use when selecting between the different compatible versions for a given
/// package requirement. /// package requirement.
/// ///
@ -1686,6 +1705,7 @@ impl From<ResolverInstallerOptions> for ResolverOptions {
no_build_isolation: value.no_build_isolation, no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package, no_build_isolation_package: value.no_build_isolation_package,
no_sources: value.no_sources, no_sources: value.no_sources,
build_dependency_strategy: value.build_dependency_strategy,
} }
} }
} }
@ -1811,6 +1831,7 @@ impl From<ToolOptions> for ResolverInstallerOptions {
no_build_package: value.no_build_package, no_build_package: value.no_build_package,
no_binary: value.no_binary, no_binary: value.no_binary,
no_binary_package: value.no_binary_package, no_binary_package: value.no_binary_package,
build_dependency_strategy: None,
} }
} }
} }
@ -1864,6 +1885,7 @@ pub struct OptionsWire {
no_build_package: Option<Vec<PackageName>>, no_build_package: Option<Vec<PackageName>>,
no_binary: Option<bool>, no_binary: Option<bool>,
no_binary_package: Option<Vec<PackageName>>, no_binary_package: Option<Vec<PackageName>>,
build_dependency_strategy: Option<BuildDependencyStrategy>,
// #[serde(flatten)] // #[serde(flatten)]
// install_mirror: PythonInstallMirrors, // install_mirror: PythonInstallMirrors,
@ -1954,6 +1976,7 @@ impl From<OptionsWire> for Options {
no_build_package, no_build_package,
no_binary, no_binary,
no_binary_package, no_binary_package,
build_dependency_strategy,
pip, pip,
cache_keys, cache_keys,
override_dependencies, override_dependencies,
@ -2021,6 +2044,7 @@ impl From<OptionsWire> for Options {
no_build_package, no_build_package,
no_binary, no_binary,
no_binary_package, no_binary_package,
build_dependency_strategy,
}, },
pip, pip,
cache_keys, cache_keys,

View File

@ -205,6 +205,7 @@ async fn build_impl(
upgrade: _, upgrade: _,
build_options, build_options,
sources, sources,
build_dependency_strategy: _,
} = settings; } = settings;
let client_builder = BaseClientBuilder::default() let client_builder = BaseClientBuilder::default()

View File

@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::fmt::Write; use std::fmt::Write;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
@ -17,9 +17,9 @@ use uv_cache::Cache;
use uv_cache_key::RepositoryUrl; use uv_cache_key::RepositoryUrl;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{ use uv_configuration::{
Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DevMode, DryRun, BuildDependencyStrategy, Concurrency, Constraints, DependencyGroups,
EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, InstallOptions, Preview, DependencyGroupsWithDefaults, DevMode, DryRun, EditableMode, ExtrasSpecification,
PreviewFeatures, SourceStrategy, ExtrasSpecificationWithDefaults, InstallOptions, Preview, PreviewFeatures, SourceStrategy,
}; };
use uv_dispatch::BuildDispatch; use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase; use uv_distribution::DistributionDatabase;
@ -27,7 +27,7 @@ use uv_distribution_types::{
Index, IndexName, IndexUrl, IndexUrls, NameRequirementSpecification, Requirement, Index, IndexName, IndexUrl, IndexUrls, NameRequirementSpecification, Requirement,
RequirementSource, UnresolvedRequirement, VersionId, RequirementSource, UnresolvedRequirement, VersionId,
}; };
use uv_fs::{LockedFile, Simplified}; use uv_fs::{CWD, LockedFile, Simplified};
use uv_git::GIT_STORE; use uv_git::GIT_STORE;
use uv_git_types::GitReference; use uv_git_types::GitReference;
use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, DefaultGroups, PackageName}; use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, DefaultGroups, PackageName};
@ -427,31 +427,27 @@ pub(crate) async fn add(
FlatIndex::from_entries(entries, None, &hasher, &settings.resolver.build_options) FlatIndex::from_entries(entries, None, &hasher, &settings.resolver.build_options)
}; };
// Load preferences from the existing lockfile if available. // Load preferences from the existing lockfile if available and if configured to do so.
let preferences = if let Ok(Some(lock)) = LockTarget::from(&target).read().await { let preferences = match settings.resolver.build_dependency_strategy {
Preferences::from_iter( BuildDependencyStrategy::PreferLocked => {
lock.packages() if let Ok(Some(lock)) = LockTarget::from(&target).read().await {
.iter() Preferences::from_iter(
.filter_map(|package| { lock.packages()
Preference::from_lock( .iter()
package, .filter_map(|package| {
match &target { Preference::from_lock(package, &target.install_path())
AddTarget::Script(_, _) => Path::new(".") .transpose()
.canonicalize() })
.unwrap_or_else(|_| PathBuf::from(".")), .collect::<Result<Vec<_>, _>>()?,
AddTarget::Project(project, _) => { &ResolverEnvironment::specific(
project.workspace().install_path().clone() target.interpreter().markers().clone().into(),
} ),
} )
.as_path(), } else {
) Preferences::default()
.transpose() }
}) }
.collect::<Result<Vec<_>, _>>()?, BuildDependencyStrategy::Latest => Preferences::default(),
&ResolverEnvironment::specific(target.interpreter().markers().clone().into()),
)
} else {
Preferences::default()
}; };
// Create a build dispatch. // Create a build dispatch.
@ -1345,6 +1341,14 @@ impl AddTarget {
} }
} }
/// Return the parent path of the target.
pub(crate) fn install_path(&self) -> &Path {
match self {
Self::Script(script, _) => script.path.parent().unwrap_or(&*CWD),
Self::Project(project, _) => project.root(),
}
}
/// Write the updated content to the target. /// Write the updated content to the target.
/// ///
/// Returns `true` if the content was modified. /// Returns `true` if the content was modified.

View File

@ -12,8 +12,8 @@ use tracing::debug;
use uv_cache::Cache; use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{ use uv_configuration::{
Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, Preview, BuildDependencyStrategy, Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun,
Reinstall, Upgrade, ExtrasSpecification, Preview, Reinstall, Upgrade,
}; };
use uv_dispatch::BuildDispatch; use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase; use uv_distribution::DistributionDatabase;
@ -440,6 +440,7 @@ async fn do_lock(
upgrade, upgrade,
build_options, build_options,
sources, sources,
build_dependency_strategy,
} = settings; } = settings;
// Collect the requirements, etc. // Collect the requirements, etc.
@ -663,24 +664,25 @@ async fn do_lock(
FlatIndex::from_entries(entries, None, &hasher, build_options) FlatIndex::from_entries(entries, None, &hasher, build_options)
}; };
// Extract preferences and git refs from the existing lockfile if available for build dispatch.
// We extract preferences before validation because validation may need to build source // We extract preferences before validation because validation may need to build source
// distributions to get their metadata. // distributions to get their metadata, and those builds should use the lockfile's preferences
let preferences = existing_lock // for accuracy. While the lockfile hasn't been validated yet, using its preferences is still
.as_ref() // better than using defaults, as most lockfiles are valid and this gives more accurate results.
.map(|existing_lock| -> Result<Preferences, ProjectError> { let preferences = match build_dependency_strategy {
let locked = read_lock_requirements(existing_lock, target.install_path(), upgrade)?; BuildDependencyStrategy::PreferLocked => existing_lock
// Populate the Git resolver. .as_ref()
for ResolvedRepositoryReference { reference, sha } in &locked.git { .map(|existing_lock| -> Result<Preferences, ProjectError> {
debug!("Inserting Git reference into resolver: `{reference:?}` at `{sha}`"); Ok(Preferences::from_iter(
state.git().insert(reference.clone(), *sha); read_lock_requirements(existing_lock, target.install_path(), upgrade)?
} .preferences,
Ok(Preferences::from_iter( &ResolverEnvironment::universal(vec![]),
locked.preferences, ))
&ResolverEnvironment::universal(vec![]), })
)) .transpose()?
}) .unwrap_or_default(),
.transpose()? BuildDependencyStrategy::Latest => Preferences::default(),
.unwrap_or_default(); };
// Create a build dispatch. // Create a build dispatch.
let build_dispatch = BuildDispatch::new( let build_dispatch = BuildDispatch::new(

View File

@ -1696,6 +1696,7 @@ pub(crate) async fn resolve_names(
resolution: _, resolution: _,
sources, sources,
upgrade: _, upgrade: _,
build_dependency_strategy: _,
}, },
compile_bytecode: _, compile_bytecode: _,
reinstall: _, reinstall: _,
@ -1851,6 +1852,7 @@ pub(crate) async fn resolve_environment(
upgrade: _, upgrade: _,
build_options, build_options,
sources, sources,
build_dependency_strategy: _,
} = settings; } = settings;
// Respect all requirements from the provided sources. // Respect all requirements from the provided sources.
@ -2201,6 +2203,7 @@ pub(crate) async fn update_environment(
resolution, resolution,
sources, sources,
upgrade, upgrade,
build_dependency_strategy: _,
}, },
compile_bytecode, compile_bytecode,
reinstall, reinstall,

View File

@ -208,6 +208,7 @@ pub(crate) async fn tree(
upgrade: _, upgrade: _,
build_options: _, build_options: _,
sources: _, sources: _,
build_dependency_strategy: _,
} = &settings; } = &settings;
let capabilities = IndexCapabilities::default(); let capabilities = IndexCapabilities::default();

View File

@ -21,11 +21,11 @@ use uv_cli::{
}; };
use uv_client::Connectivity; use uv_client::Connectivity;
use uv_configuration::{ use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, DependencyGroups, DryRun, EditableMode, BuildDependencyStrategy, BuildOptions, Concurrency, ConfigSettings, DependencyGroups, DryRun,
ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, EditableMode, ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy,
KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, Preview, ProjectBuildBackend, InstallOptions, KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, Preview,
Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, ProjectBuildBackend, Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost,
Upgrade, VersionControlSystem, TrustedPublishing, Upgrade, VersionControlSystem,
}; };
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl, Requirement}; use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl, Requirement};
use uv_install_wheel::LinkMode; use uv_install_wheel::LinkMode;
@ -2738,6 +2738,7 @@ pub(crate) struct ResolverSettings {
pub(crate) resolution: ResolutionMode, pub(crate) resolution: ResolutionMode,
pub(crate) sources: SourceStrategy, pub(crate) sources: SourceStrategy,
pub(crate) upgrade: Upgrade, pub(crate) upgrade: Upgrade,
pub(crate) build_dependency_strategy: BuildDependencyStrategy,
} }
impl ResolverSettings { impl ResolverSettings {
@ -2802,6 +2803,7 @@ impl From<ResolverOptions> for ResolverSettings {
NoBinary::from_args(value.no_binary, value.no_binary_package.unwrap_or_default()), NoBinary::from_args(value.no_binary, value.no_binary_package.unwrap_or_default()),
NoBuild::from_args(value.no_build, value.no_build_package.unwrap_or_default()), NoBuild::from_args(value.no_build, value.no_build_package.unwrap_or_default()),
), ),
build_dependency_strategy: value.build_dependency_strategy.unwrap_or_default(),
} }
} }
} }
@ -2887,6 +2889,7 @@ impl From<ResolverInstallerOptions> for ResolverInstallerSettings {
.map(Requirement::from) .map(Requirement::from)
.collect(), .collect(),
), ),
build_dependency_strategy: value.build_dependency_strategy.unwrap_or_default(),
}, },
compile_bytecode: value.compile_bytecode.unwrap_or_default(), compile_bytecode: value.compile_bytecode.unwrap_or_default(),
reinstall: Reinstall::from_args( reinstall: Reinstall::from_args(
@ -3054,6 +3057,7 @@ impl PipSettings {
no_build_package: top_level_no_build_package, no_build_package: top_level_no_build_package,
no_binary: top_level_no_binary, no_binary: top_level_no_binary,
no_binary_package: top_level_no_binary_package, no_binary_package: top_level_no_binary_package,
build_dependency_strategy: _,
} = top_level; } = top_level;
// Merge the top-level options (`tool.uv`) with the pip-specific options (`tool.uv.pip`), // Merge the top-level options (`tool.uv`) with the pip-specific options (`tool.uv.pip`),

View File

@ -13655,6 +13655,9 @@ fn add_build_dependencies_respect_locked_versions() -> Result<()> {
version = "0.1.0" version = "0.1.0"
requires-python = ">=3.9" requires-python = ">=3.9"
dependencies = ["anyio<4.1"] dependencies = ["anyio<4.1"]
[tool.uv]
build-dependency-strategy = "prefer-locked"
"#})?; "#})?;
// Create a lockfile with anyio 4.0.0 // Create a lockfile with anyio 4.0.0
@ -13717,6 +13720,9 @@ fn add_build_dependencies_respect_locked_versions() -> Result<()> {
requires-python = ">=3.9" requires-python = ">=3.9"
dependencies = ["anyio<3.8", "child"] dependencies = ["anyio<3.8", "child"]
[tool.uv]
build-dependency-strategy = "prefer-locked"
[tool.uv.sources] [tool.uv.sources]
child = { workspace = true } child = { workspace = true }

View File

@ -11457,7 +11457,7 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> {
Resolved [N] packages in [TIME] Resolved [N] packages in [TIME]
"); ");
// Now add the child dependency // Now add the child dependency with build-dependency-strategy = "prefer-locked"
pyproject_toml.write_str(indoc! {r#" pyproject_toml.write_str(indoc! {r#"
[project] [project]
name = "parent" name = "parent"
@ -11465,6 +11465,9 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> {
requires-python = ">=3.9" requires-python = ">=3.9"
dependencies = ["anyio<4.1", "child"] dependencies = ["anyio<4.1", "child"]
[tool.uv]
build-dependency-strategy = "prefer-locked"
[tool.uv.sources] [tool.uv.sources]
child = { path = "child" } child = { path = "child" }
"#})?; "#})?;
@ -11512,6 +11515,9 @@ fn sync_build_dependencies_respect_locked_versions() -> Result<()> {
requires-python = ">=3.9" requires-python = ">=3.9"
dependencies = ["anyio<3.8", "child"] dependencies = ["anyio<3.8", "child"]
[tool.uv]
build-dependency-strategy = "prefer-locked"
[tool.uv.sources] [tool.uv.sources]
child = { path = "child" } child = { path = "child" }
"#})?; "#})?;

View File

@ -749,6 +749,40 @@ bypasses SSL verification and could expose you to MITM attacks.
--- ---
### [`build-dependency-strategy`](#build-dependency-strategy) {: #build-dependency-strategy }
The strategy to use when resolving build dependencies for source distributions.
- `latest`: Use the latest compatible version of each build dependency.
- `prefer-locked`: Prefer the versions pinned in the lockfile, if available.
When set to `prefer-locked`, uv will use the locked versions of packages specified in the
lockfile as preferences when resolving build dependencies during source builds. This helps
ensure that build environments are consistent with the project's resolved dependencies.
**Default value**: `"latest"`
**Possible values**:
- `"latest"`: Use the latest compatible version of each build dependency
- `"prefer-locked"`: Prefer the versions pinned in the lockfile, if available
**Example usage**:
=== "pyproject.toml"
```toml
[tool.uv]
build-dependency-strategy = "prefer-locked"
```
=== "uv.toml"
```toml
build-dependency-strategy = "prefer-locked"
```
---
### [`cache-dir`](#cache-dir) {: #cache-dir } ### [`cache-dir`](#cache-dir) {: #cache-dir }
Path to the cache directory. Path to the cache directory.

25
uv.schema.json generated
View File

@ -46,6 +46,17 @@
"type": "string" "type": "string"
} }
}, },
"build-dependency-strategy": {
"description": "The strategy to use when resolving build dependencies for source distributions.\n\n- `latest`: Use the latest compatible version of each build dependency.\n- `prefer-locked`: Prefer the versions pinned in the lockfile, if available.\n\nWhen set to `prefer-locked`, uv will use the locked versions of packages specified in the\nlockfile as preferences when resolving build dependencies during source builds. This helps\nensure that build environments are consistent with the project's resolved dependencies.",
"anyOf": [
{
"$ref": "#/definitions/BuildDependencyStrategy"
},
{
"type": "null"
}
]
},
"cache-dir": { "cache-dir": {
"description": "Path to the cache directory.\n\nDefaults to `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux and macOS, and\n`%LOCALAPPDATA%\\uv\\cache` on Windows.", "description": "Path to the cache directory.\n\nDefaults to `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv` on Linux and macOS, and\n`%LOCALAPPDATA%\\uv\\cache` on Windows.",
"type": [ "type": [
@ -726,6 +737,20 @@
} }
} }
}, },
"BuildDependencyStrategy": {
"oneOf": [
{
"description": "Use the latest compatible version of each build dependency.",
"type": "string",
"const": "latest"
},
{
"description": "Prefer the versions pinned in the lockfile, if available.",
"type": "string",
"const": "prefer-locked"
}
]
},
"CacheKey": { "CacheKey": {
"anyOf": [ "anyOf": [
{ {