Skip to content

Commit c8ce01b

Browse files
committed
Introduce a --multi-version preference mode
1 parent dc5e35e commit c8ce01b

33 files changed

+759
-18
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 31 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 compat;
@@ -4413,6 +4415,20 @@ pub struct ResolverArgs {
44134415
#[arg(long, hide = true, help_heading = "Resolver options")]
44144416
pub pre: bool,
44154417

4418+
/// The strategy to use when selecting multiple versions of a given package across Python
4419+
/// versions and platforms.
4420+
///
4421+
/// By default, uv will minimize the number of versions selected for each package (`fewest`),
4422+
/// to minimize differences between environments. Under `latest`, uv will select the latest
4423+
/// compatible version for each environment, even if it results in more versions being selected.
4424+
#[arg(
4425+
long,
4426+
value_enum,
4427+
env = EnvVars::UV_MULTI_VERSION,
4428+
help_heading = "Resolver options"
4429+
)]
4430+
pub multi_version: Option<MultiVersionMode>,
4431+
44164432
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
44174433
#[arg(
44184434
long,
@@ -4605,6 +4621,20 @@ pub struct ResolverInstallerArgs {
46054621
#[arg(long, hide = true)]
46064622
pub pre: bool,
46074623

4624+
/// The strategy to use when selecting multiple versions of a given package across Python
4625+
/// versions and platforms.
4626+
///
4627+
/// By default, uv will minimize the number of versions selected for each package (`fewest`),
4628+
/// to minimize differences between environments. Under `latest`, uv will select the latest
4629+
/// compatible version for each environment, even if it results in more versions being selected.
4630+
#[arg(
4631+
long,
4632+
value_enum,
4633+
env = EnvVars::UV_MULTI_VERSION,
4634+
help_heading = "Resolver options"
4635+
)]
4636+
pub multi_version: Option<MultiVersionMode>,
4637+
46084638
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
46094639
#[arg(
46104640
long,

