Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a --python flag to allow installation into arbitrary Python interpreters #2000

Merged
merged 6 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ miette = { version = "6.0.0" }
nanoid = { version = "0.4.0" }
once_cell = { version = "1.19.0" }
owo-colors = { version = "4.0.0" }
pathdiff = { version = "0.2.1" }
petgraph = { version = "0.6.4" }
platform-info = { version = "2.0.2" }
plist = { version = "1.6.0" }
Expand Down
3 changes: 2 additions & 1 deletion crates/install-wheel-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ data-encoding = { workspace = true }
fs-err = { workspace = true }
mailparse = { workspace = true }
once_cell = { workspace = true }
pathdiff = { workspace = true }
platform-info = { workspace = true }
plist = { workspace = true }
reflink-copy = { workspace = true }
Expand All @@ -51,4 +52,4 @@ walkdir = { workspace = true }
zip = { workspace = true }

[dev-dependencies]
indoc = {version = "2.0.4"}
indoc = { version = "2.0.4" }
41 changes: 0 additions & 41 deletions crates/install-wheel-rs/src/install_location.rs

This file was deleted.

20 changes: 18 additions & 2 deletions crates/install-wheel-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,36 @@ use zip::result::ZipError;
use zip::ZipArchive;

use distribution_filename::WheelFilename;
pub use install_location::InstallLocation;
use pep440_rs::Version;
use platform_host::{Arch, Os};
pub use uninstall::{uninstall_wheel, Uninstall};
use uv_fs::Normalized;
use uv_normalize::PackageName;

mod install_location;
pub mod linker;
mod record;
mod script;
mod uninstall;
mod wheel;

/// The layout of the target environment into which a wheel can be installed.
pub struct Layout {
/// The Python interpreter, as returned by `sys.executable`.
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`.
pub python_version: (u8, u8),
}

/// Note: The caller is responsible for adding the path of the wheel we're installing.
#[derive(Error, Debug)]
pub enum Error {
Expand Down
62 changes: 22 additions & 40 deletions crates/install-wheel-rs/src/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,17 @@ use reflink_copy as reflink;
use tempfile::tempdir_in;
use tracing::{debug, instrument};

use crate::Error;
use distribution_filename::WheelFilename;
use pep440_rs::Version;
use pypi_types::DirectUrl;
use uv_normalize::PackageName;

use crate::install_location::InstallLocation;
use crate::script::{scripts_from_ini, Script};
use crate::wheel::{
extra_dist_info, install_data, parse_metadata, parse_wheel_version, read_record_file,
write_script_entrypoints,
extra_dist_info, install_data, parse_metadata, parse_wheel_file, read_record_file,
write_script_entrypoints, LibKind,
};
use crate::{Error, Layout};

/// Install the given wheel to the given venv
///
Expand All @@ -32,30 +31,13 @@ use crate::wheel::{
/// Wheel 1.0: <https://www.python.org/dev/peps/pep-0427/>
#[instrument(skip_all, fields(wheel = % wheel.as_ref().display()))]
pub fn install_wheel(
location: &InstallLocation<impl AsRef<Path>>,
layout: &Layout,
wheel: impl AsRef<Path>,
filename: &WheelFilename,
direct_url: Option<&DirectUrl>,
installer: Option<&str>,
link_mode: LinkMode,
) -> Result<(), Error> {
let root = location.venv_root();

// TODO(charlie): Pass this in.
let site_packages_python = format!(
"python{}.{}",
location.python_version().0,
location.python_version().1
);
let site_packages = if cfg!(target_os = "windows") {
root.as_ref().join("Lib").join("site-packages")
} else {
root.as_ref()
.join("lib")
.join(site_packages_python)
.join("site-packages")
};

let dist_info_prefix = find_dist_info(&wheel)?;
let metadata = dist_info_metadata(&dist_info_prefix, &wheel)?;
let (name, version) = parse_metadata(&dist_info_prefix, &metadata)?;
Expand All @@ -81,13 +63,16 @@ pub fn install_wheel(
.as_ref()
.join(format!("{dist_info_prefix}.dist-info/WHEEL"));
let wheel_text = fs::read_to_string(wheel_file_path)?;
parse_wheel_version(&wheel_text)?;
let lib_kind = parse_wheel_file(&wheel_text)?;

// > 1.c If Root-Is-Purelib == ‘true’, unpack archive into purelib (site-packages).
// > 1.d Else unpack archive into platlib (site-packages).
// We always install in the same virtualenv site packages
debug!(name, "Extracting file");
let num_unpacked = link_mode.link_wheel_files(&site_packages, &wheel)?;
let site_packages = match lib_kind {
LibKind::Pure => &layout.purelib,
LibKind::Plat => &layout.platlib,
};
let num_unpacked = link_mode.link_wheel_files(site_packages, &wheel)?;
debug!(name, "Extracted {num_unpacked} files");

// Read the RECORD file.
Expand All @@ -100,27 +85,20 @@ pub fn install_wheel(

debug!(name, "Writing entrypoints");
let (console_scripts, gui_scripts) =
parse_scripts(&wheel, &dist_info_prefix, None, location.python_version().1)?;
write_script_entrypoints(
&site_packages,
location,
&console_scripts,
&mut record,
false,
)?;
write_script_entrypoints(&site_packages, location, &gui_scripts, &mut record, true)?;
parse_scripts(&wheel, &dist_info_prefix, None, layout.python_version.1)?;
write_script_entrypoints(layout, site_packages, &console_scripts, &mut record, false)?;
write_script_entrypoints(layout, site_packages, &gui_scripts, &mut record, true)?;

let data_dir = site_packages.join(format!("{dist_info_prefix}.data"));
// 2.a Unpacked archive includes distribution-1.0.dist-info/ and (if there is data) distribution-1.0.data/.
// 2.b Move each subtree of distribution-1.0.data/ onto its destination path. Each subdirectory of distribution-1.0.data/ is a key into a dict of destination directories, such as distribution-1.0.data/(purelib|platlib|headers|scripts|data). The initially supported paths are taken from distutils.command.install.
let data_dir = site_packages.join(format!("{dist_info_prefix}.data"));
if data_dir.is_dir() {
debug!(name, "Installing data");
install_data(
root.as_ref(),
&site_packages,
layout,
site_packages,
&data_dir,
&name,
location,
&console_scripts,
&gui_scripts,
&mut record,
Expand All @@ -135,7 +113,7 @@ pub fn install_wheel(

debug!(name, "Writing extra metadata");
extra_dist_info(
&site_packages,
site_packages,
&dist_info_prefix,
true,
direct_url,
Expand All @@ -147,7 +125,11 @@ pub fn install_wheel(
let mut record_writer = csv::WriterBuilder::new()
.has_headers(false)
.escape(b'"')
.from_path(site_packages.join(format!("{dist_info_prefix}.dist-info/RECORD")))?;
.from_path(
layout
.platlib
.join(format!("{dist_info_prefix}.dist-info/RECORD")),
)?;
record.sort();
for entry in record {
record_writer.serialize(entry)?;
Expand Down
Loading