Treat `--upgrade-package` on the command-line as overriding `upgrade = false` in configuration (#15395)

## Summary

Right now, if you put `upgrade = false` in a `uv.toml`, then pass
`--upgrade-package numpy` on the CLI, we won't upgrade NumPy. This PR
fixes that interaction by ensuring that when we "combine", we look at
those arguments holistically (i.e., we bundle `upgrade` and
`upgrade-package` into a single struct, which then goes through the
`.combine` logic), rather than combining `upgrade` and `upgrade-package`
independently.

If approved, I then need to add the same thing for `no-build-isolation`,
`reinstall`, `no-build`, and `no-binary`.
This commit is contained in:
Charlie Marsh 2025-08-21 16:20:55 +01:00 committed by GitHub
parent b950453891
commit 0397595e53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 357 additions and 145 deletions

View File

@ -1,7 +1,8 @@
use anstream::eprintln; use anstream::eprintln;
use uv_cache::Refresh; use uv_cache::Refresh;
use uv_distribution_types::{ConfigSettings, PackageConfigSettings}; use uv_configuration::UpgradeSelection;
use uv_distribution_types::{ConfigSettings, PackageConfigSettings, Requirement};
use uv_resolver::{ExcludeNewer, ExcludeNewerPackage, PrereleaseMode}; use uv_resolver::{ExcludeNewer, ExcludeNewerPackage, PrereleaseMode};
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions}; use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
use uv_warnings::owo_colors::OwoColorize; use uv_warnings::owo_colors::OwoColorize;
@ -333,8 +334,10 @@ pub fn resolver_options(
.filter_map(Maybe::into_option) .filter_map(Maybe::into_option)
.collect() .collect()
}), }),
upgrade: flag(upgrade, no_upgrade, "no-upgrade"), upgrade: UpgradeSelection::from_args(
upgrade_package: Some(upgrade_package), flag(upgrade, no_upgrade, "no-upgrade"),
upgrade_package.into_iter().map(Requirement::from).collect(),
),
index_strategy, index_strategy,
keyring_provider, keyring_provider,
resolution, resolution,
@ -442,12 +445,10 @@ pub fn resolver_installer_options(
.filter_map(Maybe::into_option) .filter_map(Maybe::into_option)
.collect() .collect()
}), }),
upgrade: flag(upgrade, no_upgrade, "upgrade"), upgrade: UpgradeSelection::from_args(
upgrade_package: if upgrade_package.is_empty() { flag(upgrade, no_upgrade, "upgrade"),
None upgrade_package.into_iter().map(Requirement::from).collect(),
} else { ),
Some(upgrade_package)
},
reinstall: flag(reinstall, no_reinstall, "reinstall"), reinstall: flag(reinstall, no_reinstall, "reinstall"),
reinstall_package: if reinstall_package.is_empty() { reinstall_package: if reinstall_package.is_empty() {
None None

View File

@ -134,9 +134,57 @@ impl From<Reinstall> for Refresh {
} }
} }
/// An upgrade selection as specified by a user on the command line or in a configuration file.
#[derive(Debug, Default, Clone)]
pub enum UpgradeSelection {
/// Prefer pinned versions from the existing lockfile, if possible.
#[default]
None,
/// Allow package upgrades for all packages, ignoring the existing lockfile.
All,
/// Allow package upgrades, but only for the specified packages.
Packages(Vec<Requirement>),
}
impl UpgradeSelection {
/// Determine the upgrade selection strategy from the command-line arguments.
pub fn from_args(upgrade: Option<bool>, upgrade_package: Vec<Requirement>) -> Option<Self> {
match upgrade {
Some(true) => Some(Self::All),
// TODO(charlie): `--no-upgrade` with `--upgrade-package` should allow the specified
// packages to be upgraded. Right now, `--upgrade-package` is silently ignored.
Some(false) => Some(Self::None),
None if upgrade_package.is_empty() => None,
None => Some(Self::Packages(upgrade_package)),
}
}
/// Combine a set of [`UpgradeSelection`] values.
#[must_use]
pub fn combine(self, other: Self) -> Self {
match self {
// Setting `--upgrade` or `--no-upgrade` should clear previous `--upgrade-package` selections.
Self::All | Self::None => self,
Self::Packages(self_packages) => match other {
// If `--upgrade` was enabled previously, `--upgrade-package` is subsumed by upgrading all packages.
Self::All => other,
// If `--no-upgrade` was enabled previously, then `--upgrade-package` enables an explicit upgrade of those packages.
Self::None => Self::Packages(self_packages),
// If `--upgrade-package` was included twice, combine the requirements.
Self::Packages(other_packages) => {
let mut combined = self_packages;
combined.extend(other_packages);
Self::Packages(combined)
}
},
}
}
}
/// Whether to allow package upgrades. /// Whether to allow package upgrades.
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, Clone)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub enum Upgrade { pub enum Upgrade {
/// Prefer pinned versions from the existing lockfile, if possible. /// Prefer pinned versions from the existing lockfile, if possible.
#[default] #[default]
@ -149,30 +197,28 @@ pub enum Upgrade {
Packages(FxHashMap<PackageName, Vec<Requirement>>), Packages(FxHashMap<PackageName, Vec<Requirement>>),
} }
impl Upgrade { /// Determine the [`Upgrade`] strategy from the command-line arguments.
/// Determine the [`Upgrade`] strategy from the command-line arguments. impl From<Option<UpgradeSelection>> for Upgrade {
pub fn from_args(upgrade: Option<bool>, upgrade_package: Vec<Requirement>) -> Self { fn from(value: Option<UpgradeSelection>) -> Self {
match upgrade { match value {
Some(true) => Self::All, None => Self::None,
Some(false) => Self::None, Some(UpgradeSelection::None) => Self::None,
None => { Some(UpgradeSelection::All) => Self::All,
if upgrade_package.is_empty() { Some(UpgradeSelection::Packages(requirements)) => Self::Packages(
Self::None requirements
} else { .into_iter()
Self::Packages(upgrade_package.into_iter().fold( .fold(FxHashMap::default(), |mut map, requirement| {
FxHashMap::default(),
|mut map, requirement| {
map.entry(requirement.name.clone()) map.entry(requirement.name.clone())
.or_default() .or_default()
.push(requirement); .push(requirement);
map map
}, }),
)) ),
}
}
} }
} }
}
impl Upgrade {
/// Create an [`Upgrade`] strategy to upgrade a single package. /// Create an [`Upgrade`] strategy to upgrade a single package.
pub fn package(package_name: PackageName) -> Self { pub fn package(package_name: PackageName) -> Self {
Self::Packages({ Self::Packages({

View File

@ -14,7 +14,7 @@ use uv_pep440::VersionSpecifiers;
use uv_pep508::PackageName; use uv_pep508::PackageName;
use uv_pypi_types::VerbatimParsedUrl; use uv_pypi_types::VerbatimParsedUrl;
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
use uv_settings::{GlobalOptions, ResolverInstallerOptions}; use uv_settings::{GlobalOptions, ResolverInstallerSchema};
use uv_warnings::warn_user; use uv_warnings::warn_user;
use uv_workspace::pyproject::{ExtraBuildDependency, Sources}; use uv_workspace::pyproject::{ExtraBuildDependency, Sources};
@ -424,7 +424,7 @@ pub struct ToolUv {
#[serde(flatten)] #[serde(flatten)]
pub globals: GlobalOptions, pub globals: GlobalOptions,
#[serde(flatten)] #[serde(flatten)]
pub top_level: ResolverInstallerOptions, pub top_level: ResolverInstallerSchema,
pub override_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>, pub override_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
pub constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>, pub constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
pub build_constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>, pub build_constraint_dependencies: Option<Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,

View File

@ -5,7 +5,7 @@ use url::Url;
use uv_configuration::{ use uv_configuration::{
ExportFormat, IndexStrategy, KeyringProviderType, RequiredVersion, TargetTriple, ExportFormat, IndexStrategy, KeyringProviderType, RequiredVersion, TargetTriple,
TrustedPublishing, TrustedPublishing, UpgradeSelection,
}; };
use uv_distribution_types::{ use uv_distribution_types::{
ConfigSettings, ExtraBuildVariables, Index, IndexUrl, PackageConfigSettings, PipExtraIndex, ConfigSettings, ExtraBuildVariables, Index, IndexUrl, PackageConfigSettings, PipExtraIndex,
@ -181,6 +181,15 @@ impl Combine for Option<PackageConfigSettings> {
} }
} }
impl Combine for Option<UpgradeSelection> {
fn combine(self, other: Self) -> Self {
match (self, other) {
(Some(a), Some(b)) => Some(a.combine(b)),
(a, b) => a.or(b),
}
}
}
impl Combine for serde::de::IgnoredAny { impl Combine for serde::de::IgnoredAny {
fn combine(self, _other: Self) -> Self { fn combine(self, _other: Self) -> Self {
self self

View File

@ -301,7 +301,7 @@ fn warn_uv_toml_masked_fields(options: &Options) {
allow_insecure_host, allow_insecure_host,
}, },
top_level: top_level:
ResolverInstallerOptions { ResolverInstallerSchema {
index, index,
index_url, index_url,
extra_index_url, extra_index_url,

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use uv_cache_info::CacheKey; use uv_cache_info::CacheKey;
use uv_configuration::{ use uv_configuration::{
IndexStrategy, KeyringProviderType, PackageNameSpecifier, RequiredVersion, TargetTriple, IndexStrategy, KeyringProviderType, PackageNameSpecifier, RequiredVersion, TargetTriple,
TrustedHost, TrustedPublishing, TrustedHost, TrustedPublishing, UpgradeSelection,
}; };
use uv_distribution_types::{ use uv_distribution_types::{
ConfigSettings, ExtraBuildVariables, Index, IndexUrl, IndexUrlError, PackageConfigSettings, ConfigSettings, ExtraBuildVariables, Index, IndexUrl, IndexUrlError, PackageConfigSettings,
@ -52,7 +52,7 @@ pub struct Options {
pub globals: GlobalOptions, pub globals: GlobalOptions,
#[serde(flatten)] #[serde(flatten)]
pub top_level: ResolverInstallerOptions, pub top_level: ResolverInstallerSchema,
#[serde(flatten)] #[serde(flatten)]
pub install_mirrors: PythonInstallMirrors, pub install_mirrors: PythonInstallMirrors,
@ -161,7 +161,7 @@ pub struct Options {
impl Options { impl Options {
/// Construct an [`Options`] with the given global and top-level settings. /// Construct an [`Options`] with the given global and top-level settings.
pub fn simple(globals: GlobalOptions, top_level: ResolverInstallerOptions) -> Self { pub fn simple(globals: GlobalOptions, top_level: ResolverInstallerSchema) -> Self {
Self { Self {
globals, globals,
top_level, top_level,
@ -369,8 +369,7 @@ pub struct ResolverOptions {
pub config_settings_package: Option<PackageConfigSettings>, pub config_settings_package: Option<PackageConfigSettings>,
pub exclude_newer: ExcludeNewer, pub exclude_newer: ExcludeNewer,
pub link_mode: Option<LinkMode>, pub link_mode: Option<LinkMode>,
pub upgrade: Option<bool>, pub upgrade: Option<UpgradeSelection>,
pub upgrade_package: Option<Vec<Requirement<VerbatimParsedUrl>>>,
pub no_build: Option<bool>, pub no_build: Option<bool>,
pub no_build_package: Option<Vec<PackageName>>, pub no_build_package: Option<Vec<PackageName>>,
pub no_binary: Option<bool>, pub no_binary: Option<bool>,
@ -384,10 +383,159 @@ pub struct ResolverOptions {
/// Shared settings, relevant to all operations that must resolve and install dependencies. The /// Shared settings, relevant to all operations that must resolve and install dependencies. The
/// union of [`InstallerOptions`] and [`ResolverOptions`]. /// union of [`InstallerOptions`] and [`ResolverOptions`].
#[derive(Debug, Clone, Default, CombineOptions)]
pub struct ResolverInstallerOptions {
pub index: Option<Vec<Index>>,
pub index_url: Option<PipIndex>,
pub extra_index_url: Option<Vec<PipExtraIndex>>,
pub no_index: Option<bool>,
pub find_links: Option<Vec<PipFindLinks>>,
pub index_strategy: Option<IndexStrategy>,
pub keyring_provider: Option<KeyringProviderType>,
pub resolution: Option<ResolutionMode>,
pub prerelease: Option<PrereleaseMode>,
pub fork_strategy: Option<ForkStrategy>,
pub dependency_metadata: Option<Vec<StaticMetadata>>,
pub config_settings: Option<ConfigSettings>,
pub config_settings_package: Option<PackageConfigSettings>,
pub no_build_isolation: Option<bool>,
pub no_build_isolation_package: Option<Vec<PackageName>>,
pub extra_build_dependencies: Option<ExtraBuildDependencies>,
pub extra_build_variables: Option<ExtraBuildVariables>,
pub exclude_newer: Option<ExcludeNewerTimestamp>,
pub exclude_newer_package: Option<ExcludeNewerPackage>,
pub link_mode: Option<LinkMode>,
pub compile_bytecode: Option<bool>,
pub no_sources: Option<bool>,
pub upgrade: Option<UpgradeSelection>,
pub reinstall: Option<bool>,
pub reinstall_package: Option<Vec<PackageName>>,
pub no_build: Option<bool>,
pub no_build_package: Option<Vec<PackageName>>,
pub no_binary: Option<bool>,
pub no_binary_package: Option<Vec<PackageName>>,
}
impl From<ResolverInstallerSchema> for ResolverInstallerOptions {
fn from(value: ResolverInstallerSchema) -> Self {
let ResolverInstallerSchema {
index,
index_url,
extra_index_url,
no_index,
find_links,
index_strategy,
keyring_provider,
resolution,
prerelease,
fork_strategy,
dependency_metadata,
config_settings,
config_settings_package,
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
exclude_newer_package,
link_mode,
compile_bytecode,
no_sources,
upgrade,
upgrade_package,
reinstall,
reinstall_package,
no_build,
no_build_package,
no_binary,
no_binary_package,
} = value;
Self {
index,
index_url,
extra_index_url,
no_index,
find_links,
index_strategy,
keyring_provider,
resolution,
prerelease,
fork_strategy,
dependency_metadata,
config_settings,
config_settings_package,
no_build_isolation,
no_build_isolation_package,
extra_build_dependencies,
extra_build_variables,
exclude_newer,
exclude_newer_package,
link_mode,
compile_bytecode,
no_sources,
upgrade: UpgradeSelection::from_args(
upgrade,
upgrade_package
.into_iter()
.flatten()
.map(Into::into)
.collect(),
),
reinstall,
reinstall_package,
no_build,
no_build_package,
no_binary,
no_binary_package,
}
}
}
impl ResolverInstallerSchema {
/// Resolve the [`ResolverInstallerSchema`] relative to the given root directory.
pub fn relative_to(self, root_dir: &Path) -> Result<Self, IndexUrlError> {
Ok(Self {
index: self
.index
.map(|index| {
index
.into_iter()
.map(|index| index.relative_to(root_dir))
.collect::<Result<Vec<_>, _>>()
})
.transpose()?,
index_url: self
.index_url
.map(|index_url| index_url.relative_to(root_dir))
.transpose()?,
extra_index_url: self
.extra_index_url
.map(|extra_index_url| {
extra_index_url
.into_iter()
.map(|extra_index_url| extra_index_url.relative_to(root_dir))
.collect::<Result<Vec<_>, _>>()
})
.transpose()?,
find_links: self
.find_links
.map(|find_links| {
find_links
.into_iter()
.map(|find_link| find_link.relative_to(root_dir))
.collect::<Result<Vec<_>, _>>()
})
.transpose()?,
..self
})
}
}
/// The JSON schema for the `[tool.uv]` section of a `pyproject.toml` file.
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)] #[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ResolverInstallerOptions { pub struct ResolverInstallerSchema {
/// The package indexes to use when resolving dependencies. /// The package indexes to use when resolving dependencies.
/// ///
/// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/) /// Accepts either a repository compliant with [PEP 503](https://peps.python.org/pep-0503/)
@ -814,46 +962,6 @@ pub struct ResolverInstallerOptions {
pub no_binary_package: Option<Vec<PackageName>>, pub no_binary_package: Option<Vec<PackageName>>,
} }
impl ResolverInstallerOptions {
/// Resolve the [`ResolverInstallerOptions`] relative to the given root directory.
pub fn relative_to(self, root_dir: &Path) -> Result<Self, IndexUrlError> {
Ok(Self {
index: self
.index
.map(|index| {
index
.into_iter()
.map(|index| index.relative_to(root_dir))
.collect::<Result<Vec<_>, _>>()
})
.transpose()?,
index_url: self
.index_url
.map(|index_url| index_url.relative_to(root_dir))
.transpose()?,
extra_index_url: self
.extra_index_url
.map(|extra_index_url| {
extra_index_url
.into_iter()
.map(|extra_index_url| extra_index_url.relative_to(root_dir))
.collect::<Result<Vec<_>, _>>()
})
.transpose()?,
find_links: self
.find_links
.map(|find_links| {
find_links
.into_iter()
.map(|find_link| find_link.relative_to(root_dir))
.collect::<Result<Vec<_>, _>>()
})
.transpose()?,
..self
})
}
}
/// Shared settings, relevant to all operations that might create managed python installations. /// Shared settings, relevant to all operations that might create managed python installations.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)] #[derive(Debug, Clone, PartialEq, Eq, Deserialize, CombineOptions, OptionsMetadata)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
@ -1752,8 +1860,8 @@ impl PipOptions {
} }
} }
impl From<ResolverInstallerOptions> for ResolverOptions { impl From<ResolverInstallerSchema> for ResolverOptions {
fn from(value: ResolverInstallerOptions) -> Self { fn from(value: ResolverInstallerSchema) -> Self {
Self { Self {
index: value.index, index: value.index,
index_url: value.index_url, index_url: value.index_url,
@ -1778,8 +1886,15 @@ impl From<ResolverInstallerOptions> for ResolverOptions {
.collect(), .collect(),
), ),
link_mode: value.link_mode, link_mode: value.link_mode,
upgrade: value.upgrade, upgrade: UpgradeSelection::from_args(
upgrade_package: value.upgrade_package, value.upgrade,
value
.upgrade_package
.into_iter()
.flatten()
.map(Into::into)
.collect(),
),
no_build: value.no_build, no_build: value.no_build,
no_build_package: value.no_build_package, no_build_package: value.no_build_package,
no_binary: value.no_binary, no_binary: value.no_binary,
@ -1793,8 +1908,8 @@ impl From<ResolverInstallerOptions> for ResolverOptions {
} }
} }
impl From<ResolverInstallerOptions> for InstallerOptions { impl From<ResolverInstallerSchema> for InstallerOptions {
fn from(value: ResolverInstallerOptions) -> Self { fn from(value: ResolverInstallerSchema) -> Self {
Self { Self {
index: value.index, index: value.index,
index_url: value.index_url, index_url: value.index_url,
@ -1830,7 +1945,7 @@ impl From<ResolverInstallerOptions> for InstallerOptions {
/// The options persisted alongside an installed tool. /// The options persisted alongside an installed tool.
/// ///
/// A mirror of [`ResolverInstallerOptions`], without upgrades and reinstalls, which shouldn't be /// A mirror of [`ResolverInstallerSchema`], without upgrades and reinstalls, which shouldn't be
/// persisted in a tool receipt. /// persisted in a tool receipt.
#[derive( #[derive(
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, CombineOptions, OptionsMetadata, Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, CombineOptions, OptionsMetadata,
@ -1925,7 +2040,6 @@ impl From<ToolOptions> for ResolverInstallerOptions {
compile_bytecode: value.compile_bytecode, compile_bytecode: value.compile_bytecode,
no_sources: value.no_sources, no_sources: value.no_sources,
upgrade: None, upgrade: None,
upgrade_package: None,
reinstall: None, reinstall: None,
reinstall_package: None, reinstall_package: None,
no_build: value.no_build, no_build: value.no_build,
@ -2120,7 +2234,7 @@ impl From<OptionsWire> for Options {
// Used twice for backwards compatibility // Used twice for backwards compatibility
allow_insecure_host: allow_insecure_host.clone(), allow_insecure_host: allow_insecure_host.clone(),
}, },
top_level: ResolverInstallerOptions { top_level: ResolverInstallerSchema {
index, index,
index_url, index_url,
extra_index_url, extra_index_url,

View File

@ -24,7 +24,7 @@ use uv_configuration::{
BuildOptions, Concurrency, DependencyGroups, DryRun, EditableMode, ExportFormat, BuildOptions, Concurrency, DependencyGroups, DryRun, EditableMode, ExportFormat,
ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType,
NoBinary, NoBuild, Preview, ProjectBuildBackend, Reinstall, RequiredVersion, SourceStrategy, NoBinary, NoBuild, Preview, ProjectBuildBackend, Reinstall, RequiredVersion, SourceStrategy,
TargetTriple, TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, UpgradeSelection, VersionControlSystem,
}; };
use uv_distribution_types::{ use uv_distribution_types::{
ConfigSettings, DependencyMetadata, ExtraBuildVariables, Index, IndexLocations, IndexUrl, ConfigSettings, DependencyMetadata, ExtraBuildVariables, Index, IndexLocations, IndexUrl,
@ -42,7 +42,7 @@ use uv_resolver::{
}; };
use uv_settings::{ use uv_settings::{
Combine, EnvironmentOptions, FilesystemOptions, Options, PipOptions, PublishOptions, Combine, EnvironmentOptions, FilesystemOptions, Options, PipOptions, PublishOptions,
PythonInstallMirrors, ResolverInstallerOptions, ResolverOptions, PythonInstallMirrors, ResolverInstallerOptions, ResolverInstallerSchema, ResolverOptions,
}; };
use uv_static::EnvVars; use uv_static::EnvVars;
use uv_torch::TorchMode; use uv_torch::TorchMode;
@ -538,13 +538,14 @@ impl ToolRunSettings {
} }
} }
let options = resolver_installer_options(installer, build).combine( let options =
resolver_installer_options(installer, build).combine(ResolverInstallerOptions::from(
filesystem filesystem
.clone() .clone()
.map(FilesystemOptions::into_options) .map(FilesystemOptions::into_options)
.map(|options| options.top_level) .map(|options| options.top_level)
.unwrap_or_default(), .unwrap_or_default(),
); ));
let install_mirrors = filesystem let install_mirrors = filesystem
.map(FilesystemOptions::into_options) .map(FilesystemOptions::into_options)
@ -636,13 +637,14 @@ impl ToolInstallSettings {
python, python,
} = args; } = args;
let options = resolver_installer_options(installer, build).combine( let options =
resolver_installer_options(installer, build).combine(ResolverInstallerOptions::from(
filesystem filesystem
.clone() .clone()
.map(FilesystemOptions::into_options) .map(FilesystemOptions::into_options)
.map(|options| options.top_level) .map(|options| options.top_level)
.unwrap_or_default(), .unwrap_or_default(),
); ));
let install_mirrors = filesystem let install_mirrors = filesystem
.map(FilesystemOptions::into_options) .map(FilesystemOptions::into_options)
@ -777,9 +779,11 @@ impl ToolUpgradeSettings {
.clone() .clone()
.map(|options| options.install_mirrors) .map(|options| options.install_mirrors)
.unwrap_or_default(); .unwrap_or_default();
let top_level = filesystem let top_level = ResolverInstallerOptions::from(
filesystem
.map(|options| options.top_level) .map(|options| options.top_level)
.unwrap_or_default(); .unwrap_or_default(),
);
Self { Self {
names: if all { vec![] } else { name }, names: if all { vec![] } else { name },
@ -2798,6 +2802,8 @@ pub(crate) struct ResolverSettings {
impl ResolverSettings { impl ResolverSettings {
/// Resolve the [`ResolverSettings`] from the CLI and filesystem configuration. /// Resolve the [`ResolverSettings`] from the CLI and filesystem configuration.
pub(crate) fn combine(args: ResolverOptions, filesystem: Option<FilesystemOptions>) -> Self { pub(crate) fn combine(args: ResolverOptions, filesystem: Option<FilesystemOptions>) -> Self {
// The problem is that for `upgrade`... we want to combine the two `Upgrade` structs,
// not the individual fields.
let options = args.combine(ResolverOptions::from( let options = args.combine(ResolverOptions::from(
filesystem filesystem
.map(FilesystemOptions::into_options) .map(FilesystemOptions::into_options)
@ -2846,15 +2852,7 @@ impl From<ResolverOptions> for ResolverSettings {
exclude_newer: value.exclude_newer, exclude_newer: value.exclude_newer,
link_mode: value.link_mode.unwrap_or_default(), link_mode: value.link_mode.unwrap_or_default(),
sources: SourceStrategy::from_args(value.no_sources.unwrap_or_default()), sources: SourceStrategy::from_args(value.no_sources.unwrap_or_default()),
upgrade: Upgrade::from_args( upgrade: Upgrade::from(value.upgrade),
value.upgrade,
value
.upgrade_package
.into_iter()
.flatten()
.map(Requirement::from)
.collect(),
),
build_options: BuildOptions::new( build_options: BuildOptions::new(
NoBinary::from_args(value.no_binary, value.no_binary_package.unwrap_or_default()), NoBinary::from_args(value.no_binary, value.no_binary_package.unwrap_or_default()),
NoBuild::from_args(value.no_build, value.no_build_package.unwrap_or_default()), NoBuild::from_args(value.no_build, value.no_build_package.unwrap_or_default()),
@ -2881,12 +2879,12 @@ impl ResolverInstallerSettings {
args: ResolverInstallerOptions, args: ResolverInstallerOptions,
filesystem: Option<FilesystemOptions>, filesystem: Option<FilesystemOptions>,
) -> Self { ) -> Self {
let options = args.combine( let options = args.combine(ResolverInstallerOptions::from(
filesystem filesystem
.map(FilesystemOptions::into_options) .map(FilesystemOptions::into_options)
.map(|options| options.top_level) .map(|options| options.top_level)
.unwrap_or_default(), .unwrap_or_default(),
); ));
Self::from(options) Self::from(options)
} }
@ -2945,15 +2943,7 @@ impl From<ResolverInstallerOptions> for ResolverInstallerSettings {
prerelease: value.prerelease.unwrap_or_default(), prerelease: value.prerelease.unwrap_or_default(),
resolution: value.resolution.unwrap_or_default(), resolution: value.resolution.unwrap_or_default(),
sources: SourceStrategy::from_args(value.no_sources.unwrap_or_default()), sources: SourceStrategy::from_args(value.no_sources.unwrap_or_default()),
upgrade: Upgrade::from_args( upgrade: Upgrade::from(value.upgrade),
value.upgrade,
value
.upgrade_package
.into_iter()
.flatten()
.map(Requirement::from)
.collect(),
),
}, },
compile_bytecode: value.compile_bytecode.unwrap_or_default(), compile_bytecode: value.compile_bytecode.unwrap_or_default(),
reinstall: Reinstall::from_args( reinstall: Reinstall::from_args(
@ -3098,7 +3088,7 @@ impl PipSettings {
exclude_newer_package, exclude_newer_package,
} = pip.unwrap_or_default(); } = pip.unwrap_or_default();
let ResolverInstallerOptions { let ResolverInstallerSchema {
index: top_level_index, index: top_level_index,
index_url: top_level_index_url, index_url: top_level_index_url,
extra_index_url: top_level_extra_index_url, extra_index_url: top_level_extra_index_url,
@ -3327,14 +3317,23 @@ impl PipSettings {
args.no_sources.combine(no_sources).unwrap_or_default(), args.no_sources.combine(no_sources).unwrap_or_default(),
), ),
strict: args.strict.combine(strict).unwrap_or_default(), strict: args.strict.combine(strict).unwrap_or_default(),
upgrade: Upgrade::from_args( upgrade: Upgrade::from(
args.upgrade.combine(upgrade), UpgradeSelection::from_args(
args.upgrade,
args.upgrade_package args.upgrade_package
.combine(upgrade_package)
.into_iter() .into_iter()
.flatten() .flatten()
.map(Requirement::from) .map(Requirement::from)
.collect(), .collect(),
)
.combine(UpgradeSelection::from_args(
upgrade,
upgrade_package
.into_iter()
.flatten()
.map(Requirement::from)
.collect(),
)),
), ),
reinstall: Reinstall::from_args( reinstall: Reinstall::from_args(
args.reinstall.combine(reinstall), args.reinstall.combine(reinstall),
@ -3418,7 +3417,7 @@ impl PublishSettings {
trusted_publishing, trusted_publishing,
check_url, check_url,
} = publish; } = publish;
let ResolverInstallerOptions { let ResolverInstallerSchema {
keyring_provider, keyring_provider,
index, index,
extra_index_url, extra_index_url,

View File

@ -3514,7 +3514,6 @@ fn resolve_tool() -> anyhow::Result<()> {
compile_bytecode: None, compile_bytecode: None,
no_sources: None, no_sources: None,
upgrade: None, upgrade: None,
upgrade_package: None,
reinstall: None, reinstall: None,
reinstall_package: None, reinstall_package: None,
no_build: None, no_build: None,
@ -8569,7 +8568,6 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
"})?; "})?;
// Despite `upgrade = false` in the configuration file, we should mark `idna` for upgrade. // Despite `upgrade = false` in the configuration file, we should mark `idna` for upgrade.
// TODO(charlie): This doesn't mark `idna` for upgrade; it just disables upgrades.
uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path()) uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path())
.arg("--upgrade-package") .arg("--upgrade-package")
.arg("idna") .arg("idna")
@ -8727,7 +8725,30 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
hash_checking: Some( hash_checking: Some(
Verify, Verify,
), ),
upgrade: None, upgrade: Packages(
{
PackageName(
"idna",
): [
Requirement {
name: PackageName(
"idna",
),
extras: [],
groups: [],
marker: true,
source: Registry {
specifier: VersionSpecifiers(
[],
),
index: None,
conflict: None,
},
origin: None,
},
],
},
),
reinstall: None, reinstall: None,
}, },
} }
@ -9600,7 +9621,6 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
"#})?; "#})?;
// Despite `upgrade = false` in the configuration file, we should mark `idna` for upgrade. // Despite `upgrade = false` in the configuration file, we should mark `idna` for upgrade.
// TODO(charlie): This doesn't mark `idna` for upgrade; it just disables upgrades.
uv_snapshot!(context.filters(), add_shared_args(context.lock(), context.temp_dir.path()) uv_snapshot!(context.filters(), add_shared_args(context.lock(), context.temp_dir.path())
.arg("--upgrade-package") .arg("--upgrade-package")
.arg("idna") .arg("idna")
@ -9699,7 +9719,30 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
prerelease: IfNecessaryOrExplicit, prerelease: IfNecessaryOrExplicit,
resolution: Highest, resolution: Highest,
sources: Enabled, sources: Enabled,
upgrade: None, upgrade: Packages(
{
PackageName(
"idna",
): [
Requirement {
name: PackageName(
"idna",
),
extras: [],
groups: [],
marker: true,
source: Registry {
specifier: VersionSpecifiers(
[],
),
index: None,
conflict: None,
},
origin: None,
},
],
},
),
}, },
} }