Skip to content

Commit 7c4060f

Browse files
arrdemthesayyn
andauthored
fix(shim): Apply path canonicalization (#565)
Fixes a user-visible bug present in v1.5.0 which could cause the Python shim to skip over viable interpreters and fail to resolve an appropriate one. ### Changes are visible to end-users: yes - Searched for relevant documentation and updated as needed: yes - Breaking change (forces users to change their own code or config): no - Suggested release notes appear below: yes/no ### Details The Python interpreter shim exists to inspect the `pyvenv.cfg` sentinel file which defines a virtualenv, extract the interpreter version for the virtualenv and then traverse the `$PATH` attempting to identify a viable interpreter of the required version which it will chainload. As a reminder a venv will look something like ``` |-- .venv | |-- bin | | |-- activate | | |-- pip | | |-- pip3 | | |-- pip3.12 | | |-- python <- our shim | | |-- python3 -> python | | `-- python3.12 -> python | |-- include | | `-- python3.12 | |-- lib | | `-- python3.12 | | `-- site-packages | | |-- pip | | `-- pip-24.0.dist-info | |-- lib64 -> lib | `-- pyvenv.cfg `-- src `-- app `-- __main__.py ``` The trick is that since the interpreter shim is emplaced in the venv as `bin/python` and symlinked to by the other versioned Python files we can't simply `exec python${VERSION}` since when the venv is activated and `$VIRTUAL_ENV/bin` is prepended to the `$PATH` that would cause an infinite self-loop. Enabling the `debug` config on the shim crate and running the internal_venv example @ v1.5.0 produces the following (simplified) output ```shellsession $ bazel run //examples/py_venv:internal_venv INFO: Analyzed target //examples/py_venv:internal_venv (457 packages loaded, 56604 targets configured). INFO: Found 1 target... Target //examples/py_venv:internal_venv up-to-date: bazel-bin/examples/py_venv/internal_venv INFO: Elapsed time: 15.549s, Critical Path: 4.20s INFO: 4 processes: 2 internal, 2 darwin-sandbox. INFO: Build completed successfully, 4 total actions INFO: Running command line: bazel-bin/examples/py_venv/internal_venv # First attempt [aspect] Current executable path: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/.internal_venv/bin/python" [aspect] Found pyvenv.cfg at: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/.internal_venv/pyvenv.cfg" [aspect] version_info from pyvenv.cfg: 3.9.0 [aspect] Parsed target Python version (major.minor): 3.9 [aspect] Found potential Python interpreters in PATH with matching version: [aspect] - "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/bin/python3.9" [aspect] - "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/python_toolchain_aarch64-apple-darwin/bin/python3.9" [aspect] - "${HOME}/.local/pyenv/shims/python3.9" [aspect] Attempting to execute: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/bin/python3.9" with argv[0] as "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/.internal_venv/bin/python" and args as ["-B", "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/say.py"] # Second attempt [aspect] Current executable path: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/bin/python3.9" [aspect] Found pyvenv.cfg at: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/pyvenv.cfg" [aspect] version_info from pyvenv.cfg: 3.9.0 [aspect] Parsed target Python version (major.minor): 3.9 [aspect] Found potential Python interpreters in PATH with matching version: [aspect] - "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/python_toolchain_aarch64-apple-darwin/bin/python3.9" [aspect] - "${HOME}/.local/pyenv/shims/python3.9" [aspect] Attempting to execute: "${HOME}/.local/pyenv/shims/python3.9" with argv[0] as "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/bin/python3.9" and args as ["-B", "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/say.py"] # Third attempt [aspect] Current executable path: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/bin/python3.9" [aspect] Found pyvenv.cfg at: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/pyvenv.cfg" [aspect] version_info from pyvenv.cfg: 3.9.0 [aspect] Parsed target Python version (major.minor): 3.9 [aspect] Found potential Python interpreters in PATH with matching version: [aspect] - "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/python_toolchain_aarch64-apple-darwin/bin/python3.9" [aspect] - "${HOME}/.local/pyenv/shims/python3.9" Error: × Unable to find another interpreter at index 2 ``` The key detail of which is ``` # First attempt [aspect] Found pyvenv.cfg at: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/.internal_venv/pyvenv.cfg" # Second attempt [aspect] Found pyvenv.cfg at: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/pyvenv.cfg" # Third attempt [aspect] Found pyvenv.cfg at: "${EXECROOT}/aspect_rules_py/bazel-out/fastbuild/bin/examples/py_venv/.internal_venv/pyvenv.cfg" ``` What's happening here is that under `bazel run` the first `exec()` invocation is of the interpreter as it exists in a `.runfiles` tree under `bazel-out`. However that runfiles tree consists of symlinks to actual outputs elsewhere, so the observed path of the `pyvenv.cfg` (which is _not_ canonicalized and is located relative to the nominal interpreter path) appears to change between attempts going from the non-canonicalized `.runfiles/` entry to the "actual" build output dir. This causes the second attempt to change its filtering rules, and skip the viable toolchain-provided interpreter present in the `$PATH`. The fix is to canonicalize both the `$PATH` entries and the `pyvenv.cfg`'s paths when filtering out possibilities to ensure that the filtering behavior is stable between attempts. With that fix applied the shim correctly identifies the required interpreter on the first try. ```shellsession $ bazel run -c dbg //examples/py_venv:internal_venv INFO: Analyzed target //examples/py_venv:internal_venv (0 packages loaded, 56606 targets configured). INFO: From Compiling Rust rlib py (4 files): INFO: Found 1 target... Target //examples/py_venv:internal_venv up-to-date: bazel-bin/examples/py_venv/internal_venv INFO: Elapsed time: 129.494s, Critical Path: 75.62s INFO: 472 processes: 166 internal, 306 darwin-sandbox. INFO: Build completed successfully, 472 total actions INFO: Running command line: bazel-bin/examples/py_venv/internal_venv [aspect] Current executable path: "/private/var/tmp/_bazel_arrdem/93bfea6cdc1153cc29a75400cd38823a/execroot/aspect_rules_py/bazel-out/darwin_arm64-dbg/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/.internal_venv/bin/python" [aspect] Found pyvenv.cfg at: "/private/var/tmp/_bazel_arrdem/93bfea6cdc1153cc29a75400cd38823a/execroot/aspect_rules_py/bazel-out/darwin_arm64-dbg/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/.internal_venv/pyvenv.cfg" [aspect] version_info from pyvenv.cfg: 3.9.0 [aspect] Parsed target Python version (major.minor): 3.9 [aspect] Ignoring dir "/private/var/tmp/_bazel_arrdem/93bfea6cdc1153cc29a75400cd38823a/execroot/aspect_rules_py/bazel-out/darwin_arm64-dbg/bin/examples/py_venv/.internal_venv/bin" [aspect] Found potential Python interpreters in PATH with matching version: [aspect] - "/private/var/tmp/_bazel_arrdem/93bfea6cdc1153cc29a75400cd38823a/external/python_toolchain_aarch64-apple-darwin/bin/python3.9" [aspect] - "/Users/arrdem/.local/pyenv/shims/python3.9" [aspect] Attempting to execute: "/private/var/tmp/_bazel_arrdem/93bfea6cdc1153cc29a75400cd38823a/external/python_toolchain_aarch64-apple-darwin/bin/python3.9" with argv[0] as "/private/var/tmp/_bazel_arrdem/93bfea6cdc1153cc29a75400cd38823a/execroot/aspect_rules_py/bazel-out/darwin_arm64-dbg/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/.internal_venv/bin/python" and args as ["-B", "/private/var/tmp/_bazel_arrdem/93bfea6cdc1153cc29a75400cd38823a/execroot/aspect_rules_py/bazel-out/darwin_arm64-dbg/bin/examples/py_venv/internal_venv.runfiles/aspect_rules_py/examples/py_venv/say.py"] --- virtualenv: ${RUNFILES}/aspect_rules_py/examples/py_venv/.internal_venv/lib/python3.9/site-packages/_virtualenv.py sys.prefix: ${RUNFILES}/aspect_rules_py/examples/py_venv/.internal_venv sys.path: - ${BAZEL_EXECROOT}/aspect_rules_py/bazel-out/darwin_arm64-dbg/bin/examples/py_venv - ${RUNFILES}/python_toolchain_aarch64-apple-darwin/lib/python39.zip - ${RUNFILES}/python_toolchain_aarch64-apple-darwin/lib/python3.9 - ${RUNFILES}/python_toolchain_aarch64-apple-darwin/lib/python3.9/lib-dynload - ${RUNFILES}/aspect_rules_py/examples/py_venv/.internal_venv/lib/python3.9/site-packages - ${RUNFILES}/aspect_rules_py - ${RUNFILES}/aspect_rules_py/examples/py_venv - /Users/arrdem/.local/lib/python3.9/site-packages - ${RUNFILES}/python_toolchain_aarch64-apple-darwin/lib/python3.9/site-packages site.PREFIXES: - ${RUNFILES}/aspect_rules_py/examples/py_venv/.internal_venv - ${RUNFILES}/python_toolchain_aarch64-apple-darwin - ${RUNFILES}/python_toolchain_aarch64-apple-darwin ___________________________________________ | hello py_venv! (built at <BUILD_TIMESTAMP>) | =========================================== \ \ ^__^ (oo)\_______ (__)\ )\/\ ||----w | || || ``` ### Test plan - [x] The examples now actually work (sigh) - [x] The examples are now exercised in our release integration testing --------- Co-authored-by: Sahin Yort <[email protected]>
1 parent 549a60c commit 7c4060f

File tree

4 files changed

+129
-100
lines changed

4 files changed

+129
-100
lines changed

e2e/use_release/minimal_download_test.sh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,15 @@ bazel test --test_output=streamed //...
6565
cd ../..
6666
rm MODULE.bazel
6767
mv MODULE.bazel.orig MODULE.bazel
68-
)
68+
)
69+
70+
#############
71+
# Smoke test py_venv examples
72+
(
73+
cd ../..
74+
bazel run //examples/py_venv:venv -- -c 'print("Hello, world")'
75+
bazel run //examples/py_venv:internal_venv
76+
bazel run --stamp //examples/py_venv:internal_venv
77+
bazel run //examples/py_venv:external_venv
78+
bazel run --stamp //examples/py_venv:external_venv
79+
)

py/tests/py_venv_image_layer/my_app_layers_listing.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2511,9 +2511,9 @@ files:
25112511
- drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/
25122512
- drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/bin/
25132513
- -rwxr-xr-x 0 0 0 1918 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/bin/activate
2514-
- -rwxr-xr-x 0 0 0 5183072 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/bin/python
2515-
- -rwxr-xr-x 0 0 0 5183072 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/bin/python3
2516-
- -rwxr-xr-x 0 0 0 5183072 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/bin/python3.9
2514+
- -rwxr-xr-x 0 0 0 5118072 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/bin/python
2515+
- -rwxr-xr-x 0 0 0 5118072 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/bin/python3
2516+
- -rwxr-xr-x 0 0 0 5118072 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/bin/python3.9
25172517
- drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/lib/
25182518
- drwxr-xr-x 0 0 0 0 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/lib/python3.9/
25192519
- -rwxr-xr-x 0 0 0 323 Jan 1 2023 ./py/tests/py_venv_image_layer/my_app_bin.runfiles/aspect_rules_py/py/tests/py_venv_image_layer/.my_app_bin/pyvenv.cfg

py/tools/venv_shim/BUILD.bazel

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
load("//tools/release:defs.bzl", "rust_binary")
22

3+
config_setting(
4+
name = "debug_build",
5+
values = {
6+
"compilation_mode": "dbg",
7+
},
8+
)
9+
310
rust_binary(
411
name = "shim",
512
srcs = [
613
"src/main.rs",
714
],
15+
crate_features = select({
16+
":debug_build": [
17+
"debug",
18+
],
19+
"//conditions:default": [],
20+
}),
21+
rustc_flags = select({
22+
":debug_build": [],
23+
"//conditions:default": [
24+
"-C",
25+
"opt-level=3",
26+
],
27+
}),
828
deps = [
929
"@crate_index//:miette",
1030
],

py/tools/venv_shim/src/main.rs

Lines changed: 94 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use miette::miette;
1+
use miette::{miette, Context, IntoDiagnostic};
22
use std::env;
33
use std::fs;
44
use std::io::{self, BufRead};
@@ -64,6 +64,7 @@ fn find_python_executables(version_from_cfg: &str, exclude_dir: &Path) -> Option
6464
None
6565
}
6666
})
67+
.filter_map(|path| path.canonicalize().ok())
6768
.filter(|potential_executable| potential_executable.parent() != Some(exclude_dir))
6869
.filter(|potential_executable| compare_versions(version_from_cfg, &potential_executable))
6970
.collect();
@@ -80,116 +81,113 @@ fn main() -> miette::Result<()> {
8081
let args: Vec<_> = env::args().collect();
8182

8283
#[cfg(feature = "debug")]
83-
println!("[aspect] Current executable path: {:?}", current_exe);
84+
eprintln!("[aspect] Current executable path: {:?}", current_exe);
8485

85-
let pyvenv_cfg_path = match find_pyvenv_cfg(&current_exe) {
86-
Some(path) => {
87-
#[cfg(feature = "debug")]
88-
eprintln!("[aspect] Found pyvenv.cfg at: {:?}", path);
89-
path
90-
}
91-
None => {
92-
return Err(miette!("pyvenv.cfg not found one directory level up."));
93-
}
86+
let Some(pyvenv_cfg_path) = find_pyvenv_cfg(&current_exe) else {
87+
return Err(miette!("pyvenv.cfg not found one directory level up."));
9488
};
95-
96-
let version_info_result = extract_pyvenv_version_info(&pyvenv_cfg_path).unwrap();
97-
let version_info = match version_info_result {
98-
Some(v) => {
99-
#[cfg(feature = "debug")]
100-
eprintln!("[aspect] version_info from pyvenv.cfg: {}", v);
101-
v
102-
}
103-
None => {
104-
return Err(miette!("version_info key not found in pyvenv.cfg."));
105-
}
89+
#[cfg(feature = "debug")]
90+
eprintln!("[aspect] Found pyvenv.cfg at: {:?}", &pyvenv_cfg_path);
91+
92+
let version_info_result = extract_pyvenv_version_info(&pyvenv_cfg_path)
93+
.into_diagnostic()
94+
.wrap_err(format!(
95+
"Failed to parse pyvenv.cfg {}",
96+
&pyvenv_cfg_path.to_str().unwrap(),
97+
))
98+
.unwrap();
99+
100+
let Some(version_info) = version_info_result else {
101+
return Err(miette!("version_info key not found in pyvenv.cfg."));
106102
};
107103

108-
let target_python_version = match parse_version_info(&version_info) {
109-
Some(v) => {
110-
#[cfg(feature = "debug")]
111-
eprintln!("[aspect] Parsed target Python version (major.minor): {}", v);
112-
v
113-
}
114-
None => {
115-
return Err(miette!("Could not parse version_info as x.y."));
116-
}
117-
};
104+
#[cfg(feature = "debug")]
105+
eprintln!("[aspect] version_info from pyvenv.cfg: {}", &version_info);
118106

119-
let exclude_dir = current_exe.parent().unwrap();
120-
if let Some(python_executables) = find_python_executables(&target_python_version, exclude_dir) {
121-
#[cfg(feature = "debug")]
122-
{
123-
eprintln!(
124-
"[aspect] Found potential Python interpreters in PATH with matching version:"
125-
);
126-
for exe in &python_executables {
127-
println!("[aspect] - {:?}", exe);
128-
}
129-
}
107+
let Some(target_python_version) = parse_version_info(&version_info) else {
108+
return Err(miette!("Could not parse version_info as x.y."));
109+
};
130110

131-
// Attempt to break infinite recursion through this shim by counting up
132-
// the number of times we've come back to this shim and incrementing it
133-
// until we hit something on the path that DOESN'T come back here, or we
134-
// run out of path entries.
135-
let index: usize = {
136-
match env::var(COUNTER_VAR) {
137-
// Whatever the previous value was didn't work because we're
138-
// back here, so increment.
139-
Ok(it) => it.parse::<usize>().unwrap() + 1,
140-
_ => 0,
141-
}
142-
};
143-
144-
let interpreter_path = match python_executables.get(index) {
145-
Some(it) => it,
146-
None => {
147-
return Err(miette!(
148-
"Unable to find another interpreter at index {}",
149-
index
150-
))
151-
}
152-
};
111+
#[cfg(feature = "debug")]
112+
eprintln!(
113+
"[aspect] Parsed target Python version (major.minor): {}",
114+
&target_python_version
115+
);
153116

154-
let exe_path = current_exe.to_string_lossy().into_owned();
155-
let exec_args = &args[1..];
117+
let exclude_dir = current_exe.parent().unwrap().canonicalize().unwrap();
156118

157-
#[cfg(feature = "debug")]
158-
eprintln!(
159-
"[aspect] Attempting to execute: {:?} with argv[0] as {:?} and args as {:?}",
160-
interpreter_path, exe_path, exec_args,
161-
);
119+
#[cfg(feature = "debug")]
120+
eprintln!("[aspect] Ignoring dir {:?}", &exclude_dir);
162121

163-
let mut cmd = Command::new(interpreter_path);
164-
cmd.args(exec_args);
122+
let Some(python_executables) = find_python_executables(&target_python_version, &exclude_dir)
123+
else {
124+
return Err(miette!(
125+
"No suitable Python interpreter found in PATH matching version '{}'.",
126+
&version_info,
127+
));
128+
};
165129

166-
// Lie about the value of argv0 to hoodwink the interpreter as to its
167-
// location on Linux-based platforms.
168-
if cfg!(target_os = "linux") {
169-
cmd.arg0(&exe_path);
130+
#[cfg(feature = "debug")]
131+
{
132+
eprintln!("[aspect] Found potential Python interpreters in PATH with matching version:");
133+
for exe in &python_executables {
134+
println!("[aspect] - {:?}", exe);
170135
}
136+
}
171137

172-
// On MacOS however, there are facilities for asking the C runtime/OS
173-
// what the real name of the interpreter executable is, and that value
174-
// is preferred while argv[0] is ignored. So we need to use a different
175-
// mechanism to lie to the target interpreter about its own path.
176-
//
177-
// https://github.com/python/cpython/blob/68e72cf3a80362d0a2d57ff0c9f02553c378e537/Modules/getpath.c#L778
178-
// https://docs.python.org/3/using/cmdline.html#envvar-PYTHONEXECUTABLE
179-
if cfg!(target_os = "macos") {
180-
cmd.env("PYTHONEXECUTABLE", exe_path);
138+
// Attempt to break infinite recursion through this shim by counting up
139+
// the number of times we've come back to this shim and incrementing it
140+
// until we hit something on the path that DOESN'T come back here, or we
141+
// run out of path entries.
142+
let index: usize = {
143+
match env::var(COUNTER_VAR) {
144+
// Whatever the previous value was didn't work because we're
145+
// back here, so increment.
146+
Ok(it) => it.parse::<usize>().unwrap() + 1,
147+
_ => 0,
181148
}
149+
};
182150

183-
// Re-export the counter so it'll go up
184-
cmd.env(COUNTER_VAR, index.to_string());
185-
186-
let _ = cmd.exec();
187-
188-
return Ok(());
189-
} else {
151+
let Some(interpreter_path) = python_executables.get(index) else {
190152
return Err(miette!(
191-
"No suitable Python interpreter found in PATH matching version '{}'.",
192-
&version_info,
153+
"Unable to find another interpreter at index {}",
154+
index
193155
));
156+
};
157+
158+
let exe_path = current_exe.to_string_lossy().into_owned();
159+
let exec_args = &args[1..];
160+
161+
#[cfg(feature = "debug")]
162+
eprintln!(
163+
"[aspect] Attempting to execute: {:?} with argv[0] as {:?} and args as {:?}",
164+
interpreter_path, exe_path, exec_args,
165+
);
166+
167+
let mut cmd = Command::new(&interpreter_path);
168+
cmd.args(exec_args);
169+
170+
// Lie about the value of argv0 to hoodwink the interpreter as to its
171+
// location on Linux-based platforms.
172+
if cfg!(target_os = "linux") {
173+
cmd.arg0(&exe_path);
194174
}
175+
176+
// On MacOS however, there are facilities for asking the C runtime/OS
177+
// what the real name of the interpreter executable is, and that value
178+
// is preferred while argv[0] is ignored. So we need to use a different
179+
// mechanism to lie to the target interpreter about its own path.
180+
//
181+
// https://github.com/python/cpython/blob/68e72cf3a80362d0a2d57ff0c9f02553c378e537/Modules/getpath.c#L778
182+
// https://docs.python.org/3/using/cmdline.html#envvar-PYTHONEXECUTABLE
183+
if cfg!(target_os = "macos") {
184+
cmd.env("PYTHONEXECUTABLE", &exe_path);
185+
}
186+
187+
// Re-export the counter so it'll go up
188+
cmd.env(COUNTER_VAR, index.to_string());
189+
190+
let _ = cmd.exec();
191+
192+
return Ok(());
195193
}

0 commit comments

Comments
 (0)