Support relative path wheels (#5969)

Surprisingly, this is a lockfile schema change: We can't store relative
paths in urls, so we have to store a `filename` entry instead of the
whole url.

Fixes #4355
This commit is contained in:
konsti 2024-08-09 23:57:16 +02:00 committed by GitHub
parent dd32087842
commit fcbee9ce25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 221 additions and 50 deletions

View file

@ -820,7 +820,7 @@ impl Package {
.wheels
.iter()
.map(|wheel| wheel.to_registry_dist(url.to_url()))
.collect();
.collect::<Result<_, LockError>>()?;
let reg_built_dist = RegistryBuiltDist {
wheels,
best_wheel_index,
@ -833,7 +833,8 @@ impl Package {
let path_dist = PathBuiltDist {
filename,
url: verbatim_url(workspace_root.join(path), &self.id)?,
path: path.clone(),
install_path: workspace_root.join(path),
lock_path: path.clone(),
};
let built_dist = BuiltDist::Path(path_dist);
Ok(Dist::Built(built_dist))
@ -971,7 +972,8 @@ impl Package {
};
let file_url = sdist.url().ok_or_else(|| LockErrorKind::MissingUrl {
id: self.id.clone(),
name: self.id.name.clone(),
version: self.id.version.clone(),
})?;
let filename = sdist
.filename()
@ -1035,7 +1037,7 @@ impl Package {
// Add any wheels.
for wheel in &self.wheels {
let hash = wheel.hash.as_ref().map(|h| h.0.clone());
let wheel = wheel.to_registry_dist(url.to_url());
let wheel = wheel.to_registry_dist(url.to_url())?;
let compat =
WheelCompatibility::Compatible(HashComparison::Matched, None, None);
prioritized_dist.insert_built(wheel, hash, compat);
@ -1531,7 +1533,7 @@ impl Source {
}
fn from_path_built_dist(path_dist: &PathBuiltDist) -> Source {
Source::Path(path_dist.path.clone())
Source::Path(path_dist.lock_path.clone())
}
fn from_path_source_dist(path_dist: &PathSourceDist) -> Source {
@ -2059,7 +2061,7 @@ struct Wheel {
/// against was found. The location does not need to exist in the future,
/// so this should be treated as only a hint to where to look and/or
/// recording where the wheel file originally came from.
url: UrlString,
url: WheelWireSource,
/// A hash of the built distribution.
///
/// This is only present for wheels that come from registries and direct
@ -2132,7 +2134,7 @@ impl Wheel {
let hash = wheel.file.hashes.iter().max().cloned().map(Hash::from);
let size = wheel.file.size;
Ok(Wheel {
url,
url: WheelWireSource::Url { url },
hash,
size,
filename,
@ -2141,7 +2143,9 @@ impl Wheel {
fn from_direct_dist(direct_dist: &DirectUrlBuiltDist, hashes: &[HashDigest]) -> Wheel {
Wheel {
url: direct_dist.url.to_url().into(),
url: WheelWireSource::Url {
url: direct_dist.url.to_url().into(),
},
hash: hashes.iter().max().cloned().map(Hash::from),
size: None,
filename: direct_dist.filename.clone(),
@ -2150,15 +2154,27 @@ impl Wheel {
fn from_path_dist(path_dist: &PathBuiltDist, hashes: &[HashDigest]) -> Wheel {
Wheel {
url: path_dist.url.to_url().into(),
url: WheelWireSource::Filename {
filename: path_dist.filename.clone(),
},
hash: hashes.iter().max().cloned().map(Hash::from),
size: None,
filename: path_dist.filename.clone(),
}
}
fn to_registry_dist(&self, url: Url) -> RegistryBuiltWheel {
fn to_registry_dist(&self, url: Url) -> Result<RegistryBuiltWheel, LockError> {
let filename: WheelFilename = self.filename.clone();
let url_string = match &self.url {
WheelWireSource::Url { url } => url.clone(),
WheelWireSource::Filename { .. } => {
return Err(LockErrorKind::MissingUrl {
name: self.filename.name.clone(),
version: self.filename.version.clone(),
}
.into())
}
};
let file = Box::new(distribution_types::File {
dist_info_metadata: false,
filename: filename.to_string(),
@ -2166,25 +2182,22 @@ impl Wheel {
requires_python: None,
size: self.size,
upload_time_utc_ms: None,
url: FileLocation::AbsoluteUrl(self.url.clone()),
url: FileLocation::AbsoluteUrl(url_string),
yanked: None,
});
let index = IndexUrl::Url(VerbatimUrl::from_url(url));
RegistryBuiltWheel {
Ok(RegistryBuiltWheel {
filename,
file,
index,
}
})
}
}
#[derive(Clone, Debug, serde::Deserialize)]
struct WheelWire {
/// A URL or file path (via `file://`) where the wheel that was locked
/// against was found. The location does not need to exist in the future,
/// so this should be treated as only a hint to where to look and/or
/// recording where the wheel file originally came from.
url: UrlString,
#[serde(flatten)]
url: WheelWireSource,
/// A hash of the built distribution.
///
/// This is only present for wheels that come from registries and direct
@ -2197,11 +2210,39 @@ struct WheelWire {
size: Option<u64>,
}
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
#[serde(untagged)]
enum WheelWireSource {
/// Used for all wheels except path wheels.
Url {
/// A URL or file path (via `file://`) where the wheel that was locked
/// against was found. The location does not need to exist in the future,
/// so this should be treated as only a hint to where to look and/or
/// recording where the wheel file originally came from.
url: UrlString,
},
/// Used for path wheels.
///
/// We only store the filename for path wheel, since we can't store a relative path in the url
Filename {
/// We duplicate the filename since a lot of code relies on having the filename on the
/// wheel entry.
filename: WheelFilename,
},
}
impl Wheel {
/// Returns the TOML representation of this wheel.
fn to_toml(&self) -> anyhow::Result<InlineTable> {
let mut table = InlineTable::new();
table.insert("url", Value::from(self.url.base()));
match &self.url {
WheelWireSource::Url { url } => {
table.insert("url", Value::from(url.base()));
}
WheelWireSource::Filename { filename } => {
table.insert("filename", Value::from(filename.to_string()));
}
}
if let Some(ref hash) = self.hash {
table.insert("hash", Value::from(hash.to_string()));
}
@ -2216,13 +2257,16 @@ impl TryFrom<WheelWire> for Wheel {
type Error = String;
fn try_from(wire: WheelWire) -> Result<Wheel, String> {
// Extract the filename segment from the URL.
let filename = wire.url.filename().map_err(|err| err.to_string())?;
// Parse the filename as a wheel filename.
let filename = filename
.parse::<WheelFilename>()
.map_err(|err| format!("failed to parse `{filename}` as wheel filename: {err}"))?;
// If necessary, extract the filename from the URL.
let filename = match &wire.url {
WheelWireSource::Url { url } => {
let filename = url.filename().map_err(|err| err.to_string())?;
filename.parse::<WheelFilename>().map_err(|err| {
format!("failed to parse `{filename}` as wheel filename: {err}")
})?
}
WheelWireSource::Filename { filename } => filename.clone(),
};
Ok(Wheel {
url: wire.url,
@ -2593,10 +2637,12 @@ enum LockErrorKind {
},
/// An error that occurs when a distribution indicates that it is sourced from a registry, but
/// is missing a URL.
#[error("found registry distribution {id} without a valid URL")]
#[error("found registry distribution {name}=={version} without a valid URL")]
MissingUrl {
/// The ID of the distribution that is missing a URL.
id: PackageId,
/// The name of the distribution that is missing a URL.
name: PackageName,
/// The version of the distribution that is missing a URL.
version: Version,
},
/// An error that occurs when a distribution indicates that it is sourced from a registry, but
/// is missing a filename.

View file

@ -28,9 +28,11 @@ Ok(
sdist: None,
wheels: [
Wheel {
url: UrlString(
"https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl",
),
url: Url {
url: UrlString(
"https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl",
),
},
hash: None,
size: None,
filename: WheelFilename {

View file

@ -28,9 +28,11 @@ Ok(
sdist: None,
wheels: [
Wheel {
url: UrlString(
"https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl",
),
url: Url {
url: UrlString(
"https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl",
),
},
hash: Some(
Hash(
HashDigest {

View file

@ -26,9 +26,11 @@ Ok(
sdist: None,
wheels: [
Wheel {
url: UrlString(
"file:///foo/bar/anyio-4.3.0-py3-none-any.whl",
),
url: Url {
url: UrlString(
"file:///foo/bar/anyio-4.3.0-py3-none-any.whl",
),
},
hash: Some(
Hash(
HashDigest {