Skip to content

Commit 2a98168

Browse files
committed
Introduce a --multi-version preference mode
1 parent a6ab42f commit 2a98168

35 files changed

+1084
-41
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
1919
use uv_pep508::Requirement;
2020
use uv_pypi_types::VerbatimParsedUrl;
2121
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
22-
use uv_resolver::{AnnotationStyle, ExcludeNewer, PrereleaseMode, ResolutionMode};
22+
use uv_resolver::{
23+
AnnotationStyle, ExcludeNewer, MultiVersionMode, PrereleaseMode, ResolutionMode,
24+
};
2325
use uv_static::EnvVars;
2426

2527
pub mod comma;
@@ -4045,6 +4047,21 @@ pub struct ToolUpgradeArgs {
40454047
#[arg(long, hide = true)]
40464048
pub pre: bool,
40474049

4050+
/// The strategy to use when selecting multiple versions of a given package across Python
4051+
/// versions and platforms.
4052+
///
4053+
/// By default, uv will optimize for selecting the latest version of each package, for each
4054+
/// supported Python version (`requires-python`). Under `fewest`, uv will minimize the number of
4055+
/// selected versions for each package, preferring older versions that are compatible with a
4056+
/// wider range of supported Python versions or platforms.
4057+
#[arg(
4058+
long,
4059+
value_enum,
4060+
env = EnvVars::UV_MULTI_VERSION,
4061+
help_heading = "Resolver options"
4062+
)]
4063+
pub multi_version: Option<MultiVersionMode>,
4064+
40484065
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
40494066
#[arg(
40504067
long,
@@ -4834,6 +4851,21 @@ pub struct ResolverArgs {
48344851
#[arg(long, hide = true, help_heading = "Resolver options")]
48354852
pub pre: bool,
48364853

4854+
/// The strategy to use when selecting multiple versions of a given package across Python
4855+
/// versions and platforms.
4856+
///
4857+
/// By default, uv will optimize for selecting the latest version of each package, for each
4858+
/// supported Python version (`requires-python`). Under `fewest`, uv will minimize the number of
4859+
/// selected versions for each package, preferring older versions that are compatible with a
4860+
/// wider range of supported Python versions or platforms.
4861+
#[arg(
4862+
long,
4863+
value_enum,
4864+
env = EnvVars::UV_MULTI_VERSION,
4865+
help_heading = "Resolver options"
4866+
)]
4867+
pub multi_version: Option<MultiVersionMode>,
4868+
48374869
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
48384870
#[arg(
48394871
long,
@@ -5006,6 +5038,21 @@ pub struct ResolverInstallerArgs {
50065038
#[arg(long, hide = true)]
50075039
pub pre: bool,
50085040

5041+
/// The strategy to use when selecting multiple versions of a given package across Python
5042+
/// versions and platforms.
5043+
///
5044+
/// By default, uv will optimize for selecting the latest version of each package, for each
5045+
/// supported Python version (`requires-python`). Under `fewest`, uv will minimize the number of
5046+
/// selected versions for each package, preferring older versions that are compatible with a
5047+
/// wider range of supported Python versions or platforms.
5048+
#[arg(
5049+
long,
5050+
value_enum,
5051+
env = EnvVars::UV_MULTI_VERSION,
5052+
help_heading = "Resolver options"
5053+
)]
5054+
pub multi_version: Option<MultiVersionMode>,
5055+
50095056
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
50105057
#[arg(
50115058
long,

crates/uv-cli/src/options.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ impl From<ResolverArgs> for PipOptions {
4343
resolution,
4444
prerelease,
4545
pre,
46+
multi_version,
4647
config_setting,
4748
no_build_isolation,
4849
no_build_isolation_package,
@@ -58,6 +59,7 @@ impl From<ResolverArgs> for PipOptions {
5859
index_strategy,
5960
keyring_provider,
6061
resolution,
62+
multi_version,
6163
prerelease: if pre {
6264
Some(PrereleaseMode::Allow)
6365
} else {
@@ -126,6 +128,7 @@ impl From<ResolverInstallerArgs> for PipOptions {
126128
resolution,
127129
prerelease,
128130
pre,
131+
multi_version,
129132
config_setting,
130133
no_build_isolation,
131134
no_build_isolation_package,
@@ -150,6 +153,7 @@ impl From<ResolverInstallerArgs> for PipOptions {
150153
} else {
151154
prerelease
152155
},
156+
multi_version,
153157
config_settings: config_setting
154158
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
155159
no_build_isolation: flag(no_build_isolation, build_isolation),
@@ -235,6 +239,7 @@ pub fn resolver_options(
235239
resolution,
236240
prerelease,
237241
pre,
242+
multi_version,
238243
config_setting,
239244
no_build_isolation,
240245
no_build_isolation_package,
@@ -291,6 +296,7 @@ pub fn resolver_options(
291296
} else {
292297
prerelease
293298
},
299+
multi_version,
294300
dependency_metadata: None,
295301
config_settings: config_setting
296302
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
@@ -324,6 +330,7 @@ pub fn resolver_installer_options(
324330
resolution,
325331
prerelease,
326332
pre,
333+
multi_version,
327334
config_setting,
328335
no_build_isolation,
329336
no_build_isolation_package,
@@ -392,6 +399,7 @@ pub fn resolver_installer_options(
392399
} else {
393400
prerelease
394401
},
402+
multi_version,
395403
dependency_metadata: None,
396404
config_settings: config_setting
397405
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),

crates/uv-python/src/python_version.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ impl schemars::JsonSchema for PythonVersion {
5050
..schemars::schema::StringValidation::default()
5151
})),
5252
metadata: Some(Box::new(schemars::schema::Metadata {
53-
description: Some("A Python version specifier, e.g. `3.7` or `3.8.0`.".to_string()),
53+
description: Some(
54+
"A Python version specifier, e.g. `3.11` or `3.12.4`.".to_string(),
55+
),
5456
..schemars::schema::Metadata::default()
5557
})),
5658
..schemars::schema::SchemaObject::default()

crates/uv-resolver/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub use lock::{
88
ResolverManifest, SatisfiesResult, TreeDisplay, VERSION,
99
};
1010
pub use manifest::Manifest;
11+
pub use multi_version_mode::MultiVersionMode;
1112
pub use options::{Flexibility, Options, OptionsBuilder};
1213
pub use preferences::{Preference, PreferenceError, Preferences};
1314
pub use prerelease::PrereleaseMode;
@@ -46,6 +47,7 @@ mod graph_ops;
4647
mod lock;
4748
mod manifest;
4849
mod marker;
50+
mod multi_version_mode;
4951
mod options;
5052
mod pins;
5153
mod preferences;

crates/uv-resolver/src/lock/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub use crate::lock::map::PackageMap;
1717
pub use crate::lock::requirements_txt::RequirementsTxtExport;
1818
pub use crate::lock::target::InstallTarget;
1919
pub use crate::lock::tree::TreeDisplay;
20+
use crate::multi_version_mode::MultiVersionMode;
2021
use crate::requires_python::SimplifiedMarkerTree;
2122
use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
2223
use crate::universal_marker::{ConflictMarker, UniversalMarker};
@@ -239,6 +240,7 @@ impl Lock {
239240
let options = ResolverOptions {
240241
resolution_mode: resolution.options.resolution_mode,
241242
prerelease_mode: resolution.options.prerelease_mode,
243+
multi_version_mode: resolution.options.multi_version_mode,
242244
exclude_newer: resolution.options.exclude_newer,
243245
};
244246
let lock = Self::new(
@@ -548,6 +550,11 @@ impl Lock {
548550
self.options.prerelease_mode
549551
}
550552

553+
/// Returns the multi-version mode used to generate this lock.
554+
pub fn multi_version_mode(&self) -> MultiVersionMode {
555+
self.options.multi_version_mode
556+
}
557+
551558
/// Returns the exclude newer setting used to generate this lock.
552559
pub fn exclude_newer(&self) -> Option<ExcludeNewer> {
553560
self.options.exclude_newer
@@ -675,6 +682,12 @@ impl Lock {
675682
value(self.options.prerelease_mode.to_string()),
676683
);
677684
}
685+
if self.options.multi_version_mode != MultiVersionMode::default() {
686+
options_table.insert(
687+
"multi-version-mode",
688+
value(self.options.multi_version_mode.to_string()),
689+
);
690+
}
678691
if let Some(exclude_newer) = self.options.exclude_newer {
679692
options_table.insert("exclude-newer", value(exclude_newer.to_string()));
680693
}
@@ -1317,6 +1330,9 @@ struct ResolverOptions {
13171330
/// The [`PrereleaseMode`] used to generate this lock.
13181331
#[serde(default)]
13191332
prerelease_mode: PrereleaseMode,
1333+
/// The [`MultiVersionMode`] used to generate this lock.
1334+
#[serde(default)]
1335+
multi_version_mode: MultiVersionMode,
13201336
/// The [`ExcludeNewer`] used to generate this lock.
13211337
exclude_newer: Option<ExcludeNewer>,
13221338
}

crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_missing.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Ok(
3333
options: ResolverOptions {
3434
resolution_mode: Highest,
3535
prerelease_mode: IfNecessaryOrExplicit,
36+
multi_version_mode: Fewest,
3637
exclude_newer: None,
3738
},
3839
packages: [

crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_optional_present.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Ok(
3333
options: ResolverOptions {
3434
resolution_mode: Highest,
3535
prerelease_mode: IfNecessaryOrExplicit,
36+
multi_version_mode: Fewest,
3637
exclude_newer: None,
3738
},
3839
packages: [

crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__hash_required_present.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Ok(
3333
options: ResolverOptions {
3434
resolution_mode: Highest,
3535
prerelease_mode: IfNecessaryOrExplicit,
36+
multi_version_mode: Fewest,
3637
exclude_newer: None,
3738
},
3839
packages: [

crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Ok(
3333
options: ResolverOptions {
3434
resolution_mode: Highest,
3535
prerelease_mode: IfNecessaryOrExplicit,
36+
multi_version_mode: Fewest,
3637
exclude_newer: None,
3738
},
3839
packages: [

crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Ok(
3333
options: ResolverOptions {
3434
resolution_mode: Highest,
3535
prerelease_mode: IfNecessaryOrExplicit,
36+
multi_version_mode: Fewest,
3637
exclude_newer: None,
3738
},
3839
packages: [

0 commit comments

Comments
 (0)