Skip to content

Commit d0b5db2

Browse files
committed
fix(bootstrap) handle when the runfiles env vars are not correct
There was still an issue where the runfiles environment variables could be set for a parent Python binary, and if that spawned another Python binary (directly or indirectly), the child would try to use the parents runfiles. This was confirmable by adding a py_library dependency to the existing bin_calls_bin test, and having inner.py try to import it. A workaround sugggested in the bug for code outside of rules_python was to remove RUNFILES_DIR from the environment inherited by the child when launching the child process, though in some cases you might need to also remove RUNFILES_MANIFEST_FILE, but working around the issue is not very satisfactory. Diving into the bootstrapping process, it seemed like the proper fix was to conditionally unset them, if they were not correct. I found equivalent places in both bootstrap template files, and if the test to confirm "runfile_dir" was correct fails, the bootstrap code itself now removes those invalid values from the environment. Later bootstrap code assumes they are set correctly, if set. When they are not set, it goes through some fallback logic to locate them. By conditionally unsetting them, the fallback logic is not invoked when it isn't necessary, shortening the bootstrap process. With that change, the modified tests now pass. Fixes #3518
1 parent d1279c3 commit d0b5db2

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
@@ -67,6 +67,9 @@ END_UNRELEASED_TEMPLATE
6767
* (zipapp) Resolve issue passing through compression settings in
6868
`py_zippapp_binary` targets
6969
([#3646](https://github.com/bazel-contrib/rules_python/issues/3646)).
70+
* (bootstrap) Resolve RUNFILES_DIR inheritance issues, which lead to a child
71+
Python binary incorrectly using it's parent's Python binary environment
72+
([#3518](https://github.com/bazel-contrib/rules_python/issues/3518)).
7073

7174
{#v0-0-0-added}
7275
### Added

python/private/python_bootstrap_template.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ def FindModuleSpace(main_rel_path):
203203
if runfiles_dir and os.path.exists(os.path.join(runfiles_dir, main_rel_path)):
204204
return runfiles_dir
205205

206+
# Clear RUNFILES_DIR & RUNFILES_MANIFEST_FILE since the runfiles dir was
207+
# not found. These can be correctly set for a parent Python process, but
208+
# inherited by the child, and not correct for it. Later bootstrap code
209+
# assumes they're are correct if set.
210+
os.environ.pop('RUNFILES_DIR', None)
211+
os.environ.pop('RUNFILES_MANIFEST_FILE', None)
212+
206213
stub_filename = sys.argv[0]
207214
# On Windows, the path may contain both forward and backslashes.
208215
# 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,6 +1,13 @@
11
load("@rules_shell//shell:sh_test.bzl", "sh_test")
22
load("//tests/support:py_reconfig.bzl", "py_reconfig_binary")
33
load("//tests/support:support.bzl", "NOT_WINDOWS", "SUPPORTS_BOOTSTRAP_SCRIPT")
4+
load("//python:py_library.bzl", "py_library")
5+
6+
py_library(
7+
name = "inner_lib",
8+
srcs = ["inner_lib.py"],
9+
tags = ["manual"],
10+
)
411

512
# =====
613
# bootstrap_impl=system_python testing
@@ -16,6 +23,7 @@ py_reconfig_binary(
1623
py_reconfig_binary(
1724
name = "inner_bootstrap_system_python",
1825
srcs = ["inner.py"],
26+
deps = [":inner_lib"],
1927
bootstrap_impl = "system_python",
2028
main = "inner.py",
2129
tags = ["manual"],
@@ -51,6 +59,7 @@ sh_test(
5159
py_reconfig_binary(
5260
name = "inner_bootstrap_script",
5361
srcs = ["inner.py"],
62+
deps = [":inner_lib"],
5463
bootstrap_impl = "script",
5564
main = "inner.py",
5665
tags = ["manual"],
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
import os
22

33
module_space = os.environ.get("RULES_PYTHON_TESTING_MODULE_SPACE")
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_MODULE_SPACE='{module_space}'")
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 module space: $OUTER_MODULE_SPACE"
1212
echo "Inner module space: $INNER_MODULE_SPACE"
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_MODULE_SPACE" == "$INNER_MODULE_SPACE" ]; then
1628
echo "Error: Outer and Inner module spaces 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)