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)]
|
#[arg(long, short)]
|
||||||
pub force: bool,
|
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.
|
/// Use as the default Python version.
|
||||||
///
|
///
|
||||||
/// By default, only a `python{major}.{minor}` executable is installed, e.g., `python3.10`. When
|
/// 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 as python_find;
|
||||||
pub(crate) use python::find::find_script as python_find_script;
|
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::install as python_install;
|
||||||
|
pub(crate) use python::install::{PythonUpgrade, PythonUpgradeSource};
|
||||||
pub(crate) use python::list::list as python_list;
|
pub(crate) use python::list::list as python_list;
|
||||||
pub(crate) use python::pin::pin as python_pin;
|
pub(crate) use python::pin::pin as python_pin;
|
||||||
pub(crate) use python::uninstall::uninstall as python_uninstall;
|
pub(crate) use python::uninstall::uninstall as python_uninstall;
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,31 @@ enum InstallErrorKind {
|
||||||
Registry,
|
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.
|
/// Download and install Python versions.
|
||||||
#[allow(clippy::fn_params_excessive_bools)]
|
#[allow(clippy::fn_params_excessive_bools)]
|
||||||
pub(crate) async fn install(
|
pub(crate) async fn install(
|
||||||
|
|
@ -156,7 +181,7 @@ pub(crate) async fn install(
|
||||||
install_dir: Option<PathBuf>,
|
install_dir: Option<PathBuf>,
|
||||||
targets: Vec<String>,
|
targets: Vec<String>,
|
||||||
reinstall: bool,
|
reinstall: bool,
|
||||||
upgrade: bool,
|
upgrade: PythonUpgrade,
|
||||||
bin: Option<bool>,
|
bin: Option<bool>,
|
||||||
registry: Option<bool>,
|
registry: Option<bool>,
|
||||||
force: bool,
|
force: bool,
|
||||||
|
|
@ -183,11 +208,13 @@ pub(crate) async fn install(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if upgrade && !preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) {
|
if let PythonUpgrade::Enabled(source @ PythonUpgradeSource::Upgrade) = upgrade {
|
||||||
warn_user!(
|
if !preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) {
|
||||||
"`uv python upgrade` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning",
|
warn_user!(
|
||||||
PreviewFeatures::PYTHON_UPGRADE
|
"`{source}` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning",
|
||||||
);
|
PreviewFeatures::PYTHON_UPGRADE
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if default && targets.len() > 1 {
|
if default && targets.len() > 1 {
|
||||||
|
|
@ -207,8 +234,14 @@ pub(crate) async fn install(
|
||||||
// Resolve the requests
|
// Resolve the requests
|
||||||
let mut is_default_install = false;
|
let mut is_default_install = false;
|
||||||
let mut is_unspecified_upgrade = 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() {
|
let requests: Vec<_> = if targets.is_empty() {
|
||||||
if upgrade {
|
if matches!(
|
||||||
|
upgrade,
|
||||||
|
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade)
|
||||||
|
) {
|
||||||
is_unspecified_upgrade = true;
|
is_unspecified_upgrade = true;
|
||||||
// On upgrade, derive requests for all of the existing installations
|
// On upgrade, derive requests for all of the existing installations
|
||||||
let mut minor_version_requests = IndexSet::<InstallRequest>::default();
|
let mut minor_version_requests = IndexSet::<InstallRequest>::default();
|
||||||
|
|
@ -240,6 +273,7 @@ pub(crate) async fn install(
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map(PythonVersionFile::into_versions)
|
.map(PythonVersionFile::into_versions)
|
||||||
|
.inspect(|_| is_from_python_version_file = true)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
// If no version file is found and no requests were made
|
// If no version file is found and no requests were made
|
||||||
// TODO(zanieb): We should consider differentiating between a global Python version
|
// 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 requests.is_empty() {
|
||||||
if upgrade {
|
match upgrade {
|
||||||
writeln!(
|
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade) => {
|
||||||
printer.stderr(),
|
writeln!(
|
||||||
"There are no installed versions to upgrade"
|
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);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
@ -287,17 +330,25 @@ pub(crate) async fn install(
|
||||||
})
|
})
|
||||||
.collect::<IndexSet<_>>();
|
.collect::<IndexSet<_>>();
|
||||||
|
|
||||||
if upgrade
|
if let PythonUpgrade::Enabled(source) = upgrade {
|
||||||
&& let Some(request) = requests.iter().find(|request| {
|
if let Some(request) = requests.iter().find(|request| {
|
||||||
request.request.includes_patch() || request.request.includes_prerelease()
|
request.request.includes_patch() || request.request.includes_prerelease()
|
||||||
})
|
}) {
|
||||||
{
|
writeln!(
|
||||||
writeln!(
|
printer.stderr(),
|
||||||
printer.stderr(),
|
"error: `{source}` only accepts minor versions, got: {}",
|
||||||
"error: `uv python upgrade` only accepts minor versions, got: {}",
|
request.request.to_canonical_string()
|
||||||
request.request.to_canonical_string()
|
)?;
|
||||||
)?;
|
if is_from_python_version_file {
|
||||||
return Ok(ExitStatus::Failure);
|
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
|
// 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
|
// If we can find one existing installation that matches the request, it is satisfied
|
||||||
requests.iter().partition_map(|request| {
|
requests.iter().partition_map(|request| {
|
||||||
if let Some(installation) = existing_installations.iter().find(|installation| {
|
if let Some(installation) = existing_installations.iter().find(|installation| {
|
||||||
if upgrade {
|
if matches!(upgrade, PythonUpgrade::Enabled(_)) {
|
||||||
// If this is an upgrade, the requested version is a minor version
|
// If this is an upgrade, the requested version is a minor version but the
|
||||||
// but the requested download is the highest patch for that minor
|
// requested download is the highest patch for that minor version. We need to
|
||||||
// version. We need to install it unless an exact match is found.
|
// install it unless an exact match is found.
|
||||||
request.download.key() == installation.key()
|
request.download.key() == installation.key()
|
||||||
} else {
|
} else {
|
||||||
request.matches_installation(installation)
|
request.matches_installation(installation)
|
||||||
|
|
@ -498,7 +549,10 @@ pub(crate) async fn install(
|
||||||
force,
|
force,
|
||||||
default,
|
default,
|
||||||
upgradeable,
|
upgradeable,
|
||||||
upgrade,
|
matches!(
|
||||||
|
upgrade,
|
||||||
|
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade)
|
||||||
|
),
|
||||||
is_default_install,
|
is_default_install,
|
||||||
&existing_installations,
|
&existing_installations,
|
||||||
&installations,
|
&installations,
|
||||||
|
|
@ -534,7 +588,10 @@ pub(crate) async fn install(
|
||||||
);
|
);
|
||||||
|
|
||||||
for installation in minor_versions.values() {
|
for installation in minor_versions.values() {
|
||||||
if upgrade {
|
if matches!(
|
||||||
|
upgrade,
|
||||||
|
PythonUpgrade::Enabled(PythonUpgradeSource::Upgrade)
|
||||||
|
) {
|
||||||
// During an upgrade, update existing symlinks but avoid
|
// During an upgrade, update existing symlinks but avoid
|
||||||
// creating new ones.
|
// creating new ones.
|
||||||
installation.update_minor_version_link(preview)?;
|
installation.update_minor_version_link(preview)?;
|
||||||
|
|
@ -545,24 +602,38 @@ pub(crate) async fn install(
|
||||||
|
|
||||||
if changelog.installed.is_empty() && errors.is_empty() {
|
if changelog.installed.is_empty() && errors.is_empty() {
|
||||||
if is_default_install {
|
if is_default_install {
|
||||||
writeln!(
|
if matches!(
|
||||||
printer.stderr(),
|
upgrade,
|
||||||
"Python is already installed. Use `uv python install <request>` to install another version.",
|
PythonUpgrade::Enabled(PythonUpgradeSource::Install)
|
||||||
)?;
|
) {
|
||||||
} else if upgrade && requests.is_empty() {
|
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!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"There are no installed versions to upgrade"
|
"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() {
|
} else if let [request] = requests.as_slice() {
|
||||||
// Convert to the inner request
|
// Convert to the inner request
|
||||||
let request = &request.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!(
|
writeln!(
|
||||||
printer.stderr(),
|
printer.stderr(),
|
||||||
"{request} is already on the latest supported patch release"
|
"{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")?;
|
writeln!(printer.stderr(), "{request} is already installed")?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if upgrade {
|
if matches!(upgrade, PythonUpgrade::Enabled(_)) {
|
||||||
writeln!(
|
if is_unspecified_upgrade {
|
||||||
printer.stderr(),
|
writeln!(
|
||||||
"All requested versions already on latest supported patch release"
|
printer.stderr(),
|
||||||
)?;
|
"All versions already on latest supported patch release"
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"All requested versions already on latest supported patch release"
|
||||||
|
)?;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
writeln!(printer.stderr(), "All requested versions already installed")?;
|
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.
|
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||||
let args = settings::PythonInstallSettings::resolve(args, filesystem, environment);
|
let args = settings::PythonInstallSettings::resolve(args, filesystem, environment);
|
||||||
show_settings!(args);
|
show_settings!(args);
|
||||||
// TODO(john): If we later want to support `--upgrade`, we need to replace this.
|
|
||||||
let upgrade = false;
|
|
||||||
|
|
||||||
commands::python_install(
|
commands::python_install(
|
||||||
&project_dir,
|
&project_dir,
|
||||||
args.install_dir,
|
args.install_dir,
|
||||||
args.targets,
|
args.targets,
|
||||||
args.reinstall,
|
args.reinstall,
|
||||||
upgrade,
|
args.upgrade,
|
||||||
args.bin,
|
args.bin,
|
||||||
args.registry,
|
args.registry,
|
||||||
args.force,
|
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.
|
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||||
let args = settings::PythonUpgradeSettings::resolve(args, filesystem, environment);
|
let args = settings::PythonUpgradeSettings::resolve(args, filesystem, environment);
|
||||||
show_settings!(args);
|
show_settings!(args);
|
||||||
let upgrade = true;
|
let upgrade = commands::PythonUpgrade::Enabled(commands::PythonUpgradeSource::Upgrade);
|
||||||
|
|
||||||
commands::python_install(
|
commands::python_install(
|
||||||
&project_dir,
|
&project_dir,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::process;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::commands::{PythonUpgrade, PythonUpgradeSource};
|
||||||
use uv_auth::Service;
|
use uv_auth::Service;
|
||||||
use uv_cache::{CacheArgs, Refresh};
|
use uv_cache::{CacheArgs, Refresh};
|
||||||
use uv_cli::comma::CommaSeparatedRequirements;
|
use uv_cli::comma::CommaSeparatedRequirements;
|
||||||
|
|
@ -1061,6 +1062,7 @@ pub(crate) struct PythonInstallSettings {
|
||||||
pub(crate) targets: Vec<String>,
|
pub(crate) targets: Vec<String>,
|
||||||
pub(crate) reinstall: bool,
|
pub(crate) reinstall: bool,
|
||||||
pub(crate) force: bool,
|
pub(crate) force: bool,
|
||||||
|
pub(crate) upgrade: PythonUpgrade,
|
||||||
pub(crate) bin: Option<bool>,
|
pub(crate) bin: Option<bool>,
|
||||||
pub(crate) registry: Option<bool>,
|
pub(crate) registry: Option<bool>,
|
||||||
pub(crate) python_install_mirror: Option<String>,
|
pub(crate) python_install_mirror: Option<String>,
|
||||||
|
|
@ -1101,6 +1103,7 @@ impl PythonInstallSettings {
|
||||||
registry,
|
registry,
|
||||||
no_registry,
|
no_registry,
|
||||||
force,
|
force,
|
||||||
|
upgrade,
|
||||||
mirror: _,
|
mirror: _,
|
||||||
pypy_mirror: _,
|
pypy_mirror: _,
|
||||||
python_downloads_json_url: _,
|
python_downloads_json_url: _,
|
||||||
|
|
@ -1112,6 +1115,11 @@ impl PythonInstallSettings {
|
||||||
targets,
|
targets,
|
||||||
reinstall,
|
reinstall,
|
||||||
force,
|
force,
|
||||||
|
upgrade: if upgrade {
|
||||||
|
PythonUpgrade::Enabled(PythonUpgradeSource::Install)
|
||||||
|
} else {
|
||||||
|
PythonUpgrade::Disabled
|
||||||
|
},
|
||||||
bin: flag(bin, no_bin, "bin").or(environment.python_install_bin),
|
bin: flag(bin, no_bin, "bin").or(environment.python_install_bin),
|
||||||
registry: flag(registry, no_registry, "registry")
|
registry: flag(registry, no_registry, "registry")
|
||||||
.or(environment.python_install_registry),
|
.or(environment.python_install_registry),
|
||||||
|
|
|
||||||
|
|
@ -557,6 +557,18 @@ fn help_subsubcommand() {
|
||||||
|
|
||||||
Implies `--reinstall`.
|
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
|
--default
|
||||||
Use as the default Python version.
|
Use as the default Python version.
|
||||||
|
|
||||||
|
|
@ -819,6 +831,8 @@ fn help_flag_subsubcommand() {
|
||||||
Reinstall the requested Python version, if it's already installed
|
Reinstall the requested Python version, if it's already installed
|
||||||
-f, --force
|
-f, --force
|
||||||
Replace existing Python executables during installation
|
Replace existing Python executables during installation
|
||||||
|
-U, --upgrade
|
||||||
|
Upgrade existing Python installations to the latest patch version
|
||||||
--default
|
--default
|
||||||
Use as the default Python version
|
Use as the default Python version
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1379,7 +1379,8 @@ fn python_install_debug_freethreaded() {
|
||||||
let context: TestContext = TestContext::new_with_versions(&[])
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
.with_filtered_python_keys()
|
.with_filtered_python_keys()
|
||||||
.with_filtered_exe_suffix()
|
.with_filtered_exe_suffix()
|
||||||
.with_managed_python_dirs();
|
.with_managed_python_dirs()
|
||||||
|
.with_python_download_cache();
|
||||||
|
|
||||||
// Install the latest version
|
// Install the latest version
|
||||||
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13td"), @r"
|
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()) {
|
if !arch_status.is_ok_and(|x| x.success()) {
|
||||||
// Rosetta is not available to run the x86_64 interpreter
|
// Rosetta is not available to run the x86_64 interpreter
|
||||||
// fail the test in CI, otherwise skip it
|
// fail the test in CI, otherwise skip it
|
||||||
|
#[allow(clippy::manual_assert)]
|
||||||
if env::var("CI").is_ok() {
|
if env::var("CI").is_ok() {
|
||||||
panic!("x86_64 emulation is not available on this CI runner");
|
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]
|
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>
|
<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>
|
</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>
|
<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>
|
</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>
|
<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>
|
</dd></dl>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue