mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Use a cross-platform representation for relative paths in pip compile
(#3804)
## Summary I haven't tested on Windows yet, but the idea here is that we should use a portable representation when printing paths. I decided to limit the scope here to paths that we write to output files. Closes https://github.com/astral-sh/uv/issues/3800.
This commit is contained in:
parent
a7d486bc71
commit
8d566e553d
10 changed files with 75 additions and 26 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -2486,6 +2486,12 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "path-slash"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -4665,7 +4671,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"distribution-types",
|
"distribution-types",
|
||||||
"itertools 0.13.0",
|
"either",
|
||||||
"pep508_rs",
|
"pep508_rs",
|
||||||
"platform-tags",
|
"platform-tags",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
@ -4811,12 +4817,14 @@ dependencies = [
|
||||||
"backoff",
|
"backoff",
|
||||||
"cachedir",
|
"cachedir",
|
||||||
"dunce",
|
"dunce",
|
||||||
|
"either",
|
||||||
"encoding_rs_io",
|
"encoding_rs_io",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
"fs2",
|
"fs2",
|
||||||
"junction",
|
"junction",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"path-absolutize",
|
"path-absolutize",
|
||||||
|
"path-slash",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tracing",
|
"tracing",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
|
|
|
@ -73,7 +73,7 @@ derivative = { version = "2.2.0" }
|
||||||
directories = { version = "5.0.1" }
|
directories = { version = "5.0.1" }
|
||||||
dirs-sys = { version = "0.4.1" }
|
dirs-sys = { version = "0.4.1" }
|
||||||
dunce = { version = "1.0.4" }
|
dunce = { version = "1.0.4" }
|
||||||
either = { version = "1.9.0" }
|
either = { version = "1.12.0" }
|
||||||
encoding_rs_io = { version = "0.1.7" }
|
encoding_rs_io = { version = "0.1.7" }
|
||||||
flate2 = { version = "1.0.28", default-features = false }
|
flate2 = { version = "1.0.28", default-features = false }
|
||||||
fs-err = { version = "2.11.0" }
|
fs-err = { version = "2.11.0" }
|
||||||
|
@ -98,6 +98,7 @@ nanoid = { version = "0.4.0" }
|
||||||
once_cell = { version = "1.19.0" }
|
once_cell = { version = "1.19.0" }
|
||||||
owo-colors = { version = "4.0.0" }
|
owo-colors = { version = "4.0.0" }
|
||||||
path-absolutize = { version = "3.1.1" }
|
path-absolutize = { version = "3.1.1" }
|
||||||
|
path-slash = { version = "0.2.1" }
|
||||||
pathdiff = { version = "0.2.1" }
|
pathdiff = { version = "0.2.1" }
|
||||||
petgraph = { version = "0.6.4" }
|
petgraph = { version = "0.6.4" }
|
||||||
platform-info = { version = "2.0.2" }
|
platform-info = { version = "2.0.2" }
|
||||||
|
|
|
@ -22,17 +22,17 @@ impl std::fmt::Display for SourceAnnotation {
|
||||||
match self {
|
match self {
|
||||||
Self::Requirement(origin) => match origin {
|
Self::Requirement(origin) => match origin {
|
||||||
RequirementOrigin::File(path) => {
|
RequirementOrigin::File(path) => {
|
||||||
write!(f, "-r {}", path.user_display())
|
write!(f, "-r {}", path.portable_display())
|
||||||
}
|
}
|
||||||
RequirementOrigin::Project(path, project_name) => {
|
RequirementOrigin::Project(path, project_name) => {
|
||||||
write!(f, "{project_name} ({})", path.user_display())
|
write!(f, "{project_name} ({})", path.portable_display())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Self::Constraint(origin) => {
|
Self::Constraint(origin) => {
|
||||||
write!(f, "-c {}", origin.path().user_display())
|
write!(f, "-c {}", origin.path().portable_display())
|
||||||
}
|
}
|
||||||
Self::Override(origin) => {
|
Self::Override(origin) => {
|
||||||
write!(f, "--override {}", origin.path().user_display())
|
write!(f, "--override {}", origin.path().portable_display())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ uv-auth = { workspace = true }
|
||||||
uv-normalize = { workspace = true }
|
uv-normalize = { workspace = true }
|
||||||
|
|
||||||
clap = { workspace = true, features = ["derive"], optional = true }
|
clap = { workspace = true, features = ["derive"], optional = true }
|
||||||
itertools = { workspace = true }
|
either = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::hash::BuildHasherDefault;
|
use std::hash::BuildHasherDefault;
|
||||||
|
|
||||||
use itertools::Either;
|
use either::Either;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use distribution_types::Requirement;
|
use distribution_types::Requirement;
|
||||||
|
|
|
@ -18,11 +18,13 @@ uv-warnings = { workspace = true }
|
||||||
backoff = { workspace = true }
|
backoff = { workspace = true }
|
||||||
cachedir = { workspace = true }
|
cachedir = { workspace = true }
|
||||||
dunce = { workspace = true }
|
dunce = { workspace = true }
|
||||||
|
either = { workspace = true }
|
||||||
encoding_rs_io = { workspace = true }
|
encoding_rs_io = { workspace = true }
|
||||||
fs-err = { workspace = true }
|
fs-err = { workspace = true }
|
||||||
fs2 = { workspace = true }
|
fs2 = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
path-absolutize = { workspace = true }
|
path-absolutize = { workspace = true }
|
||||||
|
path-slash = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
urlencoding = { workspace = true }
|
urlencoding = { workspace = true }
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
use either::Either;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use path_slash::PathExt;
|
||||||
|
|
||||||
/// The current working directory.
|
/// The current working directory.
|
||||||
pub static CWD: Lazy<PathBuf> =
|
pub static CWD: Lazy<PathBuf> =
|
||||||
|
@ -25,7 +27,7 @@ pub trait Simplified {
|
||||||
///
|
///
|
||||||
/// On Windows, this will strip the `\\?\` prefix from paths. On other platforms, it's
|
/// On Windows, this will strip the `\\?\` prefix from paths. On other platforms, it's
|
||||||
/// equivalent to [`std::path::Display`].
|
/// equivalent to [`std::path::Display`].
|
||||||
fn simplified_display(&self) -> std::path::Display;
|
fn simplified_display(&self) -> impl std::fmt::Display;
|
||||||
|
|
||||||
/// Canonicalize a path without a `\\?\` prefix on Windows.
|
/// Canonicalize a path without a `\\?\` prefix on Windows.
|
||||||
fn simple_canonicalize(&self) -> std::io::Result<PathBuf>;
|
fn simple_canonicalize(&self) -> std::io::Result<PathBuf>;
|
||||||
|
@ -33,7 +35,18 @@ pub trait Simplified {
|
||||||
/// Render a [`Path`] for user-facing display.
|
/// Render a [`Path`] for user-facing display.
|
||||||
///
|
///
|
||||||
/// Like [`simplified_display`], but relativizes the path against the current working directory.
|
/// Like [`simplified_display`], but relativizes the path against the current working directory.
|
||||||
fn user_display(&self) -> std::path::Display;
|
fn user_display(&self) -> impl std::fmt::Display;
|
||||||
|
|
||||||
|
/// Render a [`Path`] for user-facing display, where the [`Path`] is relative to a base path.
|
||||||
|
///
|
||||||
|
/// If the [`Path`] is not relative to the base path, will attempt to relativize the path
|
||||||
|
/// against the current working directory.
|
||||||
|
fn user_display_from(&self, base: impl AsRef<Path>) -> impl std::fmt::Display;
|
||||||
|
|
||||||
|
/// Render a [`Path`] for user-facing display using a portable representation.
|
||||||
|
///
|
||||||
|
/// Like [`user_display`], but uses a portable representation for relative paths.
|
||||||
|
fn portable_display(&self) -> impl std::fmt::Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<Path>> Simplified for T {
|
impl<T: AsRef<Path>> Simplified for T {
|
||||||
|
@ -41,7 +54,7 @@ impl<T: AsRef<Path>> Simplified for T {
|
||||||
dunce::simplified(self.as_ref())
|
dunce::simplified(self.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn simplified_display(&self) -> std::path::Display {
|
fn simplified_display(&self) -> impl std::fmt::Display {
|
||||||
dunce::simplified(self.as_ref()).display()
|
dunce::simplified(self.as_ref()).display()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,17 +62,48 @@ impl<T: AsRef<Path>> Simplified for T {
|
||||||
dunce::canonicalize(self.as_ref())
|
dunce::canonicalize(self.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn user_display(&self) -> std::path::Display {
|
fn user_display(&self) -> impl std::fmt::Display {
|
||||||
let path = dunce::simplified(self.as_ref());
|
let path = dunce::simplified(self.as_ref());
|
||||||
|
|
||||||
// Attempt to strip the current working directory, then the canonicalized current working
|
// Attempt to strip the current working directory, then the canonicalized current working
|
||||||
// directory, in case they differ.
|
// directory, in case they differ.
|
||||||
path.strip_prefix(CWD.simplified())
|
let path = path.strip_prefix(CWD.simplified()).unwrap_or_else(|_| {
|
||||||
.unwrap_or_else(|_| {
|
path.strip_prefix(CANONICAL_CWD.simplified())
|
||||||
|
.unwrap_or(path)
|
||||||
|
});
|
||||||
|
|
||||||
|
path.display()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_display_from(&self, base: impl AsRef<Path>) -> impl std::fmt::Display {
|
||||||
|
let path = dunce::simplified(self.as_ref());
|
||||||
|
|
||||||
|
// Attempt to strip the base, then the current working directory, then the canonicalized
|
||||||
|
// current working directory, in case they differ.
|
||||||
|
let path = path.strip_prefix(base.as_ref()).unwrap_or_else(|_| {
|
||||||
|
path.strip_prefix(CWD.simplified()).unwrap_or_else(|_| {
|
||||||
path.strip_prefix(CANONICAL_CWD.simplified())
|
path.strip_prefix(CANONICAL_CWD.simplified())
|
||||||
.unwrap_or(path)
|
.unwrap_or(path)
|
||||||
})
|
})
|
||||||
.display()
|
});
|
||||||
|
|
||||||
|
path.display()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn portable_display(&self) -> impl std::fmt::Display {
|
||||||
|
let path = dunce::simplified(self.as_ref());
|
||||||
|
|
||||||
|
// Attempt to strip the current working directory, then the canonicalized current working
|
||||||
|
// directory, in case they differ.
|
||||||
|
let path = path.strip_prefix(CWD.simplified()).unwrap_or_else(|_| {
|
||||||
|
path.strip_prefix(CANONICAL_CWD.simplified())
|
||||||
|
.unwrap_or(path)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use a portable representation for relative paths.
|
||||||
|
path.to_slash()
|
||||||
|
.map(Either::Left)
|
||||||
|
.unwrap_or_else(|| Either::Right(path.display()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1256,17 +1256,11 @@ impl fmt::Display for InterpreterNotFound {
|
||||||
path.user_display()
|
path.user_display()
|
||||||
),
|
),
|
||||||
Self::ExecutableNotFoundInDirectory(directory, executable) => {
|
Self::ExecutableNotFoundInDirectory(directory, executable) => {
|
||||||
let executable = if let Ok(relative_executable) = executable.strip_prefix(directory)
|
|
||||||
{
|
|
||||||
relative_executable.display()
|
|
||||||
} else {
|
|
||||||
executable.user_display()
|
|
||||||
};
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Interpreter directory `{}` does not contain Python executable at `{}`",
|
"Interpreter directory `{}` does not contain Python executable at `{}`",
|
||||||
directory.user_display(),
|
directory.user_display(),
|
||||||
executable
|
executable.user_display_from(directory)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Self::ExecutableNotFoundInSearchPath(name) => {
|
Self::ExecutableNotFoundInSearchPath(name) => {
|
||||||
|
|
|
@ -707,7 +707,7 @@ fn cmd(
|
||||||
}
|
}
|
||||||
let args = env::args_os()
|
let args = env::args_os()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.map(|arg| arg.user_display().to_string())
|
.map(|arg| arg.to_string_lossy().to_string())
|
||||||
.scan(None, move |skip_next, arg| {
|
.scan(None, move |skip_next, arg| {
|
||||||
if matches!(skip_next, Some(true)) {
|
if matches!(skip_next, Some(true)) {
|
||||||
// Reset state; skip this iteration.
|
// Reset state; skip this iteration.
|
||||||
|
|
|
@ -7511,7 +7511,7 @@ fn local_version_of_remote_package() -> Result<()> {
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
# This file was autogenerated by uv via the following command:
|
# This file was autogenerated by uv via the following command:
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in
|
||||||
anyio==4.3.0
|
anyio==4.3.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==3.6
|
idna==3.6
|
||||||
|
@ -7548,7 +7548,7 @@ fn local_version_of_remote_package() -> Result<()> {
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
# This file was autogenerated by uv via the following command:
|
# This file was autogenerated by uv via the following command:
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in
|
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in
|
||||||
anyio==4.3.0
|
anyio==4.3.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==3.6
|
idna==3.6
|
||||||
|
@ -7580,7 +7580,7 @@ fn local_version_of_remote_package() -> Result<()> {
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
# This file was autogenerated by uv via the following command:
|
# This file was autogenerated by uv via the following command:
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --output-file requirements.txt
|
# uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z [TEMP_DIR]/requirements.in --output-file [TEMP_DIR]/requirements.txt
|
||||||
anyio==4.3.0
|
anyio==4.3.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==3.6
|
idna==3.6
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue