Skip to content

Commit

Permalink
Introduce a --multi-version preference mode
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Oct 29, 2024
1 parent dc5e35e commit c8ce01b
Show file tree
Hide file tree
Showing 33 changed files with 759 additions and 18 deletions.
32 changes: 31 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 compat;
Expand Down Expand Up @@ -4413,6 +4415,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 @@ -4605,6 +4621,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 @@ -44,6 +44,7 @@ impl From<ResolverArgs> for PipOptions {
resolution,
prerelease,
pre,
multi_version,
config_setting,
no_build_isolation,
no_build_isolation_package,
Expand All @@ -65,6 +66,7 @@ impl From<ResolverArgs> for PipOptions {
.collect()
}),
resolution,
multi_version,
prerelease: if pre {
Some(PrereleaseMode::Allow)
} else {
Expand Down Expand Up @@ -141,6 +143,7 @@ impl From<ResolverInstallerArgs> for PipOptions {
resolution,
prerelease,
pre,
multi_version,
config_setting,
no_build_isolation,
no_build_isolation_package,
Expand Down Expand Up @@ -171,6 +174,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 @@ -239,6 +243,7 @@ pub fn resolver_options(
resolution,
prerelease,
pre,
multi_version,
config_setting,
no_build_isolation,
no_build_isolation_package,
Expand Down Expand Up @@ -301,6 +306,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 @@ -335,6 +341,7 @@ pub fn resolver_installer_options(
resolution,
prerelease,
pre,
multi_version,
config_setting,
no_build_isolation,
no_build_isolation_package,
Expand Down Expand Up @@ -409,6 +416,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
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::{
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 @@ -40,6 +41,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 @@ -16,6 +16,7 @@ use url::Url;

pub use crate::lock::requirements_txt::RequirementsTxtExport;
pub use crate::lock::tree::TreeDisplay;
use crate::multi_version_mode::MultiVersionMode;
use crate::requires_python::SimplifiedMarkerTree;
use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
use crate::{
Expand Down Expand Up @@ -226,6 +227,7 @@ impl Lock {
let options = ResolverOptions {
resolution_mode: graph.options.resolution_mode,
prerelease_mode: graph.options.prerelease_mode,
multi_version_mode: graph.options.multi_version_mode,
exclude_newer: graph.options.exclude_newer,
};
let lock = Self::new(
Expand Down Expand Up @@ -529,6 +531,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 @@ -753,6 +760,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 @@ -1382,6 +1395,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 @@ -30,6 +30,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 @@ -30,6 +30,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 @@ -30,6 +30,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 @@ -30,6 +30,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 @@ -30,6 +30,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 @@ -30,6 +30,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 @@ -30,6 +30,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 @@ -30,6 +30,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 @@ -30,6 +30,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 @@ -30,6 +30,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: 10 additions & 7 deletions crates/uv-resolver/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ use crate::error::{NoSolutionError, ResolveError};
use crate::fork_indexes::ForkIndexes;
use crate::fork_urls::ForkUrls;
use crate::manifest::Manifest;
use crate::multi_version_mode::MultiVersionMode;
use crate::pins::FilePins;
use crate::preferences::Preferences;
use crate::pubgrub::{
Expand Down Expand Up @@ -374,13 +375,15 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag

// Walk over the selected versions, and mark them as preferences. We have to
// add forks back as to not override the preferences from the lockfile for
// the next fork
for (package, version) in &resolution.nodes {
preferences.insert(
package.name.clone(),
resolution.markers.fork_markers().cloned(),
version.clone(),
);
// the next fork.
if matches!(self.options.multi_version_mode, MultiVersionMode::Fewest) {
for (package, version) in &resolution.nodes {
preferences.insert(
package.name.clone(),
resolution.markers.fork_markers().cloned(),
version.clone(),
);
}
}

resolutions.push(resolution);
Expand Down
5 changes: 4 additions & 1 deletion crates/uv-settings/src/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipInd
use uv_install_wheel::linker::LinkMode;
use uv_pypi_types::SupportedEnvironments;
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PrereleaseMode, ResolutionMode};
use uv_resolver::{
AnnotationStyle, ExcludeNewer, MultiVersionMode, PrereleaseMode, ResolutionMode,
};

use crate::{FilesystemOptions, Options, PipOptions};

Expand Down Expand Up @@ -77,6 +79,7 @@ impl_combine_or!(IndexStrategy);
impl_combine_or!(IndexUrl);
impl_combine_or!(KeyringProviderType);
impl_combine_or!(LinkMode);
impl_combine_or!(MultiVersionMode);
impl_combine_or!(NonZeroUsize);
impl_combine_or!(PathBuf);
impl_combine_or!(PipExtraIndex);
Expand Down
Loading

0 comments on commit c8ce01b

Please sign in to comment.