mirror of
				https://github.com/astral-sh/uv.git
				synced 2025-11-04 05:34:28 +00:00 
			
		
		
		
	Update preview installation of Python executables to be non-fatal (#14612)
	
		
			
	
		
	
	
		
	
		
			Some checks are pending
		
		
	
	
		
			
				
	
				CI / check system | alpine (push) Blocked by required conditions
				
			
		
			
				
	
				CI / Determine changes (push) Waiting to run
				
			
		
			
				
	
				CI / lint (push) Waiting to run
				
			
		
			
				
	
				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 / mkdocs (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 / check windows trampoline | x86_64 (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 / 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 / build binary | macos aarch64 (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 / cargo build (msrv) (push) Blocked by required conditions
				
			
		
			
				
	
				CI / build binary | freebsd (push) Blocked by required conditions
				
			
		
			
				
	
				CI / smoke test | linux aarch64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / ecosystem test | pydantic/pydantic-core (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 | 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 | conda on ubuntu (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 | 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 | determine publish changes (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | registries (push) Blocked by required conditions
				
			
		
			
				
	
				CI / integration test | uv publish (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 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 | 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 | 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.11 on windows 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
				
			
		
			
				
	
				CI / check system | python on macos x86-64 (push) Blocked by required conditions
				
			
		
			
				
	
				CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
				
			
		
		
	
	
				
					
				
			
		
			Some checks are pending
		
		
	
	CI / check system | alpine (push) Blocked by required conditions
				
			CI / Determine changes (push) Waiting to run
				
			CI / lint (push) Waiting to run
				
			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 / mkdocs (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 / check windows trampoline | x86_64 (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 / 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 / build binary | macos aarch64 (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 / cargo build (msrv) (push) Blocked by required conditions
				
			CI / build binary | freebsd (push) Blocked by required conditions
				
			CI / smoke test | linux aarch64 (push) Blocked by required conditions
				
			CI / ecosystem test | pydantic/pydantic-core (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 | 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 | conda on ubuntu (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 | 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 | determine publish changes (push) Blocked by required conditions
				
			CI / integration test | registries (push) Blocked by required conditions
				
			CI / integration test | uv publish (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 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 | 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 | 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.11 on windows 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
				
			CI / check system | python on macos x86-64 (push) Blocked by required conditions
				
			CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
				
			Previously, if installation of executables into the bin directory failed we'd with a non-zero code. However, if we make this behavior the default we don't want it to be fatal. There's a `--bin` opt-in to _require_ successful executable installation and a `--no-bin` opt-out to silence the warning / opt-out of installation entirely. Part of https://github.com/astral-sh/uv/issues/14296 — we need this before we can stabilize the behavior. In #14614 we do the same for writing entries to the Windows registry.
This commit is contained in:
		
							parent
							
								
									cd0d5d4748
								
							
						
					
					
						commit
						bb1e9a247c
					
				
					 8 changed files with 212 additions and 40 deletions
				
			
		| 
						 | 
					@ -4941,6 +4941,19 @@ pub struct PythonInstallArgs {
 | 
				
			||||||
    #[arg(long, short, env = EnvVars::UV_PYTHON_INSTALL_DIR)]
 | 
					    #[arg(long, short, env = EnvVars::UV_PYTHON_INSTALL_DIR)]
 | 
				
			||||||
    pub install_dir: Option<PathBuf>,
 | 
					    pub install_dir: Option<PathBuf>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Install a Python executable into the `bin` directory.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// This is the default behavior. If this flag is provided explicitly, uv will error if the
 | 
				
			||||||
 | 
					    /// executable cannot be installed.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// See `UV_PYTHON_BIN_DIR` to customize the target directory.
 | 
				
			||||||
 | 
					    #[arg(long, overrides_with("no_bin"), hide = true)]
 | 
				
			||||||
 | 
					    pub bin: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Do not install a Python executable into the `bin` directory.
 | 
				
			||||||
 | 
					    #[arg(long, overrides_with("bin"), conflicts_with("default"))]
 | 
				
			||||||
 | 
					    pub no_bin: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// The Python version(s) to install.
 | 
					    /// The Python version(s) to install.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// If not provided, the requested Python version(s) will be read from the `UV_PYTHON`
 | 
					    /// If not provided, the requested Python version(s) will be read from the `UV_PYTHON`
 | 
				
			||||||
| 
						 | 
					@ -5003,7 +5016,7 @@ pub struct PythonInstallArgs {
 | 
				
			||||||
    /// and `python`.
 | 
					    /// and `python`.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// If multiple Python versions are requested, uv will exit with an error.
 | 
					    /// If multiple Python versions are requested, uv will exit with an error.
 | 
				
			||||||
    #[arg(long)]
 | 
					    #[arg(long, conflicts_with("no_bin"))]
 | 
				
			||||||
    pub default: bool,
 | 
					    pub default: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,12 +129,13 @@ fn read_registry_entry(company: &str, tag: &str, tag_key: &Key) -> Option<Window
 | 
				
			||||||
pub enum ManagedPep514Error {
 | 
					pub enum ManagedPep514Error {
 | 
				
			||||||
    #[error("Windows has an unknown pointer width for arch: `{_0}`")]
 | 
					    #[error("Windows has an unknown pointer width for arch: `{_0}`")]
 | 
				
			||||||
    InvalidPointerSize(Arch),
 | 
					    InvalidPointerSize(Arch),
 | 
				
			||||||
 | 
					    #[error("Failed to write registry entry: {0}")]
 | 
				
			||||||
 | 
					    WriteError(#[from] windows_result::Error),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Register a managed Python installation in the Windows registry following PEP 514.
 | 
					/// Register a managed Python installation in the Windows registry following PEP 514.
 | 
				
			||||||
pub fn create_registry_entry(
 | 
					pub fn create_registry_entry(
 | 
				
			||||||
    installation: &ManagedPythonInstallation,
 | 
					    installation: &ManagedPythonInstallation,
 | 
				
			||||||
    errors: &mut Vec<(PythonInstallationKey, anyhow::Error)>,
 | 
					 | 
				
			||||||
) -> Result<(), ManagedPep514Error> {
 | 
					) -> Result<(), ManagedPep514Error> {
 | 
				
			||||||
    let pointer_width = match installation.key().arch().family().pointer_width() {
 | 
					    let pointer_width = match installation.key().arch().family().pointer_width() {
 | 
				
			||||||
        Ok(PointerWidth::U32) => 32,
 | 
					        Ok(PointerWidth::U32) => 32,
 | 
				
			||||||
| 
						 | 
					@ -146,9 +147,7 @@ pub fn create_registry_entry(
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Err(err) = write_registry_entry(installation, pointer_width) {
 | 
					    write_registry_entry(installation, pointer_width)?;
 | 
				
			||||||
        errors.push((installation.key().clone(), err.into()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -135,6 +135,14 @@ impl Changelog {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
 | 
					enum InstallErrorKind {
 | 
				
			||||||
 | 
					    DownloadUnpack,
 | 
				
			||||||
 | 
					    Bin,
 | 
				
			||||||
 | 
					    #[cfg(windows)]
 | 
				
			||||||
 | 
					    Registry,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// 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(
 | 
				
			||||||
| 
						 | 
					@ -143,6 +151,7 @@ pub(crate) async fn install(
 | 
				
			||||||
    targets: Vec<String>,
 | 
					    targets: Vec<String>,
 | 
				
			||||||
    reinstall: bool,
 | 
					    reinstall: bool,
 | 
				
			||||||
    upgrade: bool,
 | 
					    upgrade: bool,
 | 
				
			||||||
 | 
					    bin: Option<bool>,
 | 
				
			||||||
    force: bool,
 | 
					    force: bool,
 | 
				
			||||||
    python_install_mirror: Option<String>,
 | 
					    python_install_mirror: Option<String>,
 | 
				
			||||||
    pypy_install_mirror: Option<String>,
 | 
					    pypy_install_mirror: Option<String>,
 | 
				
			||||||
| 
						 | 
					@ -432,12 +441,16 @@ pub(crate) async fn install(
 | 
				
			||||||
                downloaded.push(installation.clone());
 | 
					                downloaded.push(installation.clone());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(err) => {
 | 
					            Err(err) => {
 | 
				
			||||||
                errors.push((download.key().clone(), anyhow::Error::new(err)));
 | 
					                errors.push((
 | 
				
			||||||
 | 
					                    InstallErrorKind::DownloadUnpack,
 | 
				
			||||||
 | 
					                    download.key().clone(),
 | 
				
			||||||
 | 
					                    anyhow::Error::new(err),
 | 
				
			||||||
 | 
					                ));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let bin = if preview.is_enabled() {
 | 
					    let bin_dir = if matches!(bin, Some(true)) || preview.is_enabled() {
 | 
				
			||||||
        Some(python_executable_dir()?)
 | 
					        Some(python_executable_dir()?)
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        None
 | 
					        None
 | 
				
			||||||
| 
						 | 
					@ -460,7 +473,7 @@ pub(crate) async fn install(
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let bin = bin
 | 
					        let bin_dir = bin_dir
 | 
				
			||||||
            .as_ref()
 | 
					            .as_ref()
 | 
				
			||||||
            .expect("We should have a bin directory with preview enabled")
 | 
					            .expect("We should have a bin directory with preview enabled")
 | 
				
			||||||
            .as_path();
 | 
					            .as_path();
 | 
				
			||||||
| 
						 | 
					@ -468,9 +481,10 @@ pub(crate) async fn install(
 | 
				
			||||||
        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)) {
 | 
				
			||||||
            create_bin_links(
 | 
					            create_bin_links(
 | 
				
			||||||
                installation,
 | 
					                installation,
 | 
				
			||||||
            bin,
 | 
					                bin_dir,
 | 
				
			||||||
                reinstall,
 | 
					                reinstall,
 | 
				
			||||||
                force,
 | 
					                force,
 | 
				
			||||||
                default,
 | 
					                default,
 | 
				
			||||||
| 
						 | 
					@ -483,12 +497,22 @@ pub(crate) async fn install(
 | 
				
			||||||
                &mut changelog,
 | 
					                &mut changelog,
 | 
				
			||||||
                &mut errors,
 | 
					                &mut errors,
 | 
				
			||||||
                preview,
 | 
					                preview,
 | 
				
			||||||
        )?;
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if preview.is_enabled() {
 | 
					        if preview.is_enabled() {
 | 
				
			||||||
            #[cfg(windows)]
 | 
					            #[cfg(windows)]
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                uv_python::windows_registry::create_registry_entry(installation, &mut errors)?;
 | 
					                match uv_python::windows_registry::create_registry_entry(installation) {
 | 
				
			||||||
 | 
					                    Ok(()) => {}
 | 
				
			||||||
 | 
					                    Err(err) => {
 | 
				
			||||||
 | 
					                        errors.push((
 | 
				
			||||||
 | 
					                            InstallErrorKind::Registry,
 | 
				
			||||||
 | 
					                            installation.key().clone(),
 | 
				
			||||||
 | 
					                            err.into(),
 | 
				
			||||||
 | 
					                        ));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -636,24 +660,47 @@ pub(crate) async fn install(
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if preview.is_enabled() {
 | 
					        if preview.is_enabled() && !matches!(bin, Some(false)) {
 | 
				
			||||||
            let bin = bin
 | 
					            let bin_dir = bin_dir
 | 
				
			||||||
                .as_ref()
 | 
					                .as_ref()
 | 
				
			||||||
                .expect("We should have a bin directory with preview enabled")
 | 
					                .expect("We should have a bin directory with preview enabled")
 | 
				
			||||||
                .as_path();
 | 
					                .as_path();
 | 
				
			||||||
            warn_if_not_on_path(bin);
 | 
					            warn_if_not_on_path(bin_dir);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if !errors.is_empty() {
 | 
					    if !errors.is_empty() {
 | 
				
			||||||
        for (key, err) in errors
 | 
					        // If there are only bin install errors and the user didn't opt-in, we're only going to warn
 | 
				
			||||||
 | 
					        let fatal = errors
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .all(|(kind, _, _)| matches!(kind, InstallErrorKind::Bin))
 | 
				
			||||||
 | 
					            && bin.is_none();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (kind, key, err) in errors
 | 
				
			||||||
            .into_iter()
 | 
					            .into_iter()
 | 
				
			||||||
            .sorted_unstable_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b))
 | 
					            .sorted_unstable_by(|(_, key_a, _), (_, key_b, _)| key_a.cmp(key_b))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            let (level, verb) = match kind {
 | 
				
			||||||
 | 
					                InstallErrorKind::DownloadUnpack => ("error".red().bold().to_string(), "install"),
 | 
				
			||||||
 | 
					                InstallErrorKind::Bin => {
 | 
				
			||||||
 | 
					                    let level = match bin {
 | 
				
			||||||
 | 
					                        None => "warning".yellow().bold().to_string(),
 | 
				
			||||||
 | 
					                        Some(false) => continue,
 | 
				
			||||||
 | 
					                        Some(true) => "error".red().bold().to_string(),
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                    (level, "install executable for")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                #[cfg(windows)]
 | 
				
			||||||
 | 
					                InstallErrorKind::Registry => (
 | 
				
			||||||
 | 
					                    "error".red().bold().to_string(),
 | 
				
			||||||
 | 
					                    "install registry entry for",
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            writeln!(
 | 
					            writeln!(
 | 
				
			||||||
                printer.stderr(),
 | 
					                printer.stderr(),
 | 
				
			||||||
                "{}: Failed to install {}",
 | 
					                "{level}{} Failed to {verb} {}",
 | 
				
			||||||
                "error".red().bold(),
 | 
					                ":".bold(),
 | 
				
			||||||
                key.green()
 | 
					                key.green()
 | 
				
			||||||
            )?;
 | 
					            )?;
 | 
				
			||||||
            for err in err.chain() {
 | 
					            for err in err.chain() {
 | 
				
			||||||
| 
						 | 
					@ -665,6 +712,11 @@ pub(crate) async fn install(
 | 
				
			||||||
                )?;
 | 
					                )?;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if fatal {
 | 
				
			||||||
 | 
					            return Ok(ExitStatus::Success);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Ok(ExitStatus::Failure);
 | 
					        return Ok(ExitStatus::Failure);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -672,6 +724,8 @@ pub(crate) async fn install(
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Link the binaries of a managed Python installation to the bin directory.
 | 
					/// Link the binaries of a managed Python installation to the bin directory.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This function is fallible, but errors are pushed to `errors` instead of being thrown.
 | 
				
			||||||
#[allow(clippy::fn_params_excessive_bools)]
 | 
					#[allow(clippy::fn_params_excessive_bools)]
 | 
				
			||||||
fn create_bin_links(
 | 
					fn create_bin_links(
 | 
				
			||||||
    installation: &ManagedPythonInstallation,
 | 
					    installation: &ManagedPythonInstallation,
 | 
				
			||||||
| 
						 | 
					@ -686,9 +740,9 @@ fn create_bin_links(
 | 
				
			||||||
    existing_installations: &[ManagedPythonInstallation],
 | 
					    existing_installations: &[ManagedPythonInstallation],
 | 
				
			||||||
    installations: &[&ManagedPythonInstallation],
 | 
					    installations: &[&ManagedPythonInstallation],
 | 
				
			||||||
    changelog: &mut Changelog,
 | 
					    changelog: &mut Changelog,
 | 
				
			||||||
    errors: &mut Vec<(PythonInstallationKey, Error)>,
 | 
					    errors: &mut Vec<(InstallErrorKind, PythonInstallationKey, Error)>,
 | 
				
			||||||
    preview: PreviewMode,
 | 
					    preview: PreviewMode,
 | 
				
			||||||
) -> Result<(), Error> {
 | 
					) {
 | 
				
			||||||
    let targets =
 | 
					    let targets =
 | 
				
			||||||
        if (default || is_default_install) && first_request.matches_installation(installation) {
 | 
					        if (default || is_default_install) && first_request.matches_installation(installation) {
 | 
				
			||||||
            vec![
 | 
					            vec![
 | 
				
			||||||
| 
						 | 
					@ -773,6 +827,7 @@ fn create_bin_links(
 | 
				
			||||||
                                    );
 | 
					                                    );
 | 
				
			||||||
                                } else {
 | 
					                                } else {
 | 
				
			||||||
                                    errors.push((
 | 
					                                    errors.push((
 | 
				
			||||||
 | 
					                                        InstallErrorKind::Bin,
 | 
				
			||||||
                                        installation.key().clone(),
 | 
					                                        installation.key().clone(),
 | 
				
			||||||
                                        anyhow::anyhow!(
 | 
					                                        anyhow::anyhow!(
 | 
				
			||||||
                                            "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it",
 | 
					                                            "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it",
 | 
				
			||||||
| 
						 | 
					@ -848,7 +903,17 @@ fn create_bin_links(
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Replace the existing link
 | 
					                // Replace the existing link
 | 
				
			||||||
                fs_err::remove_file(&to)?;
 | 
					                if let Err(err) = fs_err::remove_file(&to) {
 | 
				
			||||||
 | 
					                    errors.push((
 | 
				
			||||||
 | 
					                        InstallErrorKind::Bin,
 | 
				
			||||||
 | 
					                        installation.key().clone(),
 | 
				
			||||||
 | 
					                        anyhow::anyhow!(
 | 
				
			||||||
 | 
					                            "Executable already exists at `{}` but could not be removed: {err}",
 | 
				
			||||||
 | 
					                            to.simplified_display()
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                    ));
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if let Some(existing) = existing {
 | 
					                if let Some(existing) = existing {
 | 
				
			||||||
                    // Ensure we do not report installation of this executable for an existing
 | 
					                    // Ensure we do not report installation of this executable for an existing
 | 
				
			||||||
| 
						 | 
					@ -860,7 +925,18 @@ fn create_bin_links(
 | 
				
			||||||
                        .remove(&target);
 | 
					                        .remove(&target);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                create_link_to_executable(&target, executable)?;
 | 
					                if let Err(err) = create_link_to_executable(&target, executable) {
 | 
				
			||||||
 | 
					                    errors.push((
 | 
				
			||||||
 | 
					                        InstallErrorKind::Bin,
 | 
				
			||||||
 | 
					                        installation.key().clone(),
 | 
				
			||||||
 | 
					                        anyhow::anyhow!(
 | 
				
			||||||
 | 
					                            "Failed to create link at `{}`: {err}",
 | 
				
			||||||
 | 
					                            target.simplified_display()
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                    ));
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                debug!(
 | 
					                debug!(
 | 
				
			||||||
                    "Updated executable at `{}` to {}",
 | 
					                    "Updated executable at `{}` to {}",
 | 
				
			||||||
                    target.simplified_display(),
 | 
					                    target.simplified_display(),
 | 
				
			||||||
| 
						 | 
					@ -874,11 +950,14 @@ fn create_bin_links(
 | 
				
			||||||
                    .insert(target.clone());
 | 
					                    .insert(target.clone());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Err(err) => {
 | 
					            Err(err) => {
 | 
				
			||||||
                errors.push((installation.key().clone(), anyhow::Error::new(err)));
 | 
					                errors.push((
 | 
				
			||||||
 | 
					                    InstallErrorKind::Bin,
 | 
				
			||||||
 | 
					                    installation.key().clone(),
 | 
				
			||||||
 | 
					                    anyhow::Error::new(err),
 | 
				
			||||||
 | 
					                ));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub(crate) fn format_executables(
 | 
					pub(crate) fn format_executables(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1402,6 +1402,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
 | 
				
			||||||
                args.targets,
 | 
					                args.targets,
 | 
				
			||||||
                args.reinstall,
 | 
					                args.reinstall,
 | 
				
			||||||
                upgrade,
 | 
					                upgrade,
 | 
				
			||||||
 | 
					                args.bin,
 | 
				
			||||||
                args.force,
 | 
					                args.force,
 | 
				
			||||||
                args.python_install_mirror,
 | 
					                args.python_install_mirror,
 | 
				
			||||||
                args.pypy_install_mirror,
 | 
					                args.pypy_install_mirror,
 | 
				
			||||||
| 
						 | 
					@ -1430,6 +1431,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
 | 
				
			||||||
                args.targets,
 | 
					                args.targets,
 | 
				
			||||||
                reinstall,
 | 
					                reinstall,
 | 
				
			||||||
                upgrade,
 | 
					                upgrade,
 | 
				
			||||||
 | 
					                args.bin,
 | 
				
			||||||
                args.force,
 | 
					                args.force,
 | 
				
			||||||
                args.python_install_mirror,
 | 
					                args.python_install_mirror,
 | 
				
			||||||
                args.pypy_install_mirror,
 | 
					                args.pypy_install_mirror,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -933,6 +933,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) bin: Option<bool>,
 | 
				
			||||||
    pub(crate) python_install_mirror: Option<String>,
 | 
					    pub(crate) python_install_mirror: Option<String>,
 | 
				
			||||||
    pub(crate) pypy_install_mirror: Option<String>,
 | 
					    pub(crate) pypy_install_mirror: Option<String>,
 | 
				
			||||||
    pub(crate) python_downloads_json_url: Option<String>,
 | 
					    pub(crate) python_downloads_json_url: Option<String>,
 | 
				
			||||||
| 
						 | 
					@ -961,6 +962,8 @@ impl PythonInstallSettings {
 | 
				
			||||||
            install_dir,
 | 
					            install_dir,
 | 
				
			||||||
            targets,
 | 
					            targets,
 | 
				
			||||||
            reinstall,
 | 
					            reinstall,
 | 
				
			||||||
 | 
					            bin,
 | 
				
			||||||
 | 
					            no_bin,
 | 
				
			||||||
            force,
 | 
					            force,
 | 
				
			||||||
            mirror: _,
 | 
					            mirror: _,
 | 
				
			||||||
            pypy_mirror: _,
 | 
					            pypy_mirror: _,
 | 
				
			||||||
| 
						 | 
					@ -973,6 +976,7 @@ impl PythonInstallSettings {
 | 
				
			||||||
            targets,
 | 
					            targets,
 | 
				
			||||||
            reinstall,
 | 
					            reinstall,
 | 
				
			||||||
            force,
 | 
					            force,
 | 
				
			||||||
 | 
					            bin: flag(bin, no_bin, "bin"),
 | 
				
			||||||
            python_install_mirror: python_mirror,
 | 
					            python_install_mirror: python_mirror,
 | 
				
			||||||
            pypy_install_mirror: pypy_mirror,
 | 
					            pypy_install_mirror: pypy_mirror,
 | 
				
			||||||
            python_downloads_json_url,
 | 
					            python_downloads_json_url,
 | 
				
			||||||
| 
						 | 
					@ -992,6 +996,7 @@ pub(crate) struct PythonUpgradeSettings {
 | 
				
			||||||
    pub(crate) pypy_install_mirror: Option<String>,
 | 
					    pub(crate) pypy_install_mirror: Option<String>,
 | 
				
			||||||
    pub(crate) python_downloads_json_url: Option<String>,
 | 
					    pub(crate) python_downloads_json_url: Option<String>,
 | 
				
			||||||
    pub(crate) default: bool,
 | 
					    pub(crate) default: bool,
 | 
				
			||||||
 | 
					    pub(crate) bin: Option<bool>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl PythonUpgradeSettings {
 | 
					impl PythonUpgradeSettings {
 | 
				
			||||||
| 
						 | 
					@ -1013,6 +1018,7 @@ impl PythonUpgradeSettings {
 | 
				
			||||||
            args.python_downloads_json_url.or(python_downloads_json_url);
 | 
					            args.python_downloads_json_url.or(python_downloads_json_url);
 | 
				
			||||||
        let force = false;
 | 
					        let force = false;
 | 
				
			||||||
        let default = false;
 | 
					        let default = false;
 | 
				
			||||||
 | 
					        let bin = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let PythonUpgradeArgs {
 | 
					        let PythonUpgradeArgs {
 | 
				
			||||||
            install_dir,
 | 
					            install_dir,
 | 
				
			||||||
| 
						 | 
					@ -1030,6 +1036,7 @@ impl PythonUpgradeSettings {
 | 
				
			||||||
            pypy_install_mirror: pypy_mirror,
 | 
					            pypy_install_mirror: pypy_mirror,
 | 
				
			||||||
            python_downloads_json_url,
 | 
					            python_downloads_json_url,
 | 
				
			||||||
            default,
 | 
					            default,
 | 
				
			||||||
 | 
					            bin,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -504,6 +504,9 @@ fn help_subsubcommand() {
 | 
				
			||||||
              
 | 
					              
 | 
				
			||||||
              [env: UV_PYTHON_INSTALL_DIR=]
 | 
					              [env: UV_PYTHON_INSTALL_DIR=]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          --no-bin
 | 
				
			||||||
 | 
					              Do not install a Python executable into the `bin` directory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          --mirror <MIRROR>
 | 
					          --mirror <MIRROR>
 | 
				
			||||||
              Set the URL to use as the source for downloading Python installations.
 | 
					              Set the URL to use as the source for downloading Python installations.
 | 
				
			||||||
              
 | 
					              
 | 
				
			||||||
| 
						 | 
					@ -790,6 +793,8 @@ fn help_flag_subsubcommand() {
 | 
				
			||||||
    Options:
 | 
					    Options:
 | 
				
			||||||
      -i, --install-dir <INSTALL_DIR>
 | 
					      -i, --install-dir <INSTALL_DIR>
 | 
				
			||||||
              The directory to store the Python installation in [env: UV_PYTHON_INSTALL_DIR=]
 | 
					              The directory to store the Python installation in [env: UV_PYTHON_INSTALL_DIR=]
 | 
				
			||||||
 | 
					          --no-bin
 | 
				
			||||||
 | 
					              Do not install a Python executable into the `bin` directory
 | 
				
			||||||
          --mirror <MIRROR>
 | 
					          --mirror <MIRROR>
 | 
				
			||||||
              Set the URL to use as the source for downloading Python installations [env:
 | 
					              Set the URL to use as the source for downloading Python installations [env:
 | 
				
			||||||
              UV_PYTHON_INSTALL_MIRROR=]
 | 
					              UV_PYTHON_INSTALL_MIRROR=]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -430,15 +430,35 @@ fn python_install_preview() {
 | 
				
			||||||
    bin_python.touch().unwrap();
 | 
					    bin_python.touch().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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"
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					    ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // With `--bin`, this should error instead of warn
 | 
				
			||||||
 | 
					    uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--bin").arg("3.13"), @r"
 | 
				
			||||||
    success: false
 | 
					    success: false
 | 
				
			||||||
    exit_code: 1
 | 
					    exit_code: 1
 | 
				
			||||||
    ----- stdout -----
 | 
					    ----- stdout -----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ----- stderr -----
 | 
					    ----- stderr -----
 | 
				
			||||||
    error: Failed to install cpython-3.13.5-[PLATFORM]
 | 
					    error: 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
 | 
					      Caused by: Executable already exists at `[BIN]/python3.13` but is not managed by uv; use `--force` to replace it
 | 
				
			||||||
    ");
 | 
					    ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // With `--no-bin`, this should be silent
 | 
				
			||||||
 | 
					    uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--no-bin").arg("3.13"), @r"
 | 
				
			||||||
 | 
					    success: true
 | 
				
			||||||
 | 
					    exit_code: 0
 | 
				
			||||||
 | 
					    ----- stdout -----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ----- stderr -----
 | 
				
			||||||
 | 
					    ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--force").arg("3.13"), @r"
 | 
					    uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--force").arg("3.13"), @r"
 | 
				
			||||||
    success: true
 | 
					    success: true
 | 
				
			||||||
    exit_code: 0
 | 
					    exit_code: 0
 | 
				
			||||||
| 
						 | 
					@ -565,6 +585,52 @@ fn python_install_preview() {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[test]
 | 
				
			||||||
 | 
					fn python_install_preview_no_bin() {
 | 
				
			||||||
 | 
					    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().arg("--preview").arg("--no-bin"), @r"
 | 
				
			||||||
 | 
					    success: true
 | 
				
			||||||
 | 
					    exit_code: 0
 | 
				
			||||||
 | 
					    ----- stdout -----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ----- stderr -----
 | 
				
			||||||
 | 
					    Installed Python 3.13.5 in [TIME]
 | 
				
			||||||
 | 
					     + cpython-3.13.5-[PLATFORM]
 | 
				
			||||||
 | 
					    ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let bin_python = context
 | 
				
			||||||
 | 
					        .bin_dir
 | 
				
			||||||
 | 
					        .child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The executable should not be installed in the bin directory
 | 
				
			||||||
 | 
					    bin_python.assert(predicate::path::missing());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--no-bin").arg("--default"), @r"
 | 
				
			||||||
 | 
					    success: false
 | 
				
			||||||
 | 
					    exit_code: 2
 | 
				
			||||||
 | 
					    ----- stdout -----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ----- stderr -----
 | 
				
			||||||
 | 
					    error: the argument '--no-bin' cannot be used with '--default'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Usage: uv python install --no-bin --install-dir <INSTALL_DIR> [TARGETS]...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    For more information, try '--help'.
 | 
				
			||||||
 | 
					    ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let bin_python = context
 | 
				
			||||||
 | 
					        .bin_dir
 | 
				
			||||||
 | 
					        .child(format!("python{}", std::env::consts::EXE_SUFFIX));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The executable should not be installed in the bin directory
 | 
				
			||||||
 | 
					    bin_python.assert(predicate::path::missing());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn python_install_preview_upgrade() {
 | 
					fn python_install_preview_upgrade() {
 | 
				
			||||||
    let context = TestContext::new_with_versions(&[])
 | 
					    let context = TestContext::new_with_versions(&[])
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2795,7 +2795,8 @@ uv python install [OPTIONS] [TARGETS]...
 | 
				
			||||||
<p>May also be set with the <code>UV_PYTHON_INSTALL_MIRROR</code> environment variable.</p></dd><dt id="uv-python-install--native-tls"><a href="#uv-python-install--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform's native certificate store.</p>
 | 
					<p>May also be set with the <code>UV_PYTHON_INSTALL_MIRROR</code> environment variable.</p></dd><dt id="uv-python-install--native-tls"><a href="#uv-python-install--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform's native certificate store.</p>
 | 
				
			||||||
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
 | 
					<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
 | 
				
			||||||
<p>However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.</p>
 | 
					<p>However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.</p>
 | 
				
			||||||
<p>May also be set with the <code>UV_NATIVE_TLS</code> environment variable.</p></dd><dt id="uv-python-install--no-cache"><a href="#uv-python-install--no-cache"><code>--no-cache</code></a>, <code>--no-cache-dir</code>, <code>-n</code></dt><dd><p>Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation</p>
 | 
					<p>May also be set with the <code>UV_NATIVE_TLS</code> environment variable.</p></dd><dt id="uv-python-install--no-bin"><a href="#uv-python-install--no-bin"><code>--no-bin</code></a></dt><dd><p>Do not install a Python executable into the <code>bin</code> directory</p>
 | 
				
			||||||
 | 
					</dd><dt id="uv-python-install--no-cache"><a href="#uv-python-install--no-cache"><code>--no-cache</code></a>, <code>--no-cache-dir</code>, <code>-n</code></dt><dd><p>Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation</p>
 | 
				
			||||||
<p>May also be set with the <code>UV_NO_CACHE</code> environment variable.</p></dd><dt id="uv-python-install--no-config"><a href="#uv-python-install--no-config"><code>--no-config</code></a></dt><dd><p>Avoid discovering configuration files (<code>pyproject.toml</code>, <code>uv.toml</code>).</p>
 | 
					<p>May also be set with the <code>UV_NO_CACHE</code> environment variable.</p></dd><dt id="uv-python-install--no-config"><a href="#uv-python-install--no-config"><code>--no-config</code></a></dt><dd><p>Avoid discovering configuration files (<code>pyproject.toml</code>, <code>uv.toml</code>).</p>
 | 
				
			||||||
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
 | 
					<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
 | 
				
			||||||
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p></dd><dt id="uv-python-install--no-managed-python"><a href="#uv-python-install--no-managed-python"><code>--no-managed-python</code></a></dt><dd><p>Disable use of uv-managed Python versions.</p>
 | 
					<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p></dd><dt id="uv-python-install--no-managed-python"><a href="#uv-python-install--no-managed-python"><code>--no-managed-python</code></a></dt><dd><p>Disable use of uv-managed Python versions.</p>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue