Skip to content

Commit f08a43d

Browse files
committed
Make the venv shim sorta work at action time
1 parent 46e66bf commit f08a43d

File tree

1 file changed

+77
-29
lines changed

1 file changed

+77
-29
lines changed

py/tools/venv_shim/src/main.rs

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use miette::{miette, IntoDiagnostic};
1+
use miette::{miette, Context, IntoDiagnostic};
22
use runfiles::Runfiles;
33
use which::which;
44
// Depended on out of rules_rust
55
use std::env::{self, current_exe};
66
use std::ffi::OsStr;
77
use std::fs;
8+
use std::hash::{DefaultHasher, Hash, Hasher};
89
use std::os::unix::process::CommandExt;
910
use std::path::{Path, PathBuf};
1011
use std::process::Command;
@@ -187,14 +188,29 @@ fn find_actual_interpreter(executable: impl AsRef<Path>, cfg: &PyCfg) -> miette:
187188
Ok(actual_interpreter_path.to_owned())
188189
}
189190
InterpreterConfig::Runfiles { rpath, repo } => {
190-
let r = Runfiles::create(executable).unwrap();
191-
if let Some(interpreter) = r.rlocation_from(rpath.as_str(), &repo) {
192-
Ok(PathBuf::from(interpreter))
191+
if let Ok(r) = Runfiles::create(executable) {
192+
if let Some(interpreter) = r.rlocation_from(rpath.as_str(), &repo) {
193+
Ok(PathBuf::from(interpreter))
194+
} else {
195+
miette::bail!(format!(
196+
"Unable to identify an interpreter for venv {:?}",
197+
cfg.interpreter,
198+
));
199+
}
193200
} else {
201+
for candidate in [
202+
PathBuf::from("external").join(&rpath),
203+
PathBuf::from("bazel-out/k8-fastbuild/bin/external").join(&rpath),
204+
PathBuf::from(".").join(&rpath),
205+
PathBuf::from("bazel-out/k8-fastbuild/bin").join(&rpath),
206+
] {
207+
if candidate.exists() {
208+
return Ok(candidate);
209+
}
210+
}
194211
miette::bail!(format!(
195-
"Unable to identify an interpreter for venv {:?}",
196-
cfg.interpreter,
197-
));
212+
"Unable to initialize runfiles and unable to identify action layout interpreter"
213+
))
198214
}
199215
}
200216
}
@@ -305,7 +321,9 @@ fn main() -> miette::Result<()> {
305321
#[cfg(feature = "debug")]
306322
eprintln!("[aspect] {:?}", venv_interpreter);
307323

308-
let actual_interpreter = find_actual_interpreter(&executable, &venv_config)?;
324+
let actual_interpreter = find_actual_interpreter(&executable, &venv_config)?
325+
.canonicalize()
326+
.into_diagnostic()?;
309327

310328
#[cfg(feature = "debug")]
311329
eprintln!(
@@ -325,23 +343,24 @@ fn main() -> miette::Result<()> {
325343

326344
let venv_bin = (&venv_root).join("bin");
327345
// TODO(arrdem|myrrlyn): PATHSEP is : on Unix and ; on Windows
328-
let mut path_segments = env::var("PATH")
329-
.into_diagnostic()? // if the variable is unset or not-utf-8, quit
330-
.split(":") // break into individual entries
331-
.filter(|&p| !p.is_empty()) // skip over `::`, which is possible
332-
.map(ToOwned::to_owned) // we're dropping the big string, so own the fragments
333-
.collect::<Vec<_>>(); // and save them.
334-
let need_venv_in_path = path_segments
335-
.iter()
336-
.find(|&p| OsStr::new(p) == &venv_bin)
337-
.is_none();
338-
if need_venv_in_path {
339-
// append to back
340-
path_segments.push(venv_bin.to_string_lossy().into_owned());
341-
// then move venv_bin to the front of PATH
342-
path_segments.rotate_right(1);
343-
// and write into the child environment. this avoids an empty PATH causing us to write `{venv_bin}:` with a trailing colon
344-
cmd.env("PATH", path_segments.join(":"));
346+
if let Ok(path) = env::var("PATH") {
347+
let mut path_segments = path
348+
.split(":") // break into individual entries
349+
.filter(|&p| !p.is_empty()) // skip over `::`, which is possible
350+
.map(ToOwned::to_owned) // we're dropping the big string, so own the fragments
351+
.collect::<Vec<_>>(); // and save them.
352+
let need_venv_in_path = path_segments
353+
.iter()
354+
.find(|&p| OsStr::new(p) == &venv_bin)
355+
.is_none();
356+
if need_venv_in_path {
357+
// append to back
358+
path_segments.push(venv_bin.to_string_lossy().into_owned());
359+
// then move venv_bin to the front of PATH
360+
path_segments.rotate_right(1);
361+
// and write into the child environment. this avoids an empty PATH causing us to write `{venv_bin}:` with a trailing colon
362+
cmd.env("PATH", path_segments.join(":"));
363+
}
345364
}
346365

347366
// Set the executable pointer for MacOS, but we do it consistently
@@ -360,10 +379,39 @@ fn main() -> miette::Result<()> {
360379
// the home = property in the pyvenv.cfg being wrong because we don't
361380
// (currently) have a good way to map the interpreter rlocation to a
362381
// relative path.
363-
cmd.env(
364-
"PYTHONHOME",
365-
&actual_interpreter.parent().unwrap().parent().unwrap(),
366-
);
382+
let home = &actual_interpreter
383+
.parent()
384+
.unwrap()
385+
.parent()
386+
.unwrap()
387+
.canonicalize()
388+
.into_diagnostic()
389+
.wrap_err("Failed to canonicalize the interpreter home")?;
390+
391+
#[cfg(feature = "debug")]
392+
eprintln!("Setting PYTHONHOME to {home:?} for {actual_interpreter:?}");
393+
cmd.env("PYTHONHOME", home);
394+
395+
let mut hasher = DefaultHasher::new();
396+
venv_interpreter.to_str().unwrap().hash(&mut hasher);
397+
home.to_str().unwrap().hash(&mut hasher);
398+
399+
cmd.env("ASPECT_PY_VALIDITY", format!("{}", hasher.finish()));
400+
401+
// For the future, we could read, validate and reuse the env state.
402+
//
403+
// if let Ok(home) = env::var("PYTHONHOME") {
404+
// if let Ok(executable) = env::var("PYTHONEXECUTABLE") {
405+
// if let Ok(checksum) = env::var("ASPECT_PY_VALIDITY") {
406+
// let mut hasher = DefaultHasher::new();
407+
// executable.hash(&mut hasher);
408+
// home.hash(&mut hasher);
409+
// if checksum == format!("{}", hasher.finish()) {
410+
// return Ok(PathBuf::from(home).join("bin/python3"));
411+
// }
412+
// }
413+
// }
414+
// }
367415

368416
// And punt
369417
let err = cmd.exec();

0 commit comments

Comments
 (0)