mirror of https://github.com/astral-sh/uv
Add `--upgrade` support to `pip install` (#1379)
Adds support for `--upgrade` — similar to `--reinstall`. Closes https://github.com/astral-sh/uv/issues/1391
This commit is contained in:
parent
e9d82cf0fa
commit
896ab1c54f
|
|
@ -467,4 +467,9 @@ impl Reinstall {
|
||||||
pub fn is_none(&self) -> bool {
|
pub fn is_none(&self) -> bool {
|
||||||
matches!(self, Self::None)
|
matches!(self, Self::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if all packages should be reinstalled.
|
||||||
|
pub fn is_all(&self) -> bool {
|
||||||
|
matches!(self, Self::All)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -425,6 +425,11 @@ impl Upgrade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if no packages should be upgraded.
|
||||||
|
pub(crate) fn is_none(&self) -> bool {
|
||||||
|
matches!(self, Self::None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if all packages should be upgraded.
|
/// Returns `true` if all packages should be upgraded.
|
||||||
pub(crate) fn is_all(&self) -> bool {
|
pub(crate) fn is_all(&self) -> bool {
|
||||||
matches!(self, Self::All)
|
matches!(self, Self::All)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anstream::eprint;
|
use anstream::eprint;
|
||||||
|
|
@ -38,6 +40,8 @@ use crate::commands::{elapsed, ChangeEvent, ChangeEventKind, ExitStatus};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsSpecification};
|
use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsSpecification};
|
||||||
|
|
||||||
|
use super::Upgrade;
|
||||||
|
|
||||||
/// Install packages into the current environment.
|
/// Install packages into the current environment.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) async fn pip_install(
|
pub(crate) async fn pip_install(
|
||||||
|
|
@ -48,6 +52,7 @@ pub(crate) async fn pip_install(
|
||||||
resolution_mode: ResolutionMode,
|
resolution_mode: ResolutionMode,
|
||||||
prerelease_mode: PreReleaseMode,
|
prerelease_mode: PreReleaseMode,
|
||||||
dependency_mode: DependencyMode,
|
dependency_mode: DependencyMode,
|
||||||
|
upgrade: Upgrade,
|
||||||
index_locations: IndexLocations,
|
index_locations: IndexLocations,
|
||||||
reinstall: &Reinstall,
|
reinstall: &Reinstall,
|
||||||
link_mode: LinkMode,
|
link_mode: LinkMode,
|
||||||
|
|
@ -115,7 +120,10 @@ pub(crate) async fn pip_install(
|
||||||
// If the requirements are already satisfied, we're done. Ideally, the resolver would be fast
|
// If the requirements are already satisfied, we're done. Ideally, the resolver would be fast
|
||||||
// enough to let us remove this check. But right now, for large environments, it's an order of
|
// enough to let us remove this check. But right now, for large environments, it's an order of
|
||||||
// magnitude faster to validate the environment than to resolve the requirements.
|
// magnitude faster to validate the environment than to resolve the requirements.
|
||||||
if reinstall.is_none() && site_packages.satisfies(&requirements, &editables, &constraints)? {
|
if reinstall.is_none()
|
||||||
|
&& upgrade.is_none()
|
||||||
|
&& site_packages.satisfies(&requirements, &editables, &constraints)?
|
||||||
|
{
|
||||||
let num_requirements = requirements.len() + editables.len();
|
let num_requirements = requirements.len() + editables.len();
|
||||||
let s = if num_requirements == 1 { "" } else { "s" };
|
let s = if num_requirements == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
@ -206,6 +214,7 @@ pub(crate) async fn pip_install(
|
||||||
&editables,
|
&editables,
|
||||||
&site_packages,
|
&site_packages,
|
||||||
reinstall,
|
reinstall,
|
||||||
|
&upgrade,
|
||||||
&interpreter,
|
&interpreter,
|
||||||
tags,
|
tags,
|
||||||
markers,
|
markers,
|
||||||
|
|
@ -378,6 +387,7 @@ async fn resolve(
|
||||||
editables: &[BuiltEditable],
|
editables: &[BuiltEditable],
|
||||||
site_packages: &SitePackages<'_>,
|
site_packages: &SitePackages<'_>,
|
||||||
reinstall: &Reinstall,
|
reinstall: &Reinstall,
|
||||||
|
upgrade: &Upgrade,
|
||||||
interpreter: &Interpreter,
|
interpreter: &Interpreter,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
markers: &MarkerEnvironment,
|
markers: &MarkerEnvironment,
|
||||||
|
|
@ -390,14 +400,25 @@ async fn resolve(
|
||||||
) -> Result<ResolutionGraph, Error> {
|
) -> Result<ResolutionGraph, Error> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
// Respect preferences from the existing environments.
|
let preferences = if upgrade.is_all() || reinstall.is_all() {
|
||||||
let preferences: Vec<Requirement> = match reinstall {
|
vec![]
|
||||||
Reinstall::All => vec![],
|
} else {
|
||||||
Reinstall::None => site_packages.requirements().collect(),
|
// Combine upgrade and reinstall lists
|
||||||
Reinstall::Packages(packages) => site_packages
|
let mut exclusions: HashSet<&PackageName> = if let Reinstall::Packages(packages) = reinstall
|
||||||
|
{
|
||||||
|
HashSet::from_iter(packages)
|
||||||
|
} else {
|
||||||
|
HashSet::default()
|
||||||
|
};
|
||||||
|
if let Upgrade::Packages(packages) = upgrade {
|
||||||
|
exclusions.extend(packages);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prefer current site packages, unless in the upgrade or reinstall lists
|
||||||
|
site_packages
|
||||||
.requirements()
|
.requirements()
|
||||||
.filter(|requirement| !packages.contains(&requirement.name))
|
.filter(|requirement| !exclusions.contains(&requirement.name))
|
||||||
.collect(),
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map the editables to their metadata.
|
// Map the editables to their metadata.
|
||||||
|
|
|
||||||
|
|
@ -470,6 +470,14 @@ struct PipInstallArgs {
|
||||||
#[clap(long, conflicts_with = "extra")]
|
#[clap(long, conflicts_with = "extra")]
|
||||||
all_extras: bool,
|
all_extras: bool,
|
||||||
|
|
||||||
|
/// Allow package upgrades.
|
||||||
|
#[clap(long)]
|
||||||
|
upgrade: bool,
|
||||||
|
|
||||||
|
/// Allow upgrade of a specific package.
|
||||||
|
#[clap(long)]
|
||||||
|
upgrade_package: Vec<PackageName>,
|
||||||
|
|
||||||
/// Reinstall all packages, regardless of whether they're already installed.
|
/// Reinstall all packages, regardless of whether they're already installed.
|
||||||
#[clap(long, alias = "force-reinstall")]
|
#[clap(long, alias = "force-reinstall")]
|
||||||
reinstall: bool,
|
reinstall: bool,
|
||||||
|
|
@ -935,6 +943,7 @@ async fn run() -> Result<ExitStatus> {
|
||||||
ExtrasSpecification::Some(&args.extra)
|
ExtrasSpecification::Some(&args.extra)
|
||||||
};
|
};
|
||||||
let reinstall = Reinstall::from_args(args.reinstall, args.reinstall_package);
|
let reinstall = Reinstall::from_args(args.reinstall, args.reinstall_package);
|
||||||
|
let upgrade = Upgrade::from_args(args.upgrade, args.upgrade_package);
|
||||||
let no_binary = NoBinary::from_args(args.no_binary);
|
let no_binary = NoBinary::from_args(args.no_binary);
|
||||||
let no_build = NoBuild::from_args(args.only_binary, args.no_build);
|
let no_build = NoBuild::from_args(args.only_binary, args.no_build);
|
||||||
let dependency_mode = if args.no_deps {
|
let dependency_mode = if args.no_deps {
|
||||||
|
|
@ -950,6 +959,7 @@ async fn run() -> Result<ExitStatus> {
|
||||||
args.resolution,
|
args.resolution,
|
||||||
args.prerelease,
|
args.prerelease,
|
||||||
dependency_mode,
|
dependency_mode,
|
||||||
|
upgrade,
|
||||||
index_urls,
|
index_urls,
|
||||||
&reinstall,
|
&reinstall,
|
||||||
args.link_mode,
|
args.link_mode,
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ fn install_requirements_txt() -> Result<()> {
|
||||||
|
|
||||||
/// Respect installed versions when resolving.
|
/// Respect installed versions when resolving.
|
||||||
#[test]
|
#[test]
|
||||||
fn respect_installed() -> Result<()> {
|
fn respect_installed_and_reinstall() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
// Install Flask.
|
// Install Flask.
|
||||||
|
|
@ -268,6 +268,29 @@ fn respect_installed() -> Result<()> {
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Re-install Flask. We should install even though the version is current
|
||||||
|
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||||
|
requirements_txt.touch()?;
|
||||||
|
requirements_txt.write_str("Flask")?;
|
||||||
|
|
||||||
|
uv_snapshot!(filters, command(&context)
|
||||||
|
.arg("-r")
|
||||||
|
.arg("requirements.txt")
|
||||||
|
.arg("--reinstall-package")
|
||||||
|
.arg("Flask")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 7 packages in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
- flask==3.0.0
|
||||||
|
+ flask==3.0.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -894,3 +917,98 @@ fn no_deps() {
|
||||||
|
|
||||||
context.assert_command("import flask").failure();
|
context.assert_command("import flask").failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upgrade a package.
|
||||||
|
#[test]
|
||||||
|
fn install_upgrade() {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Install an old version of anyio and httpcore.
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("anyio==3.6.2")
|
||||||
|
.arg("httpcore==0.16.3")
|
||||||
|
.arg("--strict"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Downloaded 6 packages in [TIME]
|
||||||
|
Installed 6 packages in [TIME]
|
||||||
|
+ anyio==3.6.2
|
||||||
|
+ certifi==2023.11.17
|
||||||
|
+ h11==0.14.0
|
||||||
|
+ httpcore==0.16.3
|
||||||
|
+ idna==3.4
|
||||||
|
+ sniffio==1.3.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
context.assert_command("import anyio").success();
|
||||||
|
|
||||||
|
// Upgrade anyio.
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("anyio")
|
||||||
|
.arg("--upgrade-package")
|
||||||
|
.arg("anyio"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
- anyio==3.6.2
|
||||||
|
+ anyio==4.0.0
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Upgrade anyio again, should not reinstall.
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("anyio")
|
||||||
|
.arg("--upgrade-package")
|
||||||
|
.arg("anyio"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Audited 3 packages in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Install httpcore, request anyio upgrade should not reinstall
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("httpcore")
|
||||||
|
.arg("--upgrade-package")
|
||||||
|
.arg("anyio"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Audited 6 packages in [TIME]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
// Upgrade httpcore with global flag
|
||||||
|
uv_snapshot!(command(&context)
|
||||||
|
.arg("httpcore")
|
||||||
|
.arg("--upgrade"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 3 packages in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
- httpcore==0.16.3
|
||||||
|
+ httpcore==1.0.2
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue