Use simpler pip-like Scheme for install paths (#2173)

## Summary

This will make it easier to use the paths returned by `distutils.py`
(for some cases). No code or behavior changes; just removing some fields
we don't need.
This commit is contained in:
Charlie Marsh 2024-03-04 12:50:13 -08:00 committed by GitHub
parent 93f5609476
commit 5fed1f6259
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 66 additions and 93 deletions

2
Cargo.lock generated
View file

@ -4508,6 +4508,7 @@ dependencies = [
"pep508_rs", "pep508_rs",
"platform-host", "platform-host",
"platform-tags", "platform-tags",
"pypi-types",
"regex", "regex",
"rmp-serde", "rmp-serde",
"same-file", "same-file",
@ -4615,6 +4616,7 @@ dependencies = [
"directories", "directories",
"fs-err", "fs-err",
"platform-host", "platform-host",
"pypi-types",
"serde", "serde",
"serde_json", "serde_json",
"tempfile", "tempfile",

View file

@ -13,6 +13,7 @@ use zip::ZipArchive;
use distribution_filename::WheelFilename; use distribution_filename::WheelFilename;
use pep440_rs::Version; use pep440_rs::Version;
use platform_host::{Arch, Os}; use platform_host::{Arch, Os};
use pypi_types::Scheme;
pub use uninstall::{uninstall_wheel, Uninstall}; pub use uninstall::{uninstall_wheel, Uninstall};
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_normalize::PackageName; use uv_normalize::PackageName;
@ -28,20 +29,12 @@ mod wheel;
pub struct Layout { pub struct Layout {
/// The Python interpreter, as returned by `sys.executable`. /// The Python interpreter, as returned by `sys.executable`.
pub sys_executable: PathBuf, pub sys_executable: PathBuf,
/// The `purelib` directory, as returned by `sysconfig.get_paths()`.
pub purelib: PathBuf,
/// The `platlib` directory, as returned by `sysconfig.get_paths()`.
pub platlib: PathBuf,
/// The `include` directory, as returned by `sysconfig.get_paths()`.
pub include: PathBuf,
/// The `scripts` directory, as returned by `sysconfig.get_paths()`.
pub scripts: PathBuf,
/// The `data` directory, as returned by `sysconfig.get_paths()`.
pub data: PathBuf,
/// The Python version, as returned by `sys.version_info`. /// The Python version, as returned by `sys.version_info`.
pub python_version: (u8, u8), pub python_version: (u8, u8),
/// The `os.name` value for the current platform. /// The `os.name` value for the current platform.
pub os_name: String, pub os_name: String,
/// The [`Scheme`] paths for the interpreter.
pub scheme: Scheme,
} }
/// Note: The caller is responsible for adding the path of the wheel we're installing. /// Note: The caller is responsible for adding the path of the wheel we're installing.

View file

@ -69,8 +69,8 @@ pub fn install_wheel(
// > 1.d Else unpack archive into platlib (site-packages). // > 1.d Else unpack archive into platlib (site-packages).
debug!(name, "Extracting file"); debug!(name, "Extracting file");
let site_packages = match lib_kind { let site_packages = match lib_kind {
LibKind::Pure => &layout.purelib, LibKind::Pure => &layout.scheme.purelib,
LibKind::Plat => &layout.platlib, LibKind::Plat => &layout.scheme.platlib,
}; };
let num_unpacked = link_mode.link_wheel_files(site_packages, &wheel)?; let num_unpacked = link_mode.link_wheel_files(site_packages, &wheel)?;
debug!(name, "Extracted {num_unpacked} files"); debug!(name, "Extracted {num_unpacked} files");

View file

@ -236,9 +236,9 @@ pub(crate) fn write_script_entrypoints(
.to_string() .to_string()
+ ".exe"; + ".exe";
layout.scripts.join(script_name) layout.scheme.scripts.join(script_name)
} else { } else {
layout.scripts.join(&entrypoint.name) layout.scheme.scripts.join(&entrypoint.name)
}; };
let entrypoint_relative = pathdiff::diff_paths(&entrypoint_absolute, site_packages) let entrypoint_relative = pathdiff::diff_paths(&entrypoint_absolute, site_packages)
@ -440,7 +440,7 @@ fn install_script(
))); )));
} }
let target_path = layout.scripts.join(file.file_name()); let target_path = layout.scheme.scripts.join(file.file_name());
let path = file.path(); let path = file.path();
let mut script = File::open(&path)?; let mut script = File::open(&path)?;
@ -520,7 +520,7 @@ pub(crate) fn install_data(
match path.file_name().and_then(|name| name.to_str()) { match path.file_name().and_then(|name| name.to_str()) {
Some("data") => { Some("data") => {
// Move the content of the folder to the root of the venv // Move the content of the folder to the root of the venv
move_folder_recorded(&path, &layout.data, site_packages, record)?; move_folder_recorded(&path, &layout.scheme.data, site_packages, record)?;
} }
Some("scripts") => { Some("scripts") => {
for file in fs::read_dir(path)? { for file in fs::read_dir(path)? {
@ -546,14 +546,14 @@ pub(crate) fn install_data(
} }
} }
Some("headers") => { Some("headers") => {
let target_path = layout.include.join(dist_name); let target_path = layout.scheme.include.join(dist_name);
move_folder_recorded(&path, &target_path, site_packages, record)?; move_folder_recorded(&path, &target_path, site_packages, record)?;
} }
Some("purelib") => { Some("purelib") => {
move_folder_recorded(&path, &layout.purelib, site_packages, record)?; move_folder_recorded(&path, &layout.scheme.purelib, site_packages, record)?;
} }
Some("platlib") => { Some("platlib") => {
move_folder_recorded(&path, &layout.platlib, site_packages, record)?; move_folder_recorded(&path, &layout.scheme.platlib, site_packages, record)?;
} }
_ => { _ => {
return Err(Error::InvalidWheel(format!( return Err(Error::InvalidWheel(format!(

View file

@ -2,10 +2,12 @@ pub use base_url::*;
pub use direct_url::*; pub use direct_url::*;
pub use lenient_requirement::*; pub use lenient_requirement::*;
pub use metadata::*; pub use metadata::*;
pub use scheme::*;
pub use simple_json::*; pub use simple_json::*;
mod base_url; mod base_url;
mod direct_url; mod direct_url;
mod lenient_requirement; mod lenient_requirement;
mod metadata; mod metadata;
mod scheme;
mod simple_json; mod simple_json;

View file

@ -2,17 +2,17 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// The installation paths returned by `sysconfig.get_paths()`. /// The paths associated with an installation scheme, typically returned by `sysconfig.get_paths()`.
///
/// See: <https://github.com/pypa/pip/blob/ae5fff36b0aad6e5e0037884927eaa29163c0611/src/pip/_internal/models/scheme.py#L12>
/// ///
/// See: <https://docs.python.org/3.12/library/sysconfig.html#installation-paths> /// See: <https://docs.python.org/3.12/library/sysconfig.html#installation-paths>
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct SysconfigPaths { pub struct Scheme {
pub stdlib: PathBuf, pub stdlib: PathBuf,
pub platstdlib: PathBuf,
pub purelib: PathBuf, pub purelib: PathBuf,
pub platlib: PathBuf, pub platlib: PathBuf,
pub include: PathBuf,
pub platinclude: PathBuf,
pub scripts: PathBuf, pub scripts: PathBuf,
pub data: PathBuf, pub data: PathBuf,
pub include: PathBuf,
} }

View file

@ -14,13 +14,14 @@ workspace = true
[dependencies] [dependencies]
cache-key = { path = "../cache-key" } cache-key = { path = "../cache-key" }
install-wheel-rs = { path = "../install-wheel-rs" }
pep440_rs = { path = "../pep440-rs" } pep440_rs = { path = "../pep440-rs" }
pep508_rs = { path = "../pep508-rs", features = ["serde"] } pep508_rs = { path = "../pep508-rs", features = ["serde"] }
platform-host = { path = "../platform-host" } platform-host = { path = "../platform-host" }
platform-tags = { path = "../platform-tags" } platform-tags = { path = "../platform-tags" }
pypi-types = { path = "../pypi-types" }
uv-cache = { path = "../uv-cache" } uv-cache = { path = "../uv-cache" }
uv-fs = { path = "../uv-fs" } uv-fs = { path = "../uv-fs" }
install-wheel-rs = { path = "../install-wheel-rs" }
configparser = { workspace = true } configparser = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] } fs-err = { workspace = true, features = ["tokio"] }

View file

@ -90,6 +90,6 @@ interpreter_info = {
"prefix": sys.prefix, "prefix": sys.prefix,
"base_executable": getattr(sys, "_base_executable", None), "base_executable": getattr(sys, "_base_executable", None),
"sys_executable": sys.executable, "sys_executable": sys.executable,
"sysconfig_paths": sysconfig.get_paths(), "scheme": sysconfig.get_paths(),
} }
print(json.dumps(interpreter_info)) print(json.dumps(interpreter_info))

View file

@ -15,12 +15,12 @@ use pep440_rs::Version;
use pep508_rs::MarkerEnvironment; use pep508_rs::MarkerEnvironment;
use platform_host::Platform; use platform_host::Platform;
use platform_tags::{Tags, TagsError}; use platform_tags::{Tags, TagsError};
use pypi_types::Scheme;
use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp}; use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp};
use uv_fs::write_atomic_sync; use uv_fs::write_atomic_sync;
use crate::python_environment::{detect_python_executable, detect_virtual_env}; use crate::python_environment::{detect_python_executable, detect_virtual_env};
use crate::python_query::try_find_default_python; use crate::python_query::try_find_default_python;
use crate::sysconfig::SysconfigPaths;
use crate::{find_requested_python, Error, PythonVersion, Virtualenv}; use crate::{find_requested_python, Error, PythonVersion, Virtualenv};
/// A Python executable and its associated platform markers. /// A Python executable and its associated platform markers.
@ -28,7 +28,7 @@ use crate::{find_requested_python, Error, PythonVersion, Virtualenv};
pub struct Interpreter { pub struct Interpreter {
platform: Platform, platform: Platform,
markers: Box<MarkerEnvironment>, markers: Box<MarkerEnvironment>,
sysconfig_paths: SysconfigPaths, scheme: Scheme,
prefix: PathBuf, prefix: PathBuf,
base_exec_prefix: PathBuf, base_exec_prefix: PathBuf,
base_prefix: PathBuf, base_prefix: PathBuf,
@ -51,7 +51,7 @@ impl Interpreter {
Ok(Self { Ok(Self {
platform, platform,
markers: Box::new(info.markers), markers: Box::new(info.markers),
sysconfig_paths: info.sysconfig_paths, scheme: info.scheme,
prefix: info.prefix, prefix: info.prefix,
base_exec_prefix: info.base_exec_prefix, base_exec_prefix: info.base_exec_prefix,
base_prefix: info.base_prefix, base_prefix: info.base_prefix,
@ -66,13 +66,11 @@ impl Interpreter {
Self { Self {
platform, platform,
markers: Box::new(markers), markers: Box::new(markers),
sysconfig_paths: SysconfigPaths { scheme: Scheme {
stdlib: PathBuf::from("/dev/null"), stdlib: PathBuf::from("/dev/null"),
platstdlib: PathBuf::from("/dev/null"),
purelib: PathBuf::from("/dev/null"), purelib: PathBuf::from("/dev/null"),
platlib: PathBuf::from("/dev/null"), platlib: PathBuf::from("/dev/null"),
include: PathBuf::from("/dev/null"), include: PathBuf::from("/dev/null"),
platinclude: PathBuf::from("/dev/null"),
scripts: PathBuf::from("/dev/null"), scripts: PathBuf::from("/dev/null"),
data: PathBuf::from("/dev/null"), data: PathBuf::from("/dev/null"),
}, },
@ -89,7 +87,7 @@ impl Interpreter {
#[must_use] #[must_use]
pub fn with_virtualenv(self, virtualenv: Virtualenv) -> Self { pub fn with_virtualenv(self, virtualenv: Virtualenv) -> Self {
Self { Self {
sysconfig_paths: virtualenv.sysconfig_paths, scheme: virtualenv.scheme,
sys_executable: virtualenv.executable, sys_executable: virtualenv.executable,
prefix: virtualenv.root, prefix: virtualenv.root,
..self ..self
@ -262,9 +260,7 @@ impl Interpreter {
return None; return None;
} }
let Ok(contents) = let Ok(contents) = fs::read_to_string(self.scheme.stdlib.join("EXTERNALLY-MANAGED")) else {
fs::read_to_string(self.sysconfig_paths.stdlib.join("EXTERNALLY-MANAGED"))
else {
return None; return None;
}; };
@ -371,37 +367,32 @@ impl Interpreter {
/// Return the `purelib` path for this Python interpreter, as returned by `sysconfig.get_paths()`. /// Return the `purelib` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn purelib(&self) -> &Path { pub fn purelib(&self) -> &Path {
&self.sysconfig_paths.purelib &self.scheme.purelib
} }
/// Return the `platlib` path for this Python interpreter, as returned by `sysconfig.get_paths()`. /// Return the `platlib` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn platlib(&self) -> &Path { pub fn platlib(&self) -> &Path {
&self.sysconfig_paths.platlib &self.scheme.platlib
} }
/// Return the `scripts` path for this Python interpreter, as returned by `sysconfig.get_paths()`. /// Return the `scripts` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn scripts(&self) -> &Path { pub fn scripts(&self) -> &Path {
&self.sysconfig_paths.scripts &self.scheme.scripts
} }
/// Return the `data` path for this Python interpreter, as returned by `sysconfig.get_paths()`. /// Return the `data` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn data(&self) -> &Path { pub fn data(&self) -> &Path {
&self.sysconfig_paths.data &self.scheme.data
} }
/// Return the `include` path for this Python interpreter, as returned by `sysconfig.get_paths()`. /// Return the `include` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn include(&self) -> &Path { pub fn include(&self) -> &Path {
&self.sysconfig_paths.include &self.scheme.include
}
/// Return the `platinclude` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn platinclude(&self) -> &Path {
&self.sysconfig_paths.platinclude
} }
/// Return the `stdlib` path for this Python interpreter, as returned by `sysconfig.get_paths()`. /// Return the `stdlib` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn stdlib(&self) -> &Path { pub fn stdlib(&self) -> &Path {
&self.sysconfig_paths.stdlib &self.scheme.stdlib
} }
/// Return the name of the Python directory used to build the path to the /// Return the name of the Python directory used to build the path to the
@ -421,23 +412,26 @@ impl Interpreter {
Layout { Layout {
python_version: self.python_tuple(), python_version: self.python_tuple(),
sys_executable: self.sys_executable().to_path_buf(), sys_executable: self.sys_executable().to_path_buf(),
purelib: self.purelib().to_path_buf(),
platlib: self.platlib().to_path_buf(),
scripts: self.scripts().to_path_buf(),
data: self.data().to_path_buf(),
include: if self.is_virtualenv() {
// If the interpreter is a venv, then the `include` directory has a different structure.
// See: https://github.com/pypa/pip/blob/0ad4c94be74cc24874c6feb5bb3c2152c398a18e/src/pip/_internal/locations/_sysconfig.py#L172
self.prefix.join("include").join("site").join(format!(
"{}{}.{}",
self.site_packages_python(),
self.python_major(),
self.python_minor()
))
} else {
self.include().to_path_buf()
},
os_name: self.markers.os_name.clone(), os_name: self.markers.os_name.clone(),
scheme: Scheme {
stdlib: self.stdlib().to_path_buf(),
purelib: self.purelib().to_path_buf(),
platlib: self.platlib().to_path_buf(),
scripts: self.scripts().to_path_buf(),
data: self.data().to_path_buf(),
include: if self.is_virtualenv() {
// If the interpreter is a venv, then the `include` directory has a different structure.
// See: https://github.com/pypa/pip/blob/0ad4c94be74cc24874c6feb5bb3c2152c398a18e/src/pip/_internal/locations/_sysconfig.py#L172
self.prefix.join("include").join("site").join(format!(
"{}{}.{}",
self.site_packages_python(),
self.python_major(),
self.python_minor()
))
} else {
self.include().to_path_buf()
},
},
} }
} }
} }
@ -460,7 +454,7 @@ impl ExternallyManaged {
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
struct InterpreterInfo { struct InterpreterInfo {
markers: MarkerEnvironment, markers: MarkerEnvironment,
sysconfig_paths: SysconfigPaths, scheme: Scheme,
prefix: PathBuf, prefix: PathBuf,
base_exec_prefix: PathBuf, base_exec_prefix: PathBuf,
base_prefix: PathBuf, base_prefix: PathBuf,
@ -661,15 +655,13 @@ mod tests {
"base_prefix": "/home/ferris/.pyenv/versions/3.12.0", "base_prefix": "/home/ferris/.pyenv/versions/3.12.0",
"prefix": "/home/ferris/projects/uv/.venv", "prefix": "/home/ferris/projects/uv/.venv",
"sys_executable": "/home/ferris/projects/uv/.venv/bin/python", "sys_executable": "/home/ferris/projects/uv/.venv/bin/python",
"sysconfig_paths": { "scheme": {
"data": "/home/ferris/.pyenv/versions/3.12.0", "data": "/home/ferris/.pyenv/versions/3.12.0",
"include": "/home/ferris/.pyenv/versions/3.12.0/include", "include": "/home/ferris/.pyenv/versions/3.12.0/include",
"platinclude": "/home/ferris/.pyenv/versions/3.12.0/include",
"platlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages", "platlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages",
"purelib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages", "purelib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages",
"scripts": "/home/ferris/.pyenv/versions/3.12.0/bin", "scripts": "/home/ferris/.pyenv/versions/3.12.0/bin",
"stdlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12", "stdlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12"
"platstdlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12"
} }
} }
"##}; "##};

View file

@ -9,7 +9,6 @@ pub use crate::interpreter::Interpreter;
pub use crate::python_environment::PythonEnvironment; pub use crate::python_environment::PythonEnvironment;
pub use crate::python_query::{find_default_python, find_requested_python}; pub use crate::python_query::{find_default_python, find_requested_python};
pub use crate::python_version::PythonVersion; pub use crate::python_version::PythonVersion;
pub use crate::sysconfig::SysconfigPaths;
pub use crate::virtualenv::Virtualenv; pub use crate::virtualenv::Virtualenv;
mod cfg; mod cfg;
@ -17,7 +16,6 @@ mod interpreter;
mod python_environment; mod python_environment;
mod python_query; mod python_query;
mod python_version; mod python_version;
mod sysconfig;
mod virtualenv; mod virtualenv;
#[derive(Debug, Error)] #[derive(Debug, Error)]

View file

@ -1,6 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::sysconfig::SysconfigPaths; use pypi_types::Scheme;
/// The layout of a virtual environment. /// The layout of a virtual environment.
#[derive(Debug)] #[derive(Debug)]
@ -12,6 +12,6 @@ pub struct Virtualenv {
/// (Unix, Python 3.11). /// (Unix, Python 3.11).
pub executable: PathBuf, pub executable: PathBuf,
/// The `sysconfig` paths for the virtualenv, as returned by `sysconfig.get_paths()`. /// The [`Scheme`] paths for the virtualenv, as returned by (e.g.) `sysconfig.get_paths()`.
pub sysconfig_paths: SysconfigPaths, pub scheme: Scheme,
} }

View file

@ -22,6 +22,7 @@ workspace = true
[dependencies] [dependencies]
platform-host = { path = "../platform-host" } platform-host = { path = "../platform-host" }
pypi-types = { path = "../pypi-types" }
uv-cache = { path = "../uv-cache" } uv-cache = { path = "../uv-cache" }
uv-fs = { path = "../uv-fs" } uv-fs = { path = "../uv-fs" }
uv-interpreter = { path = "../uv-interpreter" } uv-interpreter = { path = "../uv-interpreter" }

View file

@ -9,9 +9,10 @@ use std::path::Path;
use fs_err as fs; use fs_err as fs;
use fs_err::File; use fs_err::File;
use tracing::info; use tracing::info;
use uv_fs::Simplified;
use uv_interpreter::{Interpreter, SysconfigPaths, Virtualenv}; use pypi_types::Scheme;
use uv_fs::Simplified;
use uv_interpreter::{Interpreter, Virtualenv};
use crate::{Error, Prompt}; use crate::{Error, Prompt};
@ -292,38 +293,21 @@ pub fn create_bare_venv(
unimplemented!("Only Windows and Unix are supported") unimplemented!("Only Windows and Unix are supported")
}; };
// Construct the path to the `platstdlib` directory.
let platstdlib = if cfg!(windows) {
location.join("Lib")
} else {
location
.join("lib")
.join(format!(
"{}{}.{}",
interpreter.site_packages_python(),
interpreter.python_major(),
interpreter.python_minor()
))
.join("site-packages")
};
// Populate `site-packages` with a `_virtualenv.py` file. // Populate `site-packages` with a `_virtualenv.py` file.
fs::create_dir_all(&site_packages)?; fs::create_dir_all(&site_packages)?;
fs::write(site_packages.join("_virtualenv.py"), VIRTUALENV_PATCH)?; fs::write(site_packages.join("_virtualenv.py"), VIRTUALENV_PATCH)?;
fs::write(site_packages.join("_virtualenv.pth"), "import _virtualenv")?; fs::write(site_packages.join("_virtualenv.pth"), "import _virtualenv")?;
Ok(Virtualenv { Ok(Virtualenv {
sysconfig_paths: SysconfigPaths { scheme: Scheme {
// Paths that were already constructed above. // Paths that were already constructed above.
scripts, scripts,
platstdlib,
// Set `purelib` and `platlib` to the same value. // Set `purelib` and `platlib` to the same value.
purelib: site_packages.clone(), purelib: site_packages.clone(),
platlib: site_packages, platlib: site_packages,
// Inherited from the interpreter. // Inherited from the interpreter.
stdlib: interpreter.stdlib().to_path_buf(), stdlib: interpreter.stdlib().to_path_buf(),
include: interpreter.include().to_path_buf(), include: interpreter.include().to_path_buf(),
platinclude: interpreter.platinclude().to_path_buf(),
data: location.clone(), data: location.clone(),
}, },
root: location, root: location,