mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-18 06:18:53 +00:00
Support workspace to workspace path dependencies (#4833)
Add support for path dependencies from a package in one workspace to a package in another workspace, which it self has workspace dependencies. Say we have a main workspace with packages `a` and `b`, and a second workspace with `c` and `d`. We have `a -> b`, `b -> c`, `c -> d`. This would previously lead to a mangled path for `d`, which is now fixed. Like distribution paths, we split workspace paths into an absolute install path and a relative (or absolute, if the user provided an absolute path) lock path. Part of https://github.com/astral-sh/uv/issues/3943
This commit is contained in:
parent
b5ec859273
commit
abb6ac5127
13 changed files with 410 additions and 88 deletions
|
@ -160,7 +160,7 @@ pub fn normalize_url_path(path: &str) -> Cow<'_, str> {
|
|||
///
|
||||
/// When a relative path is provided with `..` components that extend beyond the base directory.
|
||||
/// For example, `./a/../../b` cannot be normalized because it escapes the base directory.
|
||||
pub fn normalize_path(path: &Path) -> Result<PathBuf, std::io::Error> {
|
||||
pub fn normalize_absolute_path(path: &Path) -> Result<PathBuf, std::io::Error> {
|
||||
let mut components = path.components().peekable();
|
||||
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
|
||||
components.next();
|
||||
|
@ -180,7 +180,10 @@ pub fn normalize_path(path: &Path) -> Result<PathBuf, std::io::Error> {
|
|||
if !ret.pop() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"cannot normalize a relative path beyond the base directory",
|
||||
format!(
|
||||
"cannot normalize a relative path beyond the base directory: {}",
|
||||
path.display()
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -192,6 +195,52 @@ pub fn normalize_path(path: &Path) -> Result<PathBuf, std::io::Error> {
|
|||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Normalize a path, removing things like `.` and `..`.
|
||||
///
|
||||
/// Unlike [`normalize_absolute_path`], this works with relative paths and does never error.
|
||||
///
|
||||
/// Note that we can theoretically go beyond the root dir here (e.g. `/usr/../../foo` becomes
|
||||
/// `/../foo`), but that's not a (correctness) problem, we will fail later with a file not found
|
||||
/// error with a path computed from the user's input.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// In: `../../workspace-git-path-dep-test/packages/c/../../packages/d`
|
||||
/// Out: `../../workspace-git-path-dep-test/packages/d`
|
||||
///
|
||||
/// In: `workspace-git-path-dep-test/packages/c/../../packages/d`
|
||||
/// Out: `workspace-git-path-dep-test/packages/d`
|
||||
///
|
||||
/// In: `./a/../../b`
|
||||
/// Out: `../b`
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
let mut normalized = PathBuf::new();
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Component::Prefix(_) | Component::RootDir | Component::Normal(_) => {
|
||||
// Preserve filesystem roots and regular path components.
|
||||
normalized.push(component);
|
||||
}
|
||||
Component::ParentDir => {
|
||||
match normalized.components().last() {
|
||||
None | Some(Component::ParentDir | Component::RootDir) => {
|
||||
// Preserve leading and above-root `..`
|
||||
normalized.push(component);
|
||||
}
|
||||
Some(Component::Normal(_) | Component::Prefix(_) | Component::CurDir) => {
|
||||
// Remove inner `..`
|
||||
normalized.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
Component::CurDir => {
|
||||
// Remove `.`
|
||||
}
|
||||
}
|
||||
}
|
||||
normalized
|
||||
}
|
||||
|
||||
/// Convert a path to an absolute path, relative to the current working directory.
|
||||
///
|
||||
/// Unlike [`std::fs::canonicalize`], this function does not resolve symlinks and does not require
|
||||
|
@ -402,16 +451,16 @@ mod tests {
|
|||
#[test]
|
||||
fn test_normalize_path() {
|
||||
let path = Path::new("/a/b/../c/./d");
|
||||
let normalized = normalize_path(path).unwrap();
|
||||
let normalized = normalize_absolute_path(path).unwrap();
|
||||
assert_eq!(normalized, Path::new("/a/c/d"));
|
||||
|
||||
let path = Path::new("/a/../c/./d");
|
||||
let normalized = normalize_path(path).unwrap();
|
||||
let normalized = normalize_absolute_path(path).unwrap();
|
||||
assert_eq!(normalized, Path::new("/c/d"));
|
||||
|
||||
// This should be an error.
|
||||
let path = Path::new("/a/../../c/./d");
|
||||
let err = normalize_path(path).unwrap_err();
|
||||
let err = normalize_absolute_path(path).unwrap_err();
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
|
||||
}
|
||||
|
||||
|
@ -442,4 +491,23 @@ mod tests {
|
|||
Path::new("../../../bin/foo_launcher")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_relative() {
|
||||
let cases = [
|
||||
(
|
||||
"../../workspace-git-path-dep-test/packages/c/../../packages/d",
|
||||
"../../workspace-git-path-dep-test/packages/d",
|
||||
),
|
||||
(
|
||||
"workspace-git-path-dep-test/packages/c/../../packages/d",
|
||||
"workspace-git-path-dep-test/packages/d",
|
||||
),
|
||||
("./a/../../b", "../b"),
|
||||
("/usr/../../foo", "/../foo"),
|
||||
];
|
||||
for (input, expected) in cases {
|
||||
assert_eq!(normalize_path(Path::new(input)), Path::new(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue