Skip to content

Commit 70bd22c

Browse files
committed
Dynamic resolution of runfiles-relative import roots
1 parent fe37833 commit 70bd22c

File tree

7 files changed

+82
-20
lines changed

7 files changed

+82
-20
lines changed

py/private/py_venv/py_venv.bzl

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,12 @@ def _py_venv_base_impl(ctx):
5757

5858
# Check for duplicate virtual dependency names. Those that map to the same resolution target would have been merged by the depset for us.
5959
virtual_resolution = _py_library.resolve_virtuals(ctx)
60+
# Note that this adds the workspace root for us (sigh), don't need to add to it
6061
imports_depset = _py_library.make_imports_depset(ctx, extra_imports_depsets = virtual_resolution.imports)
6162

6263
pth_lines = ctx.actions.args()
6364
pth_lines.use_param_file("%s", use_always = True)
6465
pth_lines.set_param_file_format("multiline")
65-
66-
# FIXME: This was hardcoded in the original rule_py venv and is preserved
67-
# for compatibility. Repo-absolute imports are Bad (TM) and shouldn't be on
68-
# by default. I believe that as of recent rules_python, creating these
69-
# repo-absolute imports is handled as part of the PyInfo calculation. If we
70-
# get this from rules_python, it should be removed. Or it should be moved so
71-
# that we calculate it as part of the imports depset logic.
72-
pth_lines.add(".")
7366
pth_lines.add_all(imports_depset)
7467

7568
site_packages_pth_file = ctx.actions.declare_file("{}.pth".format(ctx.attr.name))

py/tests/py_venv_conflict/test_import_roots.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
#!/usr/bin/env python3
22

3+
import os
4+
for k, v in os.environ.items():
5+
if k.startswith("BUILD_") or k.startswith("RUNFILES_"):
6+
print(k, ":", v)
7+
8+
print("---")
9+
310
from pathlib import Path
411

512
# prefix components:

py/tools/py/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ rust_library(
1010
],
1111
data = [
1212
"src/_virtualenv.py",
13+
"src/_aspect.py",
1314
"src/activate.tmpl",
1415
"src/pyvenv.cfg.tmpl",
1516
"src/runfiles_interpreter.tmpl",

py/tools/py/src/_aspect.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import os
5+
6+
_repo_mapping = {}
7+
8+
def pathadd(path: str, workspace=""):
9+
"""Attempt to soundly append a path to the sys.path.
10+
11+
See https://github.com/aspect-build/rules_py/issues/573 for gorey details.
12+
13+
Because of Bazel's symlinking behavior and `exec()` file canonicalization,
14+
the `sys.prefix` and site dir may escape from `.runfiles/` as created by
15+
Bazel. This is a problem for a ton of reasons, notably that source files
16+
aren't copied to `bazel-bin`, so relative paths from the (canonicalized!)
17+
site dir back "up" to firstparty sources may be broken.
18+
19+
Yay.
20+
21+
As a workaround, this codepath exists to try and use `$RUNFILES_DIR` (either
22+
set by Bazel or enforced by the Aspect venv `activate` script) as the basis
23+
for appending runfiles-relative logical paths to the `sys.path`.
24+
25+
"""
26+
27+
runfiles_dir = os.getenv("RUNFILES_DIR")
28+
if not runfiles_dir:
29+
print("{} ERROR: Unable to identify the runfiles root!".format(__file__))
30+
exit(1)
31+
32+
# Parse the repo mapping file if it exists
33+
if not _repo_mapping:
34+
repo_mapping_file = os.path.join(runfiles_dir, "_repo_mapping")
35+
if os.path.exists(repo_mapping_file):
36+
with open(repo_mapping_file, "r") as fp:
37+
for line in fp:
38+
line = line.strip()
39+
c, a, mca = line.split(",")
40+
_repo_mapping[(c, a)] = mca
41+
42+
# Deal with looking up a remapped repo name
43+
# HACK: This assumes the built binary is in the main workspace
44+
if _repo_mapping:
45+
repo, path = path.split("/", 1)
46+
repo = _repo_mapping[(workspace, repo)]
47+
path = os.path.join(repo, path)
48+
49+
path = os.path.normpath(os.path.join(runfiles_dir, path))
50+
if path not in sys.path:
51+
sys.path.append(path)

py/tools/py/src/runfiles_interpreter.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ source "${RUNFILES_DIR:-/dev/null}/${f}" 2>/dev/null || \
8080
} >/dev/null
8181

8282
# Look up the runfiles-based interpreter and put its dir _first_ on the path.
83-
INTERPRETER="$(realpath $(rlocation {{INTERPRETER_TARGET}}))"
83+
INTERPRETER="$(rlocation {{INTERPRETER_TARGET}})"
8484

8585
# Figure out if we're dealing with just some program or a real install
8686
# <SOMEDIR> <- possible $PYTHONHOME

py/tools/py/src/venv.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ 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+
383390
Ok(venv)
384391
}
385392

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

443450
#[cfg(feature = "debug")]
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());
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+
}
448456

449457
let source_pth = File::open(pth_file.src.as_path())
450458
.into_diagnostic()
@@ -459,7 +467,7 @@ pub fn populate_venv_with_copies(
459467
.write(
460468
b"\
461469
# Generated by Aspect py_binary
462-
# Contains relative import paths to non site-package trees within the .runfiles
470+
# Sets up imports within the .runfiles tree
463471
",
464472
)
465473
.into_diagnostic()?;
@@ -532,14 +540,16 @@ pub fn populate_venv_with_copies(
532540
//
533541
// [1] https://github.com/python/cpython/blob/ce31ae5209c976d28d1c21fcbb06c0ae5e50a896/Lib/site.py#L215
534542

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

py/tools/venv_shim/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +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())
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();

0 commit comments

Comments
 (0)