Change "toolchain" to "python" (#4735)
Whew this is a lot. The user-facing changes are: - `uv toolchain` to `uv python` e.g. `uv python find`, `uv python install`, ... - `UV_TOOLCHAIN_DIR` to` UV_PYTHON_INSTALL_DIR` - `<UV_STATE_DIR>/toolchains` to `<UV_STATE_DIR>/python` (with [automatic migration](https://github.com/astral-sh/uv/pull/4735/files#r1663029330)) - User-facing messages no longer refer to toolchains, instead using "Python", "Python versions" or "Python installations" The internal changes are: - `uv-toolchain` crate to `uv-python` - `Toolchain` no longer referenced in type names - Dropped unused `SystemPython` type (previously replaced) - Clarified the type names for "managed Python installations" - (more little things)
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
use std::{
|
||||
env, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use fs_err as fs;
|
||||
use pypi_types::Scheme;
|
||||
use thiserror::Error;
|
||||
|
||||
/// The layout of a virtual environment.
|
||||
#[derive(Debug)]
|
||||
pub struct VirtualEnvironment {
|
||||
/// The absolute path to the root of the virtualenv, e.g., `/path/to/.venv`.
|
||||
pub root: PathBuf,
|
||||
|
||||
/// The path to the Python interpreter inside the virtualenv, e.g., `.venv/bin/python`
|
||||
/// (Unix, Python 3.11).
|
||||
pub executable: PathBuf,
|
||||
|
||||
/// The [`Scheme`] paths for the virtualenv, as returned by (e.g.) `sysconfig.get_paths()`.
|
||||
pub scheme: Scheme,
|
||||
}
|
||||
|
||||
/// A parsed `pyvenv.cfg`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PyVenvConfiguration {
|
||||
/// If the `virtualenv` package was used to create the virtual environment.
|
||||
pub(crate) virtualenv: bool,
|
||||
/// If the `uv` package was used to create the virtual environment.
|
||||
pub(crate) uv: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Broken virtualenv `{0}`: `pyvenv.cfg` is missing")]
|
||||
MissingPyVenvCfg(PathBuf),
|
||||
#[error("Broken virtualenv `{0}`: `pyvenv.cfg` could not be parsed")]
|
||||
ParsePyVenvCfg(PathBuf, #[source] io::Error),
|
||||
#[error(transparent)]
|
||||
IO(#[from] io::Error),
|
||||
}
|
||||
|
||||
/// Locate an active virtual environment by inspecting environment variables.
|
||||
///
|
||||
/// Supports `VIRTUAL_ENV`.
|
||||
pub(crate) fn virtualenv_from_env() -> Option<PathBuf> {
|
||||
if let Some(dir) = env::var_os("VIRTUAL_ENV").filter(|value| !value.is_empty()) {
|
||||
return Some(PathBuf::from(dir));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Locate an active conda environment by inspecting environment variables.
|
||||
///
|
||||
/// Supports `CONDA_PREFIX`.
|
||||
pub(crate) fn conda_prefix_from_env() -> Option<PathBuf> {
|
||||
if let Some(dir) = env::var_os("CONDA_PREFIX").filter(|value| !value.is_empty()) {
|
||||
return Some(PathBuf::from(dir));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Locate a virtual environment by searching the file system.
|
||||
///
|
||||
/// Searches for a `.venv` directory in the current or any parent directory. If the current
|
||||
/// directory is itself a virtual environment (or a subdirectory of a virtual environment), the
|
||||
/// containing virtual environment is returned.
|
||||
pub(crate) fn virtualenv_from_working_dir() -> Result<Option<PathBuf>, Error> {
|
||||
let current_dir = crate::current_dir()?;
|
||||
|
||||
for dir in current_dir.ancestors() {
|
||||
// If we're _within_ a virtualenv, return it.
|
||||
if dir.join("pyvenv.cfg").is_file() {
|
||||
return Ok(Some(dir.to_path_buf()));
|
||||
}
|
||||
|
||||
// Otherwise, search for a `.venv` directory.
|
||||
let dot_venv = dir.join(".venv");
|
||||
if dot_venv.is_dir() {
|
||||
if !dot_venv.join("pyvenv.cfg").is_file() {
|
||||
return Err(Error::MissingPyVenvCfg(dot_venv));
|
||||
}
|
||||
return Ok(Some(dot_venv));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Returns the path to the `python` executable inside a virtual environment.
|
||||
pub(crate) fn virtualenv_python_executable(venv: impl AsRef<Path>) -> PathBuf {
|
||||
let venv = venv.as_ref();
|
||||
if cfg!(windows) {
|
||||
// Search for `python.exe` in the `Scripts` directory.
|
||||
let default_executable = venv.join("Scripts").join("python.exe");
|
||||
if default_executable.exists() {
|
||||
return default_executable;
|
||||
}
|
||||
|
||||
// Apparently, Python installed via msys2 on Windows _might_ produce a POSIX-like layout.
|
||||
// See: https://github.com/PyO3/maturin/issues/1108
|
||||
let executable = venv.join("bin").join("python.exe");
|
||||
if executable.exists() {
|
||||
return executable;
|
||||
}
|
||||
|
||||
// Fallback for Conda environments.
|
||||
let executable = venv.join("python.exe");
|
||||
if executable.exists() {
|
||||
return executable;
|
||||
}
|
||||
|
||||
// If none of these exist, return the standard location
|
||||
default_executable
|
||||
} else {
|
||||
// Check for both `python3` over `python`, preferring the more specific one
|
||||
let default_executable = venv.join("bin").join("python3");
|
||||
if default_executable.exists() {
|
||||
return default_executable;
|
||||
}
|
||||
|
||||
let executable = venv.join("bin").join("python");
|
||||
if executable.exists() {
|
||||
return executable;
|
||||
}
|
||||
|
||||
// If none of these exist, return the standard location
|
||||
default_executable
|
||||
}
|
||||
}
|
||||
|
||||
impl PyVenvConfiguration {
|
||||
/// Parse a `pyvenv.cfg` file into a [`PyVenvConfiguration`].
|
||||
pub fn parse(cfg: impl AsRef<Path>) -> Result<Self, Error> {
|
||||
let mut virtualenv = false;
|
||||
let mut uv = false;
|
||||
|
||||
// Per https://snarky.ca/how-virtual-environments-work/, the `pyvenv.cfg` file is not a
|
||||
// valid INI file, and is instead expected to be parsed by partitioning each line on the
|
||||
// first equals sign.
|
||||
let content = fs::read_to_string(&cfg)
|
||||
.map_err(|err| Error::ParsePyVenvCfg(cfg.as_ref().to_path_buf(), err))?;
|
||||
for line in content.lines() {
|
||||
let Some((key, _value)) = line.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
match key.trim() {
|
||||
"virtualenv" => {
|
||||
virtualenv = true;
|
||||
}
|
||||
"uv" => {
|
||||
uv = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { virtualenv, uv })
|
||||
}
|
||||
|
||||
/// Returns true if the virtual environment was created with the `virtualenv` package.
|
||||
pub fn is_virtualenv(&self) -> bool {
|
||||
self.virtualenv
|
||||
}
|
||||
|
||||
/// Returns true if the virtual environment was created with the `uv` package.
|
||||
pub fn is_uv(&self) -> bool {
|
||||
self.uv
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user