Skip to content

Commit 4c48d57

Browse files
committed
Merge remote-tracking branch 'origin/myrrlyn/interpreter-sans-activate-avec-couture' into arrdem/interpreter-sans-activate
2 parents 9cbbc92 + ae0cb9a commit 4c48d57

File tree

3 files changed

+96
-90
lines changed

3 files changed

+96
-90
lines changed

py/tools/py/src/venv.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,12 @@ const RELOCATABLE_SHEBANG: &str = "\
203203
/// - Do we _have_ to include activate scripts?
204204
/// - Do we _have_ to include a versioned symlink?
205205
pub fn create_empty_venv<'a>(
206-
repo: &str,
207-
python: &Path,
206+
repo: &'a str,
207+
python: &'a Path,
208208
version: PythonVersionInfo,
209209
location: &'a Path,
210-
env_file: &Option<PathBuf>,
211-
venv_shim: &Option<PathBuf>,
210+
env_file: Option<&'a Path>,
211+
venv_shim: Option<&'a Path>,
212212
debug: bool,
213213
include_system_site_packages: bool,
214214
include_user_site_packages: bool,
@@ -243,7 +243,7 @@ pub fn create_empty_venv<'a>(
243243
.into_diagnostic()
244244
.wrap_err("Unable to create base venv directory")?;
245245

246-
let using_runfiles_interpreter = !python.exists() && venv_shim != &None;
246+
let using_runfiles_interpreter = !python.exists() && venv_shim.is_some();
247247

248248
let interpreter_cfg_snippet = if using_runfiles_interpreter {
249249
format!(
@@ -290,7 +290,7 @@ aspect-runfiles-repo = {1}
290290
// Assume that the path to `python` is relative to the _home_ of the venv,
291291
// and add the extra `..` to that path to drop the bin dir.
292292

293-
if !python.exists() && venv_shim == &None {
293+
if !python.exists() && venv_shim.is_none() {
294294
Err(miette!(
295295
"Specified interpreter {} doesn't exist!",
296296
python.to_str().unwrap()
@@ -576,6 +576,7 @@ pub fn populate_venv_with_copies(
576576
/// interpreter to traverse up out of the venv and insert other workspaces'
577577
/// site-packages trees (and potentially other import roots) onto the path.
578578
579+
#[expect(unused_variables)]
579580
pub fn populate_venv_with_pth(
580581
venv: Virtualenv,
581582
pth_file: PthFile,

py/tools/venv_bin/src/main.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,14 @@ fn venv_cmd_handler(args: VenvArgs) -> miette::Result<()> {
131131
};
132132

133133
let venv = py::venv::create_empty_venv(
134-
&args
135-
.repo
134+
args.repo
135+
.as_deref()
136136
.expect("The --repo argument is required for static venvs!"),
137137
&args.python,
138138
py::venv::PythonVersionInfo::from_str(&version)?,
139139
&args.location,
140-
&args.env_file,
141-
&args.venv_shim,
140+
args.env_file.as_deref(),
141+
args.venv_shim.as_deref(),
142142
args.debug,
143143
args.include_system_site_packages,
144144
args.include_user_site_packages,
@@ -161,14 +161,14 @@ fn venv_cmd_handler(args: VenvArgs) -> miette::Result<()> {
161161
};
162162

163163
let venv = py::venv::create_empty_venv(
164-
&args
165-
.repo
164+
args.repo
165+
.as_deref()
166166
.expect("The --repo argument is required for static venvs!"),
167167
&args.python,
168168
py::venv::PythonVersionInfo::from_str(&version)?,
169169
&args.location,
170-
&args.env_file,
171-
&args.venv_shim,
170+
args.env_file.as_deref(),
171+
args.venv_shim.as_deref(),
172172
args.debug,
173173
args.include_system_site_packages,
174174
args.include_user_site_packages,

py/tools/venv_shim/src/main.rs

Lines changed: 81 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
use miette::{miette, IntoDiagnostic};
2-
use runfiles::Runfiles; // Depended on out of rules_rust
3-
use std::env::{self, args, var};
1+
use miette::IntoDiagnostic;
2+
use runfiles::Runfiles;
3+
// Depended on out of rules_rust
4+
use std::env;
5+
use std::ffi::OsStr;
46
use std::fs;
5-
use std::io::{self, BufRead};
67
use std::os::unix::process::CommandExt;
78
use std::path::{Path, PathBuf};
89
use std::process::Command;
910

1011
const PYVENV: &str = "pyvenv.cfg";
1112

12-
fn find_venv_root() -> miette::Result<(PathBuf, PathBuf)> {
13-
if let Ok(it) = var("VIRTUAL_ENV") {
13+
fn find_venv_root(exec_name: impl AsRef<Path>) -> miette::Result<(PathBuf, PathBuf)> {
14+
if let Ok(it) = env::var("VIRTUAL_ENV") {
1415
let root = PathBuf::from(it);
1516
let cfg = root.join(PYVENV);
1617
if cfg.exists() {
@@ -20,17 +21,15 @@ fn find_venv_root() -> miette::Result<(PathBuf, PathBuf)> {
2021
}
2122
}
2223

23-
if let Some(this) = args().next() {
24-
let this = PathBuf::from(this);
25-
if let Some(parent) = this.parent().and_then(|it| it.parent()) {
26-
let cfg = parent.join(PYVENV);
27-
if cfg.exists() {
28-
return Ok((parent.to_path_buf(), cfg));
29-
}
24+
let exec_name = exec_name.as_ref().to_owned();
25+
if let Some(parent) = exec_name.parent().and_then(|it| it.parent()) {
26+
let cfg = parent.join(PYVENV);
27+
if cfg.exists() {
28+
return Ok((parent.to_path_buf(), cfg));
3029
}
3130
}
3231

33-
return Err(miette!("Unable to identify a virtualenv home!"));
32+
miette::bail!("Unable to identify a virtualenv home!");
3433
}
3534

3635
#[derive(Debug)]
@@ -40,7 +39,7 @@ enum InterpreterConfig {
4039
}
4140

4241
#[derive(Debug)]
43-
#[allow(unused_attributes)]
42+
#[expect(dead_code)]
4443
struct PyCfg {
4544
root: PathBuf,
4645
cfg: PathBuf,
@@ -50,37 +49,32 @@ struct PyCfg {
5049
}
5150

5251
fn parse_venv_cfg(venv_root: &Path, cfg_path: &Path) -> miette::Result<PyCfg> {
53-
let file = fs::File::open(cfg_path).into_diagnostic()?;
54-
5552
let mut version: Option<String> = None;
5653
let mut bazel_interpreter: Option<String> = None;
5754
let mut bazel_repo: Option<String> = None;
5855
let mut user_site: Option<bool> = None;
5956

6057
// FIXME: Errors possible here?
61-
let reader = io::BufReader::new(file);
62-
63-
for line in reader.lines() {
64-
let line = line.into_diagnostic()?;
65-
if let Some((key, value)) = line.split_once("=") {
66-
let key = key.trim();
67-
let value = value.trim();
68-
match key {
69-
"version_info" => {
70-
version = Some(value.to_string());
71-
}
72-
"aspect-runfiles-interpreter" => {
73-
bazel_interpreter = Some(value.to_string());
74-
}
75-
"aspect-runfiles-repo" => {
76-
bazel_repo = Some(value.to_string());
77-
}
78-
"aspect-include-user-site-packages" => {
79-
user_site = Some(value == "true");
80-
}
81-
// We don't care about other keys
82-
&_ => {}
58+
let cfg_file = fs::read_to_string(cfg_path).into_diagnostic()?;
59+
60+
for (key, value) in cfg_file.lines().flat_map(|s| s.split_once("=")) {
61+
let key = key.trim();
62+
let value = value.trim();
63+
match key {
64+
"version_info" => {
65+
version = Some(value.to_string());
66+
}
67+
"aspect-runfiles-interpreter" => {
68+
bazel_interpreter = Some(value.to_string());
69+
}
70+
"aspect-runfiles-repo" => {
71+
bazel_repo = Some(value.to_string());
72+
}
73+
"aspect-include-user-site-packages" => {
74+
user_site = value.parse().ok();
8375
}
76+
// We don't care about other keys
77+
_ => continue,
8478
}
8579
}
8680

@@ -104,16 +98,14 @@ fn parse_venv_cfg(venv_root: &Path, cfg_path: &Path) -> miette::Result<PyCfg> {
10498
},
10599
user_site: user_site.expect("User site flag not set!"),
106100
}),
107-
(None, _, _) => Err(miette!(
108-
"Invalid pyvenv.cfg file! no interpreter version specified!"
109-
)),
110-
_ => Err(miette!(
111-
"Invalid pyvenv.cfg file! runfiles interpreter incompletely configured!"
112-
)),
101+
(None, _, _) => miette::bail!("Invalid pyvenv.cfg file! no interpreter version specified!"),
102+
_ => {
103+
miette::bail!("Invalid pyvenv.cfg file! runfiles interpreter incompletely configured!")
104+
}
113105
}
114106
}
115107

116-
fn parse_version_info(version_str: &String) -> Option<String> {
108+
fn parse_version_info(version_str: &str) -> Option<String> {
117109
// To avoid pulling in the regex crate, we're gonna do this by hand.
118110
let parts: Vec<_> = version_str.split(".").collect();
119111
match parts[..] {
@@ -159,10 +151,9 @@ fn find_actual_interpreter(cfg: &PyCfg) -> miette::Result<PathBuf> {
159151
InterpreterConfig::External { version } => {
160152
let Some(python_executables) = find_python_executables(&version, &cfg.root.join("bin"))
161153
else {
162-
return Err(miette!(
163-
"No suitable Python interpreter found in PATH matching version '{}'.",
164-
&version,
165-
));
154+
miette::bail!(
155+
"No suitable Python interpreter found in PATH matching version '{version}'."
156+
);
166157
};
167158

168159
#[cfg(feature = "debug")]
@@ -177,27 +168,32 @@ fn find_actual_interpreter(cfg: &PyCfg) -> miette::Result<PathBuf> {
177168

178169
// FIXME: Do we still need to offset beyond one?
179170
let Some(actual_interpreter_path) = python_executables.get(0) else {
180-
return Err(miette!("Unable to find another interpreter!",));
171+
miette::bail!("Unable to find another interpreter!");
181172
};
182173

183174
Ok(actual_interpreter_path.to_owned())
184175
}
185176
InterpreterConfig::Runfiles { rpath, repo } => {
186177
let r = Runfiles::create().unwrap();
187178
if let Some(interpreter) = r.rlocation_from(rpath.as_str(), &repo) {
188-
return Ok(PathBuf::from(interpreter));
179+
Ok(PathBuf::from(interpreter))
189180
} else {
190-
return Err(miette!(format!(
181+
miette::bail!(format!(
191182
"Unable to identify an interpreter for venv {:?}",
192183
cfg.interpreter,
193-
)));
184+
));
194185
}
195186
}
196187
}
197188
}
198189

199190
fn main() -> miette::Result<()> {
200-
let (venv_root, venv_cfg) = find_venv_root()?;
191+
let all_args: Vec<_> = env::args().collect();
192+
let Some((exec_name, exec_args)) = all_args.split_first() else {
193+
miette::bail!("could not discover an execution command-line");
194+
};
195+
196+
let (venv_root, venv_cfg) = find_venv_root(exec_name)?;
201197

202198
let venv_config = parse_venv_cfg(&venv_root, &venv_cfg)?;
203199

@@ -206,32 +202,41 @@ fn main() -> miette::Result<()> {
206202

207203
let actual_interpreter = find_actual_interpreter(&venv_config)?;
208204

209-
let args: Vec<_> = env::args().collect();
210-
211-
let exec_args = &args[1..];
212-
213205
#[cfg(feature = "debug")]
214206
eprintln!(
215207
"[aspect] Attempting to execute: {:?} with argv[0] as {:?} and args as {:?}",
216208
&actual_interpreter_path, &venv_interpreter_path, exec_args,
217209
);
218210

219211
let mut cmd = Command::new(&actual_interpreter);
220-
221-
// Pass along our args
222-
cmd.args(exec_args);
223-
224-
// Lie about the value of argv0 to hoodwink the interpreter as to its
225-
// location on Linux-based platforms.
226-
cmd.arg0(&venv_interpreter);
227-
228-
// Pseudo-`activate`
229-
cmd.env("VIRTUAL_ENV", &venv_root.to_str().unwrap());
230-
let venv_bin = (&venv_root).join("bin").to_str().unwrap().to_owned();
231-
if let Ok(current_path) = var("PATH") {
232-
if current_path.find(&venv_bin).is_none() {
233-
cmd.env("PATH", format!("{}:{}", venv_bin, current_path));
234-
}
212+
let cmd = cmd
213+
// Pass along our args
214+
.args(exec_args)
215+
// Lie about the value of argv0 to hoodwink the interpreter as to its
216+
// location on Linux-based platforms.
217+
.arg0(&venv_interpreter)
218+
// Pseudo-`activate`
219+
.env("VIRTUAL_ENV", &venv_root);
220+
221+
let venv_bin = (&venv_root).join("bin");
222+
// TODO(arrdem|myrrlyn): PATHSEP is : on Unix and ; on Windows
223+
let mut path_segments = env::var("PATH")
224+
.into_diagnostic()? // if the variable is unset or not-utf-8, quit
225+
.split(":") // break into individual entries
226+
.filter(|&p| !p.is_empty()) // skip over `::`, which is possible
227+
.map(ToOwned::to_owned) // we're dropping the big string, so own the fragments
228+
.collect::<Vec<_>>(); // and save them.
229+
let need_venv_in_path = path_segments
230+
.iter()
231+
.find(|&p| OsStr::new(p) == &venv_bin)
232+
.is_none();
233+
if need_venv_in_path {
234+
// append to back
235+
path_segments.push(venv_bin.to_string_lossy().into_owned());
236+
// then move venv_bin to the front of PATH
237+
path_segments.rotate_right(1);
238+
// and write into the child environment. this avoids an empty PATH causing us to write `{venv_bin}:` with a trailing colon
239+
cmd.env("PATH", path_segments.join(":"));
235240
}
236241

237242
// Set the executable pointer for MacOS, but we do it consistently
@@ -254,9 +259,9 @@ fn main() -> miette::Result<()> {
254259

255260
// And punt
256261
let err = cmd.exec();
257-
Err(miette!(format!(
262+
miette::bail!(
258263
"Failed to exec target {}, {}",
259264
actual_interpreter.display(),
260265
err,
261-
)))
266+
)
262267
}

0 commit comments

Comments
 (0)