mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Only use relative paths in lockfile (#6490)
For users who were using absolute paths in the `pyproject.toml` previously, this is a behavior change: We now convert all absolute paths in `path` entries to relative paths. Since i assume that no-one relies on absolute path in their lockfiles - they are intended to be portable - I'm tagging this as a bugfix. Closes https://github.com/astral-sh/uv/pull/6438 Fixes https://github.com/astral-sh/uv/issues/6371
This commit is contained in:
parent
611a9003c9
commit
f7835243c5
30 changed files with 329 additions and 383 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4828,7 +4828,6 @@ dependencies = [
|
|||
"insta",
|
||||
"install-wheel-rs",
|
||||
"nanoid",
|
||||
"path-absolutize",
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"platform-tags",
|
||||
|
|
|
@ -172,7 +172,6 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> {
|
|||
pub struct DirectorySourceUrl<'a> {
|
||||
pub url: &'a Url,
|
||||
pub install_path: Cow<'a, Path>,
|
||||
pub lock_path: Cow<'a, Path>,
|
||||
pub editable: bool,
|
||||
}
|
||||
|
||||
|
@ -187,7 +186,6 @@ impl<'a> From<&'a DirectorySourceDist> for DirectorySourceUrl<'a> {
|
|||
Self {
|
||||
url: &dist.url,
|
||||
install_path: Cow::Borrowed(&dist.install_path),
|
||||
lock_path: Cow::Borrowed(&dist.lock_path),
|
||||
editable: dist.editable,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,8 +122,7 @@ impl CachedDist {
|
|||
.map_err(|()| anyhow!("Invalid path in file URL"))?;
|
||||
Ok(Some(ParsedUrl::Directory(ParsedDirectoryUrl {
|
||||
url: dist.url.raw().clone(),
|
||||
install_path: path.clone(),
|
||||
lock_path: path,
|
||||
install_path: path,
|
||||
editable: dist.editable,
|
||||
})))
|
||||
} else {
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
//!
|
||||
//! Since we read this information from [`direct_url.json`](https://packaging.python.org/en/latest/specifications/direct-url-data-structure/), it doesn't match the information [`Dist`] exactly.
|
||||
use std::borrow::Cow;
|
||||
use std::path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -237,10 +238,6 @@ pub struct PathBuiltDist {
|
|||
pub filename: WheelFilename,
|
||||
/// The absolute path to the wheel which we use for installing.
|
||||
pub install_path: PathBuf,
|
||||
/// The absolute path or path relative to the workspace root pointing to the wheel
|
||||
/// which we use for locking. Unlike `given` on the verbatim URL all environment variables
|
||||
/// are resolved, and unlike the install path, we did not yet join it on the base directory.
|
||||
pub lock_path: PathBuf,
|
||||
/// The URL as it was provided by the user.
|
||||
pub url: VerbatimUrl,
|
||||
}
|
||||
|
@ -298,10 +295,6 @@ pub struct PathSourceDist {
|
|||
pub name: PackageName,
|
||||
/// The 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 file extension, e.g. `tar.gz`, `zip`, etc.
|
||||
pub ext: SourceDistExtension,
|
||||
/// The URL as it was provided by the user.
|
||||
|
@ -314,10 +307,6 @@ pub struct DirectorySourceDist {
|
|||
pub name: PackageName,
|
||||
/// The 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.
|
||||
|
@ -369,11 +358,10 @@ impl Dist {
|
|||
name: PackageName,
|
||||
url: VerbatimUrl,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
ext: DistExtension,
|
||||
) -> Result<Dist, Error> {
|
||||
// Convert to an absolute path.
|
||||
let install_path = std::path::absolute(install_path)?;
|
||||
let install_path = path::absolute(install_path)?;
|
||||
|
||||
// Normalize the path.
|
||||
let install_path = normalize_absolute_path(&install_path)?;
|
||||
|
@ -398,14 +386,12 @@ impl Dist {
|
|||
Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
|
||||
filename,
|
||||
install_path,
|
||||
lock_path: lock_path.to_path_buf(),
|
||||
url,
|
||||
})))
|
||||
}
|
||||
DistExtension::Source(ext) => Ok(Self::Source(SourceDist::Path(PathSourceDist {
|
||||
name,
|
||||
install_path,
|
||||
lock_path: lock_path.to_path_buf(),
|
||||
ext,
|
||||
url,
|
||||
}))),
|
||||
|
@ -417,11 +403,10 @@ impl Dist {
|
|||
name: PackageName,
|
||||
url: VerbatimUrl,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
editable: bool,
|
||||
) -> Result<Dist, Error> {
|
||||
// Convert to an absolute path.
|
||||
let install_path = std::path::absolute(install_path)?;
|
||||
let install_path = path::absolute(install_path)?;
|
||||
|
||||
// Normalize the path.
|
||||
let install_path = normalize_absolute_path(&install_path)?;
|
||||
|
@ -435,7 +420,6 @@ impl Dist {
|
|||
Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
|
||||
name,
|
||||
install_path,
|
||||
lock_path: lock_path.to_path_buf(),
|
||||
editable,
|
||||
url,
|
||||
})))
|
||||
|
@ -466,18 +450,13 @@ impl Dist {
|
|||
archive.subdirectory,
|
||||
archive.ext,
|
||||
),
|
||||
ParsedUrl::Path(file) => Self::from_file_url(
|
||||
name,
|
||||
url.verbatim,
|
||||
&file.install_path,
|
||||
&file.lock_path,
|
||||
file.ext,
|
||||
),
|
||||
ParsedUrl::Path(file) => {
|
||||
Self::from_file_url(name, url.verbatim, &file.install_path, file.ext)
|
||||
}
|
||||
ParsedUrl::Directory(directory) => Self::from_directory_url(
|
||||
name,
|
||||
url.verbatim,
|
||||
&directory.install_path,
|
||||
&directory.lock_path,
|
||||
directory.editable,
|
||||
),
|
||||
ParsedUrl::Git(git) => {
|
||||
|
|
|
@ -185,7 +185,6 @@ impl From<&ResolvedDist> for Requirement {
|
|||
}
|
||||
Dist::Built(BuiltDist::Path(wheel)) => RequirementSource::Path {
|
||||
install_path: wheel.install_path.clone(),
|
||||
lock_path: wheel.lock_path.clone(),
|
||||
url: wheel.url.clone(),
|
||||
ext: DistExtension::Wheel,
|
||||
},
|
||||
|
@ -214,13 +213,11 @@ impl From<&ResolvedDist> for Requirement {
|
|||
},
|
||||
Dist::Source(SourceDist::Path(sdist)) => RequirementSource::Path {
|
||||
install_path: sdist.install_path.clone(),
|
||||
lock_path: sdist.lock_path.clone(),
|
||||
url: sdist.url.clone(),
|
||||
ext: DistExtension::Source(sdist.ext),
|
||||
},
|
||||
Dist::Source(SourceDist::Directory(sdist)) => RequirementSource::Directory {
|
||||
install_path: sdist.install_path.clone(),
|
||||
lock_path: sdist.lock_path.clone(),
|
||||
url: sdist.url.clone(),
|
||||
editable: sdist.editable,
|
||||
},
|
||||
|
|
|
@ -71,14 +71,12 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
|||
ParsedUrl::Directory(ParsedDirectoryUrl {
|
||||
url: verbatim.to_url(),
|
||||
install_path: verbatim.as_path()?,
|
||||
lock_path: path.as_ref().to_path_buf(),
|
||||
editable: false,
|
||||
})
|
||||
} else {
|
||||
ParsedUrl::Path(ParsedPathUrl {
|
||||
url: verbatim.to_url(),
|
||||
install_path: verbatim.as_path()?,
|
||||
lock_path: path.as_ref().to_path_buf(),
|
||||
ext: DistExtension::from_path(&path).map_err(|err| {
|
||||
ParsedUrlError::MissingExtensionPath(path.as_ref().to_path_buf(), err)
|
||||
})?,
|
||||
|
@ -102,14 +100,12 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
|
|||
ParsedUrl::Directory(ParsedDirectoryUrl {
|
||||
url: verbatim.to_url(),
|
||||
install_path: verbatim.as_path()?,
|
||||
lock_path: path.as_ref().to_path_buf(),
|
||||
editable: false,
|
||||
})
|
||||
} else {
|
||||
ParsedUrl::Path(ParsedPathUrl {
|
||||
url: verbatim.to_url(),
|
||||
install_path: verbatim.as_path()?,
|
||||
lock_path: path.as_ref().to_path_buf(),
|
||||
ext: DistExtension::from_path(&path).map_err(|err| {
|
||||
ParsedUrlError::MissingExtensionPath(path.as_ref().to_path_buf(), err)
|
||||
})?,
|
||||
|
@ -187,26 +183,16 @@ pub struct ParsedPathUrl {
|
|||
pub url: Url,
|
||||
/// The 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 file extension, e.g. `tar.gz`, `zip`, etc.
|
||||
pub ext: DistExtension,
|
||||
}
|
||||
|
||||
impl ParsedPathUrl {
|
||||
/// Construct a [`ParsedPathUrl`] from a path requirement source.
|
||||
pub fn from_source(
|
||||
install_path: PathBuf,
|
||||
lock_path: PathBuf,
|
||||
ext: DistExtension,
|
||||
url: Url,
|
||||
) -> Self {
|
||||
pub fn from_source(install_path: PathBuf, ext: DistExtension, url: Url) -> Self {
|
||||
Self {
|
||||
url,
|
||||
install_path,
|
||||
lock_path,
|
||||
ext,
|
||||
}
|
||||
}
|
||||
|
@ -221,25 +207,15 @@ pub struct ParsedDirectoryUrl {
|
|||
pub url: Url,
|
||||
/// The 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 ParsedDirectoryUrl {
|
||||
/// Construct a [`ParsedDirectoryUrl`] from a path requirement source.
|
||||
pub fn from_source(
|
||||
install_path: PathBuf,
|
||||
lock_path: PathBuf,
|
||||
editable: bool,
|
||||
url: Url,
|
||||
) -> Self {
|
||||
pub fn from_source(install_path: PathBuf, editable: bool, url: Url) -> Self {
|
||||
Self {
|
||||
url,
|
||||
install_path,
|
||||
lock_path,
|
||||
editable,
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +369,6 @@ impl TryFrom<Url> for ParsedUrl {
|
|||
Ok(Self::Directory(ParsedDirectoryUrl {
|
||||
url,
|
||||
install_path: path.clone(),
|
||||
lock_path: path,
|
||||
editable: false,
|
||||
}))
|
||||
} else {
|
||||
|
@ -402,7 +377,6 @@ impl TryFrom<Url> for ParsedUrl {
|
|||
ext: DistExtension::from_path(&path)
|
||||
.map_err(|err| ParsedUrlError::MissingExtensionPath(path.clone(), err))?,
|
||||
install_path: path.clone(),
|
||||
lock_path: path,
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -10,7 +11,7 @@ use pep440_rs::VersionSpecifiers;
|
|||
use pep508_rs::{
|
||||
marker, MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl,
|
||||
};
|
||||
use uv_fs::{PortablePathBuf, CWD};
|
||||
use uv_fs::{relative_to, PortablePathBuf, CWD};
|
||||
use uv_git::{GitReference, GitSha, GitUrl};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
|
||||
|
@ -105,6 +106,14 @@ impl Requirement {
|
|||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the requirement to a [`Requirement`] relative to the given path.
|
||||
pub fn relative_to(self, path: &Path) -> Result<Self, io::Error> {
|
||||
Ok(Self {
|
||||
source: self.source.relative_to(path)?,
|
||||
..self
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Requirement> for pep508_rs::Requirement<VerbatimUrl> {
|
||||
|
@ -175,28 +184,24 @@ impl From<Requirement> for pep508_rs::Requirement<VerbatimParsedUrl> {
|
|||
}
|
||||
RequirementSource::Path {
|
||||
install_path,
|
||||
lock_path,
|
||||
ext,
|
||||
url,
|
||||
} => Some(VersionOrUrl::Url(VerbatimParsedUrl {
|
||||
parsed_url: ParsedUrl::Path(ParsedPathUrl {
|
||||
url: url.to_url(),
|
||||
install_path,
|
||||
lock_path,
|
||||
ext,
|
||||
}),
|
||||
verbatim: url,
|
||||
})),
|
||||
RequirementSource::Directory {
|
||||
install_path,
|
||||
lock_path,
|
||||
editable,
|
||||
url,
|
||||
} => Some(VersionOrUrl::Url(VerbatimParsedUrl {
|
||||
parsed_url: ParsedUrl::Directory(ParsedDirectoryUrl {
|
||||
url: url.to_url(),
|
||||
install_path,
|
||||
lock_path,
|
||||
editable,
|
||||
}),
|
||||
verbatim: url,
|
||||
|
@ -342,10 +347,6 @@ pub enum RequirementSource {
|
|||
Path {
|
||||
/// The 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,
|
||||
/// The file extension, e.g. `tar.gz`, `zip`, etc.
|
||||
ext: DistExtension,
|
||||
/// The PEP 508 style URL in the format
|
||||
|
@ -357,10 +358,6 @@ pub enum RequirementSource {
|
|||
Directory {
|
||||
/// The 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
|
||||
|
@ -376,13 +373,11 @@ impl RequirementSource {
|
|||
match parsed_url {
|
||||
ParsedUrl::Path(local_file) => RequirementSource::Path {
|
||||
install_path: local_file.install_path.clone(),
|
||||
lock_path: local_file.lock_path.clone(),
|
||||
ext: local_file.ext,
|
||||
url,
|
||||
},
|
||||
ParsedUrl::Directory(directory) => RequirementSource::Directory {
|
||||
install_path: directory.install_path.clone(),
|
||||
lock_path: directory.lock_path.clone(),
|
||||
editable: directory.editable,
|
||||
url,
|
||||
},
|
||||
|
@ -427,13 +422,11 @@ impl RequirementSource {
|
|||
}),
|
||||
Self::Path {
|
||||
install_path,
|
||||
lock_path,
|
||||
ext,
|
||||
url,
|
||||
} => Some(VerbatimParsedUrl {
|
||||
parsed_url: ParsedUrl::Path(ParsedPathUrl::from_source(
|
||||
install_path.clone(),
|
||||
lock_path.clone(),
|
||||
*ext,
|
||||
url.to_url(),
|
||||
)),
|
||||
|
@ -441,13 +434,11 @@ impl RequirementSource {
|
|||
}),
|
||||
Self::Directory {
|
||||
install_path,
|
||||
lock_path,
|
||||
editable,
|
||||
url,
|
||||
} => Some(VerbatimParsedUrl {
|
||||
parsed_url: ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
|
||||
install_path.clone(),
|
||||
lock_path.clone(),
|
||||
*editable,
|
||||
url.to_url(),
|
||||
)),
|
||||
|
@ -504,6 +495,33 @@ impl RequirementSource {
|
|||
| RequirementSource::Directory { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the source to a [`RequirementSource`] relative to the given path.
|
||||
pub fn relative_to(self, path: &Path) -> Result<Self, io::Error> {
|
||||
match self {
|
||||
RequirementSource::Registry { .. }
|
||||
| RequirementSource::Url { .. }
|
||||
| RequirementSource::Git { .. } => Ok(self),
|
||||
RequirementSource::Path {
|
||||
install_path,
|
||||
ext,
|
||||
url,
|
||||
} => Ok(Self::Path {
|
||||
install_path: relative_to(&install_path, path)?,
|
||||
ext,
|
||||
url,
|
||||
}),
|
||||
RequirementSource::Directory {
|
||||
install_path,
|
||||
editable,
|
||||
url,
|
||||
} => Ok(Self::Directory {
|
||||
install_path: relative_to(&install_path, path)?,
|
||||
editable,
|
||||
url,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RequirementSource {
|
||||
|
@ -639,26 +657,24 @@ impl From<RequirementSource> for RequirementSourceWire {
|
|||
}
|
||||
}
|
||||
RequirementSource::Path {
|
||||
install_path: _,
|
||||
lock_path,
|
||||
install_path,
|
||||
ext: _,
|
||||
url: _,
|
||||
} => Self::Path {
|
||||
path: PortablePathBuf::from(lock_path),
|
||||
path: PortablePathBuf::from(install_path),
|
||||
},
|
||||
RequirementSource::Directory {
|
||||
install_path: _,
|
||||
lock_path,
|
||||
install_path,
|
||||
editable,
|
||||
url: _,
|
||||
} => {
|
||||
if editable {
|
||||
Self::Editable {
|
||||
editable: PortablePathBuf::from(lock_path),
|
||||
editable: PortablePathBuf::from(install_path),
|
||||
}
|
||||
} else {
|
||||
Self::Directory {
|
||||
directory: PortablePathBuf::from(lock_path),
|
||||
directory: PortablePathBuf::from(install_path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -732,8 +748,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
|||
Ok(Self::Path {
|
||||
ext: DistExtension::from_path(path.as_path())
|
||||
.map_err(|err| ParsedUrlError::MissingExtensionPath(path.clone(), err))?,
|
||||
install_path: path.clone(),
|
||||
lock_path: path,
|
||||
install_path: path,
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
@ -741,8 +756,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
|||
let directory = PathBuf::from(directory);
|
||||
let url = VerbatimUrl::parse_path(&directory, &*CWD)?;
|
||||
Ok(Self::Directory {
|
||||
install_path: directory.clone(),
|
||||
lock_path: directory,
|
||||
install_path: directory,
|
||||
editable: false,
|
||||
url,
|
||||
})
|
||||
|
@ -751,8 +765,7 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
|
|||
let editable = PathBuf::from(editable);
|
||||
let url = VerbatimUrl::parse_path(&editable, &*CWD)?;
|
||||
Ok(Self::Directory {
|
||||
install_path: editable.clone(),
|
||||
lock_path: editable,
|
||||
install_path: editable,
|
||||
editable: true,
|
||||
url,
|
||||
})
|
||||
|
@ -809,7 +822,6 @@ mod tests {
|
|||
marker: MarkerTree::TRUE,
|
||||
source: RequirementSource::Directory {
|
||||
install_path: PathBuf::from(path),
|
||||
lock_path: PathBuf::from(path),
|
||||
editable: false,
|
||||
url: VerbatimUrl::from_path(Path::new(path)).unwrap(),
|
||||
},
|
||||
|
|
|
@ -1845,7 +1845,6 @@ mod test {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "/foo/bar",
|
||||
lock_path: "/foo/bar",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -22,7 +22,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
@ -72,7 +71,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
@ -126,7 +124,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "/scripts/packages/black_editable",
|
||||
lock_path: "/scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -24,7 +24,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -81,7 +80,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -138,7 +136,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -195,7 +192,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -252,7 +248,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -302,7 +297,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
lock_path: "./editable[d",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -22,7 +22,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
@ -72,7 +71,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "./scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
@ -126,7 +124,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
|
||||
lock_path: "scripts/packages/black_editable",
|
||||
editable: false,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -24,7 +24,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -81,7 +80,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -138,7 +136,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -195,7 +192,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -252,7 +248,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable",
|
||||
lock_path: "./editable",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
@ -302,7 +297,6 @@ RequirementsTxt {
|
|||
fragment: None,
|
||||
},
|
||||
install_path: "<REQUIREMENTS_DIR>/editable[d",
|
||||
lock_path: "./editable[d",
|
||||
editable: true,
|
||||
},
|
||||
),
|
||||
|
|
|
@ -35,7 +35,6 @@ anyhow = { workspace = true }
|
|||
fs-err = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
nanoid = { workspace = true }
|
||||
path-absolutize = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
reqwest-middleware = { workspace = true }
|
||||
rmp-serde = { workspace = true }
|
||||
|
|
|
@ -2,14 +2,14 @@ use std::collections::BTreeMap;
|
|||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use distribution_filename::DistExtension;
|
||||
use path_absolutize::Absolutize;
|
||||
use pep440_rs::VersionSpecifiers;
|
||||
use pep508_rs::{VerbatimUrl, VersionOrUrl};
|
||||
use pypi_types::{ParsedUrlError, Requirement, RequirementSource, VerbatimParsedUrl};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use uv_fs::{relative_to, Simplified};
|
||||
use uv_fs::Simplified;
|
||||
use uv_git::GitReference;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_warnings::warn_user_once;
|
||||
|
@ -142,14 +142,15 @@ impl LoweredRequirement {
|
|||
// relative to workspace: `packages/current_project`
|
||||
// workspace lock root: `../current_workspace`
|
||||
// relative to main workspace: `../current_workspace/packages/current_project`
|
||||
let relative_to_workspace = relative_to(member.root(), workspace.install_path())
|
||||
.map_err(LoweringError::RelativeTo)?;
|
||||
let relative_to_main_workspace = workspace.lock_path().join(relative_to_workspace);
|
||||
let url = VerbatimUrl::parse_absolute_path(member.root())?
|
||||
.with_given(relative_to_main_workspace.to_string_lossy());
|
||||
let url = VerbatimUrl::parse_absolute_path(member.root())?;
|
||||
let install_path = url.to_file_path().map_err(|()| {
|
||||
LoweringError::RelativeTo(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Invalid path in file URL",
|
||||
))
|
||||
})?;
|
||||
RequirementSource::Directory {
|
||||
install_path: member.root().clone(),
|
||||
lock_path: relative_to_main_workspace,
|
||||
install_path,
|
||||
url,
|
||||
editable: true,
|
||||
}
|
||||
|
@ -360,28 +361,20 @@ fn path_source(
|
|||
Origin::Workspace => workspace_root,
|
||||
};
|
||||
let url = VerbatimUrl::parse_path(path, base)?.with_given(path.to_string_lossy());
|
||||
let absolute_path = path
|
||||
.to_path_buf()
|
||||
.absolutize_from(base)
|
||||
.map_err(|err| LoweringError::Absolutize(path.to_path_buf(), err))?
|
||||
.to_path_buf();
|
||||
let relative_to_workspace = if path.is_relative() {
|
||||
// Relative paths in a project are relative to the project root, but the lockfile is
|
||||
// relative to the workspace root.
|
||||
relative_to(&absolute_path, workspace_root).map_err(LoweringError::RelativeTo)?
|
||||
} else {
|
||||
// If the user gave us an absolute path, we respect that.
|
||||
path.to_path_buf()
|
||||
};
|
||||
let is_dir = if let Ok(metadata) = absolute_path.metadata() {
|
||||
let install_path = url.to_file_path().map_err(|()| {
|
||||
LoweringError::RelativeTo(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Invalid path in file URL",
|
||||
))
|
||||
})?;
|
||||
let is_dir = if let Ok(metadata) = install_path.metadata() {
|
||||
metadata.is_dir()
|
||||
} else {
|
||||
absolute_path.extension().is_none()
|
||||
install_path.extension().is_none()
|
||||
};
|
||||
if is_dir {
|
||||
Ok(RequirementSource::Directory {
|
||||
install_path: absolute_path,
|
||||
lock_path: relative_to_workspace,
|
||||
install_path,
|
||||
url,
|
||||
editable,
|
||||
})
|
||||
|
@ -390,10 +383,9 @@ fn path_source(
|
|||
return Err(LoweringError::EditableFile(url.to_string()));
|
||||
}
|
||||
Ok(RequirementSource::Path {
|
||||
install_path: absolute_path,
|
||||
lock_path: relative_to_workspace,
|
||||
ext: DistExtension::from_path(path)
|
||||
ext: DistExtension::from_path(&install_path)
|
||||
.map_err(|err| ParsedUrlError::MissingExtensionPath(path.to_path_buf(), err))?,
|
||||
install_path,
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ impl Metadata {
|
|||
pub async fn from_workspace(
|
||||
metadata: Metadata23,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
sources: SourceStrategy,
|
||||
) -> Result<Self, MetadataError> {
|
||||
// Lower the requirements.
|
||||
|
@ -75,7 +74,6 @@ impl Metadata {
|
|||
provides_extras: metadata.provides_extras,
|
||||
},
|
||||
install_path,
|
||||
lock_path,
|
||||
sources,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -37,7 +37,6 @@ impl RequiresDist {
|
|||
pub async fn from_project_maybe_workspace(
|
||||
metadata: pypi_types::RequiresDist,
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
sources: SourceStrategy,
|
||||
) -> Result<Self, MetadataError> {
|
||||
match sources {
|
||||
|
@ -46,7 +45,6 @@ impl RequiresDist {
|
|||
// TODO(konsti): Cache workspace discovery.
|
||||
let Some(project_workspace) = ProjectWorkspace::from_maybe_project_root(
|
||||
install_path,
|
||||
lock_path,
|
||||
&DiscoveryOptions::default(),
|
||||
)
|
||||
.await?
|
||||
|
@ -160,7 +158,6 @@ mod test {
|
|||
let pyproject_toml = PyProjectToml::from_string(contents.to_string())?;
|
||||
let path = Path::new("pyproject.toml");
|
||||
let project_workspace = ProjectWorkspace::from_project(
|
||||
path,
|
||||
path,
|
||||
pyproject_toml
|
||||
.project
|
||||
|
|
|
@ -425,7 +425,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
let requires_dist = RequiresDist::from_project_maybe_workspace(
|
||||
requires_dist,
|
||||
project_root,
|
||||
project_root,
|
||||
self.build_context.sources(),
|
||||
)
|
||||
.await?;
|
||||
|
@ -1009,7 +1008,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
Metadata::from_workspace(
|
||||
metadata,
|
||||
resource.install_path.as_ref(),
|
||||
resource.lock_path.as_ref(),
|
||||
self.build_context.sources(),
|
||||
)
|
||||
.await?,
|
||||
|
@ -1024,7 +1022,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
Metadata::from_workspace(
|
||||
metadata,
|
||||
resource.install_path.as_ref(),
|
||||
resource.lock_path.as_ref(),
|
||||
self.build_context.sources(),
|
||||
)
|
||||
.await?,
|
||||
|
@ -1049,7 +1046,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
Metadata::from_workspace(
|
||||
metadata,
|
||||
resource.install_path.as_ref(),
|
||||
resource.lock_path.as_ref(),
|
||||
self.build_context.sources(),
|
||||
)
|
||||
.await?,
|
||||
|
@ -1081,7 +1077,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
Metadata::from_workspace(
|
||||
metadata,
|
||||
resource.install_path.as_ref(),
|
||||
resource.lock_path.as_ref(),
|
||||
self.build_context.sources(),
|
||||
)
|
||||
.await?,
|
||||
|
@ -1252,8 +1247,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
Self::read_static_metadata(source, fetch.path(), resource.subdirectory).await?
|
||||
{
|
||||
return Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, &path, &path, self.build_context.sources())
|
||||
.await?,
|
||||
Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1276,8 +1270,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
|
||||
debug!("Using cached metadata for: {source}");
|
||||
return Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, &path, &path, self.build_context.sources())
|
||||
.await?,
|
||||
Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1297,8 +1290,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map_err(Error::CacheWrite)?;
|
||||
|
||||
return Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(metadata, &path, &path, self.build_context.sources())
|
||||
.await?,
|
||||
Metadata::from_workspace(metadata, &path, self.build_context.sources()).await?,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -1324,13 +1316,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
|
|||
.map_err(Error::CacheWrite)?;
|
||||
|
||||
Ok(ArchiveMetadata::from(
|
||||
Metadata::from_workspace(
|
||||
metadata,
|
||||
fetch.path(),
|
||||
fetch.path(),
|
||||
self.build_context.sources(),
|
||||
)
|
||||
.await?,
|
||||
Metadata::from_workspace(metadata, fetch.path(), self.build_context.sources()).await?,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
@ -266,7 +266,6 @@ impl<'a> Planner<'a> {
|
|||
url,
|
||||
editable,
|
||||
install_path,
|
||||
lock_path,
|
||||
} => {
|
||||
// Convert to an absolute path.
|
||||
let install_path = std::path::absolute(install_path)?;
|
||||
|
@ -283,7 +282,6 @@ impl<'a> Planner<'a> {
|
|||
name: requirement.name.clone(),
|
||||
url: url.clone(),
|
||||
install_path,
|
||||
lock_path: lock_path.clone(),
|
||||
editable: *editable,
|
||||
};
|
||||
|
||||
|
@ -305,7 +303,6 @@ impl<'a> Planner<'a> {
|
|||
ext,
|
||||
url,
|
||||
install_path,
|
||||
lock_path,
|
||||
} => {
|
||||
// Convert to an absolute path.
|
||||
let install_path = std::path::absolute(install_path)?;
|
||||
|
@ -335,21 +332,20 @@ impl<'a> Planner<'a> {
|
|||
filename,
|
||||
url: url.clone(),
|
||||
install_path,
|
||||
lock_path: lock_path.clone(),
|
||||
};
|
||||
|
||||
if !wheel.filename.is_compatible(tags) {
|
||||
bail!(
|
||||
"A path dependency is incompatible with the current platform: {}",
|
||||
wheel.lock_path.user_display()
|
||||
);
|
||||
"A path dependency is incompatible with the current platform: {}",
|
||||
wheel.install_path.user_display()
|
||||
);
|
||||
}
|
||||
|
||||
if no_binary {
|
||||
bail!(
|
||||
"A path dependency points to a wheel which conflicts with `--no-binary`: {}",
|
||||
wheel.url
|
||||
);
|
||||
"A path dependency points to a wheel which conflicts with `--no-binary`: {}",
|
||||
wheel.url
|
||||
);
|
||||
}
|
||||
|
||||
// Find the exact wheel from the cache, since we know the filename in
|
||||
|
@ -387,7 +383,6 @@ impl<'a> Planner<'a> {
|
|||
name: requirement.name.clone(),
|
||||
url: url.clone(),
|
||||
install_path,
|
||||
lock_path: lock_path.clone(),
|
||||
ext: *ext,
|
||||
};
|
||||
|
||||
|
|
|
@ -150,7 +150,6 @@ impl RequirementSatisfaction {
|
|||
}
|
||||
RequirementSource::Path {
|
||||
install_path: requested_path,
|
||||
lock_path: _,
|
||||
ext: _,
|
||||
url: _,
|
||||
} => {
|
||||
|
@ -197,7 +196,6 @@ impl RequirementSatisfaction {
|
|||
}
|
||||
RequirementSource::Directory {
|
||||
install_path: requested_path,
|
||||
lock_path: _,
|
||||
editable: requested_editable,
|
||||
url: _,
|
||||
} => {
|
||||
|
|
|
@ -283,26 +283,17 @@ fn required_dist(requirement: &Requirement) -> Result<Option<Dist>, distribution
|
|||
}
|
||||
RequirementSource::Path {
|
||||
install_path,
|
||||
lock_path,
|
||||
ext,
|
||||
url,
|
||||
} => Dist::from_file_url(
|
||||
requirement.name.clone(),
|
||||
url.clone(),
|
||||
install_path,
|
||||
lock_path,
|
||||
*ext,
|
||||
)?,
|
||||
} => Dist::from_file_url(requirement.name.clone(), url.clone(), install_path, *ext)?,
|
||||
RequirementSource::Directory {
|
||||
install_path,
|
||||
lock_path,
|
||||
url,
|
||||
editable,
|
||||
} => Dist::from_directory_url(
|
||||
requirement.name.clone(),
|
||||
url.clone(),
|
||||
install_path,
|
||||
lock_path,
|
||||
*editable,
|
||||
)?,
|
||||
}))
|
||||
|
|
|
@ -171,7 +171,6 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
|||
let source = SourceUrl::Directory(DirectorySourceUrl {
|
||||
url: &url,
|
||||
install_path: Cow::Borrowed(source_tree),
|
||||
lock_path: Cow::Borrowed(source_tree),
|
||||
editable: false,
|
||||
});
|
||||
|
||||
|
|
|
@ -256,7 +256,6 @@ impl<'a, Context: BuildContext> NamedRequirementsResolver<'a, Context> {
|
|||
SourceUrl::Directory(DirectorySourceUrl {
|
||||
url: &requirement.url.verbatim,
|
||||
install_path: Cow::Borrowed(&parsed_directory_url.install_path),
|
||||
lock_path: Cow::Borrowed(&parsed_directory_url.lock_path),
|
||||
editable: parsed_directory_url.editable,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::borrow::Cow;
|
|||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
|
@ -30,7 +31,7 @@ use pypi_types::{
|
|||
};
|
||||
use uv_configuration::ExtrasSpecification;
|
||||
use uv_distribution::DistributionDatabase;
|
||||
use uv_fs::{relative_to, PortablePath, PortablePathBuf, Simplified};
|
||||
use uv_fs::{relative_to, PortablePath, PortablePathBuf};
|
||||
use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference};
|
||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||
use uv_types::BuildContext;
|
||||
|
@ -75,7 +76,7 @@ pub struct Lock {
|
|||
|
||||
impl Lock {
|
||||
/// Initialize a [`Lock`] from a [`ResolutionGraph`].
|
||||
pub fn from_resolution_graph(graph: &ResolutionGraph) -> Result<Self, LockError> {
|
||||
pub fn from_resolution_graph(graph: &ResolutionGraph, root: &Path) -> Result<Self, LockError> {
|
||||
let mut locked_dists = BTreeMap::new();
|
||||
|
||||
// Lock all base packages.
|
||||
|
@ -88,7 +89,7 @@ impl Lock {
|
|||
.fork_markers(dist.name(), &dist.version, dist.dist.version_or_url().url())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let mut locked_dist = Package::from_annotated_dist(dist, fork_markers)?;
|
||||
let mut locked_dist = Package::from_annotated_dist(dist, fork_markers, root)?;
|
||||
|
||||
// Add all dependencies
|
||||
for edge in graph.petgraph.edges(node_index) {
|
||||
|
@ -97,7 +98,7 @@ impl Lock {
|
|||
continue;
|
||||
};
|
||||
let marker = edge.weight().clone();
|
||||
locked_dist.add_dependency(dependency_dist, marker);
|
||||
locked_dist.add_dependency(dependency_dist, marker, root)?;
|
||||
}
|
||||
let id = locked_dist.id.clone();
|
||||
if let Some(locked_dist) = locked_dists.insert(id, locked_dist) {
|
||||
|
@ -115,7 +116,7 @@ impl Lock {
|
|||
continue;
|
||||
};
|
||||
if let Some(extra) = dist.extra.as_ref() {
|
||||
let id = PackageId::from_annotated_dist(dist);
|
||||
let id = PackageId::from_annotated_dist(dist, root)?;
|
||||
let Some(locked_dist) = locked_dists.get_mut(&id) else {
|
||||
return Err(LockErrorKind::MissingExtraBase {
|
||||
id,
|
||||
|
@ -129,11 +130,16 @@ impl Lock {
|
|||
continue;
|
||||
};
|
||||
let marker = edge.weight().clone();
|
||||
locked_dist.add_optional_dependency(extra.clone(), dependency_dist, marker);
|
||||
locked_dist.add_optional_dependency(
|
||||
extra.clone(),
|
||||
dependency_dist,
|
||||
marker,
|
||||
root,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
if let Some(group) = dist.dev.as_ref() {
|
||||
let id = PackageId::from_annotated_dist(dist);
|
||||
let id = PackageId::from_annotated_dist(dist, root)?;
|
||||
let Some(locked_dist) = locked_dists.get_mut(&id) else {
|
||||
return Err(LockErrorKind::MissingDevBase {
|
||||
id,
|
||||
|
@ -147,7 +153,7 @@ impl Lock {
|
|||
continue;
|
||||
};
|
||||
let marker = edge.weight().clone();
|
||||
locked_dist.add_dev_dependency(group.clone(), dependency_dist, marker);
|
||||
locked_dist.add_dev_dependency(group.clone(), dependency_dist, marker, root)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -981,6 +987,8 @@ pub struct ResolverManifest {
|
|||
}
|
||||
|
||||
impl ResolverManifest {
|
||||
/// Initialize a [`ResolverManifest`] with the given members, requirements, constraints, and
|
||||
/// overrides.
|
||||
pub fn new(
|
||||
members: impl IntoIterator<Item = PackageName>,
|
||||
requirements: impl IntoIterator<Item = Requirement>,
|
||||
|
@ -994,6 +1002,28 @@ impl ResolverManifest {
|
|||
overrides: overrides.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the manifest to a relative form using the given workspace.
|
||||
pub fn relative_to(self, workspace: &Workspace) -> Result<Self, io::Error> {
|
||||
Ok(Self {
|
||||
members: self.members,
|
||||
requirements: self
|
||||
.requirements
|
||||
.into_iter()
|
||||
.map(|requirement| requirement.relative_to(workspace.install_path()))
|
||||
.collect::<Result<BTreeSet<_>, _>>()?,
|
||||
constraints: self
|
||||
.constraints
|
||||
.into_iter()
|
||||
.map(|requirement| requirement.relative_to(workspace.install_path()))
|
||||
.collect::<Result<BTreeSet<_>, _>>()?,
|
||||
overrides: self
|
||||
.overrides
|
||||
.into_iter()
|
||||
.map(|requirement| requirement.relative_to(workspace.install_path()))
|
||||
.collect::<Result<BTreeSet<_>, _>>()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
|
@ -1094,8 +1124,9 @@ impl Package {
|
|||
fn from_annotated_dist(
|
||||
annotated_dist: &AnnotatedDist,
|
||||
fork_markers: Vec<MarkerTree>,
|
||||
root: &Path,
|
||||
) -> Result<Self, LockError> {
|
||||
let id = PackageId::from_annotated_dist(annotated_dist);
|
||||
let id = PackageId::from_annotated_dist(annotated_dist, root)?;
|
||||
let sdist = SourceDist::from_annotated_dist(&id, annotated_dist)?;
|
||||
let wheels = Wheel::from_annotated_dist(annotated_dist)?;
|
||||
let requires_dist = if id.source.is_immutable() {
|
||||
|
@ -1106,7 +1137,9 @@ impl Package {
|
|||
.requires_dist
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
.map(|requirement| requirement.relative_to(root))
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(LockErrorKind::RequirementRelativePath)?
|
||||
};
|
||||
let requires_dev = if id.source.is_immutable() {
|
||||
BTreeMap::default()
|
||||
|
@ -1115,8 +1148,16 @@ impl Package {
|
|||
.metadata
|
||||
.dev_dependencies
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.iter().cloned().collect()))
|
||||
.collect()
|
||||
.map(|(group, requirements)| {
|
||||
let requirements = requirements
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|requirement| requirement.relative_to(root))
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(LockErrorKind::RequirementRelativePath)?;
|
||||
Ok::<_, LockError>((group.clone(), requirements))
|
||||
})
|
||||
.collect::<Result<_, _>>()?
|
||||
};
|
||||
Ok(Package {
|
||||
id,
|
||||
|
@ -1134,17 +1175,24 @@ impl Package {
|
|||
}
|
||||
|
||||
/// Add the [`AnnotatedDist`] as a dependency of the [`Package`].
|
||||
fn add_dependency(&mut self, annotated_dist: &AnnotatedDist, marker: MarkerTree) {
|
||||
let new_dep = Dependency::from_annotated_dist(annotated_dist, marker);
|
||||
fn add_dependency(
|
||||
&mut self,
|
||||
annotated_dist: &AnnotatedDist,
|
||||
marker: MarkerTree,
|
||||
root: &Path,
|
||||
) -> Result<(), LockError> {
|
||||
let new_dep = Dependency::from_annotated_dist(annotated_dist, marker, root)?;
|
||||
for existing_dep in &mut self.dependencies {
|
||||
if existing_dep.package_id == new_dep.package_id
|
||||
&& existing_dep.marker == new_dep.marker
|
||||
{
|
||||
existing_dep.extra.extend(new_dep.extra);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
self.dependencies.push(new_dep);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add the [`AnnotatedDist`] as an optional dependency of the [`Package`].
|
||||
|
@ -1153,16 +1201,19 @@ impl Package {
|
|||
extra: ExtraName,
|
||||
annotated_dist: &AnnotatedDist,
|
||||
marker: MarkerTree,
|
||||
) {
|
||||
let dep = Dependency::from_annotated_dist(annotated_dist, marker);
|
||||
root: &Path,
|
||||
) -> Result<(), LockError> {
|
||||
let dep = Dependency::from_annotated_dist(annotated_dist, marker, root)?;
|
||||
let optional_deps = self.optional_dependencies.entry(extra).or_default();
|
||||
for existing_dep in &mut *optional_deps {
|
||||
if existing_dep.package_id == dep.package_id && existing_dep.marker == dep.marker {
|
||||
existing_dep.extra.extend(dep.extra);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
optional_deps.push(dep);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add the [`AnnotatedDist`] as a development dependency of the [`Package`].
|
||||
|
@ -1171,16 +1222,19 @@ impl Package {
|
|||
dev: GroupName,
|
||||
annotated_dist: &AnnotatedDist,
|
||||
marker: MarkerTree,
|
||||
) {
|
||||
let dep = Dependency::from_annotated_dist(annotated_dist, marker);
|
||||
root: &Path,
|
||||
) -> Result<(), LockError> {
|
||||
let dep = Dependency::from_annotated_dist(annotated_dist, marker, root)?;
|
||||
let dev_deps = self.dev_dependencies.entry(dev).or_default();
|
||||
for existing_dep in &mut *dev_deps {
|
||||
if existing_dep.package_id == dep.package_id && existing_dep.marker == dep.marker {
|
||||
existing_dep.extra.extend(dep.extra);
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
dev_deps.push(dep);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert the [`Package`] to a [`Dist`] that can be used in installation.
|
||||
|
@ -1206,7 +1260,6 @@ impl Package {
|
|||
filename,
|
||||
url: verbatim_url(workspace_root.join(path), &self.id)?,
|
||||
install_path: workspace_root.join(path),
|
||||
lock_path: path.clone(),
|
||||
};
|
||||
let built_dist = BuiltDist::Path(path_dist);
|
||||
Ok(Dist::Built(built_dist))
|
||||
|
@ -1268,7 +1321,6 @@ impl Package {
|
|||
name: self.id.name.clone(),
|
||||
url: verbatim_url(workspace_root.join(path), &self.id)?,
|
||||
install_path: workspace_root.join(path),
|
||||
lock_path: path.clone(),
|
||||
ext: SourceDistExtension::from_path(path)?,
|
||||
};
|
||||
distribution_types::SourceDist::Path(path_dist)
|
||||
|
@ -1278,7 +1330,6 @@ impl Package {
|
|||
name: self.id.name.clone(),
|
||||
url: verbatim_url(workspace_root.join(path), &self.id)?,
|
||||
install_path: workspace_root.join(path),
|
||||
lock_path: path.clone(),
|
||||
editable: false,
|
||||
};
|
||||
distribution_types::SourceDist::Directory(dir_dist)
|
||||
|
@ -1288,7 +1339,6 @@ impl Package {
|
|||
name: self.id.name.clone(),
|
||||
url: verbatim_url(workspace_root.join(path), &self.id)?,
|
||||
install_path: workspace_root.join(path),
|
||||
lock_path: path.clone(),
|
||||
editable: true,
|
||||
};
|
||||
distribution_types::SourceDist::Directory(dir_dist)
|
||||
|
@ -1691,15 +1741,18 @@ pub(crate) struct PackageId {
|
|||
}
|
||||
|
||||
impl PackageId {
|
||||
fn from_annotated_dist(annotated_dist: &AnnotatedDist) -> PackageId {
|
||||
fn from_annotated_dist(
|
||||
annotated_dist: &AnnotatedDist,
|
||||
root: &Path,
|
||||
) -> Result<PackageId, LockError> {
|
||||
let name = annotated_dist.metadata.name.clone();
|
||||
let version = annotated_dist.metadata.version.clone();
|
||||
let source = Source::from_resolved_dist(&annotated_dist.dist);
|
||||
PackageId {
|
||||
let source = Source::from_resolved_dist(&annotated_dist.dist, root)?;
|
||||
Ok(Self {
|
||||
name,
|
||||
version,
|
||||
source,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Writes this package ID inline into the table given.
|
||||
|
@ -1792,43 +1845,50 @@ enum Source {
|
|||
}
|
||||
|
||||
impl Source {
|
||||
fn from_resolved_dist(resolved_dist: &ResolvedDist) -> Source {
|
||||
fn from_resolved_dist(resolved_dist: &ResolvedDist, root: &Path) -> Result<Source, LockError> {
|
||||
match *resolved_dist {
|
||||
// We pass empty installed packages for locking.
|
||||
ResolvedDist::Installed(_) => unreachable!(),
|
||||
ResolvedDist::Installable(ref dist) => Source::from_dist(dist),
|
||||
ResolvedDist::Installable(ref dist) => Source::from_dist(dist, root),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_dist(dist: &Dist) -> Source {
|
||||
fn from_dist(dist: &Dist, root: &Path) -> Result<Source, LockError> {
|
||||
match *dist {
|
||||
Dist::Built(ref built_dist) => Source::from_built_dist(built_dist),
|
||||
Dist::Source(ref source_dist) => Source::from_source_dist(source_dist),
|
||||
Dist::Built(ref built_dist) => Source::from_built_dist(built_dist, root),
|
||||
Dist::Source(ref source_dist) => Source::from_source_dist(source_dist, root),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_built_dist(built_dist: &BuiltDist) -> Source {
|
||||
fn from_built_dist(built_dist: &BuiltDist, root: &Path) -> Result<Source, LockError> {
|
||||
match *built_dist {
|
||||
BuiltDist::Registry(ref reg_dist) => Source::from_registry_built_dist(reg_dist),
|
||||
BuiltDist::DirectUrl(ref direct_dist) => Source::from_direct_built_dist(direct_dist),
|
||||
BuiltDist::Path(ref path_dist) => Source::from_path_built_dist(path_dist),
|
||||
BuiltDist::Registry(ref reg_dist) => Ok(Source::from_registry_built_dist(reg_dist)),
|
||||
BuiltDist::DirectUrl(ref direct_dist) => {
|
||||
Ok(Source::from_direct_built_dist(direct_dist))
|
||||
}
|
||||
BuiltDist::Path(ref path_dist) => Source::from_path_built_dist(path_dist, root),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_source_dist(source_dist: &distribution_types::SourceDist) -> Source {
|
||||
fn from_source_dist(
|
||||
source_dist: &distribution_types::SourceDist,
|
||||
root: &Path,
|
||||
) -> Result<Source, LockError> {
|
||||
match *source_dist {
|
||||
distribution_types::SourceDist::Registry(ref reg_dist) => {
|
||||
Source::from_registry_source_dist(reg_dist)
|
||||
Ok(Source::from_registry_source_dist(reg_dist))
|
||||
}
|
||||
distribution_types::SourceDist::DirectUrl(ref direct_dist) => {
|
||||
Source::from_direct_source_dist(direct_dist)
|
||||
Ok(Source::from_direct_source_dist(direct_dist))
|
||||
}
|
||||
distribution_types::SourceDist::Git(ref git_dist) => {
|
||||
Ok(Source::from_git_dist(git_dist))
|
||||
}
|
||||
distribution_types::SourceDist::Git(ref git_dist) => Source::from_git_dist(git_dist),
|
||||
distribution_types::SourceDist::Path(ref path_dist) => {
|
||||
Source::from_path_source_dist(path_dist)
|
||||
Source::from_path_source_dist(path_dist, root)
|
||||
}
|
||||
distribution_types::SourceDist::Directory(ref directory) => {
|
||||
Source::from_directory_source_dist(directory)
|
||||
Source::from_directory_source_dist(directory, root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1861,22 +1921,29 @@ impl Source {
|
|||
)
|
||||
}
|
||||
|
||||
fn from_path_built_dist(path_dist: &PathBuiltDist) -> Source {
|
||||
let path = path_dist.lock_path.simplified().to_path_buf();
|
||||
Source::Path(path)
|
||||
fn from_path_built_dist(path_dist: &PathBuiltDist, root: &Path) -> Result<Source, LockError> {
|
||||
let path = relative_to(&path_dist.install_path, root)
|
||||
.map_err(LockErrorKind::DistributionRelativePath)?;
|
||||
Ok(Source::Path(path))
|
||||
}
|
||||
|
||||
fn from_path_source_dist(path_dist: &PathSourceDist) -> Source {
|
||||
let path = path_dist.install_path.simplified().to_path_buf();
|
||||
Source::Path(path)
|
||||
fn from_path_source_dist(path_dist: &PathSourceDist, root: &Path) -> Result<Source, LockError> {
|
||||
let path = relative_to(&path_dist.install_path, root)
|
||||
.map_err(LockErrorKind::DistributionRelativePath)?;
|
||||
|
||||
Ok(Source::Path(path))
|
||||
}
|
||||
|
||||
fn from_directory_source_dist(directory_dist: &DirectorySourceDist) -> Source {
|
||||
let path = directory_dist.lock_path.simplified().to_path_buf();
|
||||
fn from_directory_source_dist(
|
||||
directory_dist: &DirectorySourceDist,
|
||||
root: &Path,
|
||||
) -> Result<Source, LockError> {
|
||||
let path = relative_to(&directory_dist.install_path, root)
|
||||
.map_err(LockErrorKind::DistributionRelativePath)?;
|
||||
if directory_dist.editable {
|
||||
Source::Editable(path)
|
||||
Ok(Source::Editable(path))
|
||||
} else {
|
||||
Source::Directory(path)
|
||||
Ok(Source::Directory(path))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2649,14 +2716,18 @@ struct Dependency {
|
|||
}
|
||||
|
||||
impl Dependency {
|
||||
fn from_annotated_dist(annotated_dist: &AnnotatedDist, marker: MarkerTree) -> Dependency {
|
||||
let package_id = PackageId::from_annotated_dist(annotated_dist);
|
||||
fn from_annotated_dist(
|
||||
annotated_dist: &AnnotatedDist,
|
||||
marker: MarkerTree,
|
||||
root: &Path,
|
||||
) -> Result<Dependency, LockError> {
|
||||
let package_id = PackageId::from_annotated_dist(annotated_dist, root)?;
|
||||
let extra = annotated_dist.extra.iter().cloned().collect();
|
||||
Dependency {
|
||||
Ok(Self {
|
||||
package_id,
|
||||
extra,
|
||||
marker,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the TOML representation of this dependency.
|
||||
|
@ -2838,19 +2909,11 @@ fn normalize_requirement(
|
|||
})
|
||||
}
|
||||
RequirementSource::Path {
|
||||
install_path: _,
|
||||
lock_path,
|
||||
install_path,
|
||||
ext,
|
||||
url: _,
|
||||
} => {
|
||||
// When a path requirement comes from the lockfile, `install_path` and `lock_path` are
|
||||
// both relative to the lockfile.
|
||||
//
|
||||
// When a path requirement is deserialized from package metadata, `install_path` is
|
||||
// absolute, and `lock_path` is relative to the lockfile.
|
||||
let install_path = uv_fs::normalize_path(&workspace.install_path().join(&lock_path));
|
||||
let lock_path = relative_to(workspace.install_path(), &lock_path)
|
||||
.map_err(LockErrorKind::RequirementRelativePath)?;
|
||||
let install_path = uv_fs::normalize_path(&workspace.install_path().join(&install_path));
|
||||
let url = VerbatimUrl::from_path(&install_path)
|
||||
.map_err(LockErrorKind::RequirementVerbatimUrl)?;
|
||||
|
||||
|
@ -2860,7 +2923,6 @@ fn normalize_requirement(
|
|||
marker: requirement.marker,
|
||||
source: RequirementSource::Path {
|
||||
install_path,
|
||||
lock_path,
|
||||
ext,
|
||||
url,
|
||||
},
|
||||
|
@ -2868,14 +2930,11 @@ fn normalize_requirement(
|
|||
})
|
||||
}
|
||||
RequirementSource::Directory {
|
||||
install_path: _,
|
||||
lock_path,
|
||||
install_path,
|
||||
editable,
|
||||
url: _,
|
||||
} => {
|
||||
let install_path = uv_fs::normalize_path(&workspace.install_path().join(&lock_path));
|
||||
let lock_path = relative_to(workspace.install_path(), &lock_path)
|
||||
.map_err(LockErrorKind::RequirementRelativePath)?;
|
||||
let install_path = uv_fs::normalize_path(&workspace.install_path().join(&install_path));
|
||||
let url = VerbatimUrl::from_path(&install_path)
|
||||
.map_err(LockErrorKind::RequirementVerbatimUrl)?;
|
||||
|
||||
|
@ -2885,7 +2944,6 @@ fn normalize_requirement(
|
|||
marker: requirement.marker,
|
||||
source: RequirementSource::Directory {
|
||||
install_path,
|
||||
lock_path,
|
||||
editable,
|
||||
url,
|
||||
},
|
||||
|
@ -3072,6 +3130,13 @@ enum LockErrorKind {
|
|||
#[source]
|
||||
err: VerbatimUrlError,
|
||||
},
|
||||
/// An error that occurs when parsing an existing requirement.
|
||||
#[error("could not compute relative path between workspace and distribution")]
|
||||
DistributionRelativePath(
|
||||
/// The inner error we forward.
|
||||
#[source]
|
||||
std::io::Error,
|
||||
),
|
||||
/// An error that occurs when an ambiguous `package.dependency` is
|
||||
/// missing a `version` field.
|
||||
#[error(
|
||||
|
|
|
@ -135,11 +135,9 @@ impl PubGrubRequirement {
|
|||
ext,
|
||||
url,
|
||||
install_path,
|
||||
lock_path,
|
||||
} => {
|
||||
let parsed_url = ParsedUrl::Path(ParsedPathUrl::from_source(
|
||||
install_path.clone(),
|
||||
lock_path.clone(),
|
||||
*ext,
|
||||
url.to_url(),
|
||||
));
|
||||
|
@ -149,11 +147,9 @@ impl PubGrubRequirement {
|
|||
editable,
|
||||
url,
|
||||
install_path,
|
||||
lock_path,
|
||||
} => {
|
||||
let parsed_url = ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
|
||||
install_path.clone(),
|
||||
lock_path.clone(),
|
||||
*editable,
|
||||
url.to_url(),
|
||||
));
|
||||
|
|
|
@ -332,13 +332,13 @@ impl Source {
|
|||
|
||||
let source = match source {
|
||||
RequirementSource::Registry { .. } => return Ok(None),
|
||||
RequirementSource::Path { lock_path, .. } => Source::Path {
|
||||
RequirementSource::Path { install_path, .. } => Source::Path {
|
||||
editable,
|
||||
path: lock_path.to_string_lossy().into_owned(),
|
||||
path: install_path.to_string_lossy().into_owned(),
|
||||
},
|
||||
RequirementSource::Directory { lock_path, .. } => Source::Path {
|
||||
RequirementSource::Directory { install_path, .. } => Source::Path {
|
||||
editable,
|
||||
path: lock_path.to_string_lossy().into_owned(),
|
||||
path: install_path.to_string_lossy().into_owned(),
|
||||
},
|
||||
RequirementSource::Url {
|
||||
subdirectory, url, ..
|
||||
|
|
|
@ -10,7 +10,7 @@ use tracing::{debug, trace, warn};
|
|||
|
||||
use pep508_rs::{MarkerTree, RequirementOrigin, VerbatimUrl};
|
||||
use pypi_types::{Requirement, RequirementSource};
|
||||
use uv_fs::{absolutize_path, normalize_path, relative_to, Simplified};
|
||||
use uv_fs::{absolutize_path, Simplified};
|
||||
use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
|
@ -62,11 +62,6 @@ pub struct Workspace {
|
|||
/// The workspace root is the directory containing the top level `pyproject.toml` with
|
||||
/// the `uv.tool.workspace`, or the `pyproject.toml` in an implicit single workspace project.
|
||||
install_path: PathBuf,
|
||||
/// The same path as `install_path`, but relative to the main workspace.
|
||||
///
|
||||
/// We use this value to compute relative paths for workspace-to-workspace dependencies. It's an
|
||||
/// empty path for the main workspace.
|
||||
lock_path: PathBuf,
|
||||
/// The members of the workspace.
|
||||
packages: BTreeMap<PackageName, WorkspaceMember>,
|
||||
/// The sources table from the workspace `pyproject.toml`.
|
||||
|
@ -176,8 +171,6 @@ impl Workspace {
|
|||
|
||||
Self::collect_members(
|
||||
workspace_root.clone(),
|
||||
// This method supports only absolute paths.
|
||||
workspace_root,
|
||||
workspace_definition,
|
||||
workspace_pyproject_toml,
|
||||
current_project,
|
||||
|
@ -286,11 +279,6 @@ impl Workspace {
|
|||
marker: MarkerTree::TRUE,
|
||||
source: RequirementSource::Directory {
|
||||
install_path: member.root.clone(),
|
||||
lock_path: member
|
||||
.root
|
||||
.strip_prefix(&self.install_path)
|
||||
.expect("Project must be below workspace root")
|
||||
.to_path_buf(),
|
||||
editable: true,
|
||||
url,
|
||||
},
|
||||
|
@ -421,12 +409,6 @@ impl Workspace {
|
|||
&self.install_path
|
||||
}
|
||||
|
||||
/// The same path as `install_path()`, but relative to the main workspace. We use this value
|
||||
/// to compute relative paths for workspace-to-workspace dependencies.
|
||||
pub fn lock_path(&self) -> &PathBuf {
|
||||
&self.lock_path
|
||||
}
|
||||
|
||||
/// The path to the workspace virtual environment.
|
||||
pub fn venv(&self) -> PathBuf {
|
||||
self.install_path.join(".venv")
|
||||
|
@ -480,7 +462,6 @@ impl Workspace {
|
|||
/// Collect the workspace member projects from the `members` and `excludes` entries.
|
||||
async fn collect_members(
|
||||
workspace_root: PathBuf,
|
||||
lock_path: PathBuf,
|
||||
workspace_definition: ToolUvWorkspace,
|
||||
workspace_pyproject_toml: PyProjectToml,
|
||||
current_project: Option<WorkspaceMember>,
|
||||
|
@ -644,7 +625,6 @@ impl Workspace {
|
|||
|
||||
Ok(Workspace {
|
||||
install_path: workspace_root,
|
||||
lock_path,
|
||||
packages: workspace_members,
|
||||
sources: workspace_sources,
|
||||
pyproject_toml: workspace_pyproject_toml,
|
||||
|
@ -817,21 +797,13 @@ impl ProjectWorkspace {
|
|||
.clone()
|
||||
.ok_or_else(|| WorkspaceError::MissingProject(pyproject_path.clone()))?;
|
||||
|
||||
Self::from_project(
|
||||
project_root,
|
||||
Path::new(""),
|
||||
&project,
|
||||
&pyproject_toml,
|
||||
options,
|
||||
)
|
||||
.await
|
||||
Self::from_project(project_root, &project, &pyproject_toml, options).await
|
||||
}
|
||||
|
||||
/// If the current directory contains a `pyproject.toml` with a `project` table, discover the
|
||||
/// workspace and return it, otherwise it is a dynamic path dependency and we return `Ok(None)`.
|
||||
pub async fn from_maybe_project_root(
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Option<Self>, WorkspaceError> {
|
||||
// Read the `pyproject.toml`.
|
||||
|
@ -849,8 +821,7 @@ impl ProjectWorkspace {
|
|||
return Ok(None);
|
||||
};
|
||||
|
||||
match Self::from_project(install_path, lock_path, &project, &pyproject_toml, options).await
|
||||
{
|
||||
match Self::from_project(install_path, &project, &pyproject_toml, options).await {
|
||||
Ok(workspace) => Ok(Some(workspace)),
|
||||
Err(WorkspaceError::NonWorkspace(_)) => Ok(None),
|
||||
Err(err) => Err(err),
|
||||
|
@ -894,7 +865,6 @@ impl ProjectWorkspace {
|
|||
/// Find the workspace for a project.
|
||||
pub async fn from_project(
|
||||
install_path: &Path,
|
||||
lock_path: &Path,
|
||||
project: &Project,
|
||||
project_pyproject_toml: &PyProjectToml,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
|
@ -954,8 +924,6 @@ impl ProjectWorkspace {
|
|||
project_name: project.name.clone(),
|
||||
workspace: Workspace {
|
||||
install_path: project_path.clone(),
|
||||
// The workspace and the project are the same, so the relative path is, too.
|
||||
lock_path: lock_path.to_path_buf(),
|
||||
packages: current_project_as_members,
|
||||
// There may be package sources, but we don't need to duplicate them into the
|
||||
// workspace sources.
|
||||
|
@ -970,28 +938,8 @@ impl ProjectWorkspace {
|
|||
workspace_root.simplified_display()
|
||||
);
|
||||
|
||||
// Say we have:
|
||||
// ```
|
||||
// root
|
||||
// ├── main_workspace <- The reference point
|
||||
// │ ├── pyproject.toml
|
||||
// │ └── uv.lock
|
||||
// └──current_workspace <- We want this relative to the main workspace
|
||||
// └── packages
|
||||
// └── current_package <- We have this relative to the main workspace
|
||||
// └── pyproject.toml
|
||||
// ```
|
||||
// The lock path we need: `../current_workspace`
|
||||
// workspace root: `/root/current_workspace`
|
||||
// project path: `/root/current_workspace/packages/current_project`
|
||||
// relative to workspace: `../..`
|
||||
// lock path: `../current_workspace`
|
||||
let up_to_root = relative_to(&workspace_root, &project_path)?;
|
||||
let lock_path = normalize_path(&lock_path.join(up_to_root));
|
||||
|
||||
let workspace = Workspace::collect_members(
|
||||
workspace_root,
|
||||
lock_path,
|
||||
workspace_definition,
|
||||
workspace_pyproject_toml,
|
||||
Some(current_project),
|
||||
|
@ -1270,14 +1218,9 @@ impl VirtualProject {
|
|||
|
||||
if let Some(project) = pyproject_toml.project.as_ref() {
|
||||
// If the `pyproject.toml` contains a `[project]` table, it's a project.
|
||||
let project = ProjectWorkspace::from_project(
|
||||
project_root,
|
||||
Path::new(""),
|
||||
project,
|
||||
&pyproject_toml,
|
||||
options,
|
||||
)
|
||||
.await?;
|
||||
let project =
|
||||
ProjectWorkspace::from_project(project_root, project, &pyproject_toml, options)
|
||||
.await?;
|
||||
Ok(Self::Project(project))
|
||||
} else if let Some(workspace) = pyproject_toml
|
||||
.tool
|
||||
|
@ -1294,7 +1237,6 @@ impl VirtualProject {
|
|||
|
||||
let workspace = Workspace::collect_members(
|
||||
project_path,
|
||||
PathBuf::new(),
|
||||
workspace.clone(),
|
||||
pyproject_toml,
|
||||
None,
|
||||
|
@ -1452,7 +1394,6 @@ mod tests {
|
|||
"project_name": "bird-feeder",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-in-example/examples/bird-feeder",
|
||||
"lock_path": "",
|
||||
"packages": {
|
||||
"bird-feeder": {
|
||||
"root": "[ROOT]/albatross-in-example/examples/bird-feeder",
|
||||
|
@ -1496,7 +1437,6 @@ mod tests {
|
|||
"project_name": "bird-feeder",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder",
|
||||
"lock_path": "",
|
||||
"packages": {
|
||||
"bird-feeder": {
|
||||
"root": "[ROOT]/albatross-project-in-excluded/excluded/bird-feeder",
|
||||
|
@ -1539,7 +1479,6 @@ mod tests {
|
|||
"project_name": "albatross",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-root-workspace",
|
||||
"lock_path": "",
|
||||
"packages": {
|
||||
"albatross": {
|
||||
"root": "[ROOT]/albatross-root-workspace",
|
||||
|
@ -1624,7 +1563,6 @@ mod tests {
|
|||
"project_name": "albatross",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-virtual-workspace",
|
||||
"lock_path": "../..",
|
||||
"packages": {
|
||||
"albatross": {
|
||||
"root": "[ROOT]/albatross-virtual-workspace/packages/albatross",
|
||||
|
@ -1696,7 +1634,6 @@ mod tests {
|
|||
"project_name": "albatross",
|
||||
"workspace": {
|
||||
"install_path": "[ROOT]/albatross-just-project",
|
||||
"lock_path": "",
|
||||
"packages": {
|
||||
"albatross": {
|
||||
"root": "[ROOT]/albatross-just-project",
|
||||
|
|
|
@ -67,7 +67,6 @@ pub(crate) async fn lock(
|
|||
frozen: bool,
|
||||
python: Option<String>,
|
||||
settings: ResolverSettings,
|
||||
|
||||
python_preference: PythonPreference,
|
||||
python_downloads: PythonDownloads,
|
||||
connectivity: Connectivity,
|
||||
|
@ -523,14 +522,12 @@ async fn do_lock(
|
|||
// Notify the user of any resolution diagnostics.
|
||||
pip::operations::diagnose_resolution(resolution.diagnostics(), printer)?;
|
||||
|
||||
let manifest = ResolverManifest::new(members, requirements, constraints, overrides)
|
||||
.relative_to(workspace)?;
|
||||
|
||||
let previous = existing_lock.map(ValidatedLock::into_lock);
|
||||
let lock = Lock::from_resolution_graph(&resolution)?
|
||||
.with_manifest(ResolverManifest::new(
|
||||
members,
|
||||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
))
|
||||
let lock = Lock::from_resolution_graph(&resolution, workspace.install_path())?
|
||||
.with_manifest(manifest)
|
||||
.with_supported_environments(
|
||||
environments
|
||||
.cloned()
|
||||
|
|
|
@ -276,14 +276,14 @@ fn root_package_splits_transitive_too() -> Result<()> {
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "b1", marker = "python_full_version < '3.12'", directory = "../b1" },
|
||||
{ name = "b2", marker = "python_full_version >= '3.12'", directory = "../b2" },
|
||||
{ name = "b1", marker = "python_full_version < '3.12'", directory = "b1" },
|
||||
{ name = "b2", marker = "python_full_version >= '3.12'", directory = "b2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "b1"
|
||||
version = "0.1.0"
|
||||
source = { directory = "../b1" }
|
||||
source = { directory = "b1" }
|
||||
dependencies = [
|
||||
{ name = "iniconfig", version = "1.1.1", source = { url = "https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl" }, marker = "python_full_version < '3.12'" },
|
||||
]
|
||||
|
@ -294,7 +294,7 @@ fn root_package_splits_transitive_too() -> Result<()> {
|
|||
[[package]]
|
||||
name = "b2"
|
||||
version = "0.1.0"
|
||||
source = { directory = "../b2" }
|
||||
source = { directory = "b2" }
|
||||
dependencies = [
|
||||
{ name = "iniconfig", version = "2.0.0", source = { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }, marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
|
@ -804,12 +804,12 @@ fn dont_pre_visit_url_packages() -> Result<()> {
|
|||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "c", directory = "../c" }]
|
||||
requires-dist = [{ name = "c", directory = "c" }]
|
||||
|
||||
[[package]]
|
||||
name = "c"
|
||||
version = "0.1.0"
|
||||
source = { directory = "../c" }
|
||||
source = { directory = "c" }
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -4159,7 +4159,7 @@ fn lock_relative_and_absolute_paths() -> Result<()> {
|
|||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "b", directory = "b" },
|
||||
{ name = "c", directory = "[TEMP_DIR]/c" },
|
||||
{ name = "c", directory = "c" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4170,7 +4170,7 @@ fn lock_relative_and_absolute_paths() -> Result<()> {
|
|||
[[package]]
|
||||
name = "c"
|
||||
version = "0.1.0"
|
||||
source = { directory = "[TEMP_DIR]/c" }
|
||||
source = { directory = "c" }
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
@ -5074,7 +5074,7 @@ fn lock_same_version_multiple_urls() -> Result<()> {
|
|||
[[package]]
|
||||
name = "dependency"
|
||||
version = "0.0.1"
|
||||
source = { directory = "[TEMP_DIR]/v1" }
|
||||
source = { directory = "v1" }
|
||||
resolution-markers = [
|
||||
"sys_platform == 'darwin'",
|
||||
]
|
||||
|
@ -5088,7 +5088,7 @@ fn lock_same_version_multiple_urls() -> Result<()> {
|
|||
[[package]]
|
||||
name = "dependency"
|
||||
version = "0.0.1"
|
||||
source = { directory = "[TEMP_DIR]/v2" }
|
||||
source = { directory = "v2" }
|
||||
resolution-markers = [
|
||||
"sys_platform != 'darwin'",
|
||||
]
|
||||
|
@ -5113,14 +5113,14 @@ fn lock_same_version_multiple_urls() -> Result<()> {
|
|||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "dependency", version = "0.0.1", source = { directory = "[TEMP_DIR]/v1" }, marker = "sys_platform == 'darwin'" },
|
||||
{ name = "dependency", version = "0.0.1", source = { directory = "[TEMP_DIR]/v2" }, marker = "sys_platform != 'darwin'" },
|
||||
{ name = "dependency", version = "0.0.1", source = { directory = "v1" }, marker = "sys_platform == 'darwin'" },
|
||||
{ name = "dependency", version = "0.0.1", source = { directory = "v2" }, marker = "sys_platform != 'darwin'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "dependency", marker = "sys_platform != 'darwin'", directory = "[TEMP_DIR]/v2" },
|
||||
{ name = "dependency", marker = "sys_platform == 'darwin'", directory = "[TEMP_DIR]/v1" },
|
||||
{ name = "dependency", marker = "sys_platform != 'darwin'", directory = "v2" },
|
||||
{ name = "dependency", marker = "sys_platform == 'darwin'", directory = "v1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -7118,7 +7118,7 @@ fn lock_sources_archive() -> Result<()> {
|
|||
Url::from_file_path(&workspace_archive).unwrap(),
|
||||
})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
uv_snapshot!( context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -7129,11 +7129,11 @@ fn lock_sources_archive() -> Result<()> {
|
|||
|
||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
// insta::with_settings!({
|
||||
// filters => context.filters(),
|
||||
// }, {
|
||||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
|
@ -7171,7 +7171,7 @@ fn lock_sources_archive() -> Result<()> {
|
|||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "workspace", path = "[TEMP_DIR]/workspace.zip" }]
|
||||
requires-dist = [{ name = "workspace", path = "workspace.zip" }]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
|
@ -7185,7 +7185,7 @@ fn lock_sources_archive() -> Result<()> {
|
|||
[[package]]
|
||||
name = "workspace"
|
||||
version = "0.1.0"
|
||||
source = { path = "[TEMP_DIR]/workspace.zip" }
|
||||
source = { path = "workspace.zip" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
|
@ -7193,8 +7193,8 @@ fn lock_sources_archive() -> Result<()> {
|
|||
[package.metadata]
|
||||
requires-dist = [{ name = "anyio" }]
|
||||
"###
|
||||
);
|
||||
});
|
||||
);
|
||||
// });
|
||||
|
||||
// Re-run with `--locked`.
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
|
||||
|
@ -7293,7 +7293,7 @@ fn lock_sources_source_tree() -> Result<()> {
|
|||
[[package]]
|
||||
name = "anyio"
|
||||
version = "0.1.0"
|
||||
source = { editable = "[TEMP_DIR]/workspace/anyio" }
|
||||
source = { editable = "workspace/anyio" }
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
|
@ -7304,18 +7304,18 @@ fn lock_sources_source_tree() -> Result<()> {
|
|||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "workspace", directory = "[TEMP_DIR]/workspace" }]
|
||||
requires-dist = [{ name = "workspace", directory = "workspace" }]
|
||||
|
||||
[[package]]
|
||||
name = "workspace"
|
||||
version = "0.1.0"
|
||||
source = { directory = "[TEMP_DIR]/workspace" }
|
||||
source = { directory = "workspace" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "anyio", editable = "[TEMP_DIR]/workspace/anyio" }]
|
||||
requires-dist = [{ name = "anyio", editable = "workspace/anyio" }]
|
||||
"###
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1497,3 +1497,61 @@ fn workspace_member_name_shadows_dependencies() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that path dependencies with path dependencies resolve paths correctly across workspaces.
|
||||
///
|
||||
/// Each package is its own workspace. We put the other projects into a separate directory `libs` so
|
||||
/// the paths don't line up by accident.
|
||||
#[test]
|
||||
fn test_path_hopping() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Build the main project ...
|
||||
let deps = indoc! {r#"
|
||||
dependencies = ["foo"]
|
||||
[tool.uv.sources]
|
||||
foo = { path = "../libs/foo", editable = true }
|
||||
"#};
|
||||
let main_project_dir = context.temp_dir.join("project");
|
||||
make_project(&main_project_dir, "project", deps)?;
|
||||
|
||||
// ... that depends on foo ...
|
||||
let deps = indoc! {r#"
|
||||
dependencies = ["bar"]
|
||||
[tool.uv.sources]
|
||||
bar = { path = "../../libs/bar", editable = true }
|
||||
"#};
|
||||
make_project(&context.temp_dir.join("libs").join("foo"), "foo", deps)?;
|
||||
|
||||
// ... that depends on bar, a stub project.
|
||||
make_project(&context.temp_dir.join("libs").join("bar"), "bar", "")?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock().arg("--preview").current_dir(&main_project_dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||
Resolved 3 packages in [TIME]
|
||||
"###
|
||||
);
|
||||
|
||||
let lock: SourceLock =
|
||||
toml::from_str(&fs_err::read_to_string(main_project_dir.join("uv.lock"))?)?;
|
||||
assert_json_snapshot!(lock.sources(), @r###"
|
||||
{
|
||||
"bar": {
|
||||
"editable": "../libs/bar"
|
||||
},
|
||||
"foo": {
|
||||
"editable": "../libs/foo"
|
||||
},
|
||||
"project": {
|
||||
"editable": "."
|
||||
}
|
||||
}
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue