mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-18 11:20:40 +00:00
Add support for --upgrade in uv python install (#16676)
Some checks are pending
zizmor / Run zizmor (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | aarch64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | activate nushell venv (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | windows python install manager (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | pyenv on wsl x86-64 (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 10 (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | x86-64 python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
Some checks are pending
zizmor / Run zizmor (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | aarch64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | activate nushell venv (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | windows python install manager (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | pyenv on wsl x86-64 (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 10 (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | x86-64 python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
This allows us to suggest `uv python install --upgrade 3.14` as the canonical way to get the latest patch version of a given Python regardless of whether it is installed already. Currently, you can do `uv python upgrade 3.14` and it will install it, but I'd like to remove that behavior as I find it very surprising.
This commit is contained in:
parent
e28dc62358
commit
f5ce5b47c8
8 changed files with 341 additions and 49 deletions
|
|
@ -5868,6 +5868,19 @@ pub struct PythonInstallArgs {
|
|||
#[arg(long, short)]
|
||||
pub force: bool,
|
||||
|
||||
/// Upgrade existing Python installations to the latest patch version.
|
||||
///
|
||||
/// By default, uv will not upgrade already-installed Python versions to newer patch releases.
|
||||
/// With `--upgrade`, uv will upgrade to the latest available patch version for the specified
|
||||
/// minor version(s).
|
||||
///
|
||||
/// If the requested versions are not yet installed, uv will install them.
|
||||
///
|
||||
/// This option is only supported for minor version requests, e.g., `3.12`; uv will exit with an
|
||||
/// error if a patch version, e.g., `3.12.2`, is requested.
|
||||
#[arg(long, short = 'U')]
|
||||
pub upgrade: bool,
|
||||
|
||||
/// Use as the default Python version.
|
||||
///
|
||||
/// By default, only a `python{major}.{minor}` executable is installed, e.g., `python3.10`. When
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ pub(crate) use python::dir::dir as python_dir;
|
|||
pub(crate) use python::find::find as python_find;
|
||||
pub(crate) use python::find::find_script as python_find_script;
|
||||
pub(crate) use python::install::install as python_install;
|
||||
pub(crate) use python::install::{PythonUpgrade, PythonUpgradeSource};
|
||||
pub(crate) use python::list::list as python_list;
|
||||
pub(crate) use python::pin::pin as python_pin;
|
||||
pub(crate) use python::uninstall::uninstall as python_uninstall;
|
||||
|
|
|
|||
|
|
@ -149,6 +149,31 @@ enum InstallErrorKind {
|
|||
Registry,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum PythonUpgradeSource {
|
||||
/// The user invoked `uv python install --upgrade`
|
||||
Install,
|
||||
/// The user invoked `uv python upgrade`
|
||||
Upgrade,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PythonUpgradeSource {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Install => write!(f, "uv python install --upgrade"),
|
||||
Self::Upgrade => write!(f, "uv python upgrade"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum PythonUpgrade {
|
||||
/// Python upgrades are enabled.
|
||||
Enabled(PythonUpgradeSource),
|
||||
/// Python upgrades are disabled.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// Download and install Python versions.
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
pub(crate) async fn install(
|
||||
|
|
@ -156,7 +181,7 @@ pub(crate) async fn install(
|
|||
install_dir: Option<PathBuf>,
|
||||
targets: Vec<String>,
|
||||
reinstall: bool,
|
||||
upgrade: bool,
|
||||
upgrade: PythonUpgrade,
|
||||
bin: Option<bool>,
|
||||
registry: Option<bool>,
|
||||
force: bool,
|
||||
|
|
@ -183,11 +208,13 @@ pub(crate) async fn install(
|
|||
);
|
||||
}
|
||||
|
||||
if upgrade && !preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) {
|
||||
warn_user!(
|
||||
"`uv python upgrade` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning",
|
||||
PreviewFeatures::PYTHON_UPGRADE
|
||||
);
|
||||
if let PythonUpgrade::Enabled(source @ PythonUpgradeSource::Upgrade) = upgrade {
|
||||
if !preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) {
|
||||
warn_user!(
|
||||
"`{source}` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning",
|
||||
PreviewFeatures::PYTHON_UPGRADE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if default && targets.len() > 1 {
|
||||
|
|
@ -207,8 +234,14 @@ pub(crate) async fn install(
|
|||
// Resolve the requests
|
||||
let mut is_default_install = false;
|
||||
let mut is_unspecified_upgrade = false;
|
||||
// TODO(zanieb): We use this variable to special-case .python-version files, but it'd be nice to
|
||||
// have generalized request source tracking instead
|
||||
let mut is_from_python_version_file = false;
|
||||
let requests: Vec<_> = if targets.is_empty() {
|
||||
if upgrade {
|
||||
if matches!(
|
||||
upgrade,
|
||||
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade)
|
||||
) {
|
||||
is_unspecified_upgrade = true;
|
||||
// On upgrade, derive requests for all of the existing installations
|
||||
let mut minor_version_requests = IndexSet::<InstallRequest>::default();
|
||||
|
|
@ -240,6 +273,7 @@ pub(crate) async fn install(
|
|||
);
|
||||
})
|
||||
.map(PythonVersionFile::into_versions)
|
||||
.inspect(|_| is_from_python_version_file = true)
|
||||
.unwrap_or_else(|| {
|
||||
// If no version file is found and no requests were made
|
||||
// TODO(zanieb): We should consider differentiating between a global Python version
|
||||
|
|
@ -265,11 +299,20 @@ pub(crate) async fn install(
|
|||
};
|
||||
|
||||
if requests.is_empty() {
|
||||
if upgrade {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"There are no installed versions to upgrade"
|
||||
)?;
|
||||
match upgrade {
|
||||
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade) => {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"There are no installed versions to upgrade"
|
||||
)?;
|
||||
}
|
||||
PythonUpgrade::Enabled(PythonUpgradeSource::Install) => {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"No Python versions specified for upgrade; did you mean `uv python upgrade`?"
|
||||
)?;
|
||||
}
|
||||
PythonUpgrade::Disabled => {}
|
||||
}
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
|
@ -287,17 +330,25 @@ pub(crate) async fn install(
|
|||
})
|
||||
.collect::<IndexSet<_>>();
|
||||
|
||||
if upgrade
|
||||
&& let Some(request) = requests.iter().find(|request| {
|
||||
if let PythonUpgrade::Enabled(source) = upgrade {
|
||||
if let Some(request) = requests.iter().find(|request| {
|
||||
request.request.includes_patch() || request.request.includes_prerelease()
|
||||
})
|
||||
{
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"error: `uv python upgrade` only accepts minor versions, got: {}",
|
||||
request.request.to_canonical_string()
|
||||
)?;
|
||||
return Ok(ExitStatus::Failure);
|
||||
}) {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"error: `{source}` only accepts minor versions, got: {}",
|
||||
request.request.to_canonical_string()
|
||||
)?;
|
||||
if is_from_python_version_file {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"\n{}{} The version request came from a `.python-version` file; change the patch version in the file to upgrade instead",
|
||||
"hint".bold().cyan(),
|
||||
":".bold(),
|
||||
)?;
|
||||
}
|
||||
return Ok(ExitStatus::Failure);
|
||||
}
|
||||
}
|
||||
|
||||
// Find requests that are already satisfied
|
||||
|
|
@ -361,10 +412,10 @@ pub(crate) async fn install(
|
|||
// If we can find one existing installation that matches the request, it is satisfied
|
||||
requests.iter().partition_map(|request| {
|
||||
if let Some(installation) = existing_installations.iter().find(|installation| {
|
||||
if upgrade {
|
||||
// If this is an upgrade, the requested version is a minor version
|
||||
// but the requested download is the highest patch for that minor
|
||||
// version. We need to install it unless an exact match is found.
|
||||
if matches!(upgrade, PythonUpgrade::Enabled(_)) {
|
||||
// If this is an upgrade, the requested version is a minor version but the
|
||||
// requested download is the highest patch for that minor version. We need to
|
||||
// install it unless an exact match is found.
|
||||
request.download.key() == installation.key()
|
||||
} else {
|
||||
request.matches_installation(installation)
|
||||
|
|
@ -498,7 +549,10 @@ pub(crate) async fn install(
|
|||
force,
|
||||
default,
|
||||
upgradeable,
|
||||
upgrade,
|
||||
matches!(
|
||||
upgrade,
|
||||
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade)
|
||||
),
|
||||
is_default_install,
|
||||
&existing_installations,
|
||||
&installations,
|
||||
|
|
@ -534,7 +588,10 @@ pub(crate) async fn install(
|
|||
);
|
||||
|
||||
for installation in minor_versions.values() {
|
||||
if upgrade {
|
||||
if matches!(
|
||||
upgrade,
|
||||
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade)
|
||||
) {
|
||||
// During an upgrade, update existing symlinks but avoid
|
||||
// creating new ones.
|
||||
installation.update_minor_version_link(preview)?;
|
||||
|
|
@ -545,24 +602,38 @@ pub(crate) async fn install(
|
|||
|
||||
if changelog.installed.is_empty() && errors.is_empty() {
|
||||
if is_default_install {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"Python is already installed. Use `uv python install <request>` to install another version.",
|
||||
)?;
|
||||
} else if upgrade && requests.is_empty() {
|
||||
if matches!(
|
||||
upgrade,
|
||||
PythonUpgrade::Enabled(PythonUpgradeSource::Install)
|
||||
) {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"The default Python installation is already on the latest supported patch release. Use `uv python install <request>` to install another version.",
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"Python is already installed. Use `uv python install <request>` to install another version.",
|
||||
)?;
|
||||
}
|
||||
} else if matches!(
|
||||
upgrade,
|
||||
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade)
|
||||
) && requests.is_empty()
|
||||
{
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"There are no installed versions to upgrade"
|
||||
)?;
|
||||
} else if upgrade && is_unspecified_upgrade {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All versions already on latest supported patch release"
|
||||
)?;
|
||||
} else if let [request] = requests.as_slice() {
|
||||
// Convert to the inner request
|
||||
let request = &request.request;
|
||||
if upgrade {
|
||||
if is_unspecified_upgrade {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All versions already on latest supported patch release"
|
||||
)?;
|
||||
} else if matches!(upgrade, PythonUpgrade::Enabled(_)) {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"{request} is already on the latest supported patch release"
|
||||
|
|
@ -571,11 +642,18 @@ pub(crate) async fn install(
|
|||
writeln!(printer.stderr(), "{request} is already installed")?;
|
||||
}
|
||||
} else {
|
||||
if upgrade {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All requested versions already on latest supported patch release"
|
||||
)?;
|
||||
if matches!(upgrade, PythonUpgrade::Enabled(_)) {
|
||||
if is_unspecified_upgrade {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All versions already on latest supported patch release"
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
printer.stderr(),
|
||||
"All requested versions already on latest supported patch release"
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
writeln!(printer.stderr(), "All requested versions already installed")?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1543,15 +1543,13 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||
let args = settings::PythonInstallSettings::resolve(args, filesystem, environment);
|
||||
show_settings!(args);
|
||||
// TODO(john): If we later want to support `--upgrade`, we need to replace this.
|
||||
let upgrade = false;
|
||||
|
||||
commands::python_install(
|
||||
&project_dir,
|
||||
args.install_dir,
|
||||
args.targets,
|
||||
args.reinstall,
|
||||
upgrade,
|
||||
args.upgrade,
|
||||
args.bin,
|
||||
args.registry,
|
||||
args.force,
|
||||
|
|
@ -1573,7 +1571,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||
let args = settings::PythonUpgradeSettings::resolve(args, filesystem, environment);
|
||||
show_settings!(args);
|
||||
let upgrade = true;
|
||||
let upgrade = commands::PythonUpgrade::Enabled(commands::PythonUpgradeSource::Upgrade);
|
||||
|
||||
commands::python_install(
|
||||
&project_dir,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use std::process;
|
|||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::commands::{PythonUpgrade, PythonUpgradeSource};
|
||||
use uv_auth::Service;
|
||||
use uv_cache::{CacheArgs, Refresh};
|
||||
use uv_cli::comma::CommaSeparatedRequirements;
|
||||
|
|
@ -1061,6 +1062,7 @@ pub(crate) struct PythonInstallSettings {
|
|||
pub(crate) targets: Vec<String>,
|
||||
pub(crate) reinstall: bool,
|
||||
pub(crate) force: bool,
|
||||
pub(crate) upgrade: PythonUpgrade,
|
||||
pub(crate) bin: Option<bool>,
|
||||
pub(crate) registry: Option<bool>,
|
||||
pub(crate) python_install_mirror: Option<String>,
|
||||
|
|
@ -1101,6 +1103,7 @@ impl PythonInstallSettings {
|
|||
registry,
|
||||
no_registry,
|
||||
force,
|
||||
upgrade,
|
||||
mirror: _,
|
||||
pypy_mirror: _,
|
||||
python_downloads_json_url: _,
|
||||
|
|
@ -1112,6 +1115,11 @@ impl PythonInstallSettings {
|
|||
targets,
|
||||
reinstall,
|
||||
force,
|
||||
upgrade: if upgrade {
|
||||
PythonUpgrade::Enabled(PythonUpgradeSource::Install)
|
||||
} else {
|
||||
PythonUpgrade::Disabled
|
||||
},
|
||||
bin: flag(bin, no_bin, "bin").or(environment.python_install_bin),
|
||||
registry: flag(registry, no_registry, "registry")
|
||||
.or(environment.python_install_registry),
|
||||
|
|
|
|||
|
|
@ -557,6 +557,18 @@ fn help_subsubcommand() {
|
|||
|
||||
Implies `--reinstall`.
|
||||
|
||||
-U, --upgrade
|
||||
Upgrade existing Python installations to the latest patch version.
|
||||
|
||||
By default, uv will not upgrade already-installed Python versions to newer patch releases.
|
||||
With `--upgrade`, uv will upgrade to the latest available patch version for the specified
|
||||
minor version(s).
|
||||
|
||||
If the requested versions are not yet installed, uv will install them.
|
||||
|
||||
This option is only supported for minor version requests, e.g., `3.12`; uv will exit with
|
||||
an error if a patch version, e.g., `3.12.2`, is requested.
|
||||
|
||||
--default
|
||||
Use as the default Python version.
|
||||
|
||||
|
|
@ -819,6 +831,8 @@ fn help_flag_subsubcommand() {
|
|||
Reinstall the requested Python version, if it's already installed
|
||||
-f, --force
|
||||
Replace existing Python executables during installation
|
||||
-U, --upgrade
|
||||
Upgrade existing Python installations to the latest patch version
|
||||
--default
|
||||
Use as the default Python version
|
||||
|
||||
|
|
|
|||
|
|
@ -1379,7 +1379,8 @@ fn python_install_debug_freethreaded() {
|
|||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_managed_python_dirs();
|
||||
.with_managed_python_dirs()
|
||||
.with_python_download_cache();
|
||||
|
||||
// Install the latest version
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13td"), @r"
|
||||
|
|
@ -2748,6 +2749,7 @@ fn python_install_emulated_macos() {
|
|||
if !arch_status.is_ok_and(|x| x.success()) {
|
||||
// Rosetta is not available to run the x86_64 interpreter
|
||||
// fail the test in CI, otherwise skip it
|
||||
#[allow(clippy::manual_assert)]
|
||||
if env::var("CI").is_ok() {
|
||||
panic!("x86_64 emulation is not available on this CI runner");
|
||||
}
|
||||
|
|
@ -3774,3 +3776,177 @@ fn python_install_build_version_pypy() {
|
|||
error: No download found for request: pypy-3.10-[PLATFORM]
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_install_upgrade() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_python_download_cache()
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_managed_python_dirs();
|
||||
|
||||
// Provide `--upgrade` as an `install` option without any versions
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.14.0 in [TIME]
|
||||
+ cpython-3.14.0-[PLATFORM] (python3.14)
|
||||
");
|
||||
|
||||
// Provide `--upgrade` as an `install` option without any versions again!
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
The default Python installation is already on the latest supported patch release. Use `uv python install <request>` to install another version.
|
||||
");
|
||||
|
||||
// Install an earlier patch version
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.10.17"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.10.17 in [TIME]
|
||||
+ cpython-3.10.17-[PLATFORM] (python3.10)
|
||||
");
|
||||
|
||||
// Ask for an `--upgrade`
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade").arg("3.10"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.10.19 in [TIME]
|
||||
+ cpython-3.10.19-[PLATFORM] (python3.10)
|
||||
");
|
||||
|
||||
// Request a patch version with `--upgrade`
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade").arg("3.11.4"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: `uv python install --upgrade` only accepts minor versions, got: 3.11.4
|
||||
");
|
||||
|
||||
// Request a version that isn't installed yet
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade").arg("3.11"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.11.14 in [TIME]
|
||||
+ cpython-3.11.14-[PLATFORM] (python3.11)
|
||||
");
|
||||
|
||||
// Ask for it again
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade").arg("3.11"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Python 3.11 is already on the latest supported patch release
|
||||
");
|
||||
|
||||
// Install an outdated version
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.9.5"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.9.5 in [TIME]
|
||||
+ cpython-3.9.5-[PLATFORM] (python3.9)
|
||||
");
|
||||
|
||||
// We shouldn't update it when not relevant
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade").arg("3.11"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Python 3.11 is already on the latest supported patch release
|
||||
");
|
||||
|
||||
// Ask for multiple already satisfied versions
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade").arg("3.10").arg("3.11"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
All requested versions already on latest supported patch release
|
||||
");
|
||||
|
||||
// Mix in an unsatisfied version and a missing one
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade").arg("3.9").arg("3.10").arg("3.11").arg("3.12"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed 2 versions in [TIME]
|
||||
+ cpython-3.9.25-[PLATFORM] (python3.9)
|
||||
+ cpython-3.12.12-[PLATFORM] (python3.12)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_install_upgrade_version_file() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_python_download_cache()
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_managed_python_dirs();
|
||||
|
||||
// Pin to a minor version
|
||||
context.python_pin().arg("3.13").assert().success();
|
||||
|
||||
// Provide `--upgrade` as an `install` option without any versions
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.13.9 in [TIME]
|
||||
+ cpython-3.13.9-[PLATFORM] (python3.13)
|
||||
");
|
||||
|
||||
// Provide `--upgrade` as an `install` option without any versions again!
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Python 3.13 is already on the latest supported patch release
|
||||
");
|
||||
|
||||
// Pin to a patch version
|
||||
context.python_pin().arg("3.12.4").assert().success();
|
||||
|
||||
// Provide `--upgrade` as an `install` option without any versions
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--upgrade"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: `uv python install --upgrade` only accepts minor versions, got: 3.12.4
|
||||
|
||||
hint: The version request came from a `.python-version` file; change the patch version in the file to upgrade instead
|
||||
");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3525,6 +3525,10 @@ uv python install [OPTIONS] [TARGETS]...
|
|||
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>
|
||||
</dd><dt id="uv-python-install--reinstall"><a href="#uv-python-install--reinstall"><code>--reinstall</code></a>, <code>-r</code></dt><dd><p>Reinstall the requested Python version, if it's already installed.</p>
|
||||
<p>By default, uv will exit successfully if the version is already installed.</p>
|
||||
</dd><dt id="uv-python-install--upgrade"><a href="#uv-python-install--upgrade"><code>--upgrade</code></a>, <code>-U</code></dt><dd><p>Upgrade existing Python installations to the latest patch version.</p>
|
||||
<p>By default, uv will not upgrade already-installed Python versions to newer patch releases. With <code>--upgrade</code>, uv will upgrade to the latest available patch version for the specified minor version(s).</p>
|
||||
<p>If the requested versions are not yet installed, uv will install them.</p>
|
||||
<p>This option is only supported for minor version requests, e.g., <code>3.12</code>; uv will exit with an error if a patch version, e.g., <code>3.12.2</code>, is requested.</p>
|
||||
</dd><dt id="uv-python-install--verbose"><a href="#uv-python-install--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output.</p>
|
||||
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
|
||||
</dd></dl>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue