mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-02 12:59:45 +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
|
|
@ -216,8 +216,12 @@ pub struct DirectUrlBuiltDist {
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct PathBuiltDist {
|
pub struct PathBuiltDist {
|
||||||
pub filename: WheelFilename,
|
pub filename: WheelFilename,
|
||||||
/// The path to the wheel.
|
/// The resolved, absolute path to the wheel which we use for installing.
|
||||||
pub path: PathBuf,
|
pub install_path: PathBuf,
|
||||||
|
/// The absolute path or path relative to the workspace root pointing to the wheel
|
||||||
|
/// which we use for locking. Unlike `given` on the verbatim URL all environment variables
|
||||||
|
/// are resolved, and unlike the install path, we did not yet join it on the base directory.
|
||||||
|
pub lock_path: PathBuf,
|
||||||
/// The URL as it was provided by the user.
|
/// The URL as it was provided by the user.
|
||||||
pub url: VerbatimUrl,
|
pub url: VerbatimUrl,
|
||||||
}
|
}
|
||||||
|
|
@ -372,7 +376,8 @@ impl Dist {
|
||||||
}
|
}
|
||||||
Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
|
Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
|
||||||
filename,
|
filename,
|
||||||
path: canonicalized_path,
|
install_path: canonicalized_path.clone(),
|
||||||
|
lock_path: lock_path.to_path_buf(),
|
||||||
url,
|
url,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,8 +160,8 @@ impl From<&ResolvedDist> for Requirement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Dist::Built(BuiltDist::Path(wheel)) => RequirementSource::Path {
|
Dist::Built(BuiltDist::Path(wheel)) => RequirementSource::Path {
|
||||||
install_path: wheel.path.clone(),
|
install_path: wheel.install_path.clone(),
|
||||||
lock_path: wheel.path.clone(),
|
lock_path: wheel.lock_path.clone(),
|
||||||
url: wheel.url.clone(),
|
url: wheel.url.clone(),
|
||||||
ext: DistExtension::Wheel,
|
ext: DistExtension::Wheel,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -453,7 +453,7 @@ impl RegistryClient {
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
BuiltDist::Path(wheel) => {
|
BuiltDist::Path(wheel) => {
|
||||||
let file = fs_err::tokio::File::open(&wheel.path)
|
let file = fs_err::tokio::File::open(&wheel.install_path)
|
||||||
.await
|
.await
|
||||||
.map_err(ErrorKind::Io)?;
|
.map_err(ErrorKind::Io)?;
|
||||||
let reader = tokio::io::BufReader::new(file);
|
let reader = tokio::io::BufReader::new(file);
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
io::ErrorKind::TimedOut,
|
io::ErrorKind::TimedOut,
|
||||||
format!(
|
format!(
|
||||||
"Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: {}s).", self.client.unmanaged.timeout()
|
"Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: {}s).", self.client.unmanaged.timeout()
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -351,8 +351,14 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
||||||
wheel.filename.stem(),
|
wheel.filename.stem(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.load_wheel(&wheel.path, &wheel.filename, cache_entry, dist, hashes)
|
self.load_wheel(
|
||||||
.await
|
&wheel.install_path,
|
||||||
|
&wheel.filename,
|
||||||
|
cache_entry,
|
||||||
|
dist,
|
||||||
|
hashes,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -330,13 +330,14 @@ impl<'a> Planner<'a> {
|
||||||
let wheel = PathBuiltDist {
|
let wheel = PathBuiltDist {
|
||||||
filename,
|
filename,
|
||||||
url: url.clone(),
|
url: url.clone(),
|
||||||
path,
|
install_path: install_path.clone(),
|
||||||
|
lock_path: lock_path.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !wheel.filename.is_compatible(tags) {
|
if !wheel.filename.is_compatible(tags) {
|
||||||
bail!(
|
bail!(
|
||||||
"A path dependency is incompatible with the current platform: {}",
|
"A path dependency is incompatible with the current platform: {}",
|
||||||
wheel.path.user_display()
|
wheel.lock_path.user_display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -357,7 +358,7 @@ impl<'a> Planner<'a> {
|
||||||
.entry(format!("{}.rev", wheel.filename.stem()));
|
.entry(format!("{}.rev", wheel.filename.stem()));
|
||||||
|
|
||||||
if let Some(pointer) = LocalArchivePointer::read_from(&cache_entry)? {
|
if let Some(pointer) = LocalArchivePointer::read_from(&cache_entry)? {
|
||||||
let timestamp = ArchiveTimestamp::from_file(&wheel.path)?;
|
let timestamp = ArchiveTimestamp::from_file(&wheel.install_path)?;
|
||||||
if pointer.is_up_to_date(timestamp) {
|
if pointer.is_up_to_date(timestamp) {
|
||||||
let archive = pointer.into_archive();
|
let archive = pointer.into_archive();
|
||||||
if archive.satisfies(hasher.get(&wheel)) {
|
if archive.satisfies(hasher.get(&wheel)) {
|
||||||
|
|
|
||||||
|
|
@ -820,7 +820,7 @@ impl Package {
|
||||||
.wheels
|
.wheels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|wheel| wheel.to_registry_dist(url.to_url()))
|
.map(|wheel| wheel.to_registry_dist(url.to_url()))
|
||||||
.collect();
|
.collect::<Result<_, LockError>>()?;
|
||||||
let reg_built_dist = RegistryBuiltDist {
|
let reg_built_dist = RegistryBuiltDist {
|
||||||
wheels,
|
wheels,
|
||||||
best_wheel_index,
|
best_wheel_index,
|
||||||
|
|
@ -833,7 +833,8 @@ impl Package {
|
||||||
let path_dist = PathBuiltDist {
|
let path_dist = PathBuiltDist {
|
||||||
filename,
|
filename,
|
||||||
url: verbatim_url(workspace_root.join(path), &self.id)?,
|
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);
|
let built_dist = BuiltDist::Path(path_dist);
|
||||||
Ok(Dist::Built(built_dist))
|
Ok(Dist::Built(built_dist))
|
||||||
|
|
@ -971,7 +972,8 @@ impl Package {
|
||||||
};
|
};
|
||||||
|
|
||||||
let file_url = sdist.url().ok_or_else(|| LockErrorKind::MissingUrl {
|
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
|
let filename = sdist
|
||||||
.filename()
|
.filename()
|
||||||
|
|
@ -1035,7 +1037,7 @@ impl Package {
|
||||||
// Add any wheels.
|
// Add any wheels.
|
||||||
for wheel in &self.wheels {
|
for wheel in &self.wheels {
|
||||||
let hash = wheel.hash.as_ref().map(|h| h.0.clone());
|
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 =
|
let compat =
|
||||||
WheelCompatibility::Compatible(HashComparison::Matched, None, None);
|
WheelCompatibility::Compatible(HashComparison::Matched, None, None);
|
||||||
prioritized_dist.insert_built(wheel, hash, compat);
|
prioritized_dist.insert_built(wheel, hash, compat);
|
||||||
|
|
@ -1531,7 +1533,7 @@ impl Source {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_path_built_dist(path_dist: &PathBuiltDist) -> 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 {
|
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,
|
/// 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
|
/// so this should be treated as only a hint to where to look and/or
|
||||||
/// recording where the wheel file originally came from.
|
/// recording where the wheel file originally came from.
|
||||||
url: UrlString,
|
url: WheelWireSource,
|
||||||
/// A hash of the built distribution.
|
/// A hash of the built distribution.
|
||||||
///
|
///
|
||||||
/// This is only present for wheels that come from registries and direct
|
/// 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 hash = wheel.file.hashes.iter().max().cloned().map(Hash::from);
|
||||||
let size = wheel.file.size;
|
let size = wheel.file.size;
|
||||||
Ok(Wheel {
|
Ok(Wheel {
|
||||||
url,
|
url: WheelWireSource::Url { url },
|
||||||
hash,
|
hash,
|
||||||
size,
|
size,
|
||||||
filename,
|
filename,
|
||||||
|
|
@ -2141,7 +2143,9 @@ impl Wheel {
|
||||||
|
|
||||||
fn from_direct_dist(direct_dist: &DirectUrlBuiltDist, hashes: &[HashDigest]) -> Wheel {
|
fn from_direct_dist(direct_dist: &DirectUrlBuiltDist, hashes: &[HashDigest]) -> Wheel {
|
||||||
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),
|
hash: hashes.iter().max().cloned().map(Hash::from),
|
||||||
size: None,
|
size: None,
|
||||||
filename: direct_dist.filename.clone(),
|
filename: direct_dist.filename.clone(),
|
||||||
|
|
@ -2150,15 +2154,27 @@ impl Wheel {
|
||||||
|
|
||||||
fn from_path_dist(path_dist: &PathBuiltDist, hashes: &[HashDigest]) -> Wheel {
|
fn from_path_dist(path_dist: &PathBuiltDist, hashes: &[HashDigest]) -> Wheel {
|
||||||
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),
|
hash: hashes.iter().max().cloned().map(Hash::from),
|
||||||
size: None,
|
size: None,
|
||||||
filename: path_dist.filename.clone(),
|
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 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 {
|
let file = Box::new(distribution_types::File {
|
||||||
dist_info_metadata: false,
|
dist_info_metadata: false,
|
||||||
filename: filename.to_string(),
|
filename: filename.to_string(),
|
||||||
|
|
@ -2166,25 +2182,22 @@ impl Wheel {
|
||||||
requires_python: None,
|
requires_python: None,
|
||||||
size: self.size,
|
size: self.size,
|
||||||
upload_time_utc_ms: None,
|
upload_time_utc_ms: None,
|
||||||
url: FileLocation::AbsoluteUrl(self.url.clone()),
|
url: FileLocation::AbsoluteUrl(url_string),
|
||||||
yanked: None,
|
yanked: None,
|
||||||
});
|
});
|
||||||
let index = IndexUrl::Url(VerbatimUrl::from_url(url));
|
let index = IndexUrl::Url(VerbatimUrl::from_url(url));
|
||||||
RegistryBuiltWheel {
|
Ok(RegistryBuiltWheel {
|
||||||
filename,
|
filename,
|
||||||
file,
|
file,
|
||||||
index,
|
index,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Deserialize)]
|
||||||
struct WheelWire {
|
struct WheelWire {
|
||||||
/// A URL or file path (via `file://`) where the wheel that was locked
|
#[serde(flatten)]
|
||||||
/// against was found. The location does not need to exist in the future,
|
url: WheelWireSource,
|
||||||
/// 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,
|
|
||||||
/// A hash of the built distribution.
|
/// A hash of the built distribution.
|
||||||
///
|
///
|
||||||
/// This is only present for wheels that come from registries and direct
|
/// This is only present for wheels that come from registries and direct
|
||||||
|
|
@ -2197,11 +2210,39 @@ struct WheelWire {
|
||||||
size: Option<u64>,
|
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 {
|
impl Wheel {
|
||||||
/// Returns the TOML representation of this wheel.
|
/// Returns the TOML representation of this wheel.
|
||||||
fn to_toml(&self) -> anyhow::Result<InlineTable> {
|
fn to_toml(&self) -> anyhow::Result<InlineTable> {
|
||||||
let mut table = InlineTable::new();
|
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 {
|
if let Some(ref hash) = self.hash {
|
||||||
table.insert("hash", Value::from(hash.to_string()));
|
table.insert("hash", Value::from(hash.to_string()));
|
||||||
}
|
}
|
||||||
|
|
@ -2216,13 +2257,16 @@ impl TryFrom<WheelWire> for Wheel {
|
||||||
type Error = String;
|
type Error = String;
|
||||||
|
|
||||||
fn try_from(wire: WheelWire) -> Result<Wheel, String> {
|
fn try_from(wire: WheelWire) -> Result<Wheel, String> {
|
||||||
// Extract the filename segment from the URL.
|
// If necessary, extract the filename from the URL.
|
||||||
let filename = wire.url.filename().map_err(|err| err.to_string())?;
|
let filename = match &wire.url {
|
||||||
|
WheelWireSource::Url { url } => {
|
||||||
// Parse the filename as a wheel filename.
|
let filename = url.filename().map_err(|err| err.to_string())?;
|
||||||
let filename = filename
|
filename.parse::<WheelFilename>().map_err(|err| {
|
||||||
.parse::<WheelFilename>()
|
format!("failed to parse `{filename}` as wheel filename: {err}")
|
||||||
.map_err(|err| format!("failed to parse `{filename}` as wheel filename: {err}"))?;
|
})?
|
||||||
|
}
|
||||||
|
WheelWireSource::Filename { filename } => filename.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Wheel {
|
Ok(Wheel {
|
||||||
url: wire.url,
|
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
|
/// An error that occurs when a distribution indicates that it is sourced from a registry, but
|
||||||
/// is missing a URL.
|
/// is missing a URL.
|
||||||
#[error("found registry distribution {id} without a valid URL")]
|
#[error("found registry distribution {name}=={version} without a valid URL")]
|
||||||
MissingUrl {
|
MissingUrl {
|
||||||
/// The ID of the distribution that is missing a URL.
|
/// The name of the distribution that is missing a URL.
|
||||||
id: PackageId,
|
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
|
/// An error that occurs when a distribution indicates that it is sourced from a registry, but
|
||||||
/// is missing a filename.
|
/// is missing a filename.
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,11 @@ Ok(
|
||||||
sdist: None,
|
sdist: None,
|
||||||
wheels: [
|
wheels: [
|
||||||
Wheel {
|
Wheel {
|
||||||
url: UrlString(
|
url: Url {
|
||||||
"https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl",
|
url: UrlString(
|
||||||
),
|
"https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl",
|
||||||
|
),
|
||||||
|
},
|
||||||
hash: None,
|
hash: None,
|
||||||
size: None,
|
size: None,
|
||||||
filename: WheelFilename {
|
filename: WheelFilename {
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,11 @@ Ok(
|
||||||
sdist: None,
|
sdist: None,
|
||||||
wheels: [
|
wheels: [
|
||||||
Wheel {
|
Wheel {
|
||||||
url: UrlString(
|
url: Url {
|
||||||
"https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl",
|
url: UrlString(
|
||||||
),
|
"https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl",
|
||||||
|
),
|
||||||
|
},
|
||||||
hash: Some(
|
hash: Some(
|
||||||
Hash(
|
Hash(
|
||||||
HashDigest {
|
HashDigest {
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,11 @@ Ok(
|
||||||
sdist: None,
|
sdist: None,
|
||||||
wheels: [
|
wheels: [
|
||||||
Wheel {
|
Wheel {
|
||||||
url: UrlString(
|
url: Url {
|
||||||
"file:///foo/bar/anyio-4.3.0-py3-none-any.whl",
|
url: UrlString(
|
||||||
),
|
"file:///foo/bar/anyio-4.3.0-py3-none-any.whl",
|
||||||
|
),
|
||||||
|
},
|
||||||
hash: Some(
|
hash: Some(
|
||||||
Hash(
|
Hash(
|
||||||
HashDigest {
|
HashDigest {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assert_cmd::prelude::*;
|
use assert_cmd::prelude::*;
|
||||||
use assert_fs::prelude::*;
|
use assert_fs::prelude::*;
|
||||||
|
use insta::assert_snapshot;
|
||||||
|
|
||||||
use common::{uv_snapshot, TestContext};
|
use common::{uv_snapshot, TestContext};
|
||||||
|
|
||||||
|
|
@ -677,3 +678,109 @@ fn sync_build_isolation_package() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test that relative wheel paths are correctly preserved.
|
||||||
|
#[test]
|
||||||
|
fn sync_relative_wheel() -> Result<()> {
|
||||||
|
// TODO(charlie): On Windows, this test currently prepares two packages (vs. one of Unix). This
|
||||||
|
// is due to an error in path canonicalization, and GitHub Actions on Windows have a symlink
|
||||||
|
// in the path.
|
||||||
|
//
|
||||||
|
// See: https://github.com/astral-sh/uv/issues/5979
|
||||||
|
let context = TestContext::new("3.12").with_filtered_counts();
|
||||||
|
|
||||||
|
let requirements = r#"[project]
|
||||||
|
name = "relative_wheel"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["ok"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
ok = { path = "wheels/ok-1.0.0-py3-none-any.whl" }
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
context
|
||||||
|
.temp_dir
|
||||||
|
.child("src/relative_wheel/__init__.py")
|
||||||
|
.touch()?;
|
||||||
|
|
||||||
|
context
|
||||||
|
.temp_dir
|
||||||
|
.child("pyproject.toml")
|
||||||
|
.write_str(requirements)?;
|
||||||
|
|
||||||
|
context.temp_dir.child("wheels").create_dir_all()?;
|
||||||
|
fs_err::copy(
|
||||||
|
"../../scripts/links/ok-1.0.0-py3-none-any.whl",
|
||||||
|
context.temp_dir.join("wheels/ok-1.0.0-py3-none-any.whl"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv sync` is experimental and may change without warning
|
||||||
|
warning: `uv.sources` is experimental and may change without warning
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Prepared [N] packages in [TIME]
|
||||||
|
Installed [N] packages in [TIME]
|
||||||
|
+ ok==1.0.0 (from file://[TEMP_DIR]/wheels/ok-1.0.0-py3-none-any.whl)
|
||||||
|
+ relative-wheel==0.1.0 (from file://[TEMP_DIR]/)
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock"))?;
|
||||||
|
|
||||||
|
insta::with_settings!(
|
||||||
|
{
|
||||||
|
filters => context.filters(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
assert_snapshot!(
|
||||||
|
lock, @r###"
|
||||||
|
version = 1
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[options]
|
||||||
|
exclude-newer = "2024-03-25 00:00:00 UTC"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ok"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = { path = "wheels/ok-1.0.0-py3-none-any.whl" }
|
||||||
|
wheels = [
|
||||||
|
{ filename = "ok-1.0.0-py3-none-any.whl", hash = "sha256:79f0b33e6ce1e09eaa1784c8eee275dfe84d215d9c65c652f07c18e85fdaac5f" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "relative-wheel"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "ok" },
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that we can re-read the lockfile.
|
||||||
|
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `uv sync` is experimental and may change without warning
|
||||||
|
warning: `uv.sources` is experimental and may change without warning
|
||||||
|
Resolved [N] packages in [TIME]
|
||||||
|
Audited [N] packages in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue