mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +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
|
@ -158,7 +158,7 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> {
|
|||
fn from(dist: &'a PathSourceDist) -> Self {
|
||||
Self {
|
||||
url: &dist.url,
|
||||
path: Cow::Borrowed(&dist.path),
|
||||
path: Cow::Borrowed(&dist.install_path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ impl<'a> From<&'a DirectorySourceDist> for DirectorySourceUrl<'a> {
|
|||
fn from(dist: &'a DirectorySourceDist) -> Self {
|
||||
Self {
|
||||
url: &dist.url,
|
||||
path: Cow::Borrowed(&dist.path),
|
||||
path: Cow::Borrowed(&dist.install_path),
|
||||
editable: dist.editable,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,12 +116,14 @@ impl CachedDist {
|
|||
Self::Url(dist) => {
|
||||
if dist.editable {
|
||||
assert_eq!(dist.url.scheme(), "file", "{}", dist.url);
|
||||
let path = dist
|
||||
.url
|
||||
.to_file_path()
|
||||
.map_err(|()| anyhow!("Invalid path in file URL"))?;
|
||||
Ok(Some(ParsedUrl::Path(ParsedPathUrl {
|
||||
url: dist.url.raw().clone(),
|
||||
path: dist
|
||||
.url
|
||||
.to_file_path()
|
||||
.map_err(|()| anyhow!("Invalid path in file URL"))?,
|
||||
install_path: path.clone(),
|
||||
lock_path: path,
|
||||
editable: dist.editable,
|
||||
})))
|
||||
} else {
|
||||
|
|
|
@ -259,8 +259,12 @@ pub struct GitSourceDist {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct PathSourceDist {
|
||||
pub name: PackageName,
|
||||
/// The path to the archive.
|
||||
pub path: PathBuf,
|
||||
/// The resolved, absolute path to the distribution which we use for installing.
|
||||
pub install_path: PathBuf,
|
||||
/// The absolute path or path relative to the workspace root pointing to the distribution
|
||||
/// 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.
|
||||
pub url: VerbatimUrl,
|
||||
}
|
||||
|
@ -269,8 +273,12 @@ pub struct PathSourceDist {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct DirectorySourceDist {
|
||||
pub name: PackageName,
|
||||
/// The path to the directory.
|
||||
pub path: PathBuf,
|
||||
/// The resolved, absolute path to the distribution which we use for installing.
|
||||
pub install_path: PathBuf,
|
||||
/// The absolute path or path relative to the workspace root pointing to the distribution
|
||||
/// 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,
|
||||
/// Whether the package should be installed in editable mode.
|
||||
pub editable: bool,
|
||||
/// The URL as it was provided by the user.
|
||||
|
@ -319,11 +327,12 @@ impl Dist {
|
|||
pub fn from_file_url(
|
||||
name: PackageName,
|
||||
url: VerbatimUrl,
|
||||
path: &Path,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
editable: bool,
|
||||
) -> Result<Dist, Error> {
|
||||
// Store the canonicalized path, which also serves to validate that it exists.
|
||||
let path = match path.canonicalize() {
|
||||
let canonicalized_path = match install_path.canonicalize() {
|
||||
Ok(path) => path,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Err(Error::NotFound(url.to_url()));
|
||||
|
@ -332,14 +341,15 @@ impl Dist {
|
|||
};
|
||||
|
||||
// Determine whether the path represents an archive or a directory.
|
||||
if path.is_dir() {
|
||||
if canonicalized_path.is_dir() {
|
||||
Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
|
||||
name,
|
||||
path,
|
||||
install_path: canonicalized_path.clone(),
|
||||
lock_path: lock_path.to_path_buf(),
|
||||
editable,
|
||||
url,
|
||||
})))
|
||||
} else if path
|
||||
} else if canonicalized_path
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl"))
|
||||
{
|
||||
|
@ -359,7 +369,7 @@ impl Dist {
|
|||
|
||||
Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
|
||||
filename,
|
||||
path,
|
||||
path: canonicalized_path,
|
||||
url,
|
||||
})))
|
||||
} else {
|
||||
|
@ -369,7 +379,8 @@ impl Dist {
|
|||
|
||||
Ok(Self::Source(SourceDist::Path(PathSourceDist {
|
||||
name,
|
||||
path,
|
||||
install_path: canonicalized_path.clone(),
|
||||
lock_path: canonicalized_path,
|
||||
url,
|
||||
})))
|
||||
}
|
||||
|
@ -396,9 +407,13 @@ impl Dist {
|
|||
ParsedUrl::Archive(archive) => {
|
||||
Self::from_http_url(name, url.verbatim, archive.url, archive.subdirectory)
|
||||
}
|
||||
ParsedUrl::Path(file) => {
|
||||
Self::from_file_url(name, url.verbatim, &file.path, file.editable)
|
||||
}
|
||||
ParsedUrl::Path(file) => Self::from_file_url(
|
||||
name,
|
||||
url.verbatim,
|
||||
&file.install_path,
|
||||
&file.lock_path,
|
||||
file.editable,
|
||||
),
|
||||
ParsedUrl::Git(git) => {
|
||||
Self::from_git_url(name, url.verbatim, git.url, git.subdirectory)
|
||||
}
|
||||
|
@ -517,8 +532,8 @@ impl SourceDist {
|
|||
/// Returns the path to the source distribution, if it's a local distribution.
|
||||
pub fn as_path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Path(dist) => Some(&dist.path),
|
||||
Self::Directory(dist) => Some(&dist.path),
|
||||
Self::Path(dist) => Some(&dist.install_path),
|
||||
Self::Directory(dist) => Some(&dist.install_path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,7 +141,8 @@ impl From<&ResolvedDist> for Requirement {
|
|||
}
|
||||
}
|
||||
Dist::Built(BuiltDist::Path(wheel)) => RequirementSource::Path {
|
||||
path: wheel.path.clone(),
|
||||
install_path: wheel.path.clone(),
|
||||
lock_path: wheel.path.clone(),
|
||||
url: wheel.url.clone(),
|
||||
editable: false,
|
||||
},
|
||||
|
@ -168,12 +169,14 @@ impl From<&ResolvedDist> for Requirement {
|
|||
subdirectory: sdist.subdirectory.clone(),
|
||||
},
|
||||
Dist::Source(SourceDist::Path(sdist)) => RequirementSource::Path {
|
||||
path: sdist.path.clone(),
|
||||
install_path: sdist.install_path.clone(),
|
||||
lock_path: sdist.lock_path.clone(),
|
||||
url: sdist.url.clone(),
|
||||
editable: false,
|
||||
},
|
||||
Dist::Source(SourceDist::Directory(sdist)) => RequirementSource::Path {
|
||||
path: sdist.path.clone(),
|
||||
install_path: sdist.install_path.clone(),
|
||||
lock_path: sdist.lock_path.clone(),
|
||||
url: sdist.url.clone(),
|
||||
editable: sdist.editable,
|
||||
},
|
||||
|
|
|
@ -59,8 +59,9 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
|||
) -> Result<Self, Self::Err> {
|
||||
let verbatim = VerbatimUrl::parse_path(&path, &working_dir)?;
|
||||
let parsed_path_url = ParsedPathUrl {
|
||||
path: verbatim.as_path()?,
|
||||
url: verbatim.to_url(),
|
||||
install_path: verbatim.as_path()?,
|
||||
lock_path: path.as_ref().to_path_buf(),
|
||||
editable: false,
|
||||
};
|
||||
Ok(Self {
|
||||
|
@ -72,8 +73,9 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
|||
fn parse_absolute_path(path: impl AsRef<Path>) -> Result<Self, Self::Err> {
|
||||
let verbatim = VerbatimUrl::parse_absolute_path(&path)?;
|
||||
let parsed_path_url = ParsedPathUrl {
|
||||
path: verbatim.as_path()?,
|
||||
url: verbatim.to_url(),
|
||||
install_path: verbatim.as_path()?,
|
||||
lock_path: path.as_ref().to_path_buf(),
|
||||
editable: false,
|
||||
};
|
||||
Ok(Self {
|
||||
|
@ -171,16 +173,27 @@ impl ParsedUrl {
|
|||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Hash, Ord)]
|
||||
pub struct ParsedPathUrl {
|
||||
pub url: Url,
|
||||
pub path: PathBuf,
|
||||
/// The resolved, absolute path to the distribution which we use for installing.
|
||||
pub install_path: PathBuf,
|
||||
/// The absolute path or path relative to the workspace root pointing to the distribution
|
||||
/// 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,
|
||||
pub editable: bool,
|
||||
}
|
||||
|
||||
impl ParsedPathUrl {
|
||||
/// Construct a [`ParsedPathUrl`] from a path requirement source.
|
||||
pub fn from_source(path: PathBuf, editable: bool, url: Url) -> Self {
|
||||
pub fn from_source(
|
||||
install_path: PathBuf,
|
||||
lock_path: PathBuf,
|
||||
editable: bool,
|
||||
url: Url,
|
||||
) -> Self {
|
||||
Self {
|
||||
url,
|
||||
path,
|
||||
install_path,
|
||||
lock_path,
|
||||
editable,
|
||||
}
|
||||
}
|
||||
|
@ -311,7 +324,8 @@ impl TryFrom<Url> for ParsedUrl {
|
|||
.map_err(|()| ParsedUrlError::InvalidFileUrl(url.clone()))?;
|
||||
Ok(Self::Path(ParsedPathUrl {
|
||||
url,
|
||||
path,
|
||||
install_path: path.clone(),
|
||||
lock_path: path,
|
||||
editable: false,
|
||||
}))
|
||||
} else {
|
||||
|
|
|
@ -170,7 +170,12 @@ pub enum RequirementSource {
|
|||
/// `.tag.gz` file) or a source tree (a directory with a pyproject.toml in, or a legacy
|
||||
/// source distribution with only a setup.py but non pyproject.toml in it).
|
||||
Path {
|
||||
path: PathBuf,
|
||||
/// The resolved, absolute path to the distribution which we use for installing.
|
||||
install_path: PathBuf,
|
||||
/// The absolute path or path relative to the workspace root pointing to the distribution
|
||||
/// 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.
|
||||
lock_path: PathBuf,
|
||||
/// For a source tree (a directory), whether to install as an editable.
|
||||
editable: bool,
|
||||
/// The PEP 508 style URL in the format
|
||||
|
@ -185,7 +190,8 @@ impl RequirementSource {
|
|||
pub fn from_parsed_url(parsed_url: ParsedUrl, url: VerbatimUrl) -> Self {
|
||||
match parsed_url {
|
||||
ParsedUrl::Path(local_file) => RequirementSource::Path {
|
||||
path: local_file.path,
|
||||
install_path: local_file.install_path.clone(),
|
||||
lock_path: local_file.lock_path.clone(),
|
||||
url,
|
||||
editable: local_file.editable,
|
||||
},
|
||||
|
|
|
@ -1843,7 +1843,8 @@ mod test {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "/foo/bar",
|
||||
install_path: "/foo/bar",
|
||||
lock_path: "/foo/bar",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -21,7 +21,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
@ -70,7 +71,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
@ -123,7 +125,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "/scripts/packages/black_editable",
|
||||
install_path: "/scripts/packages/black_editable",
|
||||
lock_path: "/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -23,7 +23,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -79,7 +80,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -135,7 +137,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -212,7 +215,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -289,7 +293,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -359,7 +364,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
lock_path: "./editable[d",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -21,7 +21,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
@ -70,7 +71,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
@ -123,7 +125,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -23,7 +23,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -79,7 +80,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -135,7 +137,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -212,7 +215,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -289,7 +293,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -359,7 +364,8 @@ RequirementsTxt {
|
|||
query: None,
|
||||
fragment: None,
|
||||
},
|
||||
path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
install_path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
lock_path: "./editable[d",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -68,7 +68,8 @@ impl<'a> BuiltWheelIndex<'a> {
|
|||
};
|
||||
|
||||
// Determine the last-modified time of the source distribution.
|
||||
let modified = ArchiveTimestamp::from_file(&source_dist.path).map_err(Error::CacheRead)?;
|
||||
let modified =
|
||||
ArchiveTimestamp::from_file(&source_dist.install_path).map_err(Error::CacheRead)?;
|
||||
|
||||
// If the distribution is stale, omit it from the index.
|
||||
if !pointer.is_up_to_date(modified) {
|
||||
|
@ -106,10 +107,12 @@ impl<'a> BuiltWheelIndex<'a> {
|
|||
};
|
||||
|
||||
// Determine the last-modified time of the source distribution.
|
||||
let Some(modified) =
|
||||
ArchiveTimestamp::from_source_tree(&source_dist.path).map_err(Error::CacheRead)?
|
||||
let Some(modified) = ArchiveTimestamp::from_source_tree(&source_dist.install_path)
|
||||
.map_err(Error::CacheRead)?
|
||||
else {
|
||||
return Err(Error::DirWithoutEntrypoint(source_dist.path.clone()));
|
||||
return Err(Error::DirWithoutEntrypoint(
|
||||
source_dist.install_path.clone(),
|
||||
));
|
||||
};
|
||||
|
||||
// If the distribution is stale, omit it from the index.
|
||||
|
|
|
@ -165,7 +165,12 @@ pub(crate) fn lower_requirement(
|
|||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||
return Err(LoweringError::ConflictingUrls);
|
||||
}
|
||||
path_source(path, project_dir, editable.unwrap_or(false))?
|
||||
path_source(
|
||||
path,
|
||||
project_dir,
|
||||
workspace.root(),
|
||||
editable.unwrap_or(false),
|
||||
)?
|
||||
}
|
||||
Source::Registry { index } => match requirement.version_or_url {
|
||||
None => {
|
||||
|
@ -199,7 +204,12 @@ pub(crate) fn lower_requirement(
|
|||
.get(&requirement.name)
|
||||
.ok_or(LoweringError::UndeclaredWorkspacePackage)?
|
||||
.clone();
|
||||
path_source(path.root(), workspace.root(), editable.unwrap_or(true))?
|
||||
path_source(
|
||||
path.root(),
|
||||
workspace.root(),
|
||||
workspace.root(),
|
||||
editable.unwrap_or(true),
|
||||
)?
|
||||
}
|
||||
Source::CatchAll { .. } => {
|
||||
// Emit a dedicated error message, which is an improvement over Serde's default error.
|
||||
|
@ -219,6 +229,7 @@ pub(crate) fn lower_requirement(
|
|||
fn path_source(
|
||||
path: impl AsRef<Path>,
|
||||
project_dir: &Path,
|
||||
workspace_root: &Path,
|
||||
editable: bool,
|
||||
) -> Result<RequirementSource, LoweringError> {
|
||||
let url = VerbatimUrl::parse_path(path.as_ref(), project_dir)?
|
||||
|
@ -228,8 +239,12 @@ fn path_source(
|
|||
.absolutize_from(project_dir)
|
||||
.map_err(|err| LoweringError::Absolutize(path.as_ref().to_path_buf(), err))?
|
||||
.to_path_buf();
|
||||
let ascend_to_workspace = project_dir
|
||||
.strip_prefix(workspace_root)
|
||||
.expect("Project must be below workspace root");
|
||||
Ok(RequirementSource::Path {
|
||||
path: path_buf,
|
||||
install_path: path_buf,
|
||||
lock_path: ascend_to_workspace.join(project_dir),
|
||||
url,
|
||||
editable,
|
||||
})
|
||||
|
|
|
@ -177,7 +177,12 @@ impl Workspace {
|
|||
extras,
|
||||
marker: None,
|
||||
source: RequirementSource::Path {
|
||||
path: member.root.clone(),
|
||||
install_path: member.root.clone(),
|
||||
lock_path: member
|
||||
.root
|
||||
.strip_prefix(&self.root)
|
||||
.expect("Project must be below workspace root")
|
||||
.to_path_buf(),
|
||||
editable: true,
|
||||
url,
|
||||
},
|
||||
|
|
|
@ -272,13 +272,14 @@ impl<'a> Planner<'a> {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
RequirementSource::Path { url, editable, .. } => {
|
||||
RequirementSource::Path {
|
||||
url,
|
||||
editable,
|
||||
install_path,
|
||||
lock_path,
|
||||
} => {
|
||||
// Store the canonicalized path, which also serves to validate that it exists.
|
||||
let path = match url
|
||||
.to_file_path()
|
||||
.map_err(|()| Error::MissingFilePath(url.to_url()))?
|
||||
.canonicalize()
|
||||
{
|
||||
let path = match install_path.canonicalize() {
|
||||
Ok(path) => path,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Err(Error::NotFound(url.to_url()).into());
|
||||
|
@ -291,7 +292,8 @@ impl<'a> Planner<'a> {
|
|||
let sdist = DirectorySourceDist {
|
||||
name: requirement.name.clone(),
|
||||
url: url.clone(),
|
||||
path,
|
||||
install_path: path,
|
||||
lock_path: lock_path.clone(),
|
||||
editable: *editable,
|
||||
};
|
||||
|
||||
|
@ -369,7 +371,8 @@ impl<'a> Planner<'a> {
|
|||
let sdist = PathSourceDist {
|
||||
name: requirement.name.clone(),
|
||||
url: url.clone(),
|
||||
path,
|
||||
install_path: path,
|
||||
lock_path: lock_path.clone(),
|
||||
};
|
||||
|
||||
// Find the most-compatible wheel from the cache, since we don't know
|
||||
|
|
|
@ -149,9 +149,10 @@ impl RequirementSatisfaction {
|
|||
Ok(Self::Satisfied)
|
||||
}
|
||||
RequirementSource::Path {
|
||||
url: _,
|
||||
path: requested_path,
|
||||
install_path: requested_path,
|
||||
lock_path: _,
|
||||
editable: requested_editable,
|
||||
url: _,
|
||||
} => {
|
||||
let InstalledDist::Url(InstalledDirectUrlDist { direct_url, .. }) = &distribution
|
||||
else {
|
||||
|
|
|
@ -254,9 +254,16 @@ fn required_dist(requirement: &Requirement) -> Result<Option<Dist>, distribution
|
|||
}))
|
||||
}
|
||||
RequirementSource::Path {
|
||||
path,
|
||||
install_path,
|
||||
lock_path,
|
||||
url,
|
||||
editable,
|
||||
} => Dist::from_file_url(requirement.name.clone(), url.clone(), path, *editable)?,
|
||||
} => Dist::from_file_url(
|
||||
requirement.name.clone(),
|
||||
url.clone(),
|
||||
install_path,
|
||||
lock_path,
|
||||
*editable,
|
||||
)?,
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -139,15 +139,15 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
|
||||
let source = match &requirement.url.parsed_url {
|
||||
// If the path points to a directory, attempt to read the name from static metadata.
|
||||
ParsedUrl::Path(parsed_path_url) if parsed_path_url.path.is_dir() => {
|
||||
ParsedUrl::Path(parsed_path_url) if parsed_path_url.install_path.is_dir() => {
|
||||
// Attempt to read a `PKG-INFO` from the directory.
|
||||
if let Some(metadata) = fs_err::read(parsed_path_url.path.join("PKG-INFO"))
|
||||
if let Some(metadata) = fs_err::read(parsed_path_url.install_path.join("PKG-INFO"))
|
||||
.ok()
|
||||
.and_then(|contents| Metadata10::parse_pkg_info(&contents).ok())
|
||||
{
|
||||
debug!(
|
||||
"Found PKG-INFO metadata for {path} ({name})",
|
||||
path = parsed_path_url.path.display(),
|
||||
path = parsed_path_url.install_path.display(),
|
||||
name = metadata.name
|
||||
);
|
||||
return Ok(pep508_rs::Requirement {
|
||||
|
@ -160,7 +160,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
}
|
||||
|
||||
// Attempt to read a `pyproject.toml` file.
|
||||
let project_path = parsed_path_url.path.join("pyproject.toml");
|
||||
let project_path = parsed_path_url.install_path.join("pyproject.toml");
|
||||
if let Some(pyproject) = fs_err::read_to_string(project_path)
|
||||
.ok()
|
||||
.and_then(|contents| toml::from_str::<PyProjectToml>(&contents).ok())
|
||||
|
@ -169,7 +169,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
if let Some(project) = pyproject.project {
|
||||
debug!(
|
||||
"Found PEP 621 metadata for {path} in `pyproject.toml` ({name})",
|
||||
path = parsed_path_url.path.display(),
|
||||
path = parsed_path_url.install_path.display(),
|
||||
name = project.name
|
||||
);
|
||||
return Ok(pep508_rs::Requirement {
|
||||
|
@ -187,7 +187,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
if let Some(name) = poetry.name {
|
||||
debug!(
|
||||
"Found Poetry metadata for {path} in `pyproject.toml` ({name})",
|
||||
path = parsed_path_url.path.display(),
|
||||
path = parsed_path_url.install_path.display(),
|
||||
name = name
|
||||
);
|
||||
return Ok(pep508_rs::Requirement {
|
||||
|
@ -204,7 +204,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
|
||||
// Attempt to read a `setup.cfg` from the directory.
|
||||
if let Some(setup_cfg) =
|
||||
fs_err::read_to_string(parsed_path_url.path.join("setup.cfg"))
|
||||
fs_err::read_to_string(parsed_path_url.install_path.join("setup.cfg"))
|
||||
.ok()
|
||||
.and_then(|contents| {
|
||||
let mut ini = Ini::new_cs();
|
||||
|
@ -217,7 +217,7 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
if let Ok(name) = PackageName::from_str(name) {
|
||||
debug!(
|
||||
"Found setuptools metadata for {path} in `setup.cfg` ({name})",
|
||||
path = parsed_path_url.path.display(),
|
||||
path = parsed_path_url.install_path.display(),
|
||||
name = name
|
||||
);
|
||||
return Ok(pep508_rs::Requirement {
|
||||
|
@ -234,14 +234,14 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
|
||||
SourceUrl::Directory(DirectorySourceUrl {
|
||||
url: &requirement.url.verbatim,
|
||||
path: Cow::Borrowed(&parsed_path_url.path),
|
||||
path: Cow::Borrowed(&parsed_path_url.install_path),
|
||||
editable: parsed_path_url.editable,
|
||||
})
|
||||
}
|
||||
// If it's not a directory, assume it's a file.
|
||||
ParsedUrl::Path(parsed_path_url) => SourceUrl::Path(PathSourceUrl {
|
||||
url: &requirement.url.verbatim,
|
||||
path: Cow::Borrowed(&parsed_path_url.path),
|
||||
path: Cow::Borrowed(&parsed_path_url.install_path),
|
||||
}),
|
||||
ParsedUrl::Archive(parsed_archive_url) => SourceUrl::Direct(DirectSourceUrl {
|
||||
url: &parsed_archive_url.url,
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -91,6 +91,7 @@ pub(crate) async fn run(
|
|||
.await?;
|
||||
project::sync::do_sync(
|
||||
project.project_name(),
|
||||
project.workspace().root(),
|
||||
&venv,
|
||||
&lock,
|
||||
&index_locations,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use anyhow::Result;
|
||||
use std::path::Path;
|
||||
|
||||
use distribution_types::IndexLocations;
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
|
@ -60,6 +61,7 @@ pub(crate) async fn sync(
|
|||
// Perform the sync operation.
|
||||
do_sync(
|
||||
project.project_name(),
|
||||
project.workspace().root(),
|
||||
&venv,
|
||||
&lock,
|
||||
&index_locations,
|
||||
|
@ -77,7 +79,8 @@ pub(crate) async fn sync(
|
|||
/// Sync a lockfile with an environment.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) async fn do_sync(
|
||||
project: &PackageName,
|
||||
project_name: &PackageName,
|
||||
workspace_root: &Path,
|
||||
venv: &PythonEnvironment,
|
||||
lock: &Lock,
|
||||
index_locations: &IndexLocations,
|
||||
|
@ -108,7 +111,8 @@ pub(super) async fn do_sync(
|
|||
let tags = venv.interpreter().tags()?;
|
||||
|
||||
// Read the lockfile.
|
||||
let resolution = lock.to_resolution(markers, tags, project, &extras, &dev)?;
|
||||
let resolution =
|
||||
lock.to_resolution(workspace_root, markers, tags, project_name, &extras, &dev)?;
|
||||
|
||||
// Initialize the registry client.
|
||||
// TODO(zanieb): Support client options e.g. offline, tls, etc.
|
||||
|
|
|
@ -90,8 +90,8 @@ fn lock_wheel_registry() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "anyio"
|
||||
|
@ -173,8 +173,8 @@ fn lock_sdist_registry() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "source-distribution"
|
||||
|
@ -232,8 +232,8 @@ fn lock_sdist_git() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "uv-public-pypackage"
|
||||
|
@ -347,8 +347,8 @@ fn lock_wheel_url() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "anyio"
|
||||
|
@ -472,8 +472,8 @@ fn lock_sdist_url() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "anyio"
|
||||
|
@ -608,8 +608,8 @@ fn lock_project_extra() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "anyio"
|
||||
|
@ -893,8 +893,8 @@ fn lock_dependency_extra() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "flask"
|
||||
|
@ -1116,8 +1116,8 @@ fn lock_conditional_dependency_extra() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "requests"
|
||||
|
@ -1210,7 +1210,7 @@ fn lock_conditional_dependency_extra() -> Result<()> {
|
|||
fs_err::copy(lockfile, context_38.temp_dir.join("uv.lock"))?;
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context_38.sync(), @r###"
|
||||
uv_snapshot!(context_38.filters(), context_38.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1277,8 +1277,8 @@ fn lock_preference() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "iniconfig"
|
||||
|
@ -1330,8 +1330,8 @@ fn lock_preference() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "iniconfig"
|
||||
|
@ -1372,8 +1372,8 @@ fn lock_preference() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "iniconfig"
|
||||
|
@ -1426,8 +1426,8 @@ fn lock_git_sha() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "uv-public-pypackage"
|
||||
|
@ -1484,8 +1484,8 @@ fn lock_git_sha() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "uv-public-pypackage"
|
||||
|
@ -1527,8 +1527,8 @@ fn lock_git_sha() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "uv-public-pypackage"
|
||||
|
@ -1704,8 +1704,8 @@ fn lock_requires_python() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "pygls"
|
||||
|
@ -1869,8 +1869,8 @@ fn lock_requires_python() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "pygls"
|
||||
|
@ -2022,8 +2022,8 @@ fn lock_requires_python() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "pygls"
|
||||
|
@ -2224,8 +2224,8 @@ fn lock_requires_python_star() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "linehaul"
|
||||
|
@ -2393,8 +2393,8 @@ fn lock_requires_python_pre() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "linehaul"
|
||||
|
@ -2477,8 +2477,8 @@ fn lock_dev() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "iniconfig"
|
||||
|
@ -2578,8 +2578,8 @@ fn lock_conditional_unconditional() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "iniconfig"
|
||||
|
@ -2638,8 +2638,8 @@ fn lock_multiple_markers() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "iniconfig"
|
||||
|
|
|
@ -92,8 +92,8 @@ fn fork_basic() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "package-a"
|
||||
|
@ -220,8 +220,8 @@ fn fork_marker_accrue() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "package-a"
|
||||
|
@ -414,8 +414,8 @@ fn fork_marker_selection() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "package-a"
|
||||
|
@ -578,8 +578,8 @@ fn fork_marker_track() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "package-a"
|
||||
|
@ -715,8 +715,8 @@ fn fork_non_fork_marker_transitive() -> Result<()> {
|
|||
[[distribution]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = "editable+file://[TEMP_DIR]/"
|
||||
sdist = { url = "file://[TEMP_DIR]/" }
|
||||
source = "editable+."
|
||||
sdist = { path = "." }
|
||||
|
||||
[[distribution.dependencies]]
|
||||
name = "package-a"
|
||||
|
|
|
@ -4,7 +4,6 @@ use std::process::Command;
|
|||
|
||||
use anyhow::Result;
|
||||
use assert_cmd::assert::OutputAssertExt;
|
||||
use url::Url;
|
||||
|
||||
use crate::common::{copy_dir_ignore, get_bin, uv_snapshot, TestContext, EXCLUDE_NEWER};
|
||||
|
||||
|
@ -529,27 +528,12 @@ fn workspace_lock_idempotence(workspace: &str, subdirectories: &[&str]) -> Resul
|
|||
.assert()
|
||||
.success();
|
||||
|
||||
let raw_lock = fs_err::read_to_string(work_dir.join("uv.lock"))?;
|
||||
// Remove temp paths from lock.
|
||||
// TODO(konsti): There shouldn't be absolute paths in the lock to begin with.
|
||||
let redacted_lock = raw_lock
|
||||
.replace(
|
||||
Url::from_directory_path(&context.temp_dir)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
"file:///tmp",
|
||||
)
|
||||
.replace(
|
||||
Url::from_directory_path(fs_err::canonicalize(&context.temp_dir)?)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
"file:///tmp",
|
||||
);
|
||||
let lock = fs_err::read_to_string(work_dir.join("uv.lock"))?;
|
||||
// Check the lockfile is the same for all resolutions.
|
||||
if let Some(shared_lock) = &shared_lock {
|
||||
assert_eq!(shared_lock, &redacted_lock);
|
||||
assert_eq!(shared_lock, &lock);
|
||||
} else {
|
||||
shared_lock = Some(redacted_lock);
|
||||
shared_lock = Some(lock);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue