mirror of https://github.com/astral-sh/uv
Avoid canonicalizing user-provided interpreters (#2072)
## Summary We shouldn't be resolving symlinks on the provided interpreter; otherwise we break `pyenv`, since running `cargo run pip install mypy --python .venv/bin/python` will immediately resolve to (e.g.) `/Users/crmarsh/.pyenv/versions/3.10.2/bin/python3.10`, and pyenv relies on the path to do its lookups. Instead, the canonicalizing happens when we query the interpreter metadata. Closes https://github.com/astral-sh/uv/issues/2068. ## Test Plan Ran `cargo run pip install mypy --python .venv/bin/python -v -n` with a virtualenv created using a pyenv Python; verified that Mypy was installed into the virtual environment, rather than into the global environment.
This commit is contained in:
parent
ef15098288
commit
1f19ef670b
|
|
@ -1,12 +1,14 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
|
use uv_fs::normalize_path;
|
||||||
|
|
||||||
/// A wrapper around [`Url`] that preserves the original string.
|
/// A wrapper around [`Url`] that preserves the original string.
|
||||||
#[derive(Debug, Clone, Eq, derivative::Derivative)]
|
#[derive(Debug, Clone, Eq, derivative::Derivative)]
|
||||||
#[derivative(PartialEq, Hash)]
|
#[derivative(PartialEq, Hash)]
|
||||||
|
|
@ -60,7 +62,7 @@ impl VerbatimUrl {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Normalize the path.
|
// Normalize the path.
|
||||||
let path = normalize_path(&path);
|
let path = normalize_path(path);
|
||||||
|
|
||||||
// Convert to a URL.
|
// Convert to a URL.
|
||||||
let url = Url::from_file_path(path).expect("path is absolute");
|
let url = Url::from_file_path(path).expect("path is absolute");
|
||||||
|
|
@ -81,7 +83,7 @@ impl VerbatimUrl {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Normalize the path.
|
// Normalize the path.
|
||||||
let path = normalize_path(&path);
|
let path = normalize_path(path);
|
||||||
|
|
||||||
// Convert to a URL.
|
// Convert to a URL.
|
||||||
let url = Url::from_file_path(path).expect("path is absolute");
|
let url = Url::from_file_path(path).expect("path is absolute");
|
||||||
|
|
@ -200,36 +202,6 @@ fn expand_env_vars(s: &str, escape: bool) -> Cow<'_, str> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalize a path, removing things like `.` and `..`.
|
|
||||||
///
|
|
||||||
/// Source: <https://github.com/rust-lang/cargo/blob/b48c41aedbd69ee3990d62a0e2006edbb506a480/crates/cargo-util/src/paths.rs#L76C1-L109C2>
|
|
||||||
fn normalize_path(path: &Path) -> PathBuf {
|
|
||||||
let mut components = path.components().peekable();
|
|
||||||
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
|
|
||||||
components.next();
|
|
||||||
PathBuf::from(c.as_os_str())
|
|
||||||
} else {
|
|
||||||
PathBuf::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
for component in components {
|
|
||||||
match component {
|
|
||||||
Component::Prefix(..) => unreachable!(),
|
|
||||||
Component::RootDir => {
|
|
||||||
ret.push(component.as_os_str());
|
|
||||||
}
|
|
||||||
Component::CurDir => {}
|
|
||||||
Component::ParentDir => {
|
|
||||||
ret.pop();
|
|
||||||
}
|
|
||||||
Component::Normal(c) => {
|
|
||||||
ret.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like [`Url::parse`], but only splits the scheme. Derived from the `url` crate.
|
/// Like [`Url::parse`], but only splits the scheme. Derived from the `url` crate.
|
||||||
pub fn split_scheme(s: &str) -> Option<(&str, &str)> {
|
pub fn split_scheme(s: &str) -> Option<(&str, &str)> {
|
||||||
/// <https://url.spec.whatwg.org/#c0-controls-and-space>
|
/// <https://url.spec.whatwg.org/#c0-controls-and-space>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::Path;
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
pub trait Simplified {
|
pub trait Simplified {
|
||||||
/// Simplify a [`Path`].
|
/// Simplify a [`Path`].
|
||||||
|
|
@ -46,6 +46,36 @@ pub fn normalize_url_path(path: &str) -> Cow<'_, str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalize a path, removing things like `.` and `..`.
|
||||||
|
///
|
||||||
|
/// Source: <https://github.com/rust-lang/cargo/blob/b48c41aedbd69ee3990d62a0e2006edbb506a480/crates/cargo-util/src/paths.rs#L76C1-L109C2>
|
||||||
|
pub fn normalize_path(path: impl AsRef<Path>) -> PathBuf {
|
||||||
|
let mut components = path.as_ref().components().peekable();
|
||||||
|
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() {
|
||||||
|
components.next();
|
||||||
|
PathBuf::from(c.as_os_str())
|
||||||
|
} else {
|
||||||
|
PathBuf::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
for component in components {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(..) => unreachable!(),
|
||||||
|
Component::RootDir => {
|
||||||
|
ret.push(component.as_os_str());
|
||||||
|
}
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
ret.pop();
|
||||||
|
}
|
||||||
|
Component::Normal(c) => {
|
||||||
|
ret.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use tracing::{debug, instrument};
|
||||||
|
|
||||||
use platform_host::Platform;
|
use platform_host::Platform;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
|
use uv_fs::normalize_path;
|
||||||
|
|
||||||
use crate::{Error, Interpreter};
|
use crate::{Error, Interpreter};
|
||||||
|
|
||||||
|
|
@ -63,7 +64,8 @@ pub fn find_requested_python(
|
||||||
Interpreter::query(&executable, platform.clone(), cache).map(Some)
|
Interpreter::query(&executable, platform.clone(), cache).map(Some)
|
||||||
} else {
|
} else {
|
||||||
// `-p /home/ferris/.local/bin/python3.10`
|
// `-p /home/ferris/.local/bin/python3.10`
|
||||||
let executable = fs_err::canonicalize(request)?;
|
let executable = normalize_path(request);
|
||||||
|
|
||||||
Interpreter::query(&executable, platform.clone(), cache).map(Some)
|
Interpreter::query(&executable, platform.clone(), cache).map(Some)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue