Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve symlinks when creating virtual environments #8433

Closed
wants to merge 8 commits into from
Closed
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ jobs:
3.10
3.11
3.12
3.13

- uses: Swatinem/rust-cache@v2
with:
Expand Down
1 change: 1 addition & 0 deletions .python-versions
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
3.13.0
3.12.6
3.11.10
3.10.15
Expand Down
24 changes: 11 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ csv = { version = "1.3.0" }
ctrlc = { version = "3.4.5" }
dashmap = { version = "6.1.0" }
data-encoding = { version = "2.6.0" }
directories = { version = "5.0.1" }
dirs-sys = { version = "0.4.1" }
dotenvy = { version = "0.15.7" }
dunce = { version = "1.0.5" }
either = { version = "1.13.0" }
encoding_rs_io = { version = "0.1.7" }
Expand Down
1 change: 1 addition & 0 deletions crates/uv-build-frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ doctest = false
workspace = true

[dependencies]
uv-cache = { workspace = true }
uv-configuration = { workspace = true }
uv-distribution = { workspace = true }
uv-distribution-types = { workspace = true }
Expand Down
4 changes: 3 additions & 1 deletion crates/uv-build-frontend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use tokio::io::AsyncBufReadExt;
use tokio::process::Command;
use tokio::sync::{Mutex, Semaphore};
use tracing::{debug, info_span, instrument, Instrument};

use uv_cache::Cache;
use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, LowerBound, SourceStrategy};
use uv_distribution::RequiresDist;
use uv_distribution_types::{IndexLocations, Resolution};
Expand Down Expand Up @@ -260,6 +260,7 @@ impl SourceBuild {
mut environment_variables: FxHashMap<OsString, OsString>,
level: BuildOutput,
concurrent_builds: usize,
cache: &Cache,
) -> Result<Self, Error> {
let temp_dir = build_context.cache().environment()?;

Expand Down Expand Up @@ -306,6 +307,7 @@ impl SourceBuild {
false,
false,
false,
cache,
)?
};

Expand Down
18 changes: 17 additions & 1 deletion crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ pub struct GlobalArgs {
conflicts_with = "no_color",
value_name = "COLOR_CHOICE"
)]
pub color: ColorChoice,
pub color: Option<ColorChoice>,

