mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-01 20:31:12 +00:00
Support locking relative paths (#4205)
By splitting `path` into a lockable, relative (or absolute) and an absolute installable path and by splitting between urls and paths by dist type, we can store relative paths in the lockfile.
This commit is contained in:
parent
33cf47182f
commit
44833801b3
28 changed files with 533 additions and 364 deletions
|
|
@ -2,7 +2,9 @@
|
|||
// as we build out universal locking.
|
||||
#![allow(dead_code, unreachable_code, unused_variables)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
|
|
@ -11,6 +13,7 @@ use either::Either;
|
|||
use indexmap::IndexMap;
|
||||
use petgraph::visit::EdgeRef;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use toml_edit::{value, Array, ArrayOfTables, InlineTable, Item, Table, Value};
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -22,7 +25,7 @@ use distribution_types::{
|
|||
RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, ToUrlError,
|
||||
};
|
||||
use pep440_rs::Version;
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree, VerbatimUrl};
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree, VerbatimUrl, VerbatimUrlError};
|
||||
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||
use pypi_types::{HashDigest, ParsedArchiveUrl, ParsedGitUrl};
|
||||
use uv_configuration::ExtrasSpecification;
|
||||
|
|
@ -145,6 +148,7 @@ impl Lock {
|
|||
/// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root.
|
||||
pub fn to_resolution(
|
||||
&self,
|
||||
workspace_root: &Path,
|
||||
marker_env: &MarkerEnvironment,
|
||||
tags: &Tags,
|
||||
root_name: &PackageName,
|
||||
|
|
@ -202,7 +206,7 @@ impl Lock {
|
|||
}
|
||||
}
|
||||
let name = dist.id.name.clone();
|
||||
let resolved_dist = ResolvedDist::Installable(dist.to_dist(tags)?);
|
||||
let resolved_dist = ResolvedDist::Installable(dist.to_dist(workspace_root, tags)?);
|
||||
map.insert(name, resolved_dist);
|
||||
}
|
||||
let diagnostics = vec![];
|
||||
|
|
@ -485,9 +489,9 @@ impl TryFrom<LockWire> for Lock {
|
|||
|
||||
// Also check that our sources are consistent with whether we have
|
||||
// hashes or not.
|
||||
let requires_hash = dist.id.source.kind.requires_hash();
|
||||
let requires_hash = dist.id.source.requires_hash();
|
||||
if let Some(ref sdist) = dist.sdist {
|
||||
if requires_hash != sdist.hash.is_some() {
|
||||
if requires_hash != sdist.hash().is_some() {
|
||||
return Err(LockErrorKind::Hash {
|
||||
id: dist.id.clone(),
|
||||
artifact_type: "source distribution",
|
||||
|
|
@ -581,14 +585,14 @@ impl Distribution {
|
|||
}
|
||||
|
||||
/// Convert the [`Distribution`] to a [`Dist`] that can be used in installation.
|
||||
fn to_dist(&self, tags: &Tags) -> Result<Dist, LockError> {
|
||||
fn to_dist(&self, workspace_root: &Path, tags: &Tags) -> Result<Dist, LockError> {
|
||||
if let Some(best_wheel_index) = self.find_best_wheel(tags) {
|
||||
return match &self.id.source.kind {
|
||||
SourceKind::Registry => {
|
||||
return match &self.id.source {
|
||||
Source::Registry(url) => {
|
||||
let wheels = self
|
||||
.wheels
|
||||
.iter()
|
||||
.map(|wheel| wheel.to_registry_dist(&self.id.source))
|
||||
.map(|wheel| wheel.to_registry_dist(url, &self.id.source))
|
||||
.collect();
|
||||
let reg_built_dist = RegistryBuiltDist {
|
||||
wheels,
|
||||
|
|
@ -597,41 +601,46 @@ impl Distribution {
|
|||
};
|
||||
Ok(Dist::Built(BuiltDist::Registry(reg_built_dist)))
|
||||
}
|
||||
SourceKind::Path => {
|
||||
Source::Path(path) => {
|
||||
let filename: WheelFilename = self.wheels[best_wheel_index].filename.clone();
|
||||
let path_dist = PathBuiltDist {
|
||||
filename,
|
||||
url: VerbatimUrl::from_url(self.id.source.url.clone()),
|
||||
path: self.id.source.url.to_file_path().unwrap(),
|
||||
url: VerbatimUrl::from_path(workspace_root.join(path)).map_err(|err| {
|
||||
LockErrorKind::VerbatimUrl {
|
||||
id: self.id.clone(),
|
||||
err,
|
||||
}
|
||||
})?,
|
||||
path: path.clone(),
|
||||
};
|
||||
let built_dist = BuiltDist::Path(path_dist);
|
||||
Ok(Dist::Built(built_dist))
|
||||
}
|
||||
SourceKind::Direct(direct) => {
|
||||
Source::Direct(url, direct) => {
|
||||
let filename: WheelFilename = self.wheels[best_wheel_index].filename.clone();
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
url: self.id.source.url.clone(),
|
||||
url: url.clone(),
|
||||
subdirectory: direct.subdirectory.as_ref().map(PathBuf::from),
|
||||
});
|
||||
let direct_dist = DirectUrlBuiltDist {
|
||||
filename,
|
||||
location: self.id.source.url.clone(),
|
||||
location: url.clone(),
|
||||
url: VerbatimUrl::from_url(url),
|
||||
};
|
||||
let built_dist = BuiltDist::DirectUrl(direct_dist);
|
||||
Ok(Dist::Built(built_dist))
|
||||
}
|
||||
SourceKind::Git(_) => Err(LockErrorKind::InvalidWheelSource {
|
||||
Source::Git(_, _) => Err(LockErrorKind::InvalidWheelSource {
|
||||
id: self.id.clone(),
|
||||
source_type: "Git",
|
||||
}
|
||||
.into()),
|
||||
SourceKind::Directory => Err(LockErrorKind::InvalidWheelSource {
|
||||
Source::Directory(_) => Err(LockErrorKind::InvalidWheelSource {
|
||||
id: self.id.clone(),
|
||||
source_type: "directory",
|
||||
}
|
||||
.into()),
|
||||
SourceKind::Editable => Err(LockErrorKind::InvalidWheelSource {
|
||||
Source::Editable(_) => Err(LockErrorKind::InvalidWheelSource {
|
||||
id: self.id.clone(),
|
||||
source_type: "editable",
|
||||
}
|
||||
|
|
@ -640,43 +649,59 @@ impl Distribution {
|
|||
}
|
||||
|
||||
if let Some(sdist) = &self.sdist {
|
||||
return match &self.id.source.kind {
|
||||
SourceKind::Path => {
|
||||
return match &self.id.source {
|
||||
Source::Path(path) => {
|
||||
let path_dist = PathSourceDist {
|
||||
name: self.id.name.clone(),
|
||||
url: VerbatimUrl::from_url(self.id.source.url.clone()),
|
||||
path: self.id.source.url.to_file_path().unwrap(),
|
||||
url: VerbatimUrl::from_path(workspace_root.join(path)).map_err(|err| {
|
||||
LockErrorKind::VerbatimUrl {
|
||||
id: self.id.clone(),
|
||||
err,
|
||||
}
|
||||
})?,
|
||||
install_path: workspace_root.join(path),
|
||||
lock_path: path.clone(),
|
||||
};
|
||||
let source_dist = distribution_types::SourceDist::Path(path_dist);
|
||||
Ok(Dist::Source(source_dist))
|
||||
}
|
||||
SourceKind::Directory => {
|
||||
Source::Directory(path) => {
|
||||
let dir_dist = DirectorySourceDist {
|
||||
name: self.id.name.clone(),
|
||||
url: VerbatimUrl::from_url(self.id.source.url.clone()),
|
||||
path: self.id.source.url.to_file_path().unwrap(),
|
||||
url: VerbatimUrl::from_path(workspace_root.join(path)).map_err(|err| {
|
||||
LockErrorKind::VerbatimUrl {
|
||||
id: self.id.clone(),
|
||||
err,
|
||||
}
|
||||
})?,
|
||||
install_path: workspace_root.join(path),
|
||||
lock_path: path.clone(),
|
||||
editable: false,
|
||||
};
|
||||
let source_dist = distribution_types::SourceDist::Directory(dir_dist);
|
||||
Ok(Dist::Source(source_dist))
|
||||
}
|
||||
SourceKind::Editable => {
|
||||
Source::Editable(path) => {
|
||||
let dir_dist = DirectorySourceDist {
|
||||
name: self.id.name.clone(),
|
||||
url: VerbatimUrl::from_url(self.id.source.url.clone()),
|
||||
path: self.id.source.url.to_file_path().unwrap(),
|
||||
url: VerbatimUrl::from_path(workspace_root.join(path)).map_err(|err| {
|
||||
LockErrorKind::VerbatimUrl {
|
||||
id: self.id.clone(),
|
||||
err,
|
||||
}
|
||||
})?,
|
||||
install_path: workspace_root.join(path),
|
||||
lock_path: path.clone(),
|
||||
editable: true,
|
||||
};
|
||||
let source_dist = distribution_types::SourceDist::Directory(dir_dist);
|
||||
Ok(Dist::Source(source_dist))
|
||||
}
|
||||
SourceKind::Git(git) => {
|
||||
Source::Git(url, git) => {
|
||||
// Reconstruct the `GitUrl` from the `GitSource`.
|
||||
let git_url = uv_git::GitUrl::new(
|
||||
self.id.source.url.clone(),
|
||||
GitReference::from(git.kind.clone()),
|
||||
)
|
||||
.with_precise(git.precise);
|
||||
let git_url =
|
||||
uv_git::GitUrl::new(url.clone(), GitReference::from(git.kind.clone()))
|
||||
.with_precise(git.precise);
|
||||
|
||||
// Reconstruct the PEP 508-compatible URL from the `GitSource`.
|
||||
let url = Url::from(ParsedGitUrl {
|
||||
|
|
@ -693,32 +718,32 @@ impl Distribution {
|
|||
let source_dist = distribution_types::SourceDist::Git(git_dist);
|
||||
Ok(Dist::Source(source_dist))
|
||||
}
|
||||
SourceKind::Direct(direct) => {
|
||||
Source::Direct(url, direct) => {
|
||||
let url = Url::from(ParsedArchiveUrl {
|
||||
url: self.id.source.url.clone(),
|
||||
url: url.clone(),
|
||||
subdirectory: direct.subdirectory.as_ref().map(PathBuf::from),
|
||||
});
|
||||
let direct_dist = DirectUrlSourceDist {
|
||||
name: self.id.name.clone(),
|
||||
location: self.id.source.url.clone(),
|
||||
location: url.clone(),
|
||||
subdirectory: direct.subdirectory.as_ref().map(PathBuf::from),
|
||||
url: VerbatimUrl::from_url(url),
|
||||
};
|
||||
let source_dist = distribution_types::SourceDist::DirectUrl(direct_dist);
|
||||
Ok(Dist::Source(source_dist))
|
||||
}
|
||||
SourceKind::Registry => {
|
||||
Source::Registry(url) => {
|
||||
let file = Box::new(distribution_types::File {
|
||||
dist_info_metadata: false,
|
||||
filename: sdist.url.filename().unwrap().to_string(),
|
||||
filename: sdist.filename().unwrap().to_string(),
|
||||
hashes: vec![],
|
||||
requires_python: None,
|
||||
size: sdist.size,
|
||||
size: sdist.size(),
|
||||
upload_time_utc_ms: None,
|
||||
url: FileLocation::AbsoluteUrl(sdist.url.to_string()),
|
||||
url: FileLocation::AbsoluteUrl(url.to_string()),
|
||||
yanked: None,
|
||||
});
|
||||
let index = IndexUrl::Url(VerbatimUrl::from_url(self.id.source.url.clone()));
|
||||
let index = IndexUrl::Url(VerbatimUrl::from_url(url.clone()));
|
||||
let reg_dist = RegistrySourceDist {
|
||||
name: self.id.name.clone(),
|
||||
version: self.id.version.clone(),
|
||||
|
|
@ -765,10 +790,10 @@ impl Distribution {
|
|||
|
||||
/// Returns the [`ResolvedRepositoryReference`] for the distribution, if it is a Git source.
|
||||
pub fn as_git_ref(&self) -> Option<ResolvedRepositoryReference> {
|
||||
match &self.id.source.kind {
|
||||
SourceKind::Git(git) => Some(ResolvedRepositoryReference {
|
||||
match &self.id.source {
|
||||
Source::Git(url, git) => Some(ResolvedRepositoryReference {
|
||||
reference: RepositoryReference {
|
||||
url: RepositoryUrl::new(&self.id.source.url),
|
||||
url: RepositoryUrl::new(url),
|
||||
reference: GitReference::from(git.kind.clone()),
|
||||
},
|
||||
sha: git.precise,
|
||||
|
|
@ -804,10 +829,28 @@ impl std::fmt::Display for DistributionId {
|
|||
}
|
||||
}
|
||||
|
||||
/// NOTE: Care should be taken when adding variants to this enum. Namely, new
|
||||
/// variants should be added without changing the relative ordering of other
|
||||
/// variants. Otherwise, this could cause the lock file to have a different
|
||||
/// canonical ordering of distributions.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||
struct Source {
|
||||
kind: SourceKind,
|
||||
url: Url,
|
||||
enum Source {
|
||||
Registry(Url),
|
||||
Git(Url, GitSource),
|
||||
Direct(Url, DirectSource),
|
||||
Path(PathBuf),
|
||||
Directory(PathBuf),
|
||||
Editable(PathBuf),
|
||||
}
|
||||
|
||||
/// A [`PathBuf`], but we show `.` instead of an empty path.
|
||||
fn serialize_path_with_dot(path: &Path) -> Cow<str> {
|
||||
let path = path.to_string_lossy();
|
||||
if path.is_empty() {
|
||||
Cow::Borrowed(".")
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
impl Source {
|
||||
|
|
@ -862,70 +905,58 @@ impl Source {
|
|||
}
|
||||
|
||||
fn from_direct_built_dist(direct_dist: &DirectUrlBuiltDist) -> Source {
|
||||
Source {
|
||||
kind: SourceKind::Direct(DirectSource { subdirectory: None }),
|
||||
url: direct_dist.url.to_url(),
|
||||
}
|
||||
Source::Direct(
|
||||
direct_dist.url.to_url(),
|
||||
DirectSource { subdirectory: None },
|
||||
)
|
||||
}
|
||||
|
||||
fn from_direct_source_dist(direct_dist: &DirectUrlSourceDist) -> Source {
|
||||
Source {
|
||||
kind: SourceKind::Direct(DirectSource {
|
||||
Source::Direct(
|
||||
direct_dist.url.to_url(),
|
||||
DirectSource {
|
||||
subdirectory: direct_dist
|
||||
.subdirectory
|
||||
.as_deref()
|
||||
.and_then(Path::to_str)
|
||||
.map(ToString::to_string),
|
||||
}),
|
||||
url: direct_dist.url.to_url(),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn from_path_built_dist(path_dist: &PathBuiltDist) -> Source {
|
||||
Source {
|
||||
kind: SourceKind::Path,
|
||||
url: path_dist.url.to_url(),
|
||||
}
|
||||
Source::Path(path_dist.path.clone())
|
||||
}
|
||||
|
||||
fn from_path_source_dist(path_dist: &PathSourceDist) -> Source {
|
||||
Source {
|
||||
kind: SourceKind::Path,
|
||||
url: path_dist.url.to_url(),
|
||||
}
|
||||
Source::Path(path_dist.install_path.clone())
|
||||
}
|
||||
|
||||
fn from_directory_source_dist(directory_dist: &DirectorySourceDist) -> Source {
|
||||
Source {
|
||||
kind: if directory_dist.editable {
|
||||
SourceKind::Editable
|
||||
} else {
|
||||
SourceKind::Directory
|
||||
},
|
||||
url: directory_dist.url.to_url(),
|
||||
if directory_dist.editable {
|
||||
Source::Editable(directory_dist.lock_path.clone())
|
||||
} else {
|
||||
Source::Directory(directory_dist.lock_path.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn from_index_url(index_url: &IndexUrl) -> Source {
|
||||
match *index_url {
|
||||
IndexUrl::Pypi(ref verbatim_url) => Source {
|
||||
kind: SourceKind::Registry,
|
||||
url: verbatim_url.to_url(),
|
||||
},
|
||||
IndexUrl::Url(ref verbatim_url) => Source {
|
||||
kind: SourceKind::Registry,
|
||||
url: verbatim_url.to_url(),
|
||||
},
|
||||
IndexUrl::Path(ref verbatim_url) => Source {
|
||||
kind: SourceKind::Path,
|
||||
url: verbatim_url.to_url(),
|
||||
},
|
||||
IndexUrl::Pypi(ref verbatim_url) => Source::Registry(verbatim_url.to_url()),
|
||||
IndexUrl::Url(ref verbatim_url) => Source::Registry(verbatim_url.to_url()),
|
||||
// TODO(konsti): Retain path on index url without converting to URL.
|
||||
IndexUrl::Path(ref verbatim_url) => Source::Path(
|
||||
verbatim_url
|
||||
.to_file_path()
|
||||
.expect("Could not convert index url to path"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_git_dist(git_dist: &GitSourceDist) -> Source {
|
||||
Source {
|
||||
kind: SourceKind::Git(GitSource {
|
||||
Source::Git(
|
||||
locked_git_url(git_dist),
|
||||
GitSource {
|
||||
kind: GitSourceKind::from(git_dist.git.reference().clone()),
|
||||
precise: git_dist.git.precise().expect("precise commit"),
|
||||
subdirectory: git_dist
|
||||
|
|
@ -933,9 +964,8 @@ impl Source {
|
|||
.as_deref()
|
||||
.and_then(Path::to_str)
|
||||
.map(ToString::to_string),
|
||||
}),
|
||||
url: locked_git_url(git_dist),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -943,45 +973,45 @@ impl std::str::FromStr for Source {
|
|||
type Err = SourceParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Source, SourceParseError> {
|
||||
let (kind, url) = s.split_once('+').ok_or_else(|| SourceParseError::NoPlus {
|
||||
let (kind, url_or_path) = s.split_once('+').ok_or_else(|| SourceParseError::NoPlus {
|
||||
given: s.to_string(),
|
||||
})?;
|
||||
let mut url = Url::parse(url).map_err(|err| SourceParseError::InvalidUrl {
|
||||
given: s.to_string(),
|
||||
err,
|
||||
})?;
|
||||
match kind {
|
||||
"registry" => Ok(Source {
|
||||
kind: SourceKind::Registry,
|
||||
url,
|
||||
}),
|
||||
"git" => Ok(Source {
|
||||
kind: SourceKind::Git(GitSource::from_url(&mut url).map_err(|err| match err {
|
||||
"registry" => {
|
||||
let url = Url::parse(url_or_path).map_err(|err| SourceParseError::InvalidUrl {
|
||||
given: s.to_string(),
|
||||
err,
|
||||
})?;
|
||||
Ok(Source::Registry(url))
|
||||
}
|
||||
"git" => {
|
||||
let mut url =
|
||||
Url::parse(url_or_path).map_err(|err| SourceParseError::InvalidUrl {
|
||||
given: s.to_string(),
|
||||
err,
|
||||
})?;
|
||||
let git_source = GitSource::from_url(&mut url).map_err(|err| match err {
|
||||
GitSourceError::InvalidSha => SourceParseError::InvalidSha {
|
||||
given: s.to_string(),
|
||||
},
|
||||
GitSourceError::MissingSha => SourceParseError::MissingSha {
|
||||
given: s.to_string(),
|
||||
},
|
||||
})?),
|
||||
url,
|
||||
}),
|
||||
"direct" => Ok(Source {
|
||||
kind: SourceKind::Direct(DirectSource::from_url(&mut url)),
|
||||
url,
|
||||
}),
|
||||
"path" => Ok(Source {
|
||||
kind: SourceKind::Path,
|
||||
url,
|
||||
}),
|
||||
"directory" => Ok(Source {
|
||||
kind: SourceKind::Directory,
|
||||
url,
|
||||
}),
|
||||
"editable" => Ok(Source {
|
||||
kind: SourceKind::Editable,
|
||||
url,
|
||||
}),
|
||||
})?;
|
||||
Ok(Source::Git(url, git_source))
|
||||
}
|
||||
"direct" => {
|
||||
let mut url =
|
||||
Url::parse(url_or_path).map_err(|err| SourceParseError::InvalidUrl {
|
||||
given: s.to_string(),
|
||||
err,
|
||||
})?;
|
||||
let direct_source = DirectSource::from_url(&mut url);
|
||||
Ok(Source::Direct(url, direct_source))
|
||||
}
|
||||
"path" => Ok(Source::Path(PathBuf::from(url_or_path))),
|
||||
"directory" => Ok(Source::Directory(PathBuf::from(url_or_path))),
|
||||
"editable" => Ok(Source::Editable(PathBuf::from(url_or_path))),
|
||||
name => Err(SourceParseError::UnrecognizedSourceName {
|
||||
given: s.to_string(),
|
||||
name: name.to_string(),
|
||||
|
|
@ -992,7 +1022,14 @@ impl std::str::FromStr for Source {
|
|||
|
||||
impl std::fmt::Display for Source {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}+{}", self.kind.name(), self.url)
|
||||
match self {
|
||||
Source::Registry(url) | Source::Git(url, _) | Source::Direct(url, _) => {
|
||||
write!(f, "{}+{}", self.name(), url)
|
||||
}
|
||||
Source::Path(path) | Source::Directory(path) | Source::Editable(path) => {
|
||||
write!(f, "{}+{}", self.name(), serialize_path_with_dot(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1006,29 +1043,15 @@ impl<'de> serde::Deserialize<'de> for Source {
|
|||
}
|
||||
}
|
||||
|
||||
/// NOTE: Care should be taken when adding variants to this enum. Namely, new
|
||||
/// variants should be added without changing the relative ordering of other
|
||||
/// variants. Otherwise, this could cause the lock file to have a different
|
||||
/// canonical ordering of distributions.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||
enum SourceKind {
|
||||
Registry,
|
||||
Git(GitSource),
|
||||
Direct(DirectSource),
|
||||
Path,
|
||||
Directory,
|
||||
Editable,
|
||||
}
|
||||
|
||||
impl SourceKind {
|
||||
impl Source {
|
||||
fn name(&self) -> &str {
|
||||
match *self {
|
||||
SourceKind::Registry => "registry",
|
||||
SourceKind::Git(_) => "git",
|
||||
SourceKind::Direct(_) => "direct",
|
||||
SourceKind::Path => "path",
|
||||
SourceKind::Directory => "directory",
|
||||
SourceKind::Editable => "editable",
|
||||
Self::Registry(..) => "registry",
|
||||
Self::Git(..) => "git",
|
||||
Self::Direct(..) => "direct",
|
||||
Self::Path(..) => "path",
|
||||
Self::Directory(..) => "directory",
|
||||
Self::Editable(..) => "editable",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1038,8 +1061,8 @@ impl SourceKind {
|
|||
/// _not_ be present.
|
||||
fn requires_hash(&self) -> bool {
|
||||
match *self {
|
||||
SourceKind::Registry | SourceKind::Direct(_) | SourceKind::Path => true,
|
||||
SourceKind::Git(_) | SourceKind::Directory | SourceKind::Editable => false,
|
||||
Self::Registry(..) | Self::Direct(..) | Self::Path(..) => true,
|
||||
Self::Git(..) | Self::Directory(..) | Self::Editable(..) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1127,12 +1150,7 @@ enum GitSourceKind {
|
|||
|
||||
/// Inspired by: <https://discuss.python.org/t/lock-files-again-but-this-time-w-sdists/46593>
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
struct SourceDist {
|
||||
/// A URL or file path (via `file://`) where the source dist 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 source dist file originally came from.
|
||||
url: Url,
|
||||
struct SourceDistMetadata {
|
||||
/// A hash of the source distribution.
|
||||
///
|
||||
/// This is only present for source distributions that come from registries
|
||||
|
|
@ -1145,15 +1163,79 @@ struct SourceDist {
|
|||
size: Option<u64>,
|
||||
}
|
||||
|
||||
/// A URL or file path where the source dist 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 source dist file originally came from.
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum SourceDist {
|
||||
Url {
|
||||
url: Url,
|
||||
#[serde(flatten)]
|
||||
metadata: SourceDistMetadata,
|
||||
},
|
||||
Path {
|
||||
#[serde(deserialize_with = "deserialize_path_with_dot")]
|
||||
path: PathBuf,
|
||||
#[serde(flatten)]
|
||||
metadata: SourceDistMetadata,
|
||||
},
|
||||
}
|
||||
|
||||
/// A [`PathBuf`], but we show `.` instead of an empty path.
|
||||
fn deserialize_path_with_dot<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let path = String::deserialize(deserializer)?;
|
||||
if path == "." {
|
||||
Ok(PathBuf::new())
|
||||
} else {
|
||||
Ok(PathBuf::from(path))
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceDist {
|
||||
pub(crate) fn filename(&self) -> Option<Cow<str>> {
|
||||
match self {
|
||||
SourceDist::Url { url, .. } => url.filename().ok(),
|
||||
SourceDist::Path { path, .. } => {
|
||||
path.file_name().map(|filename| filename.to_string_lossy())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(&self) -> Option<&Hash> {
|
||||
match &self {
|
||||
SourceDist::Url { metadata, .. } => metadata.hash.as_ref(),
|
||||
SourceDist::Path { metadata, .. } => metadata.hash.as_ref(),
|
||||
}
|
||||
}
|
||||
fn size(&self) -> Option<u64> {
|
||||
match &self {
|
||||
SourceDist::Url { metadata, .. } => metadata.size,
|
||||
SourceDist::Path { metadata, .. } => metadata.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceDist {
|
||||
/// Returns the TOML representation of this source distribution.
|
||||
fn to_toml(&self) -> Result<InlineTable> {
|
||||
let mut table = InlineTable::new();
|
||||
table.insert("url", Value::from(self.url.to_string()));
|
||||
if let Some(ref hash) = self.hash {
|
||||
match &self {
|
||||
SourceDist::Url { url, .. } => {
|
||||
table.insert("url", Value::from(url.as_str()));
|
||||
}
|
||||
SourceDist::Path { path, .. } => {
|
||||
table.insert("path", Value::from(serialize_path_with_dot(path).as_ref()));
|
||||
}
|
||||
}
|
||||
if let Some(hash) = self.hash() {
|
||||
table.insert("hash", Value::from(hash.to_string()));
|
||||
}
|
||||
if let Some(size) = self.size {
|
||||
if let Some(size) = self.size() {
|
||||
table.insert("size", Value::from(i64::try_from(size)?));
|
||||
}
|
||||
Ok(table)
|
||||
|
|
@ -1219,30 +1301,39 @@ impl SourceDist {
|
|||
.map_err(LockError::from)?;
|
||||
let hash = reg_dist.file.hashes.first().cloned().map(Hash::from);
|
||||
let size = reg_dist.file.size;
|
||||
Ok(SourceDist { url, hash, size })
|
||||
Ok(SourceDist::Url {
|
||||
url,
|
||||
metadata: SourceDistMetadata { hash, size },
|
||||
})
|
||||
}
|
||||
|
||||
fn from_direct_dist(direct_dist: &DirectUrlSourceDist, hashes: &[HashDigest]) -> SourceDist {
|
||||
SourceDist {
|
||||
SourceDist::Url {
|
||||
url: direct_dist.url.to_url(),
|
||||
hash: hashes.first().cloned().map(Hash::from),
|
||||
size: None,
|
||||
metadata: SourceDistMetadata {
|
||||
hash: hashes.first().cloned().map(Hash::from),
|
||||
size: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_git_dist(git_dist: &GitSourceDist, hashes: &[HashDigest]) -> SourceDist {
|
||||
SourceDist {
|
||||
SourceDist::Url {
|
||||
url: locked_git_url(git_dist),
|
||||
hash: hashes.first().cloned().map(Hash::from),
|
||||
size: None,
|
||||
metadata: SourceDistMetadata {
|
||||
hash: hashes.first().cloned().map(Hash::from),
|
||||
size: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_path_dist(path_dist: &PathSourceDist, hashes: &[HashDigest]) -> SourceDist {
|
||||
SourceDist {
|
||||
url: path_dist.url.to_url(),
|
||||
hash: hashes.first().cloned().map(Hash::from),
|
||||
size: None,
|
||||
SourceDist::Path {
|
||||
path: path_dist.lock_path.clone(),
|
||||
metadata: SourceDistMetadata {
|
||||
hash: hashes.first().cloned().map(Hash::from),
|
||||
size: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1250,10 +1341,12 @@ impl SourceDist {
|
|||
directory_dist: &DirectorySourceDist,
|
||||
hashes: &[HashDigest],
|
||||
) -> SourceDist {
|
||||
SourceDist {
|
||||
url: directory_dist.url.to_url(),
|
||||
hash: hashes.first().cloned().map(Hash::from),
|
||||
size: None,
|
||||
SourceDist::Path {
|
||||
path: directory_dist.lock_path.clone(),
|
||||
metadata: SourceDistMetadata {
|
||||
hash: hashes.first().cloned().map(Hash::from),
|
||||
size: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1439,7 +1532,7 @@ impl Wheel {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_registry_dist(&self, source: &Source) -> RegistryBuiltWheel {
|
||||
fn to_registry_dist(&self, url: &Url, source: &Source) -> RegistryBuiltWheel {
|
||||
let filename: WheelFilename = self.filename.clone();
|
||||
let file = Box::new(distribution_types::File {
|
||||
dist_info_metadata: false,
|
||||
|
|
@ -1451,7 +1544,7 @@ impl Wheel {
|
|||
url: FileLocation::AbsoluteUrl(self.url.to_string()),
|
||||
yanked: None,
|
||||
});
|
||||
let index = IndexUrl::Url(VerbatimUrl::from_url(source.url.clone()));
|
||||
let index = IndexUrl::Url(VerbatimUrl::from_url(url.clone()));
|
||||
RegistryBuiltWheel {
|
||||
filename,
|
||||
file,
|
||||
|
|
@ -1632,7 +1725,7 @@ impl<'de> serde::Deserialize<'de> for Hash {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct LockError(Box<LockErrorKind>);
|
||||
|
||||
|
|
@ -1651,7 +1744,7 @@ where
|
|||
/// For example, if there are two or more duplicative distributions given
|
||||
/// to `Lock::new`, then an error is returned. It's likely that the fault
|
||||
/// is with the caller somewhere in such cases.
|
||||
#[derive(Clone, Debug, thiserror::Error)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum LockErrorKind {
|
||||
/// An error that occurs when multiple distributions with the same
|
||||
/// ID were found.
|
||||
|
|
@ -1732,7 +1825,7 @@ enum LockErrorKind {
|
|||
},
|
||||
/// An error that occurs when a hash is expected (or not) for a particular
|
||||
/// artifact, but one was not found (or was).
|
||||
#[error("since the distribution `{id}` comes from a {source} dependency, a hash was {expected} but one was not found for {artifact_type}", source = id.source.kind.name(), expected = if *expected { "expected" } else { "not expected" })]
|
||||
#[error("since the distribution `{id}` comes from a {source} dependency, a hash was {expected} but one was not found for {artifact_type}", source = id.source.name(), expected = if *expected { "expected" } else { "not expected" })]
|
||||
Hash {
|
||||
/// The ID of the distribution that has a missing hash.
|
||||
id: DistributionId,
|
||||
|
|
@ -1777,6 +1870,15 @@ enum LockErrorKind {
|
|||
/// The ID of the distribution that has a missing base.
|
||||
id: DistributionId,
|
||||
},
|
||||
/// An error that occurs when converting between URLs and paths.
|
||||
#[error("found dependency `{id}` with no locked distribution")]
|
||||
VerbatimUrl {
|
||||
/// The ID of the distribution that has a missing base.
|
||||
id: DistributionId,
|
||||
/// The inner error we forward.
|
||||
#[source]
|
||||
err: VerbatimUrlError,
|
||||
},
|
||||
}
|
||||
|
||||
/// An error that occurs when a source string could not be parsed.
|
||||
|
|
@ -1827,7 +1929,7 @@ impl std::error::Error for HashParseError {}
|
|||
|
||||
impl std::fmt::Display for HashParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
Display::fmt(self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -334,7 +334,8 @@ impl PubGrubRequirement {
|
|||
RequirementSource::Path {
|
||||
editable,
|
||||
url,
|
||||
path,
|
||||
install_path,
|
||||
lock_path,
|
||||
} => {
|
||||
let Some(expected) = urls.get(&requirement.name) else {
|
||||
return Err(ResolveError::DisallowedUrl(
|
||||
|
|
@ -344,7 +345,8 @@ impl PubGrubRequirement {
|
|||
};
|
||||
|
||||
let parsed_url = ParsedUrl::Path(ParsedPathUrl::from_source(
|
||||
path.clone(),
|
||||
install_path.clone(),
|
||||
lock_path.clone(),
|
||||
*editable,
|
||||
url.to_url(),
|
||||
));
|
||||
|
|
|
|||
|
|
@ -177,7 +177,9 @@ fn iter_locals(source: &RequirementSource) -> Box<dyn Iterator<Item = Version> +
|
|||
.filter(pep440_rs::Version::is_local),
|
||||
),
|
||||
RequirementSource::Git { .. } => Box::new(iter::empty()),
|
||||
RequirementSource::Path { path, .. } => Box::new(
|
||||
RequirementSource::Path {
|
||||
install_path: path, ..
|
||||
} => Box::new(
|
||||
path.file_name()
|
||||
.and_then(|filename| {
|
||||
let filename = filename.to_string_lossy();
|
||||
|
|
|
|||
|
|
@ -53,13 +53,15 @@ impl Urls {
|
|||
}
|
||||
}
|
||||
RequirementSource::Path {
|
||||
path,
|
||||
install_path,
|
||||
lock_path,
|
||||
editable,
|
||||
url,
|
||||
} => {
|
||||
let url = VerbatimParsedUrl {
|
||||
parsed_url: ParsedUrl::Path(ParsedPathUrl::from_source(
|
||||
path.clone(),
|
||||
install_path.clone(),
|
||||
lock_path.clone(),
|
||||
*editable,
|
||||
url.to_url(),
|
||||
)),
|
||||
|
|
@ -140,7 +142,8 @@ impl Urls {
|
|||
a.subdirectory == b.subdirectory && git.same_ref(&a.url, &b.url)
|
||||
}
|
||||
(ParsedUrl::Path(a), ParsedUrl::Path(b)) => {
|
||||
a.path == b.path || is_same_file(&a.path, &b.path).unwrap_or(false)
|
||||
a.install_path == b.install_path
|
||||
|| is_same_file(&a.install_path, &b.install_path).unwrap_or(false)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,20 +12,9 @@ Ok(
|
|||
"anyio",
|
||||
),
|
||||
version: "4.3.0",
|
||||
source: Source {
|
||||
kind: Path,
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/foo/bar",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
},
|
||||
source: Path(
|
||||
"file:///foo/bar",
|
||||
),
|
||||
},
|
||||
sdist: None,
|
||||
wheels: [
|
||||
|
|
@ -80,20 +69,9 @@ Ok(
|
|||
"anyio",
|
||||
),
|
||||
version: "4.3.0",
|
||||
source: Source {
|
||||
kind: Path,
|
||||
url: Url {
|
||||
scheme: "file",
|
||||
cannot_be_a_base: false,
|
||||
username: "",
|
||||
password: None,
|
||||
host: None,
|
||||
port: None,
|
||||
path: "/foo/bar",
|
||||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
},
|
||||
source: Path(
|
||||
"file:///foo/bar",
|
||||
),
|
||||
}: 0,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue