Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

py_binary: ambient `PYTHONPATH` shadows runfiles packages under `bazel run`

未关闭
#3,847 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
55/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
冷清
技术栈
python

调研方向

从 python/private/stage1_bootstrap_template.sh 的第 263-275 行和共享的 stage-2 bootstrap 注释开始,然后使用 bazel run 重现以冒号开头的 PYTHONPATH 情况。将 launcher 的行为与 toolchains_repo.bzl、whl_library.bzl 和 runtime_env_repo.bzl 中的 -I 调用进行比较。当重现不再允许环境中的 PYTHONPATH 覆盖 runfiles,并且涵盖受影响的 py_binary/py_test 行为和环境变量指南时,即表示完成。

由索引模型根据 Issue 内容生成。

描述

🐞 bug report

Affected Rule

py_binary (via bazel run), for both --bootstrap_impl=script and --bootstrap_impl=system_python.

The fault lives in the shared py_binary/py_test stage1/stage2 launcher, so py_test is affected in principle too, but only when a hostile PYTHONPATH is explicitly forwarded with --test_env, and a default bazel test does not trip it (see Scope below).

Is this a regression?

No.
This has been the behavior since PYTHONSAFEPATH was adopted as the launcher's only sys.path mitigation (the fix for #382).
Ambient PYTHONPATH shadowing was never addressed.

Description

An ambient PYTHONPATH in the environment that invokes bazel run is honored by the launched interpreter and prepended to sys.path ahead of the program's own runfiles.
When a directory on that path shares a top-level package name with a runfiles package, it shadows it as a PEP 420 namespace package, and the program fails to import its own declared dependencies.
A target thus resolves imports against the developer's shell state instead of its declared deps, which contradicts Bazel's "depend only on declared inputs" contract.

This is especially easy to trigger because a leading (or trailing, or doubled) : in PYTHONPATH is interpreted by CPython as the current working directory, and under bazel run the cwd is the workspace root, which routinely contains directories whose names collide with common top-level packages (pkg, lib, src, etc.).

Concretely, this breaks rules_pkg's install script.
It is enough for a developer's PYTHONPATH to merely begin with a colon (e.g. :/home/dev/another-project/src, which is what happens when some unrelated tool prepends an entry): that leading empty entry resolves to the current working directory, which under bazel run is the workspace root.
The workspace root may contain a top-level pkg/ directory, a very common monorepo layout (Go uses it by convention, but it is widespread beyond Go and has nothing to do with Python here).
Because the leading colon placed the cwd on sys.path ahead of the runfiles, import pkg binds to that pkg/ as a namespace package, and pkg.private (shipped by rules_pkg) is no longer importable (see the traceback below).

Root cause

The stage-1 launcher sets PYTHONSAFEPATH=1, which removes the auto-prepended sys.path[0] (the script directory / cwd), the fix for #382, but -P / PYTHONSAFEPATH does not stop CPython from honoring PYTHONPATH entries:
https://github.com/bazel-contrib/rules_python/blob/e7d137877d4fba2cb58cd4188b7af480e211ef5b/python/private/stage1_bootstrap_template.sh#L263-L275

The asymmetry

rules_python already runs its own interpreter invocations in isolated mode (-I, which implies -E -P -s) precisely so that userspace variables such as PYTHONPATH cannot interfere:
https://github.com/bazel-contrib/rules_python/blob/e7d137877d4fba2cb58cd4188b7af480e211ef5b/python/private/toolchains_repo.bzl#L371-L374
https://github.com/bazel-contrib/rules_python/blob/e7d137877d4fba2cb58cd4188b7af480e211ef5b/python/private/pypi/whl_library.bzl#L121-L124
https://github.com/bazel-contrib/rules_python/blob/e7d137877d4fba2cb58cd4188b7af480e211ef5b/python/private/runtime_env_repo.bzl#L23-L30

User binaries get only -P, and the gap is not yet documented, so a user has no way to discover that bazel run may quietly resolve imports against their shell environment instead of the target's declared deps.

Scope: bazel run vs bazel test

