Skip to content

Commit df7a168

Browse files
adhoc-bobcataignas
andauthored
fix(bootstrap) handle when the runfiles env vars are not correct (#3644)
This change addresses an issue that has existed since 1.7.0 where Python binaries launched as subprocesses could incorrectly inherit and use runfiles environment variables (`RUNFILES_DIR`, `RUNFILES_MANIFEST_FILE`) from the parent process. ## Why this change is needed: When a Python binary spawns another Python binary, the child process inherits the environment variables. If the parent had runfiles-related environment variables set, the child would attempt to use the parent's runfiles tree, which is incorrect and leads to import errors if the child has different dependencies or a different runfiles layout. ## Behavior Before: A child Python process would trust and use any existing `RUNFILES_DIR` and `RUNFILES_MANIFEST_FILE` environment variables. If these were set by a parent Python process, they would point to the parent's runfiles, causing the child to fail when trying to load its own resources or dependencies. ## Behavior After: The Python bootstrap scripts now include a check to validate the inherited runfiles environment variables. If the runfiles variables exist but do not point to the correct location for the current binary, the bootstrap script will unset both `RUNFILES_DIR` and `RUNFILES_MANIFEST_FILE` from the environment. This allows the subsequent fallback logic in the bootstrap process to correctly locate the runfiles for the current process, ensuring dependencies are resolved properly. This modification has been applied to both bootstrap template files. This change ensures that even nested Python binary calls correctly find their respective runfiles. Fixes #3518 --------- Co-authored-by: Ignas Anikevicius <240938+aignas@users.noreply.github.com>
1 parent 782ae91 commit df7a168

File tree

8 files changed

+58
-1
lines changed

8 files changed

+58
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ Other changes:
102102
* (bootstrap) Fixed incorrect runfiles path construction in bootstrap
103103
scripts when binary is defined in another bazel module
104104
([#3563](https://github.com/bazel-contrib/rules_python/issues/3563)).
105+
* (bootstrap) Resolve `RUNFILES_DIR` inheritance issues, which lead to a child
106+
Python binary incorrectly using it's parent's Python binary environment
107+
([#3518](https://github.com/bazel-contrib/rules_python/issues/3518)).
105108
* (uv) Downloads for versions `>=0.10` work again. In order to fix this we had
106109
drop support for `powerpc64` platform. People interested in the platform can
107110
bring it back via the `uv.default` API. Like:

python/private/python_bootstrap_template.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,13 @@ def find_runfiles_root(main_rel_path):
214214
if runfiles_dir and os.path.exists(os.path.join(runfiles_dir, main_rel_path)):
215215
return runfiles_dir
216216

217+
# Clear RUNFILES_DIR & RUNFILES_MANIFEST_FILE since the runfiles dir was
218+
# not found. These can be correctly set for a parent Python process, but
219+
# inherited by the child, and not correct for it. Later bootstrap code
220+
# assumes they're are correct if set.
221+
os.environ.pop('RUNFILES_DIR', None)
222+
os.environ.pop('RUNFILES_MANIFEST_FILE', None)
223+
217224
stub_filename = sys.argv[0]
218225
# On Windows, the path may contain both forward and backslashes.
219226
# Normalize to the OS separator because the regex used later assumes

python/private/stage2_bootstrap_template.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ def find_runfiles_root(main_rel_path):
185185
if runfiles_dir and os.path.exists(os.path.join(runfiles_dir, main_rel_path)):
186186
return runfiles_dir
187187

188+
# Clear RUNFILES_DIR & RUNFILES_MANIFEST_FILE since the runfiles dir was
189+
# not found. These can be correctly set for a parent Python process, but
190+
# inherited by the child, and not correct for it. Later bootstrap code
191+
# assumes they're are correct if set.
192+
os.environ.pop('RUNFILES_DIR', None)
193+
os.environ.pop('RUNFILES_MANIFEST_FILE', None)
194+
188195
stub_filename = sys.argv[0]
189196
if not os.path.isabs(stub_filename):
190197
stub_filename = os.path.join(os.getcwd(), stub_filename)

tests/bootstrap_impls/bin_calls_bin/BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
load("@rules_shell//shell:sh_test.bzl", "sh_test")
2+
load("//python:py_library.bzl", "py_library")
23
load("//tests/support:py_reconfig.bzl", "py_reconfig_binary")
34
load("//tests/support:support.bzl", "NOT_WINDOWS", "SUPPORTS_BOOTSTRAP_SCRIPT")
45

6+
py_library(
7+
name = "inner_lib",
8+
srcs = ["inner_lib.py"],
9+
tags = ["manual"],
10+
)
11+
512
# =====
613
# bootstrap_impl=system_python testing
714
# =====
@@ -19,6 +26,7 @@ py_reconfig_binary(
1926
bootstrap_impl = "system_python",
2027
main = "inner.py",
2128
tags = ["manual"],
29+
deps = [":inner_lib"],
2230
)
2331

2432
genrule(
@@ -54,6 +62,7 @@ py_reconfig_binary(
5462
bootstrap_impl = "script",
5563
main = "inner.py",
5664
tags = ["manual"],
65+
deps = [":inner_lib"],
5766
)
5867

5968
py_reconfig_binary(
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
import os
22

33
runfiles_root = os.environ.get("RULES_PYTHON_TESTING_RUNFILES_ROOT")
4+
runfiles_dir = os.environ.get("RUNFILES_DIR")
5+
runfiles_manifest_file = os.environ.get("RUNFILES_MANIFEST_FILE")
46
print(f"inner: RULES_PYTHON_TESTING_RUNFILES_ROOT='{runfiles_root}'")
7+
print(f"inner: RUNFILES_DIR='{runfiles_dir}'")
8+
print(f"inner: RUNFILES_MANIFEST_FILE='{runfiles_manifest_file}'")
9+
10+
try:
11+
import tests.bootstrap_impls.bin_calls_bin.inner_lib as inner_lib
12+
print(f"inner: import_result='{inner_lib.confirm()}'")
13+
except ImportError as e:
14+
print(f"inner: import_result='{e}'")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Rather than having a completely empty file...
2+
def confirm():
3+
return "success"

tests/bootstrap_impls/bin_calls_bin/outer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
[inner_binary_path],
1212
capture_output=True,
1313
text=True,
14-
check=True,
1514
)
1615
print(result.stdout, end="")
1716
if result.stderr:
1817
print(result.stderr, end="", file=sys.stderr)
18+
sys.exit(result.returncode)

tests/bootstrap_impls/bin_calls_bin/verify.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ verify_output() {
1111
echo "Outer runfiles root: $OUTER_RUNFILES_ROOT"
1212
echo "Inner runfiles root: $INNER_RUNFILES_ROOT"
1313

14+
# Extract the inner runfiles values
15+
local INNER_RUNFILES_DIR=$(grep "inner: RUNFILES_DIR" "$OUTPUT_FILE" | sed "s/inner: RUNFILES_DIR='\(.*\)'/\1/")
16+
local INNER_RUNFILES_MANIFEST_FILE=$(grep "inner: RUNFILES_MANIFEST_FILE" "$OUTPUT_FILE" | sed "s/inner: RUNFILES_MANIFEST_FILE='\(.*\)'/\1/")
17+
18+
echo "Inner runfiles dir: $INNER_RUNFILES_DIR"
19+
echo "Inner runfiles manifest file: $INNER_RUNFILES_MANIFEST_FILE"
20+
21+
# Extract the inner lib import result
22+
local INNER_LIB_IMPORT=$(grep "inner: import_result" "$OUTPUT_FILE" | sed "s/inner: import_result='\(.*\)'/\1/")
23+
echo "Inner lib import result: $INNER_LIB_IMPORT"
24+
25+
1426
# Check 1: The two values are different
1527
if [ "$OUTER_RUNFILES_ROOT" == "$INNER_RUNFILES_ROOT" ]; then
1628
echo "Error: Outer and Inner runfiles roots are the same."
@@ -28,5 +40,11 @@ verify_output() {
2840
;;
2941
esac
3042

43+
# Check 3: inner_lib was imported
44+
if [ "$INNER_LIB_IMPORT" != "success" ]; then
45+
echo "Error: Inner lib was not successfully imported."
46+
exit 1
47+
fi
48+
3149
echo "Verification successful."
3250
}

0 commit comments

Comments
 (0)