Skip to content

Add option to prefer locked versions of packages in build environments #14944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/uv-bench/benches/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ mod resolver {
use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment};
use uv_python::Interpreter;
use uv_resolver::{
ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement,
Resolver, ResolverEnvironment, ResolverOutput,
ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, Preferences,
PythonRequirement, Resolver, ResolverEnvironment, ResolverOutput,
};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_workspace::WorkspaceCache;
Expand Down Expand Up @@ -197,6 +197,7 @@ mod resolver {
workspace_cache,
concurrency,
Preview::default(),
Preferences::default(),
);

let markers = if universal {
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ pub fn resolver_options(
no_binary: flag(no_binary, binary, "binary"),
no_binary_package: Some(no_binary_package),
no_sources: if no_sources { Some(true) } else { None },
build_dependency_strategy: None,
}
}

Expand Down Expand Up @@ -492,5 +493,6 @@ pub fn resolver_installer_options(
Some(no_binary_package)
},
no_sources: if no_sources { Some(true) } else { None },
build_dependency_strategy: None,
}
}
20 changes: 20 additions & 0 deletions crates/uv-configuration/src/build_dependency_strategy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum BuildDependencyStrategy {
/// Use the latest compatible version of each build dependency.
#[default]
Latest,
/// Prefer the versions pinned in the lockfile, if available.
PreferLocked,
}

impl std::fmt::Display for BuildDependencyStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Latest => write!(f, "latest"),
Self::PreferLocked => write!(f, "prefer-locked"),
}
}
}
2 changes: 2 additions & 0 deletions crates/uv-configuration/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub use authentication::*;
pub use build_dependency_strategy::*;
pub use build_options::*;
pub use concurrency::*;
pub use config_settings::*;
Expand All @@ -24,6 +25,7 @@ pub use trusted_publishing::*;
pub use vcs::*;

mod authentication;
mod build_dependency_strategy;
mod build_options;
mod concurrency;
mod config_settings;
Expand Down
7 changes: 7 additions & 0 deletions crates/uv-configuration/src/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ bitflags::bitflags! {
const JSON_OUTPUT = 1 << 2;
const PYLOCK = 1 << 3;
const ADD_BOUNDS = 1 << 4;
const PREFER_LOCKED_BUILDS = 1 << 5;
}
}

