Skip to content

Commit 2a023f2

Browse files
committed
Take II: Use PYTHONEXECUTABLE not a custom rlocation
1 parent 9d3df45 commit 2a023f2

File tree

4 files changed

+55
-45
lines changed

4 files changed

+55
-45
lines changed

py/tools/py/src/activate.tmpl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- mode: sh -*-
12
# Adapted from CPython Lib/venv/scripts/common/activate
23

34
set -eu -o pipefail
@@ -58,9 +59,15 @@ deactivate nondestructive
5859
# The runfiles library code has some deps on this so we just set it :/
5960
: "${BASH_SOURCE:=$0}"
6061

61-
VIRTUAL_ENV="$(realpath "$(dirname "$(dirname "${BASH_SOURCE}")")")"
62+
VIRTUAL_ENV="$(dirname "$(dirname "${BASH_SOURCE}")")"
6263
export VIRTUAL_ENV
6364

65+
# HACK: (Ab)use the MacOS $PYTHONEXECUTABLE to record the `.runfiles`-relative
66+
# interpreter path. This helps us avoid issues with the interpreter's path being
67+
# `realpath`-ed in such a way that it escapes the `.runfiles` tree.
68+
PYTHONEXECUTABLE="${VIRTUAL_ENV}/bin/python"
69+
export PYTHONEXECUTABLE
70+
6471
# unset PYTHONHOME if set
6572
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
6673
# could use `if (set -u; : $PYTHONHOME) ;` in bash.

py/tools/py/src/runfiles_interpreter.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# -*- mode: sh -*-
12
# --- Runfiles-based interpreter setup --
23

34
# If the runfiles dir is unset AND we will fail to find a runfiles manifest

py/tools/py/src/venv.rs

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -380,13 +380,6 @@ pub fn create_empty_venv<'a>(
380380
)
381381
.into_diagnostic()?;
382382

383-
// And include our horrid path machinery
384-
fs::write(
385-
&venv.site_dir.join("_aspect.py"),
386-
include_str!("_aspect.py"),
387-
)
388-
.into_diagnostic()?;
389-
390383
Ok(venv)
391384
}
392385

@@ -448,11 +441,10 @@ pub fn populate_venv_with_copies(
448441
let action_bin_dir = action_src_dir.join(bin_dir);
449442

450443
#[cfg(feature = "debug")]
451-
{
452-
println!("action_src_dir: {}", &action_src_dir.to_str().unwrap());
453-
println!("action_bin_dir: {}", &action_bin_dir.to_str().unwrap());
454-
println!("venv.site_dir: {}", &venv.site_dir.to_str().unwrap());
455-
}
444+
eprintln!("action_src_dir: {}", &action_src_dir.to_str().unwrap());
445+
446+
#[cfg(feature = "debug")]
447+
eprintln!("action_bin_dir: {}", &action_bin_dir.to_str().unwrap());
456448

457449
let source_pth = File::open(pth_file.src.as_path())
458450
.into_diagnostic()
@@ -467,7 +459,7 @@ pub fn populate_venv_with_copies(
467459
.write(
468460
b"\
469461
# Generated by Aspect py_binary
470-
# Sets up imports within the .runfiles tree
462+
# Contains relative import paths to non site-package trees within the .runfiles
471463
",
472464
)
473465
.into_diagnostic()?;
@@ -540,16 +532,14 @@ pub fn populate_venv_with_copies(
540532
//
541533
// [1] https://github.com/python/cpython/blob/ce31ae5209c976d28d1c21fcbb06c0ae5e50a896/Lib/site.py#L215
542534

535+
let path_to_runfiles =
536+
diff_paths(&action_bin_dir, action_bin_dir.join(&venv.site_dir)).unwrap();
537+
543538
writeln!(dest_pth_writer, "# @{}", line).into_diagnostic()?;
544539
writeln!(
545540
dest_pth_writer,
546-
"import _aspect;_aspect.pathadd('{}', workspace='{}')",
547-
line.replace("'", "\\'"),
548-
if workspace == main_module {
549-
&""
550-
} else {
551-
main_module.to_str().unwrap()
552-
},
541+
"{}",
542+
path_to_runfiles.join(entry).to_str().unwrap()
553543
)
554544
.into_diagnostic()?;
555545
}

py/tools/venv_shim/src/main.rs

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use miette::{miette, Context, IntoDiagnostic};
22
use std::env;
3+
use std::env::VarError;
34
use std::fs;
45
use std::io::{self, BufRead};
56
use std::os::unix::process::CommandExt;
@@ -64,7 +65,6 @@ fn find_python_executables(version_from_cfg: &str, exclude_dir: &Path) -> Option
6465
None
6566
}
6667
})
67-
//.filter_map(|path| path.canonicalize().ok())
6868
.filter(|potential_executable| potential_executable.parent() != Some(exclude_dir))
6969
.filter(|potential_executable| compare_versions(version_from_cfg, &potential_executable))
7070
.collect();
@@ -77,16 +77,32 @@ fn find_python_executables(version_from_cfg: &str, exclude_dir: &Path) -> Option
7777
}
7878