crates/uv-cli/src/options.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl From<ResolverArgs> for PipOptions {
4444
resolution,
4545
prerelease,
4646
pre,
47+
multi_version,
4748
config_setting,
4849
no_build_isolation,
4950
no_build_isolation_package,
@@ -65,6 +66,7 @@ impl From<ResolverArgs> for PipOptions {
6566
.collect()
6667
}),
6768
resolution,
69+
multi_version,
6870
prerelease: if pre {
6971
Some(PrereleaseMode::Allow)
7072
} else {
@@ -141,6 +143,7 @@ impl From<ResolverInstallerArgs> for PipOptions {
141143
resolution,
142144
prerelease,
143145
pre,
146+
multi_version,
144147
config_setting,
145148
no_build_isolation,
146149
no_build_isolation_package,
@@ -171,6 +174,7 @@ impl From<ResolverInstallerArgs> for PipOptions {
171174
} else {
172175
prerelease
173176
},
177+
multi_version,
174178
config_settings: config_setting
175179
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
176180
no_build_isolation: flag(no_build_isolation, build_isolation),
@@ -239,6 +243,7 @@ pub fn resolver_options(
239243
resolution,
240244
prerelease,
241245
pre,
246+
multi_version,
242247
config_setting,
243248
no_build_isolation,
244249
no_build_isolation_package,
@@ -301,6 +306,7 @@ pub fn resolver_options(
301306
} else {
302307
prerelease
303308
},
309+
multi_version,
304310
dependency_metadata: None,
305311
config_settings: config_setting
306312
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
@@ -335,6 +341,7 @@ pub fn resolver_installer_options(
335341
resolution,
336342
prerelease,
337343
pre,
344+
multi_version,
338345
config_setting,
339346
no_build_isolation,
340347
no_build_isolation_package,
@@ -409,6 +416,7 @@ pub fn resolver_installer_options(
409416
} else {
410417
prerelease
411418
},
419+
multi_version,
412420
dependency_metadata: None,
413421
config_settings: config_setting
414422
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),

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
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;
@@ -40,6 +41,7 @@ mod graph_ops;
4041
mod lock;
4142
mod manifest;
4243
mod marker;
44+
mod multi_version_mode;
4345
mod options;
4446
mod pins;
4547
mod preferences;

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use url::Url;
1616

1717
pub use crate::lock::requirements_txt::RequirementsTxtExport;
1818
pub use crate::lock::tree::TreeDisplay;
19+
use crate::multi_version_mode::MultiVersionMode;
1920
use crate::requires_python::SimplifiedMarkerTree;
2021
use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
2122
use crate::{
@@ -226,6 +227,7 @@ impl Lock {
226227
let options = ResolverOptions {
227228
resolution_mode: graph.options.resolution_mode,
228229
prerelease_mode: graph.options.prerelease_mode,
230+
multi_version_mode: graph.options.multi_version_mode,
229231
exclude_newer: graph.options.exclude_newer,
230232
};
231233
let lock = Self::new(
@@ -529,6 +531,11 @@ impl Lock {
529531
self.options.prerelease_mode
530532
}
531533

534+
/// Returns the multi-version mode used to generate this lock.
535+
pub fn multi_version_mode(&self) -> MultiVersionMode {
536+
self.options.multi_version_mode
537+
}
538+
532539
/// Returns the exclude newer setting used to generate this lock.
533540
pub fn exclude_newer(&self) -> Option<ExcludeNewer> {
534541
self.options.exclude_newer
@@ -753,6 +760,12 @@ impl Lock {
753760
value(self.options.prerelease_mode.to_string()),
754761
);
755762
}
763+
if self.options.multi_version_mode != MultiVersionMode::default() {
764+
options_table.insert(
765+
"multi-version-mode",
766+
value(self.options.multi_version_mode.to_string()),
767+
);
768+
}
756769
if let Some(exclude_newer) = self.options.exclude_newer {
757770
options_table.insert("exclude-newer", value(exclude_newer.to_string()));
758771
}
@@ -1382,6 +1395,9 @@ struct ResolverOptions {
13821395
/// The [`PrereleaseMode`] used to generate this lock.
13831396
#[serde(default)]
13841397
prerelease_mode: PrereleaseMode,
1398+
/// The [`MultiVersionMode`] used to generate this lock.
1399+
#[serde(default)]
1400+
multi_version_mode: MultiVersionMode,
13851401
/// The [`ExcludeNewer`] used to generate this lock.
13861402
exclude_newer: Option<ExcludeNewer>,
13871403
}

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
@@ -30,6 +30,7 @@ Ok(
3030
options: ResolverOptions {
3131
resolution_mode: Highest,
3232
prerelease_mode: IfNecessaryOrExplicit,
33+
multi_version_mode: Fewest,
3334
exclude_newer: None,
3435
},
3536
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
@@ -30,6 +30,7 @@ Ok(
3030
options: ResolverOptions {
3131
resolution_mode: Highest,
3232
prerelease_mode: IfNecessaryOrExplicit,
33+
multi_version_mode: Fewest,
3334
exclude_newer: None,
3435
},
3536
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
@@ -30,6 +30,7 @@ Ok(
3030
options: ResolverOptions {
3131
resolution_mode: Highest,
3232
prerelease_mode: IfNecessaryOrExplicit,
33+
multi_version_mode: Fewest,
3334
exclude_newer: None,
3435
},
3536
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
@@ -30,6 +30,7 @@ Ok(
3030
options: ResolverOptions {
3131
resolution_mode: Highest,
3232
prerelease_mode: IfNecessaryOrExplicit,
33+
multi_version_mode: Fewest,
3334
exclude_newer: None,
3435
},
3536
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
@@ -30,6 +30,7 @@ Ok(
3030
options: ResolverOptions {
3131
resolution_mode: Highest,
3232
prerelease_mode: IfNecessaryOrExplicit,
33+
multi_version_mode: Fewest,
3334
exclude_newer: None,
3435
},
3536
packages: [

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Ok(
3030
options: ResolverOptions {
3131
resolution_mode: Highest,
3232
prerelease_mode: IfNecessaryOrExplicit,
33+
multi_version_mode: Fewest,
3334
exclude_newer: None,
3435
},
3536
packages: [

0 commit comments

Comments
 (0)