mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Write direct_url.json
in wheel installer (#337)
## Summary This PR just adds the logic in `install-wheel-rs` to write `direct_url.json`. We're not actually taking advantage of it yet (or wiring it through) in Puffin. Part of https://github.com/astral-sh/puffin/issues/332.
This commit is contained in:
parent
9b077f3d0f
commit
d9bcfafa16
13 changed files with 110 additions and 20 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1449,12 +1449,14 @@ dependencies = [
|
||||||
"reflink-copy",
|
"reflink-copy",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"url",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
|
@ -54,8 +54,8 @@ pub(crate) fn install_base_packages(
|
||||||
let install_location = InstallLocation::new(location.canonicalize()?, info.simple_version());
|
let install_location = InstallLocation::new(location.canonicalize()?, info.simple_version());
|
||||||
let install_location = install_location.acquire_lock()?;
|
let install_location = install_location.acquire_lock()?;
|
||||||
|
|
||||||
// TODO: Use the json api instead
|
// TODO(konstin): Use the json api instead
|
||||||
// TODO: Only check the json API so often (monthly? daily?)
|
// TODO(konstin): Only check the json API so often (monthly? daily?)
|
||||||
let packages = [
|
let packages = [
|
||||||
("pip-23.2.1-py3-none-any.whl", "https://files.pythonhosted.org/packages/50/c2/e06851e8cc28dcad7c155f4753da8833ac06a5c704c109313b8d5a62968a/pip-23.2.1-py3-none-any.whl"),
|
("pip-23.2.1-py3-none-any.whl", "https://files.pythonhosted.org/packages/50/c2/e06851e8cc28dcad7c155f4753da8833ac06a5c704c109313b8d5a62968a/pip-23.2.1-py3-none-any.whl"),
|
||||||
("setuptools-68.2.2-py3-none-any.whl", "https://files.pythonhosted.org/packages/bb/26/7945080113158354380a12ce26873dd6c1ebd88d47f5bc24e2c5bb38c16a/setuptools-68.2.2-py3-none-any.whl"),
|
("setuptools-68.2.2-py3-none-any.whl", "https://files.pythonhosted.org/packages/bb/26/7945080113158354380a12ce26873dd6c1ebd88d47f5bc24e2c5bb38c16a/setuptools-68.2.2-py3-none-any.whl"),
|
||||||
|
@ -73,6 +73,7 @@ pub(crate) fn install_base_packages(
|
||||||
&install_location,
|
&install_location,
|
||||||
File::open(wheel_file)?,
|
File::open(wheel_file)?,
|
||||||
&parsed_filename,
|
&parsed_filename,
|
||||||
|
None,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
&[],
|
&[],
|
||||||
|
|
|
@ -37,12 +37,14 @@ rayon = { version = "1.8.0", optional = true }
|
||||||
reflink-copy = { workspace = true }
|
reflink-copy = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
serde_json = { workspace = true }
|
||||||
sha2 = { workspace = true }
|
sha2 = { workspace = true }
|
||||||
target-lexicon = { workspace = true }
|
target-lexicon = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true, optional = true }
|
tracing-subscriber = { workspace = true, optional = true }
|
||||||
|
url = { workspace = true }
|
||||||
walkdir = { workspace = true }
|
walkdir = { workspace = true }
|
||||||
zip = { workspace = true }
|
zip = { workspace = true }
|
||||||
|
|
||||||
|
|
59
crates/install-wheel-rs/src/direct_url.rs
Normal file
59
crates/install-wheel-rs/src/direct_url.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Metadata for a distribution that was installed via a direct URL.
|
||||||
|
///
|
||||||
|
/// See: <https://packaging.python.org/en/latest/specifications/direct-url-data-structure/>
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum DirectUrl {
|
||||||
|
/// The direct URL is a path to an archive. For example:
|
||||||
|
/// ```json
|
||||||
|
/// {"archive_info": {"hash": "sha256=75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8", "hashes": {"sha256": "75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8"}}, "url": "https://files.pythonhosted.org/packages/b8/8b/31273bf66016be6ad22bb7345c37ff350276cfd46e389a0c2ac5da9d9073/wheel-0.41.2-py3-none-any.whl"}
|
||||||
|
/// ```
|
||||||
|
ArchiveUrl {
|
||||||
|
url: String,
|
||||||
|
archive_info: ArchiveInfo,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
subdirectory: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
/// The direct URL is path to a VCS repository. For example:
|
||||||
|
/// ```json
|
||||||
|
/// {"url": "https://github.com/pallets/flask.git", "vcs_info": {"commit_id": "8d9519df093864ff90ca446d4af2dc8facd3c542", "vcs": "git"}}
|
||||||
|
/// ```
|
||||||
|
VcsUrl {
|
||||||
|
url: String,
|
||||||
|
vcs_info: VcsInfo,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
subdirectory: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct ArchiveInfo {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub hash: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub hashes: Option<HashMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct VcsInfo {
|
||||||
|
pub vcs: VcsKind,
|
||||||
|
pub commit_id: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub requested_revision: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum VcsKind {
|
||||||
|
Git,
|
||||||
|
Hg,
|
||||||
|
Bzr,
|
||||||
|
Svn,
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ use platform_info::PlatformInfoError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use zip::result::ZipError;
|
use zip::result::ZipError;
|
||||||
|
|
||||||
|
pub use direct_url::DirectUrl;
|
||||||
pub use install_location::{normalize_name, InstallLocation, LockedDir};
|
pub use install_location::{normalize_name, InstallLocation, LockedDir};
|
||||||
use platform_host::{Arch, Os};
|
use platform_host::{Arch, Os};
|
||||||
pub use record::RecordEntry;
|
pub use record::RecordEntry;
|
||||||
|
@ -17,6 +18,7 @@ pub use wheel::{
|
||||||
relative_to, SHEBANG_PYTHON,
|
relative_to, SHEBANG_PYTHON,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod direct_url;
|
||||||
mod install_location;
|
mod install_location;
|
||||||
pub mod linker;
|
pub mod linker;
|
||||||
#[cfg(feature = "python_bindings")]
|
#[cfg(feature = "python_bindings")]
|
||||||
|
@ -60,6 +62,8 @@ pub enum Error {
|
||||||
PlatformInfo(#[source] PlatformInfoError),
|
PlatformInfo(#[source] PlatformInfoError),
|
||||||
#[error("Invalid version specification, only none or == is supported")]
|
#[error("Invalid version specification, only none or == is supported")]
|
||||||
Pep440,
|
Pep440,
|
||||||
|
#[error("Invalid direct_url.json")]
|
||||||
|
DirectUrlJson(#[from] serde_json::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::wheel::{
|
||||||
extra_dist_info, install_data, parse_wheel_version, read_scripts_from_section,
|
extra_dist_info, install_data, parse_wheel_version, read_scripts_from_section,
|
||||||
write_script_entrypoints,
|
write_script_entrypoints,
|
||||||
};
|
};
|
||||||
use crate::{read_record_file, Error, Script};
|
use crate::{read_record_file, DirectUrl, Error, Script};
|
||||||
|
|
||||||
/// Install the given wheel to the given venv
|
/// Install the given wheel to the given venv
|
||||||
///
|
///
|
||||||
|
@ -27,6 +27,7 @@ use crate::{read_record_file, Error, Script};
|
||||||
pub fn install_wheel(
|
pub fn install_wheel(
|
||||||
location: &InstallLocation<impl AsRef<Path>>,
|
location: &InstallLocation<impl AsRef<Path>>,
|
||||||
wheel: impl AsRef<Path>,
|
wheel: impl AsRef<Path>,
|
||||||
|
direct_url: Option<&DirectUrl>,
|
||||||
link_mode: LinkMode,
|
link_mode: LinkMode,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let root = location.venv_root();
|
let root = location.venv_root();
|
||||||
|
@ -105,8 +106,13 @@ pub fn install_wheel(
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(name, "Writing extra metadata");
|
debug!(name, "Writing extra metadata");
|
||||||
|
extra_dist_info(
|
||||||
extra_dist_info(&site_packages, &dist_info_prefix, true, &mut record)?;
|
&site_packages,
|
||||||
|
&dist_info_prefix,
|
||||||
|
true,
|
||||||
|
direct_url,
|
||||||
|
&mut record,
|
||||||
|
)?;
|
||||||
|
|
||||||
debug!(name, "Writing record");
|
debug!(name, "Writing record");
|
||||||
let mut record_writer = csv::WriterBuilder::new()
|
let mut record_writer = csv::WriterBuilder::new()
|
||||||
|
|
|
@ -65,6 +65,7 @@ fn main() -> Result<(), Error> {
|
||||||
&locked_dir,
|
&locked_dir,
|
||||||
File::open(wheel)?,
|
File::open(wheel)?,
|
||||||
&filename,
|
&filename,
|
||||||
|
None,
|
||||||
args.compile,
|
args.compile,
|
||||||
!args.skip_hashes,
|
!args.skip_hashes,
|
||||||
&[],
|
&[],
|
||||||
|
|
|
@ -62,6 +62,7 @@ impl LockedVenv {
|
||||||
&self.location,
|
&self.location,
|
||||||
File::open(wheel)?,
|
File::open(wheel)?,
|
||||||
&filename,
|
&filename,
|
||||||
|
None,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
&[],
|
&[],
|
||||||
|
|
|
@ -14,6 +14,7 @@ use mailparse::MailHeaderMap;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use tracing::{debug, error, span, warn, Level};
|
use tracing::{debug, error, span, warn, Level};
|
||||||
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use zip::result::ZipError;
|
use zip::result::ZipError;
|
||||||
use zip::write::FileOptions;
|
use zip::write::FileOptions;
|
||||||
|
@ -24,7 +25,7 @@ use distribution_filename::WheelFilename;
|
||||||
use crate::install_location::{InstallLocation, LockedDir};
|
use crate::install_location::{InstallLocation, LockedDir};
|
||||||
use crate::record::RecordEntry;
|
use crate::record::RecordEntry;
|
||||||
use crate::script::Script;
|
use crate::script::Script;
|
||||||
use crate::Error;
|
use crate::{DirectUrl, Error};
|
||||||
|
|
||||||
/// `#!/usr/bin/env python`
|
/// `#!/usr/bin/env python`
|
||||||
pub const SHEBANG_PYTHON: &str = "#!/usr/bin/env python";
|
pub const SHEBANG_PYTHON: &str = "#!/usr/bin/env python";
|
||||||
|
@ -810,28 +811,32 @@ pub(crate) fn write_file_recorded(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds INSTALLER, REQUESTED and `direct_url.json` to the .dist-info dir
|
/// Adds `INSTALLER`, `REQUESTED` and `direct_url.json` to the .dist-info dir
|
||||||
pub(crate) fn extra_dist_info(
|
pub(crate) fn extra_dist_info(
|
||||||
site_packages: &Path,
|
site_packages: &Path,
|
||||||
dist_info_prefix: &str,
|
dist_info_prefix: &str,
|
||||||
requested: bool,
|
requested: bool,
|
||||||
|
direct_url: Option<&DirectUrl>,
|
||||||
record: &mut Vec<RecordEntry>,
|
record: &mut Vec<RecordEntry>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let dist_info_dir = PathBuf::from(format!("{dist_info_prefix}.dist-info"));
|
||||||
write_file_recorded(
|
write_file_recorded(
|
||||||
site_packages,
|
site_packages,
|
||||||
&PathBuf::from(format!("{dist_info_prefix}.dist-info")).join("INSTALLER"),
|
&dist_info_dir.join("INSTALLER"),
|
||||||
env!("CARGO_PKG_NAME"),
|
env!("CARGO_PKG_NAME"),
|
||||||
record,
|
record,
|
||||||
)?;
|
)?;
|
||||||
if requested {
|
if requested {
|
||||||
|
write_file_recorded(site_packages, &dist_info_dir.join("REQUESTED"), "", record)?;
|
||||||
|
}
|
||||||
|
if let Some(direct_url) = direct_url {
|
||||||
write_file_recorded(
|
write_file_recorded(
|
||||||
site_packages,
|
site_packages,
|
||||||
&PathBuf::from(format!("{dist_info_prefix}.dist-info")).join("REQUESTED"),
|
&dist_info_dir.join("direct_url.json"),
|
||||||
"",
|
serde_json::to_string(direct_url)?.as_bytes(),
|
||||||
record,
|
record,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -891,6 +896,7 @@ pub fn install_wheel(
|
||||||
location: &InstallLocation<LockedDir>,
|
location: &InstallLocation<LockedDir>,
|
||||||
reader: impl Read + Seek,
|
reader: impl Read + Seek,
|
||||||
filename: &WheelFilename,
|
filename: &WheelFilename,
|
||||||
|
direct_url: Option<&DirectUrl>,
|
||||||
compile: bool,
|
compile: bool,
|
||||||
check_hashes: bool,
|
check_hashes: bool,
|
||||||
// initially used to the console scripts, currently unused. Keeping it because we likely need
|
// initially used to the console scripts, currently unused. Keeping it because we likely need
|
||||||
|
@ -1006,7 +1012,13 @@ pub fn install_wheel(
|
||||||
|
|
||||||
debug!(name = name.as_str(), "Writing extra metadata");
|
debug!(name = name.as_str(), "Writing extra metadata");
|
||||||
|
|
||||||
extra_dist_info(&site_packages, &dist_info_prefix, true, &mut record)?;
|
extra_dist_info(
|
||||||
|
&site_packages,
|
||||||
|
&dist_info_prefix,
|
||||||
|
true,
|
||||||
|
direct_url,
|
||||||
|
&mut record,
|
||||||
|
)?;
|
||||||
|
|
||||||
debug!(name = name.as_str(), "Writing record");
|
debug!(name = name.as_str(), "Writing record");
|
||||||
let mut record_writer = csv::WriterBuilder::new()
|
let mut record_writer = csv::WriterBuilder::new()
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl<'a, T: BuildContext + Send + Sync> Builder<'a, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a set of source distributions.
|
/// Build a set of source distributions.
|
||||||
pub async fn build(&'a self, distributions: Vec<SourceDistribution>) -> Result<Vec<Wheel>> {
|
pub async fn build(&self, distributions: Vec<SourceDistribution>) -> Result<Vec<Wheel>> {
|
||||||
// Sort the distributions by size.
|
// Sort the distributions by size.
|
||||||
let mut distributions = distributions;
|
let mut distributions = distributions;
|
||||||
distributions.sort_unstable_by_key(|distribution| match &distribution.remote {
|
distributions.sort_unstable_by_key(|distribution| match &distribution.remote {
|
||||||
|
|
|
@ -46,10 +46,7 @@ impl<'a> Downloader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download a set of distributions.
|
/// Download a set of distributions.
|
||||||
pub async fn download(
|
pub async fn download(&self, distributions: Vec<RemoteDistribution>) -> Result<Vec<Download>> {
|
||||||
&'a self,
|
|
||||||
distributions: Vec<RemoteDistribution>,
|
|
||||||
) -> Result<Vec<Download>> {
|
|
||||||
// Sort the distributions by size.
|
// Sort the distributions by size.
|
||||||
let mut distributions = distributions;
|
let mut distributions = distributions;
|
||||||
distributions.sort_unstable_by_key(|wheel| match wheel {
|
distributions.sort_unstable_by_key(|wheel| match wheel {
|
||||||
|
|
|
@ -44,8 +44,13 @@ impl<'a> Installer<'a> {
|
||||||
self.venv.interpreter_info().simple_version(),
|
self.venv.interpreter_info().simple_version(),
|
||||||
);
|
);
|
||||||
|
|
||||||
install_wheel_rs::linker::install_wheel(&location, wheel.path(), self.link_mode)
|
install_wheel_rs::linker::install_wheel(
|
||||||
.with_context(|| format!("Failed to install: {wheel}"))?;
|
&location,
|
||||||
|
wheel.path(),
|
||||||
|
None,
|
||||||
|
self.link_mode,
|
||||||
|
)
|
||||||
|
.with_context(|| format!("Failed to install: {wheel}"))?;
|
||||||
|
|
||||||
if let Some(reporter) = self.reporter.as_ref() {
|
if let Some(reporter) = self.reporter.as_ref() {
|
||||||
reporter.on_install_progress(wheel);
|
reporter.on_install_progress(wheel);
|
||||||
|
|
|
@ -615,7 +615,7 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
|
||||||
Ok::<(), ResolveError>(())
|
Ok::<(), ResolveError>(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_request(&'a self, request: Request) -> Result<Response, ResolveError> {
|
async fn process_request(&self, request: Request) -> Result<Response, ResolveError> {
|
||||||
match request {
|
match request {
|
||||||
// Fetch package metadata from the registry.
|
// Fetch package metadata from the registry.
|
||||||
Request::Package(package_name) => {
|
Request::Package(package_name) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue