diff --git a/Cargo.lock b/Cargo.lock index 95db78e..b6f7d95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,6 +1459,12 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + [[package]] name = "pep440_rs" version = "0.7.3" @@ -1594,6 +1600,7 @@ dependencies = [ "log", "logging_timer", "pex", + "platform", "regex", "venv", ] @@ -1630,6 +1637,7 @@ dependencies = [ "fs-err", "is_executable", "nix", + "pathdiff", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 62fcf20..346a70d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,6 +118,7 @@ log = "0.4" logging_timer = "1.1" nix = { version = "0.31", features = ["fs"] } owo-colors = "4.3" +pathdiff = "0.2" pep440_rs = "0.7" pep508_rs = "0.9" python-pkginfo = "0.6" diff --git a/crates/pexrs/Cargo.toml b/crates/pexrs/Cargo.toml index 8251323..0063745 100644 --- a/crates/pexrs/Cargo.toml +++ b/crates/pexrs/Cargo.toml @@ -14,5 +14,6 @@ itertools = { workspace = true } log = { workspace = true } logging_timer = { workspace = true } pex = { path = "../pex" } +platform = { path = "../platform" } regex = { workspace = true } venv = { path = "../venv" } \ No newline at end of file diff --git a/crates/pexrs/src/lib.rs b/crates/pexrs/src/lib.rs index b265b9a..6a63a5c 100644 --- a/crates/pexrs/src/lib.rs +++ b/crates/pexrs/src/lib.rs @@ -112,9 +112,10 @@ fn prepare_venv<'a>( #[cfg(unix)] if let Some(sh_boot_seed_dir) = sh_boot_seed_dir { fs::create_dir_all(&sh_boot_seed_dir)?; - std::os::unix::fs::symlink( + platform::unix::symlink( venv_dir.join("pex"), sh_boot_seed_dir.join(venv_interpreter.most_specific_exe_name()), + true, )?; } Virtualenv::enclosing(venv_interpreter) diff --git a/crates/platform/Cargo.toml b/crates/platform/Cargo.toml index de489ce..39374cd 100644 --- a/crates/platform/Cargo.toml +++ b/crates/platform/Cargo.toml @@ -8,6 +8,7 @@ edition = "2024" [dependencies] anyhow = { workspace = true } fs-err = { workspace = true } +pathdiff = { workspace = true } # N.B.: Although is_executable is cross-platform, we only use it for Windows. The nix unistd access # function is more robust that the check is_executable does on unix. diff --git a/crates/platform/src/lib.rs b/crates/platform/src/lib.rs index 07cecfb..3ddbf50 100644 --- a/crates/platform/src/lib.rs +++ b/crates/platform/src/lib.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(unix)] -mod unix; +pub mod unix; #[cfg(windows)] mod windows; diff --git a/crates/platform/src/unix.rs b/crates/platform/src/unix.rs index 6c9e87b..79d060d 100644 --- a/crates/platform/src/unix.rs +++ b/crates/platform/src/unix.rs @@ -1,9 +1,10 @@ // Copyright 2026 Pex project contributors. // SPDX-License-Identifier: Apache-2.0 +use std::borrow::Cow; use std::fs::File; use std::os::unix::ffi::OsStrExt; -use std::os::unix::fs::{PermissionsExt, symlink}; +use std::os::unix::fs::PermissionsExt; use std::path::Path; use anyhow::{anyhow, bail}; @@ -11,8 +12,24 @@ use nix::errno::Errno; use nix::unistd; use nix::unistd::AccessFlags; -pub fn link_or_copy(src: impl AsRef, dst: impl AsRef) -> anyhow::Result<()> { - symlink(src, dst).map_err(anyhow::Error::new) +pub fn link_or_copy( + src: impl AsRef, + dst: impl AsRef, + relative: bool, +) -> anyhow::Result<()> { + symlink(src, dst, relative) +} + +pub fn symlink(src: impl AsRef, dst: impl AsRef, relative: bool) -> anyhow::Result<()> { + let src = if relative + && let Some(rel_base) = dst.as_ref().parent() + && let Some(rel_path) = pathdiff::diff_paths(src.as_ref(), rel_base) + { + Cow::Owned(rel_path) + } else { + Cow::Borrowed(src.as_ref()) + }; + std::os::unix::fs::symlink(src, dst).map_err(anyhow::Error::new) } pub fn is_executable(path: impl AsRef) -> anyhow::Result { diff --git a/crates/platform/src/windows.rs b/crates/platform/src/windows.rs index 4f896af..769e608 100644 --- a/crates/platform/src/windows.rs +++ b/crates/platform/src/windows.rs @@ -7,7 +7,11 @@ use std::path::Path; use fs_err as fs; use is_executable::IsExecutable; -pub fn link_or_copy(src: impl AsRef, dst: impl AsRef) -> anyhow::Result<()> { +pub fn link_or_copy( + src: impl AsRef, + dst: impl AsRef, + _relative: bool, +) -> anyhow::Result<()> { fs::hard_link(&src, &dst) .or_else(|_| fs::copy(src, dst).map(|_| ())) .map_err(anyhow::Error::new) diff --git a/crates/venv/src/venv_pex.rs b/crates/venv/src/venv_pex.rs index daf9a57..2a22918 100644 --- a/crates/venv/src/venv_pex.rs +++ b/crates/venv/src/venv_pex.rs @@ -263,7 +263,7 @@ if __name__ == "__main__": ) )?; mark_executable(main_py_fp.file_mut())?; - link_or_copy(&main_py, venv.prefix().join("pex")) + link_or_copy(&main_py, venv.prefix().join("pex"), true) } fn write_repl<'a>( diff --git a/crates/venv/src/virtualenv.rs b/crates/venv/src/virtualenv.rs index da02ca2..68b15f6 100644 --- a/crates/venv/src/virtualenv.rs +++ b/crates/venv/src/virtualenv.rs @@ -129,7 +129,11 @@ fn create_pep_405_venv<'a>( )?; let scripts_dir = path.join(SCRIPTS_DIR); fs::create_dir_all(&scripts_dir)?; - link_or_copy(&base_interpreter.realpath, scripts_dir.join(PYTHON_EXE))?; + link_or_copy( + &base_interpreter.realpath, + scripts_dir.join(PYTHON_EXE), + false, + )?; let site_packages_relpath = site_packages_relpath(&base_interpreter); fs::create_dir_all(path.join(site_packages_relpath.as_ref()))?; Ok(site_packages_relpath)