/// Whether to load TLS certificates from the platform's native certificate store.
///
Expand Down Expand Up @@ -618,6 +618,9 @@ pub enum ProjectCommand {
/// arguments to uv. All options to uv must be provided before the command,
/// e.g., `uv run --verbose foo`. A `--` can be used to separate the command
/// from uv options for clarity, e.g., `uv run --python 3.12 -- python`.
///
/// Respects `.env` files in the current directory unless `--no-env-file` is
/// provided.
#[command(
after_help = "Use `uv help run` for more details.",
after_long_help = ""
Expand Down Expand Up @@ -2628,6 +2631,19 @@ pub struct RunArgs {
#[arg(long)]
pub no_editable: bool,

/// Load environment variables from a `.env` file.
///
/// Can be provided multiple times, with subsequent files overriding values defined in
/// previous files.
///
/// Defaults to reading `.env` in the current working directory.
#[arg(long, env = EnvVars::UV_ENV_FILE)]
pub env_file: Vec<PathBuf>,

/// Avoid reading environment variables from a `.env` file.
#[arg(long, conflicts_with = "env_file", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_ENV_FILE)]
pub no_env_file: bool,

/// The command to run.
///
/// If the path to a Python script (i.e., ending in `.py`), it will be
Expand Down
2 changes: 0 additions & 2 deletions crates/uv-dirs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,4 @@ workspace = true
[dependencies]
uv-static = { workspace = true }

dirs-sys = { workspace = true }
directories = { workspace = true }
etcetera = { workspace = true }
40 changes: 29 additions & 11 deletions crates/uv-dirs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{ffi::OsString, path::PathBuf};

use etcetera::BaseStrategy;

Expand All @@ -20,19 +20,15 @@ use uv_static::EnvVars;
pub fn user_executable_directory(override_variable: Option<&'static str>) -> Option<PathBuf> {
override_variable
.and_then(std::env::var_os)
.and_then(dirs_sys::is_absolute_path)
.or_else(|| std::env::var_os(EnvVars::XDG_BIN_HOME).and_then(dirs_sys::is_absolute_path))
.and_then(parse_path)
.or_else(|| std::env::var_os(EnvVars::XDG_BIN_HOME).and_then(parse_path))
.or_else(|| {
std::env::var_os(EnvVars::XDG_DATA_HOME)
.and_then(dirs_sys::is_absolute_path)
.and_then(parse_path)
.map(|path| path.join("../bin"))
})
.or_else(|| {
// See https://github.com/dirs-dev/dirs-rs/blob/50b50f31f3363b7656e5e63b3fa1060217cbc844/src/win.rs#L5C58-L5C78
#[cfg(windows)]
let home_dir = dirs_sys::known_folder_profile();
#[cfg(not(windows))]
let home_dir = dirs_sys::home_dir();
let home_dir = etcetera::home_dir().ok();
home_dir.map(|path| path.join(".local").join("bin"))
})
}
Expand All @@ -51,7 +47,16 @@ pub fn user_cache_dir() -> Option<PathBuf> {
/// Uses `/Users/user/Library/Application Support/uv` on macOS, in contrast to the new preference
/// for using the XDG directories on all Unix platforms.
pub fn legacy_user_cache_dir() -> Option<PathBuf> {
directories::ProjectDirs::from("", "", "uv").map(|dirs| dirs.cache_dir().to_path_buf())
etcetera::base_strategy::choose_native_strategy()
.ok()
.map(|dirs| dirs.cache_dir().join("uv"))
.map(|dir| {
if cfg!(windows) {
dir.join("cache")
} else {
dir
}
})
}

/// Returns an appropriate user-level directory for storing application state.
Expand All @@ -68,5 +73,18 @@ pub fn user_state_dir() -> Option<PathBuf> {
/// Uses `/Users/user/Library/Application Support/uv` on macOS, in contrast to the new preference
/// for using the XDG directories on all Unix platforms.
pub fn legacy_user_state_dir() -> Option<PathBuf> {
directories::ProjectDirs::from("", "", "uv").map(|dirs| dirs.data_dir().to_path_buf())
etcetera::base_strategy::choose_native_strategy()
.ok()
.map(|dirs| dirs.data_dir().join("uv"))
.map(|dir| if cfg!(windows) { dir.join("data") } else { dir })
}

/// Return a [`PathBuf`] if the given [`OsString`] is an absolute path.
fn parse_path(path: OsString) -> Option<PathBuf> {
let path = PathBuf::from(path);
if path.is_absolute() {
Some(path)
} else {
None
}
}
1 change: 1 addition & 0 deletions crates/uv-dispatch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
self.build_extra_env_vars.clone(),
build_output,
self.concurrency.builds,
self.cache,
)
.boxed_local()
.await?;
Expand Down
21 changes: 20 additions & 1 deletion crates/uv-fs/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::io;
use std::path::{Component, Path, PathBuf};
use std::sync::LazyLock;

Expand Down Expand Up @@ -236,7 +237,7 @@ pub fn normalize_path(path: &Path) -> PathBuf {
}

/// Like `fs_err::canonicalize`, but avoids attempting to resolve symlinks on Windows.
pub fn canonicalize_executable(path: impl AsRef<Path>) -> std::io::Result<PathBuf> {
pub fn canonicalize_executable(path: impl AsRef<Path>) -> io::Result<PathBuf> {
let path = path.as_ref();
debug_assert!(
path.is_absolute(),
Expand All @@ -250,6 +251,24 @@ pub fn canonicalize_executable(path: impl AsRef<Path>) -> std::io::Result<PathBu
}
}

/// Resolve a symlink for an executable. Unlike `fs_err::canonicalize`, this does not resolve
/// symlinks recursively.
pub fn read_executable_link(executable: &Path) -> Result<Option<PathBuf>, io::Error> {
let Ok(link) = executable.read_link() else {
return Ok(None);
};
if link.is_absolute() {
Ok(Some(link))
} else {
executable
.parent()
.map(|parent| parent.join(link))
.as_deref()
.map(normalize_absolute_path)
.transpose()
}
}

/// Compute a path describing `path` relative to `base`.
///
/// `lib/python/site-packages/foo/__init__.py` and `lib/python/site-packages` -> `foo/__init__.py`
Expand Down
Loading
Loading