Skip to content
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

Introduce a --multi-version preference mode #8686

Closed
wants to merge 1 commit into from
Closed
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
46 changes: 45 additions & 1 deletion crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep508::Requirement;
use uv_pypi_types::VerbatimParsedUrl;
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PrereleaseMode, ResolutionMode};
use uv_resolver::{
AnnotationStyle, ExcludeNewer, MultiVersionMode, PrereleaseMode, ResolutionMode,
};
use uv_static::EnvVars;

pub mod comma;
Expand Down Expand Up @@ -4045,6 +4047,20 @@ pub struct ToolUpgradeArgs {
#[arg(long, hide = true)]
pub pre: bool,

/// The strategy to use when selecting multiple versions of a given package across Python
/// versions and platforms.
///
/// By default, uv will minimize the number of versions selected for each package (`fewest`),
/// to minimize differences between environments. Under `latest`, uv will select the latest
/// compatible version for each environment, even if it results in more versions being selected.
#[arg(
long,
value_enum,
env = EnvVars::UV_MULTI_VERSION,
help_heading = "Resolver options"
)]
pub multi_version: Option<MultiVersionMode>,

/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
#[arg(
long,
Expand Down Expand Up @@ -4834,6 +4850,20 @@ pub struct ResolverArgs {
#[arg(long, hide = true, help_heading = "Resolver options")]
pub pre: bool,

/// The strategy to use when selecting multiple versions of a given package across Python
/// versions and platforms.
///
/// By default, uv will minimize the number of versions selected for each package (`fewest`),
/// to minimize differences between environments. Under `latest`, uv will select the latest
/// compatible version for each environment, even if it results in more versions being selected.
#[arg(
long,
value_enum,
env = EnvVars::UV_MULTI_VERSION,
help_heading = "Resolver options"
)]
pub multi_version: Option<MultiVersionMode>,

/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
#[arg(
long,
Expand Down Expand Up @@ -5006,6 +5036,20 @@ pub struct ResolverInstallerArgs {
#[arg(long, hide = true)]
pub pre: bool,

/// The strategy to use when selecting multiple versions of a given package across Python
/// versions and platforms.
///
/// By default, uv will minimize the number of versions selected for each package (`fewest`),
/// to minimize differences between environments. Under `latest`, uv will select the latest
/// compatible version for each environment, even if it results in more versions being selected.
#[arg(
long,
value_enum,
env = EnvVars::UV_MULTI_VERSION,
help_heading = "Resolver options"
)]
pub multi_version: Option<MultiVersionMode>,