py_binary and py_test share the same stage1/stage2 launcher, so the flaw is in principle common to both.
In practice it surfaces under bazel run but not under a default bazel test, for two independent reasons:

  • bazel run inherits the full client environment, so the ambient PYTHONPATH reaches the interpreter as bazel test does not propagate it by default (the test process sees PYTHONPATH=None unless --test_env=PYTHONPATH is passed),
  • bazel run's cwd is the workspace root (so a leading-: empty entry resolves to it, where the stray pkg/ lives), whereas a test's cwd is its runfiles tree, i.e. the correct layout.

A py_test can still be made to fail by explicitly forwarding a hostile PYTHONPATH via --test_env=PYTHONPATH=/dir/containing/pkg (the entry does land in sys.path ahead of the runfiles), so the launcher fix matters for both, even though only bazel run trips it implicitly.

Potential resolution
  1. make runfiles win over ambient PYTHONPATH: order sys.path in the stage-2 bootstrap so the program's runfiles / venv site-packages precede any ambient PYTHONPATH entries.
    This kills the shadowing of a target's declared dependencies without changing how much of the environment the interpreter otherwise honors, and without propagating anything to child processes, so it sidesteps the tension in #2060 / #2122 (where users wanted less stickiness, not more).
    This would be a minimal correctness fix.
  2. discoverable documentation, e.g. a note in the bootstrap / environment-variables docs explaining that script and system_python launchers honor ambient PYTHON* variables (notably PYTHONPATH), that this can shadow a target's runfiles, and that the interpreter can be hardened via interpreter_args = ["-I"] or RULES_PYTHON_ADDITIONAL_INTERPRETER_ARGS=-I.
    The stage2 header comment already documents the sys.path[0] / PYTHONSAFEPATH story but is silent on PYTHONPATH.

The strongest alternative would be to default the launcher to -I instead of -P, converging on Bazel's hermeticity expectation, but one has to keep in mind that -I implies -E, thus ignoring all PYTHON* variables, which conflicts with the direction of #2060 / #2122.
A default flip would also need a real opt-out:

  • the existing RULES_PYTHON_ADDITIONAL_INTERPRETER_ARGS is additive, so an empty value cannot cancel a hardcoded -I,
  • the variable would have to become an override of the default isolation args, which is itself a breaking change.

🔬 Minimal Reproduction

  1. A py_binary whose runfiles contain a top-level package foo (with a submodule foo.bar).
  2. A directory named foo/ in the workspace root that is not that package (e.g. a non-Python directory, or a foo/ without bar.py).
  3. Run with an empty PYTHONPATH entry:
    PYTHONPATH=: bazel run //path/to:app
    
  4. Observe ModuleNotFoundError: No module named 'foo.bar': the workspace-root foo/ shadows the runfiles foo/.

Clearing the variable (PYTHONPATH= bazel run ...) or isolating the interpreter (RULES_PYTHON_ADDITIONAL_INTERPRETER_ARGS=-I) makes it pass.

🔥 Exception or Error


INFO: Running command line: bazel-bin/pkg/sth/install <args omitted>
Traceback (most recent call last):
  ...
  File ".../install.runfiles/_main/pkg/sth/install_install_script.py", line 29, in <module>
    from pkg.private import manifest
ModuleNotFoundError: No module named 'pkg.private'

🌍 Your Environment

Operating System:

  
Linux
macOS
Windows
  

Output of bazel version:

  
Bazelisk version: development
Build label: 9.1.1
Build target: @@//src/main/java/com/google/devtools/build/lib/bazel:BazelServer
Build time: Wed Jun 03 15:41:13 2026 (1780501273)
Build timestamp: 1780501273
Build timestamp as int: 1780501273
  

Rules_python version:

  
2.0.3
  

Anything else relevant?
Prior art:

  • #382 (sys.path[0], fixed via PYTHONSAFEPATH),
  • #2060 (opt-out of PYTHONSAFEPATH),
  • #2122 (closed PR moving env to -P),
  • #3437 (subprocess module resolution under system_python).
主要语言
Starlark
星标
690
派生
722
平均合并
1 天 55 分钟
30 天内合并 PR
38

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

bazel-contrib/rules_python 的其他 Issue

查看 bazel-contrib/rules_python 的全部 Issue

相似的 Issue

更多 Build System Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。