mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-26 20:19:08 +00:00
Stabilize addition of Python executables to the bin (#14626)
Closes https://github.com/astral-sh/uv/issues/14296 As mentioned in #14681, this does not stabilize the `--default` behavior.
This commit is contained in:
parent
ff30f14d50
commit
0077f2357f
8 changed files with 593 additions and 113 deletions
|
@ -4810,10 +4810,9 @@ pub enum PythonCommand {
|
||||||
/// Python versions are installed into the uv Python directory, which can be retrieved with `uv
|
/// Python versions are installed into the uv Python directory, which can be retrieved with `uv
|
||||||
/// python dir`.
|
/// python dir`.
|
||||||
///
|
///
|
||||||
/// A `python` executable is not made globally available, managed Python versions are only used
|
/// By default, Python executables are added to a directory on the path with a minor version
|
||||||
/// in uv commands or in active virtual environments. There is experimental support for adding
|
/// suffix, e.g., `python3.13`. To install `python3` and `python`, use the `--default` flag. Use
|
||||||
/// Python executables to a directory on the path — use the `--preview` flag to enable this
|
/// `uv python dir --bin` to see the target directory.
|
||||||
/// behavior and `uv python dir --bin` to retrieve the target directory.
|
|
||||||
///
|
///
|
||||||
/// Multiple Python versions may be requested.
|
/// Multiple Python versions may be requested.
|
||||||
///
|
///
|
||||||
|
|
|
@ -166,12 +166,14 @@ pub(crate) async fn install(
|
||||||
) -> Result<ExitStatus> {
|
) -> Result<ExitStatus> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
|
// TODO(zanieb): We should consider marking the Python installation as the default when
|
||||||
|
// `--default` is used. It's not clear how this overlaps with a global Python pin, but I'd be
|
||||||
|
// surprised if `uv python find` returned the "newest" Python version rather than the one I just
|
||||||
|
// installed with the `--default` flag.
|
||||||
if default && !preview.is_enabled() {
|
if default && !preview.is_enabled() {
|
||||||
writeln!(
|
warn_user!(
|
||||||
printer.stderr(),
|
"The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning"
|
||||||
"The `--default` flag is only available in preview mode; add the `--preview` flag to use `--default`"
|
);
|
||||||
)?;
|
|
||||||
return Ok(ExitStatus::Failure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if upgrade && preview.is_disabled() {
|
if upgrade && preview.is_disabled() {
|
||||||
|
@ -222,6 +224,8 @@ pub(crate) async fn install(
|
||||||
.map(PythonVersionFile::into_versions)
|
.map(PythonVersionFile::into_versions)
|
||||||
.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
|
||||||
|
// file here, allowing a request from there to enable `is_default_install`.
|
||||||
is_default_install = true;
|
is_default_install = true;
|
||||||
vec![if reinstall {
|
vec![if reinstall {
|
||||||
// On bare `--reinstall`, reinstall all Python versions
|
// On bare `--reinstall`, reinstall all Python versions
|
||||||
|
@ -451,10 +455,10 @@ pub(crate) async fn install(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bin_dir = if matches!(bin, Some(true)) || preview.is_enabled() {
|
let bin_dir = if matches!(bin, Some(false)) {
|
||||||
Some(python_executable_dir()?)
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
|
} else {
|
||||||
|
Some(python_executable_dir()?)
|
||||||
};
|
};
|
||||||
|
|
||||||
let installations: Vec<_> = downloaded.iter().chain(satisfied.iter().copied()).collect();
|
let installations: Vec<_> = downloaded.iter().chain(satisfied.iter().copied()).collect();
|
||||||
|
@ -469,20 +473,10 @@ pub(crate) async fn install(
|
||||||
e.warn_user(installation);
|
e.warn_user(installation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if preview.is_disabled() {
|
|
||||||
debug!("Skipping installation of Python executables, use `--preview` to enable.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bin_dir = bin_dir
|
|
||||||
.as_ref()
|
|
||||||
.expect("We should have a bin directory with preview enabled")
|
|
||||||
.as_path();
|
|
||||||
|
|
||||||
let upgradeable = (default || is_default_install)
|
let upgradeable = (default || is_default_install)
|
||||||
|| requested_minor_versions.contains(&installation.key().version().python_version());
|
|| requested_minor_versions.contains(&installation.key().version().python_version());
|
||||||
|
|
||||||
if !matches!(bin, Some(false)) {
|
if let Some(bin_dir) = bin_dir.as_ref() {
|
||||||
create_bin_links(
|
create_bin_links(
|
||||||
installation,
|
installation,
|
||||||
bin_dir,
|
bin_dir,
|
||||||
|
@ -661,11 +655,7 @@ pub(crate) async fn install(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if preview.is_enabled() && !matches!(bin, Some(false)) {
|
if let Some(bin_dir) = bin_dir.as_ref() {
|
||||||
let bin_dir = bin_dir
|
|
||||||
.as_ref()
|
|
||||||
.expect("We should have a bin directory with preview enabled")
|
|
||||||
.as_path();
|
|
||||||
warn_if_not_on_path(bin_dir);
|
warn_if_not_on_path(bin_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -749,8 +739,12 @@ fn create_bin_links(
|
||||||
errors: &mut Vec<(InstallErrorKind, PythonInstallationKey, Error)>,
|
errors: &mut Vec<(InstallErrorKind, PythonInstallationKey, Error)>,
|
||||||
preview: PreviewMode,
|
preview: PreviewMode,
|
||||||
) {
|
) {
|
||||||
let targets =
|
// TODO(zanieb): We want more feedback on the `is_default_install` behavior before stabilizing
|
||||||
if (default || is_default_install) && first_request.matches_installation(installation) {
|
// it. In particular, it may be confusing because it does not apply when versions are loaded
|
||||||
|
// from a `.python-version` file.
|
||||||
|
let targets = if (default || (is_default_install && preview.is_enabled()))
|
||||||
|
&& first_request.matches_installation(installation)
|
||||||
|
{
|
||||||
vec![
|
vec![
|
||||||
installation.key().executable_name_minor(),
|
installation.key().executable_name_minor(),
|
||||||
installation.key().executable_name_major(),
|
installation.key().executable_name_major(),
|
||||||
|
|
|
@ -220,17 +220,30 @@ impl TestContext {
|
||||||
/// and `.exe` suffixes.
|
/// and `.exe` suffixes.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_filtered_python_names(mut self) -> Self {
|
pub fn with_filtered_python_names(mut self) -> Self {
|
||||||
|
use env::consts::EXE_SUFFIX;
|
||||||
|
let exe_suffix = regex::escape(EXE_SUFFIX);
|
||||||
|
|
||||||
|
self.filters.push((
|
||||||
|
format!(r"python\d.\d\d{exe_suffix}"),
|
||||||
|
"[PYTHON]".to_string(),
|
||||||
|
));
|
||||||
|
self.filters
|
||||||
|
.push((format!(r"python\d{exe_suffix}"), "[PYTHON]".to_string()));
|
||||||
|
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
|
// On Windows, we want to filter out all `python.exe` instances
|
||||||
self.filters
|
self.filters
|
||||||
.push((r"python\.exe".to_string(), "[PYTHON]".to_string()));
|
.push((format!(r"python{exe_suffix}"), "[PYTHON]".to_string()));
|
||||||
|
// Including ones where we'd already stripped the `.exe` in another filter
|
||||||
|
self.filters
|
||||||
|
.push((r"[\\/]python".to_string(), "/[PYTHON]".to_string()));
|
||||||
} else {
|
} else {
|
||||||
|
// On Unix, it's a little trickier — we don't want to clobber use of `python` in the
|
||||||
|
// middle of something else, e.g., `cpython`. For this reason, we require a leading `/`.
|
||||||
self.filters
|
self.filters
|
||||||
.push((r"python\d.\d\d".to_string(), "[PYTHON]".to_string()));
|
.push((format!(r"/python{exe_suffix}"), "/[PYTHON]".to_string()));
|
||||||
self.filters
|
|
||||||
.push((r"python\d".to_string(), "[PYTHON]".to_string()));
|
|
||||||
self.filters
|
|
||||||
.push((r"/python".to_string(), "/[PYTHON]".to_string()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -469,10 +469,9 @@ fn help_subsubcommand() {
|
||||||
Python versions are installed into the uv Python directory, which can be retrieved with `uv python
|
Python versions are installed into the uv Python directory, which can be retrieved with `uv python
|
||||||
dir`.
|
dir`.
|
||||||
|
|
||||||
A `python` executable is not made globally available, managed Python versions are only used in uv
|
By default, Python executables are added to a directory on the path with a minor version suffix,
|
||||||
commands or in active virtual environments. There is experimental support for adding Python
|
e.g., `python3.13`. To install `python3` and `python`, use the `--default` flag. Use `uv python dir
|
||||||
executables to a directory on the path — use the `--preview` flag to enable this behavior and `uv
|
--bin` to see the target directory.
|
||||||
python dir --bin` to retrieve the target directory.
|
|
||||||
|
|
||||||
Multiple Python versions may be requested.
|
Multiple Python versions may be requested.
|
||||||
|
|
||||||
|
|
|
@ -30,15 +30,49 @@ fn python_install() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.13.5 in [TIME]
|
Installed Python 3.13.5 in [TIME]
|
||||||
+ cpython-3.13.5-[PLATFORM]
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
let bin_python = context
|
let bin_python = context
|
||||||
.bin_dir
|
.bin_dir
|
||||||
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// The executable should not be installed in the bin directory (requires preview)
|
// The executable should be installed in the bin directory
|
||||||
bin_python.assert(predicate::path::missing());
|
bin_python.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
// On Unix, it should be a link
|
||||||
|
#[cfg(unix)]
|
||||||
|
bin_python.assert(predicate::path::is_symlink());
|
||||||
|
|
||||||
|
// The link should be a path to the binary
|
||||||
|
if cfg!(unix) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The executable should "work"
|
||||||
|
uv_snapshot!(context.filters(), Command::new(bin_python.as_os_str())
|
||||||
|
.arg("-c").arg("import subprocess; print('hello world')"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
hello world
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
// Should be a no-op when already installed
|
// Should be a no-op when already installed
|
||||||
uv_snapshot!(context.filters(), context.python_install(), @r###"
|
uv_snapshot!(context.filters(), context.python_install(), @r###"
|
||||||
|
@ -67,9 +101,12 @@ fn python_install() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.13.5 in [TIME]
|
Installed Python 3.13.5 in [TIME]
|
||||||
~ cpython-3.13.5-[PLATFORM]
|
~ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
|
// The executable should still be present in the bin directory
|
||||||
|
bin_python.assert(predicate::path::exists());
|
||||||
|
|
||||||
// Uninstallation requires an argument
|
// Uninstallation requires an argument
|
||||||
uv_snapshot!(context.filters(), context.python_uninstall(), @r###"
|
uv_snapshot!(context.filters(), context.python_uninstall(), @r###"
|
||||||
success: false
|
success: false
|
||||||
|
@ -93,8 +130,11 @@ fn python_install() {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Searching for Python versions matching: Python 3.13
|
Searching for Python versions matching: Python 3.13
|
||||||
Uninstalled Python 3.13.5 in [TIME]
|
Uninstalled Python 3.13.5 in [TIME]
|
||||||
- cpython-3.13.5-[PLATFORM]
|
- cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
|
// The executable should be removed
|
||||||
|
bin_python.assert(predicate::path::missing());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -112,8 +152,8 @@ fn python_reinstall() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed 2 versions in [TIME]
|
Installed 2 versions in [TIME]
|
||||||
+ cpython-3.12.11-[PLATFORM]
|
+ cpython-3.12.11-[PLATFORM] (python3.12)
|
||||||
+ cpython-3.13.5-[PLATFORM]
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
// Reinstall a single version
|
// Reinstall a single version
|
||||||
|
@ -124,7 +164,7 @@ fn python_reinstall() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.13.5 in [TIME]
|
Installed Python 3.13.5 in [TIME]
|
||||||
~ cpython-3.13.5-[PLATFORM]
|
~ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
// Reinstall multiple versions
|
// Reinstall multiple versions
|
||||||
|
@ -135,8 +175,8 @@ fn python_reinstall() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed 2 versions in [TIME]
|
Installed 2 versions in [TIME]
|
||||||
~ cpython-3.12.11-[PLATFORM]
|
~ cpython-3.12.11-[PLATFORM] (python3.12)
|
||||||
~ cpython-3.13.5-[PLATFORM]
|
~ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
// Reinstalling a version that is not installed should also work
|
// Reinstalling a version that is not installed should also work
|
||||||
|
@ -147,7 +187,7 @@ fn python_reinstall() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.11.13 in [TIME]
|
Installed Python 3.11.13 in [TIME]
|
||||||
+ cpython-3.11.13-[PLATFORM]
|
+ cpython-3.11.13-[PLATFORM] (python3.11)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +207,7 @@ fn python_reinstall_patch() {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed 2 versions in [TIME]
|
Installed 2 versions in [TIME]
|
||||||
+ cpython-3.12.6-[PLATFORM]
|
+ cpython-3.12.6-[PLATFORM]
|
||||||
+ cpython-3.12.7-[PLATFORM]
|
+ cpython-3.12.7-[PLATFORM] (python3.12)
|
||||||
");
|
");
|
||||||
|
|
||||||
// Reinstall all "3.12" versions
|
// Reinstall all "3.12" versions
|
||||||
|
@ -180,7 +220,7 @@ fn python_reinstall_patch() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.12.11 in [TIME]
|
Installed Python 3.12.11 in [TIME]
|
||||||
+ cpython-3.12.11-[PLATFORM]
|
+ cpython-3.12.11-[PLATFORM] (python3.12)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,6 +368,208 @@ fn regression_cpython() {
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn python_install_force() {
|
||||||
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
|
.with_filtered_python_keys()
|
||||||
|
.with_filtered_exe_suffix()
|
||||||
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
|
// Install the latest version
|
||||||
|
uv_snapshot!(context.filters(), context.python_install(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Installed Python 3.13.5 in [TIME]
|
||||||
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
|
");
|
||||||
|
|
||||||
|
let bin_python = context
|
||||||
|
.bin_dir
|
||||||
|
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
|
// You can force replacement of the executables
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("--force"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Installed Python 3.13.5 in [TIME]
|
||||||
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
|
");
|
||||||
|
|
||||||
|
// The executable should still be present in the bin directory
|
||||||
|
bin_python.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
// If an unmanaged executable is present, `--force` is required
|
||||||
|
fs_err::remove_file(bin_python.path()).unwrap();
|
||||||
|
bin_python.touch().unwrap();
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: Failed to install executable for cpython-3.13.5-[PLATFORM]
|
||||||
|
Caused by: Executable already exists at `[BIN]/python3.13` but is not managed by uv; use `--force` to replace it
|
||||||
|
");
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("--force").arg("3.13"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Installed Python 3.13.5 in [TIME]
|
||||||
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
|
");
|
||||||
|
|
||||||
|
bin_python.assert(predicate::path::exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn python_install_minor() {
|
||||||
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
|
.with_filtered_python_keys()
|
||||||
|
.with_filtered_exe_suffix()
|
||||||
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
|
// Install a minor version
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("3.11"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Installed Python 3.11.13 in [TIME]
|
||||||
|
+ cpython-3.11.13-[PLATFORM] (python3.11)
|
||||||
|
");
|
||||||
|
|
||||||
|
let bin_python = context
|
||||||
|
.bin_dir
|
||||||
|
.child(format!("python3.11{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
|
// The executable should be installed in the bin directory
|
||||||
|
bin_python.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
// It should be a link to the minor version
|
||||||
|
if cfg!(unix) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.11.13-[PLATFORM]/bin/python3.11"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.11.13-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.python_uninstall().arg("3.11"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Searching for Python versions matching: Python 3.11
|
||||||
|
Uninstalled Python 3.11.13 in [TIME]
|
||||||
|
- cpython-3.11.13-[PLATFORM] (python3.11)
|
||||||
|
");
|
||||||
|
|
||||||
|
// The executable should be removed
|
||||||
|
bin_python.assert(predicate::path::missing());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn python_install_multiple_patch() {
|
||||||
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
|
.with_filtered_python_keys()
|
||||||
|
.with_filtered_exe_suffix()
|
||||||
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
|
// Install multiple patch versions
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("3.12.8").arg("3.12.6"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Installed 2 versions in [TIME]
|
||||||
|
+ cpython-3.12.6-[PLATFORM]
|
||||||
|
+ cpython-3.12.8-[PLATFORM] (python3.12)
|
||||||
|
");
|
||||||
|
|
||||||
|
let bin_python = context
|
||||||
|
.bin_dir
|
||||||
|
.child(format!("python3.12{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
|
// The executable should be installed in the bin directory
|
||||||
|
bin_python.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
// The link should resolve to the newer patch version
|
||||||
|
if cfg!(unix) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/bin/python3.12"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.8-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.python_uninstall().arg("3.12.8"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Searching for Python versions matching: Python 3.12.8
|
||||||
|
Uninstalled Python 3.12.8 in [TIME]
|
||||||
|
- cpython-3.12.8-[PLATFORM] (python3.12)
|
||||||
|
");
|
||||||
|
|
||||||
|
// TODO(zanieb): This behavior is not implemented yet
|
||||||
|
// // The executable should be installed in the bin directory
|
||||||
|
// bin_python.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
// // When the version is removed, the link should point to the other patch version
|
||||||
|
// if cfg!(unix) {
|
||||||
|
// insta::with_settings!({
|
||||||
|
// filters => context.filters(),
|
||||||
|
// }, {
|
||||||
|
// insta::assert_snapshot!(
|
||||||
|
// canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/bin/python3.12"
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// } else if cfg!(windows) {
|
||||||
|
// insta::with_settings!({
|
||||||
|
// filters => context.filters(),
|
||||||
|
// }, {
|
||||||
|
// insta::assert_snapshot!(
|
||||||
|
// canonicalize_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.12.6-[PLATFORM]/python"
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn python_install_preview() {
|
fn python_install_preview() {
|
||||||
let context: TestContext = TestContext::new_with_versions(&[])
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
|
@ -853,7 +1095,7 @@ fn python_install_freethreaded() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.13.5 in [TIME]
|
Installed Python 3.13.5 in [TIME]
|
||||||
+ cpython-3.13.5-[PLATFORM]
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
// Should not work with older Python versions
|
// Should not work with older Python versions
|
||||||
|
@ -875,7 +1117,7 @@ fn python_install_freethreaded() {
|
||||||
Searching for Python installations
|
Searching for Python installations
|
||||||
Uninstalled 2 versions in [TIME]
|
Uninstalled 2 versions in [TIME]
|
||||||
- cpython-3.13.5+freethreaded-[PLATFORM] (python3.13t)
|
- cpython-3.13.5+freethreaded-[PLATFORM] (python3.13t)
|
||||||
- cpython-3.13.5-[PLATFORM]
|
- cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -936,15 +1178,243 @@ fn python_install_default() {
|
||||||
.bin_dir
|
.bin_dir
|
||||||
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// `--preview` is required for `--default`
|
// Install a specific version
|
||||||
uv_snapshot!(context.filters(), context.python_install().arg("--default"), @r###"
|
uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r"
|
||||||
success: false
|
success: true
|
||||||
exit_code: 1
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
The `--default` flag is only available in preview mode; add the `--preview` flag to use `--default`
|
Installed Python 3.13.5 in [TIME]
|
||||||
"###);
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
|
");
|
||||||
|
|
||||||
|
// Only the minor versioned executable should be installed
|
||||||
|
bin_python_minor_13.assert(predicate::path::exists());
|
||||||
|
bin_python_major.assert(predicate::path::missing());
|
||||||
|
bin_python_default.assert(predicate::path::missing());
|
||||||
|
|
||||||
|
// Install again, with `--default`
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("--default").arg("3.13"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning
|
||||||
|
Installed Python 3.13.5 in [TIME]
|
||||||
|
+ cpython-3.13.5-[PLATFORM] (python, python3)
|
||||||
|
");
|
||||||
|
|
||||||
|
// Now all the executables should be installed
|
||||||
|
bin_python_minor_13.assert(predicate::path::exists());
|
||||||
|
bin_python_major.assert(predicate::path::exists());
|
||||||
|
bin_python_default.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
// Uninstall
|
||||||
|
uv_snapshot!(context.filters(), context.python_uninstall().arg("--all"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Searching for Python installations
|
||||||
|
Uninstalled Python 3.13.5 in [TIME]
|
||||||
|
- cpython-3.13.5-[PLATFORM] (python, python3, python3.13)
|
||||||
|
");
|
||||||
|
|
||||||
|
// The executables should be removed
|
||||||
|
bin_python_minor_13.assert(predicate::path::missing());
|
||||||
|
bin_python_major.assert(predicate::path::missing());
|
||||||
|
bin_python_default.assert(predicate::path::missing());
|
||||||
|
|
||||||
|
// Install the latest version, i.e., a "default install"
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("--default"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning
|
||||||
|
Installed Python 3.13.5 in [TIME]
|
||||||
|
+ cpython-3.13.5-[PLATFORM] (python, python3, python3.13)
|
||||||
|
");
|
||||||
|
|
||||||
|
// Since it's a default install, we should include all of the executables
|
||||||
|
bin_python_minor_13.assert(predicate::path::exists());
|
||||||
|
bin_python_major.assert(predicate::path::exists());
|
||||||
|
bin_python_default.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
// And 3.13 should be the default
|
||||||
|
if cfg!(unix) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/bin/python3.13"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_minor_13), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.13.5-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uninstall again
|
||||||
|
uv_snapshot!(context.filters(), context.python_uninstall().arg("3.13"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Searching for Python versions matching: Python 3.13
|
||||||
|
Uninstalled Python 3.13.5 in [TIME]
|
||||||
|
- cpython-3.13.5-[PLATFORM] (python, python3, python3.13)
|
||||||
|
");
|
||||||
|
|
||||||
|
// We should remove all the executables
|
||||||
|
bin_python_minor_13.assert(predicate::path::missing());
|
||||||
|
bin_python_major.assert(predicate::path::missing());
|
||||||
|
bin_python_default.assert(predicate::path::missing());
|
||||||
|
|
||||||
|
// Install multiple versions, with the `--default` flag
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("3.12").arg("3.13").arg("--default"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning
|
||||||
|
error: The `--default` flag cannot be used with multiple targets
|
||||||
|
");
|
||||||
|
|
||||||
|
// Install 3.12 as a new default
|
||||||
|
uv_snapshot!(context.filters(), context.python_install().arg("3.12").arg("--default"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning
|
||||||
|
Installed Python 3.12.11 in [TIME]
|
||||||
|
+ cpython-3.12.11-[PLATFORM] (python, python3, python3.12)
|
||||||
|
");
|
||||||
|
|
||||||
|
let bin_python_minor_12 = context
|
||||||
|
.bin_dir
|
||||||
|
.child(format!("python3.12{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
|
// All the executables should exist
|
||||||
|
bin_python_minor_12.assert(predicate::path::exists());
|
||||||
|
bin_python_major.assert(predicate::path::exists());
|
||||||
|
bin_python_default.assert(predicate::path::exists());
|
||||||
|
|
||||||
|
// And 3.12 should be the default
|
||||||
|
if cfg!(unix) {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/bin/python3.12"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_major), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_minor_12), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => context.filters(),
|
||||||
|
}, {
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
read_link(&bin_python_default), @"[TEMP_DIR]/managed/cpython-3.12.11-[PLATFORM]/python"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn python_install_default_preview() {
|
||||||
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
|
.with_filtered_python_keys()
|
||||||
|
.with_filtered_exe_suffix()
|
||||||
|
.with_managed_python_dirs();
|
||||||
|
|
||||||
|
let bin_python_minor_13 = context
|
||||||
|
.bin_dir
|
||||||
|
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
|
let bin_python_major = context
|
||||||
|
.bin_dir
|
||||||
|
.child(format!("python3{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
|
let bin_python_default = context
|
||||||
|
.bin_dir
|
||||||
|
.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// Install a specific version
|
// Install a specific version
|
||||||
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13"), @r"
|
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13"), @r"
|
||||||
|
@ -1342,7 +1812,7 @@ fn python_install_unknown() {
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn python_install_preview_broken_link() {
|
fn python_install_broken_link() {
|
||||||
use assert_fs::prelude::PathCreateDir;
|
use assert_fs::prelude::PathCreateDir;
|
||||||
use fs_err::os::unix::fs::symlink;
|
use fs_err::os::unix::fs::symlink;
|
||||||
|
|
||||||
|
@ -1358,7 +1828,7 @@ fn python_install_preview_broken_link() {
|
||||||
symlink(context.temp_dir.join("does-not-exist"), &bin_python).unwrap();
|
symlink(context.temp_dir.join("does-not-exist"), &bin_python).unwrap();
|
||||||
|
|
||||||
// Install
|
// Install
|
||||||
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13"), @r"
|
uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -1393,7 +1863,7 @@ fn python_install_default_from_env() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.12.11 in [TIME]
|
Installed Python 3.12.11 in [TIME]
|
||||||
+ cpython-3.12.11-[PLATFORM]
|
+ cpython-3.12.11-[PLATFORM] (python3.12)
|
||||||
");
|
");
|
||||||
|
|
||||||
// But prefer explicit requests
|
// But prefer explicit requests
|
||||||
|
@ -1404,7 +1874,7 @@ fn python_install_default_from_env() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.11.13 in [TIME]
|
Installed Python 3.11.13 in [TIME]
|
||||||
+ cpython-3.11.13-[PLATFORM]
|
+ cpython-3.11.13-[PLATFORM] (python3.11)
|
||||||
");
|
");
|
||||||
|
|
||||||
// We should ignore `UV_PYTHON` here and complain there is not a target
|
// We should ignore `UV_PYTHON` here and complain there is not a target
|
||||||
|
@ -1431,8 +1901,8 @@ fn python_install_default_from_env() {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Searching for Python installations
|
Searching for Python installations
|
||||||
Uninstalled 2 versions in [TIME]
|
Uninstalled 2 versions in [TIME]
|
||||||
- cpython-3.11.13-[PLATFORM]
|
- cpython-3.11.13-[PLATFORM] (python3.11)
|
||||||
- cpython-3.12.11-[PLATFORM]
|
- cpython-3.12.11-[PLATFORM] (python3.12)
|
||||||
");
|
");
|
||||||
|
|
||||||
// Uninstall with no targets should error
|
// Uninstall with no targets should error
|
||||||
|
@ -1516,8 +1986,6 @@ fn python_install_314() {
|
||||||
let context: TestContext = TestContext::new_with_versions(&[])
|
let context: TestContext = TestContext::new_with_versions(&[])
|
||||||
.with_filtered_python_keys()
|
.with_filtered_python_keys()
|
||||||
.with_managed_python_dirs()
|
.with_managed_python_dirs()
|
||||||
.with_filtered_python_install_bin()
|
|
||||||
.with_filtered_python_names()
|
|
||||||
.with_filtered_exe_suffix();
|
.with_filtered_exe_suffix();
|
||||||
|
|
||||||
// Install 3.14
|
// Install 3.14
|
||||||
|
@ -1529,7 +1997,7 @@ fn python_install_314() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.14.0b4 in [TIME]
|
Installed Python 3.14.0b4 in [TIME]
|
||||||
+ cpython-3.14.0b4-[PLATFORM]
|
+ cpython-3.14.0b4-[PLATFORM] (python3.14)
|
||||||
");
|
");
|
||||||
|
|
||||||
// Install a specific pre-release
|
// Install a specific pre-release
|
||||||
|
@ -1543,6 +2011,17 @@ fn python_install_314() {
|
||||||
+ cpython-3.14.0a4-[PLATFORM]
|
+ cpython-3.14.0a4-[PLATFORM]
|
||||||
");
|
");
|
||||||
|
|
||||||
|
// Add name filtering for the `find` tests, we avoid it in `install` tests because it clobbers
|
||||||
|
// the version suffixes which matter in the install logs
|
||||||
|
let filters = context
|
||||||
|
.filters()
|
||||||
|
.iter()
|
||||||
|
.map(|(a, b)| ((*a).to_string(), (*b).to_string()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let context = context
|
||||||
|
.with_filtered_python_install_bin()
|
||||||
|
.with_filtered_python_names();
|
||||||
|
|
||||||
// We should be able to find this version without opt-in, because there is no stable release
|
// We should be able to find this version without opt-in, because there is no stable release
|
||||||
// installed
|
// installed
|
||||||
uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @r"
|
uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @r"
|
||||||
|
@ -1574,14 +2053,14 @@ fn python_install_314() {
|
||||||
");
|
");
|
||||||
|
|
||||||
// If we install a stable version, that should be preferred though
|
// If we install a stable version, that should be preferred though
|
||||||
uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r"
|
uv_snapshot!(filters, context.python_install().arg("3.13"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.13.5 in [TIME]
|
Installed Python 3.13.5 in [TIME]
|
||||||
+ cpython-3.13.5-[PLATFORM]
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.python_find().arg("3"), @r"
|
uv_snapshot!(context.filters(), context.python_find().arg("3"), @r"
|
||||||
|
@ -1621,15 +2100,15 @@ fn python_install_cached() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.13.5 in [TIME]
|
Installed Python 3.13.5 in [TIME]
|
||||||
+ cpython-3.13.5-[PLATFORM]
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
let bin_python = context
|
let bin_python = context
|
||||||
.bin_dir
|
.bin_dir
|
||||||
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
// The executable should not be installed in the bin directory (requires preview)
|
// The executable should be installed in the bin directory
|
||||||
bin_python.assert(predicate::path::missing());
|
bin_python.assert(predicate::path::exists());
|
||||||
|
|
||||||
// Should be a no-op when already installed
|
// Should be a no-op when already installed
|
||||||
uv_snapshot!(context.filters(), context
|
uv_snapshot!(context.filters(), context
|
||||||
|
@ -1651,7 +2130,7 @@ fn python_install_cached() {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Searching for Python versions matching: Python 3.13
|
Searching for Python versions matching: Python 3.13
|
||||||
Uninstalled Python 3.13.5 in [TIME]
|
Uninstalled Python 3.13.5 in [TIME]
|
||||||
- cpython-3.13.5-[PLATFORM]
|
- cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
// The cached archive can be installed offline
|
// The cached archive can be installed offline
|
||||||
|
@ -1665,7 +2144,7 @@ fn python_install_cached() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.13.5 in [TIME]
|
Installed Python 3.13.5 in [TIME]
|
||||||
+ cpython-3.13.5-[PLATFORM]
|
+ cpython-3.13.5-[PLATFORM] (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
// 3.12 isn't cached, so it can't be installed
|
// 3.12 isn't cached, so it can't be installed
|
||||||
|
@ -1714,7 +2193,7 @@ fn python_install_emulated_macos() {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Installed Python 3.13.5 in [TIME]
|
Installed Python 3.13.5 in [TIME]
|
||||||
+ cpython-3.13.5-macos-x86_64-none
|
+ cpython-3.13.5-macos-x86_64-none (python3.13)
|
||||||
");
|
");
|
||||||
|
|
||||||
// It should be discoverable with `uv python find`
|
// It should be discoverable with `uv python find`
|
||||||
|
|
|
@ -121,28 +121,17 @@ present, uv will install all the Python versions listed in the file.
|
||||||
|
|
||||||
### Installing Python executables
|
### Installing Python executables
|
||||||
|
|
||||||
!!! important
|
uv installs Python executables into your `PATH` by default, e.g., `uv python install 3.12` will
|
||||||
|
install a Python executable into `~/.local/bin`, e.g., as `python3.12`.
|
||||||
Support for installing Python executables is in _preview_. This means the behavior is experimental
|
|
||||||
and subject to change.
|
|
||||||
|
|
||||||
To install Python executables into your `PATH`, provide the `--preview` option:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ uv python install 3.12 --preview
|
|
||||||
```
|
|
||||||
|
|
||||||
This will install a Python executable for the requested version into `~/.local/bin`, e.g., as
|
|
||||||
`python3.12`.
|
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
|
|
||||||
If `~/.local/bin` is not in your `PATH`, you can add it with `uv tool update-shell`.
|
If `~/.local/bin` is not in your `PATH`, you can add it with `uv tool update-shell`.
|
||||||
|
|
||||||
To install `python` and `python3` executables, include the `--default` option:
|
To install `python` and `python3` executables, include the experimental `--default` option:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ uv python install 3.12 --default --preview
|
$ uv python install 3.12 --default
|
||||||
```
|
```
|
||||||
|
|
||||||
When installing Python executables, uv will only overwrite an existing executable if it is managed
|
When installing Python executables, uv will only overwrite an existing executable if it is managed
|
||||||
|
@ -153,9 +142,9 @@ uv will update executables that it manages. However, it will prefer the latest p
|
||||||
Python minor version by default. For example:
|
Python minor version by default. For example:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ uv python install 3.12.7 --preview # Adds `python3.12` to `~/.local/bin`
|
$ uv python install 3.12.7 # Adds `python3.12` to `~/.local/bin`
|
||||||
$ uv python install 3.12.6 --preview # Does not update `python3.12`
|
$ uv python install 3.12.6 # Does not update `python3.12`
|
||||||
$ uv python install 3.12.8 --preview # Updates `python3.12` to point to 3.12.8
|
$ uv python install 3.12.8 # Updates `python3.12` to point to 3.12.8
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrading Python versions
|
## Upgrading Python versions
|
||||||
|
|
|
@ -24,17 +24,24 @@ $ uv python install
|
||||||
|
|
||||||
Python does not publish official distributable binaries. As such, uv uses distributions from the Astral [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone) project. See the [Python distributions](../concepts/python-versions.md#managed-python-distributions) documentation for more details.
|
Python does not publish official distributable binaries. As such, uv uses distributions from the Astral [`python-build-standalone`](https://github.com/astral-sh/python-build-standalone) project. See the [Python distributions](../concepts/python-versions.md#managed-python-distributions) documentation for more details.
|
||||||
|
|
||||||
Once Python is installed, it will be used by `uv` commands automatically.
|
Once Python is installed, it will be used by `uv` commands automatically. uv also adds the installed
|
||||||
|
version to your `PATH`:
|
||||||
|
|
||||||
!!! important
|
```console
|
||||||
|
$ python3.13
|
||||||
|
```
|
||||||
|
|
||||||
When Python is installed by uv, it will not be available globally (i.e. via the `python` command).
|
uv only installs a _versioned_ executable by default. To install `python` and `python3` executables,
|
||||||
Support for this feature is in _preview_. See [Installing Python executables](../concepts/python-versions.md#installing-python-executables)
|
include the experimental `--default` option:
|
||||||
for details.
|
|
||||||
|
|
||||||
You can still use
|
```console
|
||||||
[`uv run`](../guides/scripts.md#using-different-python-versions) or
|
$ uv python install --default
|
||||||
[create and activate a virtual environment](../pip/environments.md) to use `python` directly.
|
```
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
|
||||||
|
See the documentation on [installing Python executables](../concepts/python-versions.md#installing-python-executables)
|
||||||
|
for more details.
|
||||||
|
|
||||||
## Installing a specific version
|
## Installing a specific version
|
||||||
|
|
||||||
|
|
|
@ -2739,7 +2739,7 @@ Supports CPython and PyPy. CPython distributions are downloaded from the Astral
|
||||||
|
|
||||||
Python versions are installed into the uv Python directory, which can be retrieved with `uv python dir`.
|
Python versions are installed into the uv Python directory, which can be retrieved with `uv python dir`.
|
||||||
|
|
||||||
A `python` executable is not made globally available, managed Python versions are only used in uv commands or in active virtual environments. There is experimental support for adding Python executables to a directory on the path — use the `--preview` flag to enable this behavior and `uv python dir --bin` to retrieve the target directory.
|
By default, Python executables are added to a directory on the path with a minor version suffix, e.g., `python3.13`. To install `python3` and `python`, use the `--default` flag. Use `uv python dir --bin` to see the target directory.
|
||||||
|
|
||||||
Multiple Python versions may be requested.
|
Multiple Python versions may be requested.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue