mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-01 20:31:12 +00:00
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:
parent
dd32087842
commit
fcbee9ce25
10 changed files with 221 additions and 50 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue