Skip to content

Commit 1b85ec8

Browse files
fix: add runfiles root for system_python bootstrap (#3423)
When the system_bootstrap code was changed to using the site init for adding to sys.path, importing `bazel_tools.tools.python.runfiles` stopped working. This is because the runfiles root was no longer being added to sys.path. This is somewhat WAI because: 1. Always adding the runfiles root to sys.path is a deprecated legacy behavior because it can interfere with Python imports (a repo name can mask a legitimate import). 2. Under bzlmod, repo directory names aren't importable Python names, so having the runfiles root on sys.path doesn't do much. An exception to (2) is bazel_tools: this is special cased to use the directory name `bazel_tools` in runfiles. This is where the legacy runfiles library for Python is. In any case, forgetting the runfiles root on sys.path for the system_python bootstrap was an oversight, as the intention was to be a more no-op refactoring. Fixes #3422 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 5bc7ba3 commit 1b85ec8

File tree

5 files changed

+55
-7
lines changed

5 files changed

+55
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ END_UNRELEASED_TEMPLATE
9191
* (performance) 90% reduction in py_binary/py_test analysis phase cost.
9292
([#3381](https://github.com/bazel-contrib/rules_python/pull/3381)).
9393
* (gazelle) Fix `gazelle_python_manifest.test` so that it accesses manifest files via `runfile` path handling rather than directly ([#3397](https://github.com/bazel-contrib/rules_python/issues/3397)).
94-
94+
* (core rules) For the system_python bootstrap, the runfiles root is added to
95+
sys.path.
9596

9697
{#v0-0-0-added}
9798
### Added

python/private/py_executable.bzl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ def _create_executable(
345345
output_prefix = base_executable_name,
346346
imports = imports,
347347
runtime_details = runtime_details,
348+
add_runfiles_root_to_sys_path = (
349+
"1" if BootstrapImplFlag.get_value(ctx) == BootstrapImplFlag.SYSTEM_PYTHON else "0"
350+
),
348351
)
349352

350353
stage2_bootstrap = _create_stage2_bootstrap(
@@ -508,7 +511,7 @@ def _create_zip_main(ctx, *, stage2_bootstrap, runtime_details, venv):
508511
# * https://snarky.ca/how-virtual-environments-work/
509512
# * https://github.com/python/cpython/blob/main/Modules/getpath.py
510513
# * https://github.com/python/cpython/blob/main/Lib/site.py
511-
def _create_venv(ctx, output_prefix, imports, runtime_details):
514+
def _create_venv(ctx, output_prefix, imports, runtime_details, add_runfiles_root_to_sys_path):
512515
create_full_venv = BootstrapImplFlag.get_value(ctx) == BootstrapImplFlag.SCRIPT
513516
venv = "_{}.venv".format(output_prefix.lstrip("_"))
514517

@@ -596,6 +599,7 @@ def _create_venv(ctx, output_prefix, imports, runtime_details):
596599
template = runtime.site_init_template,
597600
output = site_init,
598601
substitutions = {
602+
"%add_runfiles_root_to_sys_path%": add_runfiles_root_to_sys_path,
599603
"%coverage_tool%": _get_coverage_tool_runfiles_path(ctx, runtime),
600604
"%import_all%": "True" if read_possibly_native_flag(ctx, "python_import_all_repositories") else "False",
601605
"%site_init_runfiles_path%": "{}/{}".format(ctx.workspace_name, site_init.short_path),

python/private/site_init_template.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
_SELF_RUNFILES_RELATIVE_PATH = "%site_init_runfiles_path%"
2727
# Runfiles-relative path to the coverage tool entry point, if any.
2828
_COVERAGE_TOOL = "%coverage_tool%"
29+
# True if the runfiles root should be added to sys.path
30+
_ADD_RUNFILES_ROOT_TO_SYS_PATH = "%add_runfiles_root_to_sys_path%" == "1"
2931

3032

3133
def _is_verbose():
@@ -127,11 +129,6 @@ def _search_path(name):
127129
def _setup_sys_path():
128130
"""Perform Bazel/binary specific sys.path setup.
129131
130-
NOTE: We do not add _RUNFILES_ROOT to sys.path for two reasons:
131-
1. Under workspace, it makes every external repository importable. If a Bazel
132-
repository matches a Python import name, they conflict.
133-
2. Under bzlmod, the repo names in the runfiles directory aren't importable
134-
Python names, so there's no point in adding the runfiles root to sys.path.
135132
"""
136133
seen = set(sys.path)
137134
python_path_entries = []
@@ -147,6 +144,17 @@ def _maybe_add_path(path):
147144
sys.path.append(path)
148145
seen.add(path)
149146

147+
# Adding the runfiles root to sys.path is a legacy behavior that will be
148+
# removed. We don't want to add it to sys.path for two reasons:
149+
# 1. Under workspace, it makes every external repository importable. If a Bazel
150+
# repository matches a Python import name, they conflict.
151+
# 2. Under bzlmod, the repo names in the runfiles directory aren't importable
152+
# Python names, so there's no point in adding the runfiles root to sys.path.
153+
# For temporary compatibility with the original system_python bootstrap
154+
# behavior, it is conditionally added for that boostrap mode.
155+
if _ADD_RUNFILES_ROOT_TO_SYS_PATH:
156+
_maybe_add_path(_RUNFILES_ROOT)
157+
150158
for rel_path in _IMPORTS_STR.split(":"):
151159
abs_path = os.path.join(_RUNFILES_ROOT, rel_path)
152160
_maybe_add_path(abs_path)

tests/bootstrap_impls/BUILD.bazel

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ sh_py_run_test(
104104
target_compatible_with = SUPPORTS_BOOTSTRAP_SCRIPT,
105105
)
106106

107+
py_reconfig_test(
108+
name = "bazel_tools_importable_system_python_test",
109+
srcs = ["bazel_tools_importable_test.py"],
110+
bootstrap_impl = "system_python",
111+
# Necessary because bazel_tools doesn't have __init__.py files.
112+
legacy_create_init = True,
113+
main = "bazel_tools_importable_test.py",
114+
deps = [
115+
"@bazel_tools//tools/python/runfiles",
116+
],
117+
)
118+
107119
py_reconfig_test(
108120
name = "sys_path_order_bootstrap_script_test",
109121
srcs = ["sys_path_order_test.py"],
@@ -121,6 +133,9 @@ py_reconfig_test(
121133
env = {"BOOTSTRAP": "system_python"},
122134
imports = ["./site-packages"],
123135
main = "sys_path_order_test.py",
136+
deps = [
137+
"@bazel_tools//tools/python/runfiles",
138+
],
124139
)
125140

126141
py_reconfig_test(
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import sys
2+
import unittest
3+
4+
5+
class BazelToolsImportableTest(unittest.TestCase):
6+
def test_bazel_tools_importable(self):
7+
try:
8+
import bazel_tools
9+
import bazel_tools.tools.python
10+
import bazel_tools.tools.python.runfiles
11+
except ImportError as exc:
12+
raise AssertionError(
13+
"Failed to import bazel_tools.python.runfiles\n"
14+
+ "sys.path:\n"
15+
+ "\n".join(f"{i}: {v}" for i, v in enumerate(sys.path))
16+
) from exc
17+
18+
19+
if __name__ == "__main__":
20+
unittest.main()

0 commit comments

Comments
 (0)