Expand All @@ -28,6 +29,7 @@ impl PreviewFeatures {
Self::JSON_OUTPUT => "json-output",
Self::PYLOCK => "pylock",
Self::ADD_BOUNDS => "add-bounds",
Self::PREFER_LOCKED_BUILDS => "prefer-locked-builds",
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
}
}
Expand Down Expand Up @@ -70,6 +72,7 @@ impl FromStr for PreviewFeatures {
"json-output" => Self::JSON_OUTPUT,
"pylock" => Self::PYLOCK,
"add-bounds" => Self::ADD_BOUNDS,
"prefer-locked-builds" => Self::PREFER_LOCKED_BUILDS,
_ => {
warn_user_once!("Unknown preview feature: `{part}`");
continue;
Expand Down Expand Up @@ -232,6 +235,10 @@ mod tests {
assert_eq!(PreviewFeatures::JSON_OUTPUT.flag_as_str(), "json-output");
assert_eq!(PreviewFeatures::PYLOCK.flag_as_str(), "pylock");
assert_eq!(PreviewFeatures::ADD_BOUNDS.flag_as_str(), "add-bounds");
assert_eq!(
PreviewFeatures::PREFER_LOCKED_BUILDS.flag_as_str(),
"prefer-locked-builds"
);
}

#[test]
Expand Down
9 changes: 7 additions & 2 deletions crates/uv-dispatch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
use uv_pypi_types::Conflicts;
use uv_python::{Interpreter, PythonEnvironment};
use uv_resolver::{
ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest, OptionsBuilder,
ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest, OptionsBuilder, Preferences,
PythonRequirement, Resolver, ResolverEnvironment,
};
use uv_types::{
Expand Down Expand Up @@ -100,6 +100,7 @@ pub struct BuildDispatch<'a> {
workspace_cache: WorkspaceCache,
concurrency: Concurrency,
preview: Preview,
preferences: Preferences,
}

impl<'a> BuildDispatch<'a> {
Expand All @@ -124,6 +125,7 @@ impl<'a> BuildDispatch<'a> {
workspace_cache: WorkspaceCache,
concurrency: Concurrency,
preview: Preview,
preferences: Preferences,
) -> Self {
Self {
client,
Expand All @@ -148,6 +150,7 @@ impl<'a> BuildDispatch<'a> {
workspace_cache,
concurrency,
preview,
preferences,
}
}

Expand Down Expand Up @@ -229,7 +232,9 @@ impl BuildContext for BuildDispatch<'_> {
let tags = self.interpreter.tags()?;

let resolver = Resolver::new(
Manifest::simple(requirements.to_vec()).with_constraints(self.constraints.clone()),
Manifest::simple(requirements.to_vec())
.with_constraints(self.constraints.clone())
.with_preferences(self.preferences.clone()),
OptionsBuilder::new()
.exclude_newer(self.exclude_newer.clone())
.index_strategy(self.index_strategy)
Expand Down
6 changes: 6 additions & 0 deletions crates/uv-resolver/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ impl Manifest {
self
}

#[must_use]
pub fn with_preferences(mut self, preferences: Preferences) -> Self {
self.preferences = preferences;
self
}

/// Return an iterator over all requirements, constraints, and overrides, in priority order,
/// such that requirements come first, followed by constraints, followed by overrides.
///
Expand Down
5 changes: 3 additions & 2 deletions crates/uv-settings/src/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use std::path::PathBuf;
use url::Url;

use uv_configuration::{
ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType, PackageConfigSettings,
RequiredVersion, TargetTriple, TrustedPublishing,
BuildDependencyStrategy, ConfigSettings, ExportFormat, IndexStrategy, KeyringProviderType,
PackageConfigSettings, RequiredVersion, TargetTriple, TrustedPublishing,
};
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
use uv_install_wheel::LinkMode;
Expand Down Expand Up @@ -80,6 +80,7 @@ macro_rules! impl_combine_or {

impl_combine_or!(AddBoundsKind);
impl_combine_or!(AnnotationStyle);
impl_combine_or!(BuildDependencyStrategy);
impl_combine_or!(ExcludeNewer);
impl_combine_or!(ExcludeNewerTimestamp);
impl_combine_or!(ExportFormat);
Expand Down
1 change: 1 addition & 0 deletions crates/uv-settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ fn warn_uv_toml_masked_fields(options: &Options) {
no_build_package,
no_binary,
no_binary_package,
build_dependency_strategy: _,
},
install_mirrors:
PythonInstallMirrors {
Expand Down
30 changes: 28 additions & 2 deletions crates/uv-settings/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use serde::{Deserialize, Serialize};

use uv_cache_info::CacheKey;
use uv_configuration::{
ConfigSettings, IndexStrategy, KeyringProviderType, PackageConfigSettings,
PackageNameSpecifier, RequiredVersion, TargetTriple, TrustedHost, TrustedPublishing,
BuildDependencyStrategy, ConfigSettings, IndexStrategy, KeyringProviderType,
PackageConfigSettings, PackageNameSpecifier, RequiredVersion, TargetTriple, TrustedHost,
TrustedPublishing,
};
use uv_distribution_types::{
Index, IndexUrl, IndexUrlError, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata,
Expand Down Expand Up @@ -377,6 +378,7 @@ pub struct ResolverOptions {
pub no_build_isolation: Option<bool>,
pub no_build_isolation_package: Option<Vec<PackageName>>,
pub no_sources: Option<bool>,
pub build_dependency_strategy: Option<BuildDependencyStrategy>,
}

/// Shared settings, relevant to all operations that must resolve and install dependencies. The
Expand Down Expand Up @@ -513,6 +515,25 @@ pub struct ResolverInstallerOptions {
"#
)]
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, such that
/// the locked version of a package will be used as long as it doesn't conflict with version
/// constraints declared by the package being built. 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
/// package requirement.
///
Expand Down Expand Up @@ -1720,6 +1741,7 @@ impl From<ResolverInstallerOptions> for ResolverOptions {
no_build_isolation: value.no_build_isolation,
no_build_isolation_package: value.no_build_isolation_package,
no_sources: value.no_sources,
build_dependency_strategy: value.build_dependency_strategy,
}
}
}
Expand Down Expand Up @@ -1857,6 +1879,7 @@ impl From<ToolOptions> for ResolverInstallerOptions {
no_build_package: value.no_build_package,
no_binary: value.no_binary,
no_binary_package: value.no_binary_package,
build_dependency_strategy: None,
}
}
}
Expand Down Expand Up @@ -1911,6 +1934,7 @@ pub struct OptionsWire {
no_build_package: Option<Vec<PackageName>>,
no_binary: Option<bool>,
no_binary_package: Option<Vec<PackageName>>,
build_dependency_strategy: Option<BuildDependencyStrategy>,

// #[serde(flatten)]
// install_mirror: PythonInstallMirrors,
Expand Down Expand Up @@ -2002,6 +2026,7 @@ impl From<OptionsWire> for Options {
no_build_package,
no_binary,
no_binary_package,
build_dependency_strategy,
pip,
cache_keys,
override_dependencies,
Expand Down Expand Up @@ -2049,6 +2074,7 @@ impl From<OptionsWire> for Options {
find_links,
index_strategy,
keyring_provider,
build_dependency_strategy,
resolution,
prerelease,
fork_strategy,
Expand Down
4 changes: 3 additions & 1 deletion crates/uv/src/commands/build_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use uv_python::{
VersionRequest,
};
use uv_requirements::RequirementsSource;
use uv_resolver::{ExcludeNewer, FlatIndex};
use uv_resolver::{ExcludeNewer, FlatIndex, Preferences};
use uv_settings::PythonInstallMirrors;
use uv_types::{AnyErrorBuild, BuildContext, BuildIsolation, BuildStack, HashStrategy};
use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache, WorkspaceError};
Expand Down Expand Up @@ -205,6 +205,7 @@ async fn build_impl(
upgrade: _,
build_options,
sources,
build_dependency_strategy: _,
} = settings;

let client_builder = BaseClientBuilder::default()
Expand Down Expand Up @@ -581,6 +582,7 @@ async fn build_package(
workspace_cache,
concurrency,
preview,
Preferences::default(),
);

prepare_output_directory(&output_dir).await?;
Expand Down
5 changes: 3 additions & 2 deletions crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ use uv_requirements::{
};
use uv_resolver::{
AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, ForkStrategy,
InMemoryIndex, OptionsBuilder, PrereleaseMode, PylockToml, PythonRequirement, ResolutionMode,
ResolverEnvironment,
InMemoryIndex, OptionsBuilder, Preferences, PrereleaseMode, PylockToml, PythonRequirement,
ResolutionMode, ResolverEnvironment,
};
use uv_torch::{TorchMode, TorchStrategy};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
Expand Down Expand Up @@ -490,6 +490,7 @@ pub(crate) async fn pip_compile(
WorkspaceCache::default(),
concurrency,
preview,
Preferences::default(),
);

let options = OptionsBuilder::new()
Expand Down
5 changes: 3 additions & 2 deletions crates/uv/src/commands/pip/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ use uv_python::{
};
use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification};
use uv_resolver::{
DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml,
PythonRequirement, ResolutionMode, ResolverEnvironment,
DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, Preferences, PrereleaseMode,
PylockToml, PythonRequirement, ResolutionMode, ResolverEnvironment,
};
use uv_torch::{TorchMode, TorchStrategy};
use uv_types::{BuildIsolation, HashStrategy};
Expand Down Expand Up @@ -434,6 +434,7 @@ pub(crate) async fn pip_install(
WorkspaceCache::default(),
concurrency,
preview,
Preferences::default(),
);

let (resolution, hasher) = if let Some(pylock) = pylock {
Expand Down
5 changes: 3 additions & 2 deletions crates/uv/src/commands/pip/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ use uv_python::{
};
use uv_requirements::{GroupsSpecification, RequirementsSource, RequirementsSpecification};
use uv_resolver::{
DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PylockToml,
PythonRequirement, ResolutionMode, ResolverEnvironment,
DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, Preferences, PrereleaseMode,
PylockToml, PythonRequirement, ResolutionMode, ResolverEnvironment,
};
use uv_torch::{TorchMode, TorchStrategy};
use uv_types::{BuildIsolation, HashStrategy};
Expand Down Expand Up @@ -369,6 +369,7 @@ pub(crate) async fn pip_sync(
WorkspaceCache::default(),
concurrency,
preview,
Preferences::default(),
);

// Determine the set of installed packages.
Expand Down
Loading
Loading