diff --git a/Cargo.lock b/Cargo.lock index d82a42156..ff05acaf1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5067,6 +5067,7 @@ dependencies = [ "clap", "directories", "fs-err", + "itertools 0.12.1", "pathdiff", "platform-tags", "pypi-types", diff --git a/crates/uv-virtualenv/Cargo.toml b/crates/uv-virtualenv/Cargo.toml index c3c94b6f0..bd3be567e 100644 --- a/crates/uv-virtualenv/Cargo.toml +++ b/crates/uv-virtualenv/Cargo.toml @@ -32,6 +32,7 @@ anstream = { workspace = true } clap = { workspace = true, features = ["derive"], optional = true } directories = { workspace = true } fs-err = { workspace = true } +itertools = { workspace = true } pathdiff = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } diff --git a/crates/uv-virtualenv/src/activator/activate.fish b/crates/uv-virtualenv/src/activator/activate.fish index b2202c2d6..423a86b5b 100644 --- a/crates/uv-virtualenv/src/activator/activate.fish +++ b/crates/uv-virtualenv/src/activator/activate.fish @@ -83,7 +83,7 @@ set -gx VIRTUAL_ENV '{{ VIRTUAL_ENV_DIR }}' # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling if test (echo $FISH_VERSION | head -c 1) -lt 3 - set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) + set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) else set -gx _OLD_VIRTUAL_PATH $PATH end diff --git a/crates/uv-virtualenv/src/activator/activate.ps1 b/crates/uv-virtualenv/src/activator/activate.ps1 index ba2213865..8eb53231d 100644 --- a/crates/uv-virtualenv/src/activator/activate.ps1 +++ b/crates/uv-virtualenv/src/activator/activate.ps1 @@ -67,7 +67,7 @@ else { New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH -$env:PATH = "$env:VIRTUAL_ENV/{{ BIN_NAME }};" + $env:PATH +$env:PATH = "$env:VIRTUAL_ENV/{{ BIN_NAME }}{{ PATH_SEP }}" + $env:PATH if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) { function global:_old_virtual_prompt { "" diff --git a/crates/uv-virtualenv/src/activator/activate_this.py b/crates/uv-virtualenv/src/activator/activate_this.py index 601c81b08..d54a6740f 100644 --- a/crates/uv-virtualenv/src/activator/activate_this.py +++ b/crates/uv-virtualenv/src/activator/activate_this.py @@ -26,6 +26,7 @@ Use exec(open(this_file).read(), {'__file__': this_file}). This can be used when you must use an existing Python interpreter, not the virtualenv bin/python. """ # noqa: D415 + from __future__ import annotations import os @@ -35,7 +36,7 @@ import sys try: abs_file = os.path.abspath(__file__) except NameError as exc: - msg = "You must use exec(open(this_file).read(), {'__file__': this_file}))" + msg = "You must use exec(open(this_file).read(), {'__file__': this_file})" raise AssertionError(msg) from exc bin_dir = os.path.dirname(abs_file) @@ -44,13 +45,13 @@ base = bin_dir[: -len("{{ BIN_NAME }}") - 1] # strip away the bin part from the # prepend bin to PATH (this file is inside the bin directory) os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)]) os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory -os.environ["VIRTUAL_ENV_PROMPT"] = "" or os.path.basename(base) # noqa: SIM222 +os.environ["VIRTUAL_ENV_PROMPT"] = "{{ VIRTUAL_PROMPT }}" or os.path.basename(base) # noqa: SIM222 # add the virtual environments libraries to the host python import mechanism prev_length = len(sys.path) for lib in "{{ RELATIVE_SITE_PACKAGES }}".split(os.pathsep): path = os.path.realpath(os.path.join(bin_dir, lib)) - site.addsitedir(path.decode("utf-8") if "" else path) + site.addsitedir(path) sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length] sys.real_prefix = sys.prefix diff --git a/crates/uv-virtualenv/src/bare.rs b/crates/uv-virtualenv/src/bare.rs index 1e912c87b..b832ae1af 100644 --- a/crates/uv-virtualenv/src/bare.rs +++ b/crates/uv-virtualenv/src/bare.rs @@ -1,4 +1,4 @@ -//! Create a bare virtualenv without any packages install +//! Create a bare virtualenv without any packages installed. use std::env; use std::env::consts::EXE_SUFFIX; @@ -8,14 +8,16 @@ use std::path::Path; use fs_err as fs; use fs_err::File; -use pypi_types::Scheme; +use itertools::Itertools; use tracing::info; -use crate::{Error, Prompt}; +use pypi_types::Scheme; use uv_fs::{cachedir, Simplified}; use uv_interpreter::{Interpreter, VirtualEnvironment}; use uv_version::version; +use crate::{Error, Prompt}; + /// The bash activate scripts with the venv dependent paths patches out const ACTIVATE_TEMPLATES: &[(&str, &str)] = &[ ("activate", include_str!("activator/activate")), @@ -195,11 +197,21 @@ pub fn create_bare_venv( // Add all the activate scripts for different shells for (name, template) in ACTIVATE_TEMPLATES { - let relative_site_packages = pathdiff::diff_paths( - &interpreter.virtualenv().purelib, - &interpreter.virtualenv().scripts, - ) - .expect("Failed to calculate relative path to site-packages"); + let path_sep = if cfg!(windows) { ";" } else { ":" }; + + let relative_site_packages = [ + interpreter.virtualenv().purelib.as_path(), + interpreter.virtualenv().platlib.as_path(), + ] + .iter() + .dedup() + .map(|path| { + pathdiff::diff_paths(path, &interpreter.virtualenv().scripts) + .expect("Failed to calculate relative path to site-packages") + }) + .map(|path| path.simplified().to_str().unwrap().replace('\\', "\\\\")) + .join(path_sep); + let activator = template .replace( "{{ VIRTUAL_ENV_DIR }}", @@ -211,10 +223,8 @@ pub fn create_bare_venv( "{{ VIRTUAL_PROMPT }}", prompt.as_deref().unwrap_or_default(), ) - .replace( - "{{ RELATIVE_SITE_PACKAGES }}", - relative_site_packages.simplified().to_str().unwrap(), - ); + .replace("{{ PATH_SEP }}", path_sep) + .replace("{{ RELATIVE_SITE_PACKAGES }}", &relative_site_packages); fs::write(scripts.join(name), activator)?; }