7979
fn main() -> miette::Result<()> {
80-
let current_exe = env::current_exe().unwrap();
80+
let venv_home_path = PathBuf::from(
81+
env::var("VIRTUAL_ENV")
82+
.into_diagnostic()
83+
.wrap_err("[aspect] $VIRTUAL_ENV was unbound! A venv must be activated.")
84+
.unwrap(),
85+
);
86+
87+
let venv_interpreter_path: PathBuf = env::var("PYTHONEXECUTABLE")
88+
.map(|x| PathBuf::from(x))
89+
.map_err(|_| Ok::<PathBuf, VarError>(venv_home_path.join("bin/python")))
90+
.unwrap();
91+
92+
let excluded_interpreters_dir = &venv_home_path.join("bin");
93+
8194
let args: Vec<_> = env::args().collect();
8295

83-
#[cfg(feature = "debug")]
84-
eprintln!("[aspect] Current executable path: {:?}", current_exe);
96+
//#[cfg(feature = "debug")]
97+
eprintln!(
98+
"[aspect] Current executable path: {:?}",
99+
&venv_interpreter_path
100+
);
85101

86-
let Some(pyvenv_cfg_path) = find_pyvenv_cfg(&current_exe) else {
102+
let Some(pyvenv_cfg_path) = find_pyvenv_cfg(&venv_interpreter_path) else {
87103
return Err(miette!("pyvenv.cfg not found one directory level up."));
88104
};
89-
#[cfg(feature = "debug")]
105+
//#[cfg(feature = "debug")]
90106
eprintln!("[aspect] Found pyvenv.cfg at: {:?}", &pyvenv_cfg_path);
91107

92108
let version_info_result = extract_pyvenv_version_info(&pyvenv_cfg_path)
@@ -101,33 +117,32 @@ fn main() -> miette::Result<()> {
101117
return Err(miette!("version_info key not found in pyvenv.cfg."));
102118
};
103119

104-
#[cfg(feature = "debug")]
105-
eprintln!("[aspect] version_info from pyvenv.cfg: {}", &version_info);
120+
//#[cfg(feature = "debug")]
121+
eprintln!("[aspect] version_info from pyvenv.cfg: {:?}", &version_info);
106122

107123
let Some(target_python_version) = parse_version_info(&version_info) else {
108124
return Err(miette!("Could not parse version_info as x.y."));
109125
};
110126

111-
#[cfg(feature = "debug")]
127+
//#[cfg(feature = "debug")]
112128
eprintln!(
113129
"[aspect] Parsed target Python version (major.minor): {}",
114130
&target_python_version
115131
);
116132

117-
let exclude_dir = current_exe.parent().unwrap().canonicalize().unwrap();
133+
//#[cfg(feature = "debug")]
134+
eprintln!("[aspect] Ignoring dir {:?}", &excluded_interpreters_dir);
118135

119-
#[cfg(feature = "debug")]
120-
eprintln!("[aspect] Ignoring dir {:?}", &exclude_dir);
121-
122-
let Some(python_executables) = find_python_executables(&target_python_version, &exclude_dir)
136+
let Some(python_executables) =
137+
find_python_executables(&target_python_version, &excluded_interpreters_dir)
123138
else {
124139
return Err(miette!(
125140
"No suitable Python interpreter found in PATH matching version '{}'.",
126141
&version_info,
127142
));
128143
};
129144

130-
#[cfg(feature = "debug")]
145+
//#[cfg(feature = "debug")]
131146
{
132147
eprintln!("[aspect] Found potential Python interpreters in PATH with matching version:");
133148
for exe in &python_executables {
@@ -148,30 +163,27 @@ fn main() -> miette::Result<()> {
148163
}
149164
};
150165

151-
let Some(interpreter_path) = python_executables.get(index) else {
166+
let Some(actual_interpreter_path) = python_executables.get(index) else {
152167
return Err(miette!(
153168
"Unable to find another interpreter at index {}",
154169
index
155170
));
156171
};
157172

158-
let exe_path = current_exe.to_string_lossy().into_owned();
159173
let exec_args = &args[1..];
160174

161-
#[cfg(feature = "debug")]
175+
//#[cfg(feature = "debug")]
162176
eprintln!(
163177
"[aspect] Attempting to execute: {:?} with argv[0] as {:?} and args as {:?}",
164-
interpreter_path, exe_path, exec_args,
178+
&actual_interpreter_path, &venv_interpreter_path, exec_args,
165179
);
166180

167-
let mut cmd = Command::new(&interpreter_path);
181+
let mut cmd = Command::new(&actual_interpreter_path);
168182
cmd.args(exec_args);
169183

170184
// Lie about the value of argv0 to hoodwink the interpreter as to its
171185
// location on Linux-based platforms.
172-
if cfg!(target_os = "linux") {
173-
cmd.arg0(&exe_path);
174-
}
186+
cmd.arg0(&venv_interpreter_path);
175187

176188
// On MacOS however, there are facilities for asking the C runtime/OS
177189
// what the real name of the interpreter executable is, and that value
@@ -181,7 +193,7 @@ fn main() -> miette::Result<()> {
181193
// https://github.com/python/cpython/blob/68e72cf3a80362d0a2d57ff0c9f02553c378e537/Modules/getpath.c#L778
182194
// https://docs.python.org/3/using/cmdline.html#envvar-PYTHONEXECUTABLE
183195
if cfg!(target_os = "macos") {
184-
cmd.env("PYTHONEXECUTABLE", &exe_path);
196+
cmd.env("PYTHONEXECUTABLE", &venv_interpreter_path);
185197
}
186198

187199
// Re-export the counter so it'll go up

0 commit comments

Comments
 (0)