mirror of https://github.com/astral-sh/uv
uv-install-wheel: Split installation logic and link logic (#11166)
uv-install-wheel had the logic for laying out the installation and for linking a directory in the same module. We split them up to isolate each module's logic and tighten the crate's interface to only expose top level members. No logic changes, only moving code around.
This commit is contained in:
parent
cca1d34432
commit
1cfe5be355
|
|
@ -92,7 +92,7 @@ mod resolver {
|
|||
use uv_dispatch::{BuildDispatch, SharedState};
|
||||
use uv_distribution::DistributionDatabase;
|
||||
use uv_distribution_types::{DependencyMetadata, IndexLocations};
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_pep440::Version;
|
||||
use uv_pep508::{MarkerEnvironment, MarkerEnvironmentBuilder};
|
||||
use uv_platform_tags::{Arch, Os, Platform, Tags};
|
||||
|
|
|
|||
|
|
@ -2445,7 +2445,7 @@ pub struct VenvArgs {
|
|||
/// Defaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and
|
||||
/// Windows.
|
||||
#[arg(long, value_enum, env = EnvVars::UV_LINK_MODE)]
|
||||
pub link_mode: Option<uv_install_wheel::linker::LinkMode>,
|
||||
pub link_mode: Option<uv_install_wheel::LinkMode>,
|
||||
|
||||
#[command(flatten)]
|
||||
pub refresh: RefreshArgs,
|
||||
|
|
@ -4170,7 +4170,7 @@ pub struct ToolUpgradeArgs {
|
|||
env = EnvVars::UV_LINK_MODE,
|
||||
help_heading = "Installer options"
|
||||
)]
|
||||
pub link_mode: Option<uv_install_wheel::linker::LinkMode>,
|
||||
pub link_mode: Option<uv_install_wheel::LinkMode>,
|
||||
|
||||
/// Compile Python files to bytecode after installation.
|
||||
///
|
||||
|
|
@ -4790,7 +4790,7 @@ pub struct InstallerArgs {
|
|||
env = EnvVars::UV_LINK_MODE,
|
||||
help_heading = "Installer options"
|
||||
)]
|
||||
pub link_mode: Option<uv_install_wheel::linker::LinkMode>,
|
||||
pub link_mode: Option<uv_install_wheel::LinkMode>,
|
||||
|
||||
/// Compile Python files to bytecode after installation.
|
||||
///
|
||||
|
|
@ -4986,7 +4986,7 @@ pub struct ResolverArgs {
|
|||
env = EnvVars::UV_LINK_MODE,
|
||||
help_heading = "Installer options"
|
||||
)]
|
||||
pub link_mode: Option<uv_install_wheel::linker::LinkMode>,
|
||||
pub link_mode: Option<uv_install_wheel::LinkMode>,
|
||||
|
||||
/// Ignore the `tool.uv.sources` table when resolving dependencies. Used to lock against the
|
||||
/// standards-compliant, publishable package metadata, as opposed to using any workspace, Git,
|
||||
|
|
@ -5174,7 +5174,7 @@ pub struct ResolverInstallerArgs {
|
|||
env = EnvVars::UV_LINK_MODE,
|
||||
help_heading = "Installer options"
|
||||
)]
|
||||
pub link_mode: Option<uv_install_wheel::linker::LinkMode>,
|
||||
pub link_mode: Option<uv_install_wheel::LinkMode>,
|
||||
|
||||
/// Compile Python files to bytecode after installation.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ pub struct BuildDispatch<'a> {
|
|||
shared_state: SharedState,
|
||||
dependency_metadata: &'a DependencyMetadata,
|
||||
build_isolation: BuildIsolation<'a>,
|
||||
link_mode: uv_install_wheel::linker::LinkMode,
|
||||
link_mode: uv_install_wheel::LinkMode,
|
||||
build_options: &'a BuildOptions,
|
||||
config_settings: &'a ConfigSettings,
|
||||
hasher: &'a HashStrategy,
|
||||
|
|
@ -112,7 +112,7 @@ impl<'a> BuildDispatch<'a> {
|
|||
index_strategy: IndexStrategy,
|
||||
config_settings: &'a ConfigSettings,
|
||||
build_isolation: BuildIsolation<'a>,
|
||||
link_mode: uv_install_wheel::linker::LinkMode,
|
||||
link_mode: uv_install_wheel::LinkMode,
|
||||
build_options: &'a BuildOptions,
|
||||
hasher: &'a HashStrategy,
|
||||
exclude_newer: Option<ExcludeNewer>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,224 @@
|
|||
//! Like `wheel.rs`, but for installing wheels that have already been unzipped, rather than
|
||||
//! reading from a zip file.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::linker::{LinkMode, Locks};
|
||||
use crate::script::{scripts_from_ini, Script};
|
||||
use crate::wheel::{
|
||||
install_data, parse_wheel_file, read_record_file, write_installer_metadata,
|
||||
write_script_entrypoints, LibKind,
|
||||
};
|
||||
use crate::{Error, Layout};
|
||||
use fs_err as fs;
|
||||
use fs_err::File;
|
||||
use tracing::{instrument, trace};
|
||||
use uv_cache_info::CacheInfo;
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_pypi_types::{DirectUrl, Metadata12};
|
||||
|
||||
/// Install the given wheel to the given venv
|
||||
///
|
||||
/// The caller must ensure that the wheel is compatible to the environment.
|
||||
///
|
||||
/// <https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl>
|
||||
///
|
||||
/// Wheel 1.0: <https://www.python.org/dev/peps/pep-0427/>
|
||||
#[instrument(skip_all, fields(wheel = %filename))]
|
||||
pub fn install_wheel(
|
||||
layout: &Layout,
|
||||
relocatable: bool,
|
||||
wheel: impl AsRef<Path>,
|
||||
filename: &WheelFilename,
|
||||
direct_url: Option<&DirectUrl>,
|
||||
cache_info: Option<&CacheInfo>,
|
||||
installer: Option<&str>,
|
||||
installer_metadata: bool,
|
||||
link_mode: LinkMode,
|
||||
locks: &Locks,
|
||||
) -> Result<(), Error> {
|
||||
let dist_info_prefix = find_dist_info(&wheel)?;
|
||||
let metadata = dist_info_metadata(&dist_info_prefix, &wheel)?;
|
||||
let Metadata12 { name, version, .. } = Metadata12::parse_metadata(&metadata)
|
||||
.map_err(|err| Error::InvalidWheel(err.to_string()))?;
|
||||
|
||||
// Validate the wheel name and version.
|
||||
{
|
||||
if name != filename.name {
|
||||
return Err(Error::MismatchedName(name, filename.name.clone()));
|
||||
}
|
||||
|
||||
if version != filename.version && version != filename.version.clone().without_local() {
|
||||
return Err(Error::MismatchedVersion(version, filename.version.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// We're going step by step though
|
||||
// https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl
|
||||
// > 1.a Parse distribution-1.0.dist-info/WHEEL.
|
||||
// > 1.b Check that installer is compatible with Wheel-Version. Warn if minor version is greater, abort if major version is greater.
|
||||
let wheel_file_path = wheel
|
||||
.as_ref()
|
||||
.join(format!("{dist_info_prefix}.dist-info/WHEEL"));
|
||||
let wheel_text = fs::read_to_string(wheel_file_path)?;
|
||||
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).
|
||||
trace!(?name, "Extracting file");
|
||||
let site_packages = match lib_kind {
|
||||
LibKind::Pure => &layout.scheme.purelib,
|
||||
LibKind::Plat => &layout.scheme.platlib,
|
||||
};
|
||||
let num_unpacked = link_mode.link_wheel_files(site_packages, &wheel, locks)?;
|
||||
trace!(?name, "Extracted {num_unpacked} files");
|
||||
|
||||
// Read the RECORD file.
|
||||
let mut record_file = File::open(
|
||||
wheel
|
||||
.as_ref()
|
||||
.join(format!("{dist_info_prefix}.dist-info/RECORD")),
|
||||
)?;
|
||||
let mut record = read_record_file(&mut record_file)?;
|
||||
|
||||
let (console_scripts, gui_scripts) =
|
||||
parse_scripts(&wheel, &dist_info_prefix, None, layout.python_version.1)?;
|
||||
|
||||
if console_scripts.is_empty() && gui_scripts.is_empty() {
|
||||
trace!(?name, "No entrypoints");
|
||||
} else {
|
||||
trace!(?name, "Writing entrypoints");
|
||||
|
||||
fs_err::create_dir_all(&layout.scheme.scripts)?;
|
||||
write_script_entrypoints(
|
||||
layout,
|
||||
relocatable,
|
||||
site_packages,
|
||||
&console_scripts,
|
||||
&mut record,
|
||||
false,
|
||||
)?;
|
||||
write_script_entrypoints(
|
||||
layout,
|
||||
relocatable,
|
||||
site_packages,
|
||||
&gui_scripts,
|
||||
&mut record,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
trace!(?name, "Installing data");
|
||||
install_data(
|
||||
layout,
|
||||
relocatable,
|
||||
site_packages,
|
||||
&data_dir,
|
||||
&name,
|
||||
&console_scripts,
|
||||
&gui_scripts,
|
||||
&mut record,
|
||||
)?;
|
||||
// 2.c If applicable, update scripts starting with #!python to point to the correct interpreter.
|
||||
// Script are unsupported through data
|
||||
// 2.e Remove empty distribution-1.0.data directory.
|
||||
fs::remove_dir_all(data_dir)?;
|
||||
} else {
|
||||
trace!(?name, "No data");
|
||||
}
|
||||
|
||||
if installer_metadata {
|
||||
trace!(?name, "Writing installer metadata");
|
||||
write_installer_metadata(
|
||||
site_packages,
|
||||
&dist_info_prefix,
|
||||
true,
|
||||
direct_url,
|
||||
cache_info,
|
||||
installer,
|
||||
&mut record,
|
||||
)?;
|
||||
}
|
||||
|
||||
trace!(?name, "Writing record");
|
||||
let mut record_writer = csv::WriterBuilder::new()
|
||||
.has_headers(false)
|
||||
.escape(b'"')
|
||||
.from_path(site_packages.join(format!("{dist_info_prefix}.dist-info/RECORD")))?;
|
||||
record.sort();
|
||||
for entry in record {
|
||||
record_writer.serialize(entry)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find the `dist-info` directory in an unzipped wheel.
|
||||
///
|
||||
/// See: <https://github.com/PyO3/python-pkginfo-rs>
|
||||
///
|
||||
/// See: <https://github.com/pypa/pip/blob/36823099a9cdd83261fdbc8c1d2a24fa2eea72ca/src/pip/_internal/utils/wheel.py#L38>
|
||||
fn find_dist_info(path: impl AsRef<Path>) -> Result<String, Error> {
|
||||
// Iterate over `path` to find the `.dist-info` directory. It should be at the top-level.
|
||||
let Some(dist_info) = fs::read_dir(path.as_ref())?.find_map(|entry| {
|
||||
let entry = entry.ok()?;
|
||||
let file_type = entry.file_type().ok()?;
|
||||
if file_type.is_dir() {
|
||||
let path = entry.path();
|
||||
if path.extension().is_some_and(|ext| ext == "dist-info") {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) else {
|
||||
return Err(Error::InvalidWheel(
|
||||
"Missing .dist-info directory".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let Some(dist_info_prefix) = dist_info.file_stem() else {
|
||||
return Err(Error::InvalidWheel(
|
||||
"Missing .dist-info directory".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
Ok(dist_info_prefix.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// Read the `dist-info` metadata from a directory.
|
||||
fn dist_info_metadata(dist_info_prefix: &str, wheel: impl AsRef<Path>) -> Result<Vec<u8>, Error> {
|
||||
let metadata_file = wheel
|
||||
.as_ref()
|
||||
.join(format!("{dist_info_prefix}.dist-info/METADATA"));
|
||||
Ok(fs::read(metadata_file)?)
|
||||
}
|
||||
|
||||
/// Parses the `entry_points.txt` entry in the wheel for console scripts
|
||||
///
|
||||
/// Returns (`script_name`, module, function)
|
||||
///
|
||||
/// Extras are supposed to be ignored, which happens if you pass None for extras.
|
||||
fn parse_scripts(
|
||||
wheel: impl AsRef<Path>,
|
||||
dist_info_prefix: &str,
|
||||
extras: Option<&[String]>,
|
||||
python_minor: u8,
|
||||
) -> Result<(Vec<Script>, Vec<Script>), Error> {
|
||||
let entry_points_path = wheel
|
||||
.as_ref()
|
||||
.join(format!("{dist_info_prefix}.dist-info/entry_points.txt"));
|
||||
|
||||
// Read the entry points mapping. If the file doesn't exist, we just return an empty mapping.
|
||||
let Ok(ini) = fs::read_to_string(entry_points_path) else {
|
||||
return Ok((Vec::new(), Vec::new()));
|
||||
};
|
||||
|
||||
scripts_from_ini(extras, python_minor, ini)
|
||||
}
|
||||
|
|
@ -7,15 +7,19 @@ use platform_info::PlatformInfoError;
|
|||
use thiserror::Error;
|
||||
use zip::result::ZipError;
|
||||
|
||||
pub use uninstall::{uninstall_egg, uninstall_legacy_editable, uninstall_wheel, Uninstall};
|
||||
use uv_fs::Simplified;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_platform_tags::{Arch, Os};
|
||||
use uv_pypi_types::Scheme;
|
||||
|
||||
pub use install::install_wheel;
|
||||
pub use linker::{LinkMode, Locks};
|
||||
pub use uninstall::{uninstall_egg, uninstall_legacy_editable, uninstall_wheel, Uninstall};
|
||||
pub use wheel::{parse_wheel_file, read_record_file, LibKind};
|
||||
|
||||
pub mod linker;
|
||||
mod install;
|
||||
mod linker;
|
||||
mod record;
|
||||
mod script;
|
||||
mod uninstall;
|
||||
|
|
|
|||
|
|
@ -1,238 +1,20 @@
|
|||
//! Like `wheel.rs`, but for installing wheels that have already been unzipped, rather than
|
||||
//! reading from a zip file.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::script::{scripts_from_ini, Script};
|
||||
use crate::wheel::{
|
||||
install_data, parse_wheel_file, read_record_file, write_installer_metadata,
|
||||
write_script_entrypoints, LibKind,
|
||||
};
|
||||
use crate::{Error, Layout};
|
||||
use crate::Error;
|
||||
use fs_err as fs;
|
||||
use fs_err::{DirEntry, File};
|
||||
use fs_err::DirEntry;
|
||||
use reflink_copy as reflink;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::SystemTime;
|
||||
use tempfile::tempdir_in;
|
||||
use tracing::{debug, instrument, trace};
|
||||
use uv_cache_info::CacheInfo;
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_pypi_types::{DirectUrl, Metadata12};
|
||||
use uv_warnings::warn_user_once;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Locks(Mutex<FxHashMap<PathBuf, Arc<Mutex<()>>>>);
|
||||
|
||||
/// Install the given wheel to the given venv
|
||||
///
|
||||
/// The caller must ensure that the wheel is compatible to the environment.
|
||||
///
|
||||
/// <https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl>
|
||||
///
|
||||
/// Wheel 1.0: <https://www.python.org/dev/peps/pep-0427/>
|
||||
#[instrument(skip_all, fields(wheel = %filename))]
|
||||
pub fn install_wheel(
|
||||
layout: &Layout,
|
||||
relocatable: bool,
|
||||
wheel: impl AsRef<Path>,
|
||||
filename: &WheelFilename,
|
||||
direct_url: Option<&DirectUrl>,
|
||||
cache_info: Option<&CacheInfo>,
|
||||
installer: Option<&str>,
|
||||
installer_metadata: bool,
|
||||
link_mode: LinkMode,
|
||||
locks: &Locks,
|
||||
) -> Result<(), Error> {
|
||||
let dist_info_prefix = find_dist_info(&wheel)?;
|
||||
let metadata = dist_info_metadata(&dist_info_prefix, &wheel)?;
|
||||
let Metadata12 { name, version, .. } = Metadata12::parse_metadata(&metadata)
|
||||
.map_err(|err| Error::InvalidWheel(err.to_string()))?;
|
||||
|
||||
// Validate the wheel name and version.
|
||||
{
|
||||
if name != filename.name {
|
||||
return Err(Error::MismatchedName(name, filename.name.clone()));
|
||||
}
|
||||
|
||||
if version != filename.version && version != filename.version.clone().without_local() {
|
||||
return Err(Error::MismatchedVersion(version, filename.version.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// We're going step by step though
|
||||
// https://packaging.python.org/en/latest/specifications/binary-distribution-format/#installing-a-wheel-distribution-1-0-py32-none-any-whl
|
||||
// > 1.a Parse distribution-1.0.dist-info/WHEEL.
|
||||
// > 1.b Check that installer is compatible with Wheel-Version. Warn if minor version is greater, abort if major version is greater.
|
||||
let wheel_file_path = wheel
|
||||
.as_ref()
|
||||
.join(format!("{dist_info_prefix}.dist-info/WHEEL"));
|
||||
let wheel_text = fs::read_to_string(wheel_file_path)?;
|
||||
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).
|
||||
trace!(?name, "Extracting file");
|
||||
let site_packages = match lib_kind {
|
||||
LibKind::Pure => &layout.scheme.purelib,
|
||||
LibKind::Plat => &layout.scheme.platlib,
|
||||
};
|
||||
let num_unpacked = link_mode.link_wheel_files(site_packages, &wheel, locks)?;
|
||||
trace!(?name, "Extracted {num_unpacked} files");
|
||||
|
||||
// Read the RECORD file.
|
||||
let mut record_file = File::open(
|
||||
wheel
|
||||
.as_ref()
|
||||
.join(format!("{dist_info_prefix}.dist-info/RECORD")),
|
||||
)?;
|
||||
let mut record = read_record_file(&mut record_file)?;
|
||||
|
||||
let (console_scripts, gui_scripts) =
|
||||
parse_scripts(&wheel, &dist_info_prefix, None, layout.python_version.1)?;
|
||||
|
||||
if console_scripts.is_empty() && gui_scripts.is_empty() {
|
||||
trace!(?name, "No entrypoints");
|
||||
} else {
|
||||
trace!(?name, "Writing entrypoints");
|
||||
|
||||
fs_err::create_dir_all(&layout.scheme.scripts)?;
|
||||
write_script_entrypoints(
|
||||
layout,
|
||||
relocatable,
|
||||
site_packages,
|
||||
&console_scripts,
|
||||
&mut record,
|
||||
false,
|
||||
)?;
|
||||
write_script_entrypoints(
|
||||
layout,
|
||||
relocatable,
|
||||
site_packages,
|
||||
&gui_scripts,
|
||||
&mut record,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
trace!(?name, "Installing data");
|
||||
install_data(
|
||||
layout,
|
||||
relocatable,
|
||||
site_packages,
|
||||
&data_dir,
|
||||
&name,
|
||||
&console_scripts,
|
||||
&gui_scripts,
|
||||
&mut record,
|
||||
)?;
|
||||
// 2.c If applicable, update scripts starting with #!python to point to the correct interpreter.
|
||||
// Script are unsupported through data
|
||||
// 2.e Remove empty distribution-1.0.data directory.
|
||||
fs::remove_dir_all(data_dir)?;
|
||||
} else {
|
||||
trace!(?name, "No data");
|
||||
}
|
||||
|
||||
if installer_metadata {
|
||||
trace!(?name, "Writing installer metadata");
|
||||
write_installer_metadata(
|
||||
site_packages,
|
||||
&dist_info_prefix,
|
||||
true,
|
||||
direct_url,
|
||||
cache_info,
|
||||
installer,
|
||||
&mut record,
|
||||
)?;
|
||||
}
|
||||
|
||||
trace!(?name, "Writing record");
|
||||
let mut record_writer = csv::WriterBuilder::new()
|
||||
.has_headers(false)
|
||||
.escape(b'"')
|
||||
.from_path(site_packages.join(format!("{dist_info_prefix}.dist-info/RECORD")))?;
|
||||
record.sort();
|
||||
for entry in record {
|
||||
record_writer.serialize(entry)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find the `dist-info` directory in an unzipped wheel.
|
||||
///
|
||||
/// See: <https://github.com/PyO3/python-pkginfo-rs>
|
||||
///
|
||||
/// See: <https://github.com/pypa/pip/blob/36823099a9cdd83261fdbc8c1d2a24fa2eea72ca/src/pip/_internal/utils/wheel.py#L38>
|
||||
fn find_dist_info(path: impl AsRef<Path>) -> Result<String, Error> {
|
||||
// Iterate over `path` to find the `.dist-info` directory. It should be at the top-level.
|
||||
let Some(dist_info) = fs::read_dir(path.as_ref())?.find_map(|entry| {
|
||||
let entry = entry.ok()?;
|
||||
let file_type = entry.file_type().ok()?;
|
||||
if file_type.is_dir() {
|
||||
let path = entry.path();
|
||||
if path.extension().is_some_and(|ext| ext == "dist-info") {
|
||||
Some(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) else {
|
||||
return Err(Error::InvalidWheel(
|
||||
"Missing .dist-info directory".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let Some(dist_info_prefix) = dist_info.file_stem() else {
|
||||
return Err(Error::InvalidWheel(
|
||||
"Missing .dist-info directory".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
Ok(dist_info_prefix.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// Read the `dist-info` metadata from a directory.
|
||||
fn dist_info_metadata(dist_info_prefix: &str, wheel: impl AsRef<Path>) -> Result<Vec<u8>, Error> {
|
||||
let metadata_file = wheel
|
||||
.as_ref()
|
||||
.join(format!("{dist_info_prefix}.dist-info/METADATA"));
|
||||
Ok(fs::read(metadata_file)?)
|
||||
}
|
||||
|
||||
/// Parses the `entry_points.txt` entry in the wheel for console scripts
|
||||
///
|
||||
/// Returns (`script_name`, module, function)
|
||||
///
|
||||
/// Extras are supposed to be ignored, which happens if you pass None for extras.
|
||||
fn parse_scripts(
|
||||
wheel: impl AsRef<Path>,
|
||||
dist_info_prefix: &str,
|
||||
extras: Option<&[String]>,
|
||||
python_minor: u8,
|
||||
) -> Result<(Vec<Script>, Vec<Script>), Error> {
|
||||
let entry_points_path = wheel
|
||||
.as_ref()
|
||||
.join(format!("{dist_info_prefix}.dist-info/entry_points.txt"));
|
||||
|
||||
// Read the entry points mapping. If the file doesn't exist, we just return an empty mapping.
|
||||
let Ok(ini) = fs::read_to_string(entry_points_path) else {
|
||||
return Ok((Vec::new(), Vec::new()));
|
||||
};
|
||||
|
||||
scripts_from_ini(extras, python_minor, ini)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use tracing::instrument;
|
|||
use uv_cache::Cache;
|
||||
use uv_configuration::RAYON_INITIALIZE;
|
||||
use uv_distribution_types::CachedDist;
|
||||
use uv_install_wheel::{linker::LinkMode, Layout};
|
||||
use uv_install_wheel::{Layout, LinkMode};
|
||||
use uv_python::PythonEnvironment;
|
||||
|
||||
pub struct Installer<'a> {
|
||||
|
|
@ -34,7 +34,7 @@ impl<'a> Installer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the [`LinkMode`][`uv_install_wheel::linker::LinkMode`] to use for this installer.
|
||||
/// Set the [`LinkMode`][`uv_install_wheel::LinkMode`] to use for this installer.
|
||||
#[must_use]
|
||||
pub fn with_link_mode(self, link_mode: LinkMode) -> Self {
|
||||
Self { link_mode, ..self }
|
||||
|
|
@ -158,9 +158,9 @@ fn install(
|
|||
) -> Result<Vec<CachedDist>> {
|
||||
// Initialize the threadpool with the user settings.
|
||||
LazyLock::force(&RAYON_INITIALIZE);
|
||||
let locks = uv_install_wheel::linker::Locks::default();
|
||||
let locks = uv_install_wheel::Locks::default();
|
||||
wheels.par_iter().try_for_each(|wheel| {
|
||||
uv_install_wheel::linker::install_wheel(
|
||||
uv_install_wheel::install_wheel(
|
||||
&layout,
|
||||
relocatable,
|
||||
wheel.path(),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use uv_configuration::{
|
|||
TrustedPublishing,
|
||||
};
|
||||
use uv_distribution_types::{Index, IndexUrl, PipExtraIndex, PipFindLinks, PipIndex};
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_pypi_types::{SchemaConflicts, SupportedEnvironments};
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
||||
use uv_resolver::{AnnotationStyle, ExcludeNewer, ForkStrategy, PrereleaseMode, ResolutionMode};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use uv_configuration::{
|
|||
use uv_distribution_types::{
|
||||
Index, IndexUrl, IndexUrlError, PipExtraIndex, PipFindLinks, PipIndex, StaticMetadata,
|
||||
};
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_macros::{CombineOptions, OptionsMetadata};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_pep508::Requirement;
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ use uv_distribution_filename::{
|
|||
};
|
||||
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, SourceDist};
|
||||
use uv_fs::{relative_to, Simplified};
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_python::{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use uv_distribution_types::{
|
|||
Origin, UnresolvedRequirementSpecification, Verbatim,
|
||||
};
|
||||
use uv_fs::Simplified;
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pypi_types::{Conflicts, Requirement, SupportedEnvironments};
|
||||
use uv_python::{
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ use uv_distribution_types::{
|
|||
UnresolvedRequirementSpecification,
|
||||
};
|
||||
use uv_fs::Simplified;
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_installer::{SatisfiesResult, SitePackages};
|
||||
use uv_pep508::PackageName;
|
||||
use uv_pypi_types::{Conflicts, Requirement};
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use uv_distribution_types::{
|
|||
DistributionMetadata, IndexLocations, InstalledMetadata, Name, Resolution,
|
||||
};
|
||||
use uv_fs::Simplified;
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_installer::{Plan, Planner, Preparer, SitePackages};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_platform_tags::Tags;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use uv_configuration::{KeyringProviderType, TargetTriple};
|
|||
use uv_dispatch::{BuildDispatch, SharedState};
|
||||
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, Origin, Resolution};
|
||||
use uv_fs::Simplified;
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_installer::SitePackages;
|
||||
use uv_pep508::PackageName;
|
||||
use uv_pypi_types::Conflicts;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use uv_configuration::{
|
|||
use uv_dispatch::{BuildDispatch, SharedState};
|
||||
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations};
|
||||
use uv_fs::Simplified;
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_pypi_types::Requirement;
|
||||
use uv_python::{
|
||||
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ use uv_configuration::{
|
|||
SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem,
|
||||
};
|
||||
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl};
|
||||
use uv_install_wheel::linker::LinkMode;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep508::{ExtraName, RequirementOrigin};
|
||||
use uv_pypi_types::{Requirement, SupportedEnvironments};
|
||||
|
|
|
|||
Loading…
Reference in New Issue