/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
#[arg(
long,
Expand Down
8 changes: 8 additions & 0 deletions crates/uv-cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ impl From<ResolverArgs> for PipOptions {
resolution,
prerelease,
pre,
multi_version,
config_setting,
no_build_isolation,
no_build_isolation_package,
Expand All @@ -58,6 +59,7 @@ impl From<ResolverArgs> for PipOptions {
index_strategy,
keyring_provider,
resolution,
multi_version,
prerelease: if pre {
Some(PrereleaseMode::Allow)
} else {
Expand Down Expand Up @@ -126,6 +128,7 @@ impl From<ResolverInstallerArgs> for PipOptions {
resolution,
prerelease,
pre,
multi_version,
config_setting,
no_build_isolation,
no_build_isolation_package,
Expand All @@ -150,6 +153,7 @@ impl From<ResolverInstallerArgs> for PipOptions {
} else {
prerelease
},
multi_version,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
no_build_isolation: flag(no_build_isolation, build_isolation),
Expand Down Expand Up @@ -235,6 +239,7 @@ pub fn resolver_options(
resolution,
prerelease,
pre,
multi_version,
config_setting,
no_build_isolation,
no_build_isolation_package,
Expand Down Expand Up @@ -291,6 +296,7 @@ pub fn resolver_options(
} else {
prerelease
},
multi_version,
dependency_metadata: None,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
Expand Down Expand Up @@ -324,6 +330,7 @@ pub fn resolver_installer_options(
resolution,
prerelease,
pre,
multi_version,
config_setting,
no_build_isolation,
no_build_isolation_package,
Expand Down Expand Up @@ -392,6 +399,7 @@ pub fn resolver_installer_options(
} else {
prerelease
},
multi_version,
dependency_metadata: None,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
Expand Down
4 changes: 3 additions & 1 deletion crates/uv-python/src/python_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ impl schemars::JsonSchema for PythonVersion {
..schemars::schema::StringValidation::default()
})),
metadata: Some(Box::new(schemars::schema::Metadata {
description: Some("A Python version specifier, e.g. `3.7` or `3.8.0`.".to_string()),
description: Some(
"A Python version specifier, e.g. `3.11` or `3.12.4`.".to_string(),
),
..schemars::schema::Metadata::default()
})),
..schemars::schema::SchemaObject::default()
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub use lock::{
ResolverManifest, SatisfiesResult, TreeDisplay, VERSION,
};
pub use manifest::Manifest;
pub use multi_version_mode::MultiVersionMode;
pub use options::{Flexibility, Options, OptionsBuilder};
pub use preferences::{Preference, PreferenceError, Preferences};
pub use prerelease::PrereleaseMode;
Expand Down Expand Up @@ -46,6 +47,7 @@ mod graph_ops;
mod lock;
mod manifest;
mod marker;
mod multi_version_mode;
mod options;
mod pins;
mod preferences;
Expand Down
16 changes: 16 additions & 0 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub use crate::lock::map::PackageMap;
pub use crate::lock::requirements_txt::RequirementsTxtExport;
pub use crate::lock::target::InstallTarget;
pub use crate::lock::tree::TreeDisplay;
use crate::multi_version_mode::MultiVersionMode;
use crate::requires_python::SimplifiedMarkerTree;
use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
use crate::universal_marker::{ConflictMarker, UniversalMarker};
Expand Down Expand Up @@ -239,6 +240,7 @@ impl Lock {
let options = ResolverOptions {
resolution_mode: resolution.options.resolution_mode,
prerelease_mode: resolution.options.prerelease_mode,
multi_version_mode: resolution.options.multi_version_mode,
exclude_newer: resolution.options.exclude_newer,
};
let lock = Self::new(
Expand Down Expand Up @@ -548,6 +550,11 @@ impl Lock {
self.options.prerelease_mode
}

/// Returns the multi-version mode used to generate this lock.
pub fn multi_version_mode(&self) -> MultiVersionMode {
self.options.multi_version_mode
}

/// Returns the exclude newer setting used to generate this lock.
pub fn exclude_newer(&self) -> Option<ExcludeNewer> {
self.options.exclude_newer
Expand Down Expand Up @@ -675,6 +682,12 @@ impl Lock {
value(self.options.prerelease_mode.to_string()),
);
}
if self.options.multi_version_mode != MultiVersionMode::default() {
options_table.insert(
"multi-version-mode",
value(self.options.multi_version_mode.to_string()),
);
}
if let Some(exclude_newer) = self.options.exclude_newer {
options_table.insert("exclude-newer", value(exclude_newer.to_string()));
}
Expand Down Expand Up @@ -1317,6 +1330,9 @@ struct ResolverOptions {
/// The [`PrereleaseMode`] used to generate this lock.
#[serde(default)]
prerelease_mode: PrereleaseMode,
/// The [`MultiVersionMode`] used to generate this lock.
#[serde(default)]
multi_version_mode: MultiVersionMode,
/// The [`ExcludeNewer`] used to generate this lock.
exclude_newer: Option<ExcludeNewer>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Ok(
options: ResolverOptions {
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
multi_version_mode: Fewest,
exclude_newer: None,
},
packages: [
Expand Down
20 changes: 20 additions & 0 deletions crates/uv-resolver/src/multi_version_mode.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 MultiVersionMode {
/// Resolve the highest compatible version of each package.
#[default]
Fewest,
/// Resolve the lowest compatible version of each package.
Latest,
}

impl std::fmt::Display for MultiVersionMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fewest => write!(f, "fewest"),
Self::Latest => write!(f, "latest"),
}
}
}
11 changes: 11 additions & 0 deletions crates/uv-resolver/src/options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use uv_configuration::IndexStrategy;

use crate::multi_version_mode::MultiVersionMode;
use crate::{DependencyMode, ExcludeNewer, PrereleaseMode, ResolutionMode};

/// Options for resolving a manifest.
Expand All @@ -8,6 +9,7 @@ pub struct Options {
pub resolution_mode: ResolutionMode,
pub prerelease_mode: PrereleaseMode,
pub dependency_mode: DependencyMode,
pub multi_version_mode: MultiVersionMode,
pub exclude_newer: Option<ExcludeNewer>,
pub index_strategy: IndexStrategy,
pub flexibility: Flexibility,
Expand All @@ -19,6 +21,7 @@ pub struct OptionsBuilder {
resolution_mode: ResolutionMode,
prerelease_mode: PrereleaseMode,
dependency_mode: DependencyMode,
multi_version_mode: MultiVersionMode,
exclude_newer: Option<ExcludeNewer>,
index_strategy: IndexStrategy,
flexibility: Flexibility,
Expand Down Expand Up @@ -51,6 +54,13 @@ impl OptionsBuilder {
self
}

/// Sets the multi-version mode.
#[must_use]
pub fn multi_version_mode(mut self, multi_version_mode: MultiVersionMode) -> Self {
self.multi_version_mode = multi_version_mode;
self
}

/// Sets the exclusion date.
#[must_use]
pub fn exclude_newer(mut self, exclude_newer: Option<ExcludeNewer>) -> Self {
Expand Down Expand Up @@ -78,6 +88,7 @@ impl OptionsBuilder {
resolution_mode: self.resolution_mode,
prerelease_mode: self.prerelease_mode,
dependency_mode: self.dependency_mode,
multi_version_mode: self.multi_version_mode,
exclude_newer: self.exclude_newer,
index_strategy: self.index_strategy,
flexibility: self.flexibility,
Expand Down
17 changes: 0 additions & 17 deletions crates/uv-resolver/src/requires_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,23 +278,6 @@ impl RequiresPython {
}
}

/// Returns the [`RequiresPythonBound`] truncated to the major and minor version.
pub fn bound_major_minor(&self) -> LowerBound {
match self.range.lower().as_ref() {
// Ex) `>=3.10.1` -> `>=3.10`
Bound::Included(version) => LowerBound(Bound::Included(Version::new(
version.release().iter().take(2),
))),
// Ex) `>3.10.1` -> `>=3.10`
// This is unintuitive, but `>3.10.1` does indicate that _some_ version of Python 3.10
// is supported.
Bound::Excluded(version) => LowerBound(Bound::Included(Version::new(
version.release().iter().take(2),
))),
Bound::Unbounded => LowerBound(Bound::Unbounded),
}
}

/// Returns the [`Range`] bounding the `Requires-Python` specifier.
pub fn range(&self) -> &RequiresPythonRange {
&self.range
Expand Down
Loading
Loading