From b15a07041b353a6cebb0bcc15be64da601827779 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 7 Feb 2026 13:25:49 -0800 Subject: [PATCH 01/40] disable build_python_zip --- .bazelrc | 4 ++++ examples/build_file_generation/.bazelrc | 4 ++++ examples/bzlmod/.bazelrc | 4 ++++ examples/multi_python_versions/.bazelrc | 4 ++++ examples/pip_parse/.bazelrc | 4 ++++ examples/pip_parse_vendored/.bazelrc | 4 ++++ examples/pip_repository_annotations/.bazelrc | 4 ++++ gazelle/.bazelrc | 4 ++++ gazelle/examples/bzlmod_build_file_generation/.bazelrc | 4 ++++ tests/integration/compile_pip_requirements/.bazelrc | 4 ++++ .../compile_pip_requirements_test_from_external_repo/.bazelrc | 4 ++++ tests/integration/local_toolchains/.bazelrc | 4 ++++ tests/integration/pip_parse/.bazelrc | 4 ++++ tests/integration/py_cc_toolchain_registered/.bazelrc | 4 ++++ 14 files changed, 56 insertions(+) diff --git a/.bazelrc b/.bazelrc index 24676574e6..2545f30f80 100644 --- a/.bazelrc +++ b/.bazelrc @@ -41,3 +41,7 @@ common --incompatible_python_disallow_native_rules common --incompatible_no_implicit_file_export build --lockfile_mode=update + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false diff --git a/examples/build_file_generation/.bazelrc b/examples/build_file_generation/.bazelrc index f1ae44fac8..e14ed7c9f6 100644 --- a/examples/build_file_generation/.bazelrc +++ b/examples/build_file_generation/.bazelrc @@ -8,3 +8,7 @@ build --enable_runfiles common --noenable_bzlmod common --enable_workspace common --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/examples/bzlmod/.bazelrc b/examples/bzlmod/.bazelrc index 28a44a7523..a18c7e2551 100644 --- a/examples/bzlmod/.bazelrc +++ b/examples/bzlmod/.bazelrc @@ -24,3 +24,7 @@ test --test_output=errors --enable_runfiles # Windows requires these for multi-python support: build --enable_runfiles common:bazel7.x --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/examples/multi_python_versions/.bazelrc b/examples/multi_python_versions/.bazelrc index 97a973bd85..39ed55aff4 100644 --- a/examples/multi_python_versions/.bazelrc +++ b/examples/multi_python_versions/.bazelrc @@ -5,3 +5,7 @@ build --enable_runfiles coverage --java_runtime_version=remotejdk_11 common:bazel7.x --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/examples/pip_parse/.bazelrc b/examples/pip_parse/.bazelrc index f263a1744d..1bdae98af6 100644 --- a/examples/pip_parse/.bazelrc +++ b/examples/pip_parse/.bazelrc @@ -1,3 +1,7 @@ # https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file try-import %workspace%/user.bazelrc common --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/examples/pip_parse_vendored/.bazelrc b/examples/pip_parse_vendored/.bazelrc index a6ea2d9138..e846b1ac11 100644 --- a/examples/pip_parse_vendored/.bazelrc +++ b/examples/pip_parse_vendored/.bazelrc @@ -8,3 +8,7 @@ build --enable_runfiles common --noenable_bzlmod common --enable_workspace common --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/examples/pip_repository_annotations/.bazelrc b/examples/pip_repository_annotations/.bazelrc index 9397bd31b8..82c7a1dbd2 100644 --- a/examples/pip_repository_annotations/.bazelrc +++ b/examples/pip_repository_annotations/.bazelrc @@ -7,3 +7,7 @@ common --noenable_bzlmod common --enable_workspace common --legacy_external_runfiles=false common --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/gazelle/.bazelrc b/gazelle/.bazelrc index 9a38133e9d..0f46b5b584 100644 --- a/gazelle/.bazelrc +++ b/gazelle/.bazelrc @@ -16,3 +16,7 @@ build --@rules_python//python/config_settings:incompatible_default_to_explicit_i build --enable_runfiles common:bazel7.x --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/gazelle/examples/bzlmod_build_file_generation/.bazelrc b/gazelle/examples/bzlmod_build_file_generation/.bazelrc index 31097b41de..5ab484ee67 100644 --- a/gazelle/examples/bzlmod_build_file_generation/.bazelrc +++ b/gazelle/examples/bzlmod_build_file_generation/.bazelrc @@ -14,3 +14,7 @@ common:bazel7.x --incompatible_python_disallow_native_rules # rules_python code. In the BCR presubmits, this override is removed # and the bazel_dep version of rules_python is used. common --override_module=rules_python=../../../ + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/tests/integration/compile_pip_requirements/.bazelrc b/tests/integration/compile_pip_requirements/.bazelrc index b85f03bcb6..be2ceedf66 100644 --- a/tests/integration/compile_pip_requirements/.bazelrc +++ b/tests/integration/compile_pip_requirements/.bazelrc @@ -3,3 +3,7 @@ test --test_output=errors # Windows requires these for multi-python support: build --enable_runfiles common:bazel7.x --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/tests/integration/compile_pip_requirements_test_from_external_repo/.bazelrc b/tests/integration/compile_pip_requirements_test_from_external_repo/.bazelrc index ab10c8caf7..80d2b0d97d 100644 --- a/tests/integration/compile_pip_requirements_test_from_external_repo/.bazelrc +++ b/tests/integration/compile_pip_requirements_test_from_external_repo/.bazelrc @@ -1,2 +1,6 @@ test --test_output=errors common:bazel7.x --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/tests/integration/local_toolchains/.bazelrc b/tests/integration/local_toolchains/.bazelrc index aed08b0790..6a920b180c 100644 --- a/tests/integration/local_toolchains/.bazelrc +++ b/tests/integration/local_toolchains/.bazelrc @@ -6,3 +6,7 @@ build --enable_runfiles common:bazel7.x --incompatible_python_disallow_native_rules build --//:py=local common --announce_rc + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/tests/integration/pip_parse/.bazelrc b/tests/integration/pip_parse/.bazelrc index a74909297d..3d3d8bd67f 100644 --- a/tests/integration/pip_parse/.bazelrc +++ b/tests/integration/pip_parse/.bazelrc @@ -6,3 +6,7 @@ build --enable_runfiles try-import %workspace%/user.bazelrc common:bazel7.x --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file diff --git a/tests/integration/py_cc_toolchain_registered/.bazelrc b/tests/integration/py_cc_toolchain_registered/.bazelrc index fb31561892..e5a95490e6 100644 --- a/tests/integration/py_cc_toolchain_registered/.bazelrc +++ b/tests/integration/py_cc_toolchain_registered/.bazelrc @@ -1,3 +1,7 @@ # This aids debugging on failure build --toolchain_resolution_debug=python common:bazel7.x --incompatible_python_disallow_native_rules + +# See issue 3567. Disable implicit python zip creation. +common --build_python_zip=false +common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file From 31b5a3d8db2c328343b5711d4271014bd4c04764 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 7 Feb 2026 17:45:48 -0800 Subject: [PATCH 02/40] add some error reporting --- python/private/stage2_bootstrap_template.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index 959e631ad1..4eecb5b174 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -48,6 +48,7 @@ COVERAGE_INSTRUMENTED = "%coverage_instrumented%" == "1" # runfiles-root-relative path to a file with binary-specific build information +# It uses forward slashes, so must be converted for proper usage on Windows. BUILD_DATA_FILE = "%build_data_file%" # ===== Template substitutions end ===== @@ -64,9 +65,17 @@ def get_build_data(self): import runfiles except ImportError: from python.runfiles import runfiles - path = runfiles.Create().Rlocation(self.BUILD_DATA_FILE) - with open(path) as fp: - return fp.read() + rlocation_path = self.BUILD_DATA_FILE + if is_windows(): + rlocation_path = rlocation_path.replace("/", "\\") + path = runfiles.Create().Rlocation(rlocation_path) + try: + with open(path) as fp: + return fp.read() + except Exception as exc: + if hasattr(exc, "add_note"): + exc.add_note(f"runfiles lookup path: {rlocation_path}") + raise sys.modules["bazel_binary_info"] = BazelBinaryInfoModule("bazel_binary_info") From 22cf202b7f794f63e47b82e1b55a442c09c4845c Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 7 Feb 2026 17:51:46 -0800 Subject: [PATCH 03/40] tag the tool_build_data genrule as manual so that it runs as part of testing phase --- tests/build_data/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/build_data/BUILD.bazel b/tests/build_data/BUILD.bazel index 64db005f51..599e04b875 100644 --- a/tests/build_data/BUILD.bazel +++ b/tests/build_data/BUILD.bazel @@ -21,5 +21,6 @@ genrule( name = "tool_build_data", outs = ["tool_build_data.txt"], cmd = "$(location :print_build_data) > $(OUTS)", + tags = ["manual"], tools = [":print_build_data"], ) From 4d1030243947cdf5ed3159c11ee82958d7428eaa Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 7 Feb 2026 20:42:14 -0800 Subject: [PATCH 04/40] try IO way of writing file from gemini --- python/private/build_data_writer.ps1 | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/python/private/build_data_writer.ps1 b/python/private/build_data_writer.ps1 index db7a48e676..652f00057a 100644 --- a/python/private/build_data_writer.ps1 +++ b/python/private/build_data_writer.ps1 @@ -1,17 +1,24 @@ $OutputPath = $env:OUTPUT - -Add-Content -Path $OutputPath -Value "TARGET $env:TARGET" -Add-Content -Path $OutputPath -Value "CONFIG_MODE $env:CONFIG_MODE" -Add-Content -Path $OutputPath -Value "STAMPED $env:STAMPED" +$Lines = @( + "TARGET $env:TARGET", + "CONFIG_MODE $env:CONFIG_MODE", + "STAMPED $env:STAMPED" +) $VersionFilePath = $env:VERSION_FILE -if (-not [string]::IsNullOrEmpty($VersionFilePath)) { - Get-Content -Path $VersionFilePath | Add-Content -Path $OutputPath +if (-not [string]::IsNullOrEmpty($VersionFilePath) -and (Test-Path $VersionFilePath)) { + $Lines += Get-Content -Path $VersionFilePath } $InfoFilePath = $env:INFO_FILE -if (-not [string]::IsNullOrEmpty($InfoFilePath)) { - Get-Content -Path $InfoFilePath | Add-Content -Path $OutputPath +if (-not [string]::IsNullOrEmpty($InfoFilePath) -and (Test-Path $InfoFilePath)) { + $Lines += Get-Content -Path $InfoFilePath } +# Use .NET to write file to avoid PowerShell encoding/locking quirks +# We use UTF8 without BOM for compatibility with how the bash script writes (and +# what consumers expect). +$Utf8NoBom = New-Object System.Text.UTF8Encoding $False +[System.IO.File]::WriteAllLines($OutputPath, $Lines, $Utf8NoBom) + exit 0 From 767361037ce5d0edd27792e615471c5a0f753936 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 16:41:08 -0800 Subject: [PATCH 05/40] ignore cleanup error on windows --- tests/pypi/whl_installer/wheel_installer_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/pypi/whl_installer/wheel_installer_test.py b/tests/pypi/whl_installer/wheel_installer_test.py index 7040b0cfd8..91adddf15a 100644 --- a/tests/pypi/whl_installer/wheel_installer_test.py +++ b/tests/pypi/whl_installer/wheel_installer_test.py @@ -63,7 +63,9 @@ def setUp(self) -> None: shutil.copy(os.path.join("examples", "wheel", self.wheel_name), self.wheel_dir) def tearDown(self): - shutil.rmtree(self.wheel_dir) + # On windows, the wheel file remains open, so gives an error upon + # deletion for some reason. + shutil.rmtree(self.wheel_dir, ignore_errors=True) def test_wheel_exists(self) -> None: wheel_installer._extract_wheel( From 050bbe9ee40d74d08a2b86cc69fd47a3a9cdad88 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 17:43:54 -0800 Subject: [PATCH 06/40] debugging of why file is missing --- python/private/python_bootstrap_template.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index f2d5a42fda..0548586ccf 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -412,6 +412,11 @@ def Main(): main_filename = os.path.join(module_space, main_rel_path) main_filename = GetWindowsPathWithUNCPrefix(main_filename) + if not os.path.exists(main_filename): + import pathlib, sys + rf = pathlib.Path(module_space) + for x in rf.glob("**"): + print(x, file=sys.stderr) assert os.path.exists(main_filename), \ 'Cannot exec() %r: file not found.' % main_filename assert os.access(main_filename, os.R_OK), \ From 44ae1140f070a0237ad6324731229fe2c142ce34 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 17:54:11 -0800 Subject: [PATCH 07/40] remove extra sys import --- python/private/python_bootstrap_template.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 0548586ccf..21299d27b3 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -413,7 +413,7 @@ def Main(): main_filename = os.path.join(module_space, main_rel_path) main_filename = GetWindowsPathWithUNCPrefix(main_filename) if not os.path.exists(main_filename): - import pathlib, sys + import pathlib rf = pathlib.Path(module_space) for x in rf.glob("**"): print(x, file=sys.stderr) From f1b373c9c5fa809593d9fdca785c583cdccaf810 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 18:10:41 -0800 Subject: [PATCH 08/40] debug logic --- python/private/python_bootstrap_template.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 21299d27b3..2b4f51a1ad 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -415,7 +415,11 @@ def Main(): if not os.path.exists(main_filename): import pathlib rf = pathlib.Path(module_space) + lines = [] for x in rf.glob("**"): + lines.append(str(x)) + lines = "\n".join(lines) + raise Exception(f"{main_filename} not found, lines:\n{lines}") print(x, file=sys.stderr) assert os.path.exists(main_filename), \ 'Cannot exec() %r: file not found.' % main_filename From 5108ea705a2c93e4089666627b5b1483bdcd8fda Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 18:13:34 -0800 Subject: [PATCH 09/40] fix syntax --- python/private/python_bootstrap_template.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 2b4f51a1ad..134622ab6a 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -420,7 +420,6 @@ def Main(): lines.append(str(x)) lines = "\n".join(lines) raise Exception(f"{main_filename} not found, lines:\n{lines}") - print(x, file=sys.stderr) assert os.path.exists(main_filename), \ 'Cannot exec() %r: file not found.' % main_filename assert os.access(main_filename, os.R_OK), \ From fe7c47afabc2582dbc9bd0fe633bb8f8bd9e4653 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 18:32:11 -0800 Subject: [PATCH 10/40] recurse symlnks --- python/private/python_bootstrap_template.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 134622ab6a..c51232ad6e 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -416,7 +416,7 @@ def Main(): import pathlib rf = pathlib.Path(module_space) lines = [] - for x in rf.glob("**"): + for x in rf.glob("**", recurse_symlinks=True): lines.append(str(x)) lines = "\n".join(lines) raise Exception(f"{main_filename} not found, lines:\n{lines}") From 74992d77f5e7abdfc06a7b47a00a5a15e7f912b5 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 18:38:35 -0800 Subject: [PATCH 11/40] remove recurse symlinks arg --- python/private/python_bootstrap_template.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index c51232ad6e..134622ab6a 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -416,7 +416,7 @@ def Main(): import pathlib rf = pathlib.Path(module_space) lines = [] - for x in rf.glob("**", recurse_symlinks=True): + for x in rf.glob("**"): lines.append(str(x)) lines = "\n".join(lines) raise Exception(f"{main_filename} not found, lines:\n{lines}") From c2e492708778174da4117d23dbcdd315da38cff9 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 18:40:31 -0800 Subject: [PATCH 12/40] enable bootstrap debug for pyconsole gen --- python/private/py_console_script_gen.bzl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/private/py_console_script_gen.bzl b/python/private/py_console_script_gen.bzl index de016036b2..528ef8c4fb 100644 --- a/python/private/py_console_script_gen.bzl +++ b/python/private/py_console_script_gen.bzl @@ -55,6 +55,9 @@ def _py_console_script_gen_impl(ctx): mnemonic = "PyConsoleScriptBinaryGen", progress_message = "Generating py_console_script_binary main: %{label}", executable = ctx.executable._tool, + env = { + "RULES_PYTHON_BOOTSTRAP_VERBOSE": "1", + }, ) return [DefaultInfo( From a63ee89a1fb6316d2f868b977adb816194fce3cb Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 18:54:45 -0800 Subject: [PATCH 13/40] add more debugging --- python/private/python_bootstrap_template.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 134622ab6a..9ed46f06c8 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -381,6 +381,13 @@ def Main(): print_verbose("initial cwd:", os.getcwd()) print_verbose("initial environ:", mapping=os.environ) print_verbose("initial sys.path:", values=sys.path) + print_verbose("STAGE2_BOOTSTRAP:", STAGE2_BOOTSTRAP) + print_verbose("PYTHON_BINARY:", PYTHON_BINARY) + print_verbose("PYTHON_BINARY_ACTUAL:", PYTHON_BINARY_ACTUAL) + print_verbose("IS_ZIPFILE:", IS_ZIPFILE) + print_verbose("RECREATE_VENV_AT_RUNTIME:", RECREATE_VENV_AT_RUNTIME) + print_verbose("WORKSPACE_NAME :", WORKSPACE_NAME ) + args = sys.argv[1:] new_env = {} @@ -391,6 +398,7 @@ def Main(): # matters if `_main` doesn't exist (which can occur if a binary # is packaged and needs no artifacts from the main repo) main_rel_path = os.path.normpath(STAGE2_BOOTSTRAP) + print_verbose("main_rel_path:", main_rel_path) if IsRunningFromZip(): module_space = CreateModuleSpace() @@ -399,6 +407,8 @@ def Main(): module_space = FindModuleSpace(main_rel_path) delete_module_space = False + print_verbose("runfiles root:", module_space) + if os.environ.get("RULES_PYTHON_TESTING_TELL_MODULE_SPACE"): new_env["RULES_PYTHON_TESTING_MODULE_SPACE"] = module_space From fd4434bcbee0d1b73885816735ffb6cadc4e1391 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 19:29:44 -0800 Subject: [PATCH 14/40] print manifest lines --- python/private/python_bootstrap_template.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 9ed46f06c8..b989d67963 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -424,6 +424,12 @@ def Main(): main_filename = GetWindowsPathWithUNCPrefix(main_filename) if not os.path.exists(main_filename): import pathlib + + manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] + manifest = pathlib.Path(manifest_path) + manifest_text = manifest.read_text() + print_verbose("RUNFILES MANIFEST:\n", manifest_test) + rf = pathlib.Path(module_space) lines = [] for x in rf.glob("**"): From 7cbb490278acc6d9bbb747f2a7baccb733359f9d Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 8 Feb 2026 19:32:14 -0800 Subject: [PATCH 15/40] fix var name --- python/private/python_bootstrap_template.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index b989d67963..3b065c6ea5 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -428,7 +428,7 @@ def Main(): manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] manifest = pathlib.Path(manifest_path) manifest_text = manifest.read_text() - print_verbose("RUNFILES MANIFEST:\n", manifest_test) + print_verbose("RUNFILES MANIFEST:\n", manifest_text) rf = pathlib.Path(module_space) lines = [] From 295f840a26dfe9f75ab7ffd984c9cbc48930b696 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Fri, 13 Feb 2026 19:49:18 -0800 Subject: [PATCH 16/40] force build_runfile_links to true --- python/private/py_binary_macro.bzl | 8 +++++++- python/private/py_executable.bzl | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/python/private/py_binary_macro.bzl b/python/private/py_binary_macro.bzl index fa10f2e8a3..26970f33fc 100644 --- a/python/private/py_binary_macro.bzl +++ b/python/private/py_binary_macro.bzl @@ -21,4 +21,10 @@ def py_binary(**kwargs): def py_binary_macro(py_rule, **kwargs): convert_legacy_create_init_to_int(kwargs) - py_rule(**kwargs) + py_rule( + what_os = select({ + "@platforms//os:windows": "windows", + "//conditions:default": "not-windows", + }), + **kwargs + ) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 45a18abb05..988d50cf24 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -169,6 +169,7 @@ Valid values are: target level. """, ), + "what_os": lambda: attrb.String(), "python_version": lambda: attrb.String( # TODO(b/203567235): In the Java impl, the default comes from # --python_version. Not clear what the Starlark equivalent is. @@ -1779,6 +1780,12 @@ def _transition_executable_impl(settings, attr): if attr.stamp != -1: settings["//command_line_option:stamp"] = str(attr.stamp) + + print("what os:", attr.what_os) + if attr.what_os == "windows": + settings["//command_line_option:build_runfile_links"] = "true" + settings["//command_line_option:build_runfile_links"] = "true" + return settings def create_executable_rule(*, attrs, **kwargs): @@ -1832,10 +1839,12 @@ def create_executable_rule_builder(implementation, **kwargs): inputs = TRANSITION_LABELS + [ labels.PYTHON_VERSION, "//command_line_option:stamp", + "//command_line_option:build_runfile_links", ], outputs = TRANSITION_LABELS + [ labels.PYTHON_VERSION, "//command_line_option:stamp", + "//command_line_option:build_runfile_links", ], ), **kwargs From f8d37d0dc835118ba63714b75aa431fa221c41d8 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 14 Feb 2026 08:26:40 -0800 Subject: [PATCH 17/40] grant everyone read, see if it works around permission error --- python/private/build_data_writer.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/private/build_data_writer.ps1 b/python/private/build_data_writer.ps1 index 652f00057a..846399f194 100644 --- a/python/private/build_data_writer.ps1 +++ b/python/private/build_data_writer.ps1 @@ -21,4 +21,9 @@ if (-not [string]::IsNullOrEmpty($InfoFilePath) -and (Test-Path $InfoFilePath)) $Utf8NoBom = New-Object System.Text.UTF8Encoding $False [System.IO.File]::WriteAllLines($OutputPath, $Lines, $Utf8NoBom) +$Acl = Get-Acl $OutputPath +$AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("Everyone", "Read", "Allow") +$Acl.SetAccessRule($AccessRule) +Set-Acl $OutputPath $Acl + exit 0 From e37f7bd4a759565ca42e7f32590c0fb515fbac51 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 14 Feb 2026 08:35:16 -0800 Subject: [PATCH 18/40] try to fix label resolution --- python/private/BUILD.bazel | 5 +++++ python/private/py_binary_macro.bzl | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index 70f7f86413..35e0db9fe0 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -831,6 +831,11 @@ config_setting( }, ) +##alias( +## name = "is_windows", +## actual = "@platforms//os:windows", +##) + alias( name = "debugger_if_target_config", actual = select({ diff --git a/python/private/py_binary_macro.bzl b/python/private/py_binary_macro.bzl index 26970f33fc..2cdff7f62b 100644 --- a/python/private/py_binary_macro.bzl +++ b/python/private/py_binary_macro.bzl @@ -16,6 +16,8 @@ load(":py_binary_rule.bzl", py_binary_rule = "py_binary") load(":py_executable.bzl", "convert_legacy_create_init_to_int") +_PLATFORMS_OS_WINDOWS = str(Label("@platforms//os:windows")) + def py_binary(**kwargs): py_binary_macro(py_binary_rule, **kwargs) @@ -23,7 +25,8 @@ def py_binary_macro(py_rule, **kwargs): convert_legacy_create_init_to_int(kwargs) py_rule( what_os = select({ - "@platforms//os:windows": "windows", + ##"@platforms//os:windows": "windows", + _PLATFORMS_OS_WINDOWS: "windows", "//conditions:default": "not-windows", }), **kwargs From 9b1a481d2608ba937ae46188d4b486c52216259e Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 14 Feb 2026 11:32:12 -0800 Subject: [PATCH 19/40] Set internal attribute for py_test too --- python/private/BUILD.bazel | 11 +++++++---- python/private/py_binary_macro.bzl | 10 +++++----- python/private/py_executable.bzl | 12 +++++++----- python/private/py_test_macro.bzl | 7 +++++++ 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index 35e0db9fe0..13951bf417 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -831,10 +831,13 @@ config_setting( }, ) -##alias( -## name = "is_windows", -## actual = "@platforms//os:windows", -##) +alias( + name = "what_os", + actual = select({ + "@platforms//os:windows": "@platforms//os:windows", + "//conditions:default": "@platforms//os:linux", + }), +) alias( name = "debugger_if_target_config", diff --git a/python/private/py_binary_macro.bzl b/python/private/py_binary_macro.bzl index 2cdff7f62b..4a0f84e246 100644 --- a/python/private/py_binary_macro.bzl +++ b/python/private/py_binary_macro.bzl @@ -23,11 +23,11 @@ def py_binary(**kwargs): def py_binary_macro(py_rule, **kwargs): convert_legacy_create_init_to_int(kwargs) + kwargs["$build_runfiles_links"] = select({ + ##"@platforms//os:windows": "windows", + _PLATFORMS_OS_WINDOWS: "true", + "//conditions:default": "inherit", + }) py_rule( - what_os = select({ - ##"@platforms//os:windows": "windows", - _PLATFORMS_OS_WINDOWS: "windows", - "//conditions:default": "not-windows", - }), **kwargs ) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index dda034bd3b..b456687001 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -169,7 +169,9 @@ Valid values are: target level. """, ), - "what_os": lambda: attrb.String(), + "_build_runfiles_links": lambda: attrb.String( + default = "DEFAULT", + ), "python_version": lambda: attrb.String( # TODO(b/203567235): In the Java impl, the default comes from # --python_version. Not clear what the Starlark equivalent is. @@ -1782,10 +1784,10 @@ def _transition_executable_impl(settings, attr): if attr.stamp != -1: settings["//command_line_option:stamp"] = str(attr.stamp) - print("what os:", attr.what_os) - if attr.what_os == "windows": - settings["//command_line_option:build_runfile_links"] = "true" - settings["//command_line_option:build_runfile_links"] = "true" + if attr._build_runfiles_links == "DEFAULT": + fail("build_runfiles_links not being passed: {}", attr.name) + if attr._build_runfiles_links and attr._build_runfiles_links != "inherit": + settings["//command_line_option:build_runfile_links"] = attr._build_runfiles_links return settings diff --git a/python/private/py_test_macro.bzl b/python/private/py_test_macro.bzl index 028dee6678..7e8456b979 100644 --- a/python/private/py_test_macro.bzl +++ b/python/private/py_test_macro.bzl @@ -16,9 +16,16 @@ load(":py_executable.bzl", "convert_legacy_create_init_to_int") load(":py_test_rule.bzl", py_test_rule = "py_test") +_PLATFORMS_OS_WINDOWS = str(Label("@platforms//os:windows")) + def py_test(**kwargs): py_test_macro(py_test_rule, **kwargs) def py_test_macro(py_rule, **kwargs): convert_legacy_create_init_to_int(kwargs) + kwargs["$build_runfiles_links"] = select({ + ##"@platforms//os:windows": "windows", + _PLATFORMS_OS_WINDOWS: "true", + "//conditions:default": "inherit", + }) py_rule(**kwargs) From 64abc065a454ef30b5399bd4941b0b5d983b1980 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 14 Feb 2026 19:44:31 -0800 Subject: [PATCH 20/40] clean up build_runfile_links transition --- python/private/attributes.bzl | 18 +++++++++++++++++- python/private/common_labels.bzl | 5 +++++ python/private/py_binary_macro.bzl | 13 +++---------- python/private/py_executable.bzl | 29 +++++++++++++++++++++-------- python/private/py_test_macro.bzl | 11 ++--------- 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/python/private/attributes.bzl b/python/private/attributes.bzl index 362eee8f2e..665eadd24a 100644 --- a/python/private/attributes.bzl +++ b/python/private/attributes.bzl @@ -400,6 +400,18 @@ particular CPU, or defining a custom setting that `select()` uses elsewhere to pick between `pip.parse` hubs. See the [How to guide on multiple versions of a library] for a more concrete example. +:::{important} +Labels with package `command_line_option` are handled specially: they are treated +as aliases for the Bazel-builtin `//command_line_option:` psuedo-targets. + +e.g. `@foo//command_line_option:NAME` will attempt to transition +the Bazel-builtin `//command_line_option:NAME` setting. + +See the {obj}`@rules_python//command_line_option` package for some predefined +aliases, or define your own by putting them in your own `command_line_option` +directory. +::: + :::{note} These values are transitioned on, so will affect the analysis graph and the associated memory overhead. The more unique configurations in your overall @@ -426,7 +438,11 @@ def apply_config_settings_attr(settings, attr): {type}`dict[str, object]` the input `settings` value. """ for key, value in attr.config_settings.items(): - settings[str(key)] = value + if key.package == "command_line_option": + str_key = "//command_line_option:" + key.name + else: + str_key = str(key) + settings[str_key] = value return settings AGNOSTIC_EXECUTABLE_ATTRS = dicts.add( diff --git a/python/private/common_labels.bzl b/python/private/common_labels.bzl index 9c21198a62..31f05a57df 100644 --- a/python/private/common_labels.bzl +++ b/python/private/common_labels.bzl @@ -8,6 +8,7 @@ labels = struct( ADD_SRCS_TO_RUNFILES = str(Label("//python/config_settings:add_srcs_to_runfiles")), BOOTSTRAP_IMPL = str(Label("//python/config_settings:bootstrap_impl")), BUILD_PYTHON_ZIP = str(Label("//python/config_settings:build_python_zip")), + BUILD_RUNFILE_LINKS = str(Label("//command_line_option:build_runfile_links")), DEBUGGER = str(Label("//python/config_settings:debugger")), EXEC_TOOLS_TOOLCHAIN = str(Label("//python/config_settings:exec_tools_toolchain")), PIP_ENV_MARKER_CONFIG = str(Label("//python/config_settings:pip_env_marker_config")), @@ -17,6 +18,7 @@ labels = struct( PIP_WHL_MUSLC_VERSION = str(Label("//python/config_settings:pip_whl_muslc_version")), PIP_WHL_OSX_ARCH = str(Label("//python/config_settings:pip_whl_osx_arch")), PIP_WHL_OSX_VERSION = str(Label("//python/config_settings:pip_whl_osx_version")), + PLATFORMS_OS_WINDOWS = str(Label("@platforms//os:windows")), PRECOMPILE = str(Label("//python/config_settings:precompile")), PRECOMPILE_SOURCE_RETENTION = str(Label("//python/config_settings:precompile_source_retention")), PYC_COLLECTION = str(Label("//python/config_settings:pyc_collection")), @@ -30,4 +32,7 @@ labels = struct( VENVS_SITE_PACKAGES = str(Label("//python/config_settings:venvs_site_packages")), VENVS_USE_DECLARE_SYMLINK = str(Label("//python/config_settings:venvs_use_declare_symlink")), VISIBLE_FOR_TESTING = str(Label("//python/private:visible_for_testing")), + # NOTE: This target is special. While this label resolves to the + # rules_python target, config_setting processing code converts it to the + # Bazel-builtin //command_line_label:build_runfile_links psuedo-target ) diff --git a/python/private/py_binary_macro.bzl b/python/private/py_binary_macro.bzl index 4a0f84e246..6a8268ccf3 100644 --- a/python/private/py_binary_macro.bzl +++ b/python/private/py_binary_macro.bzl @@ -14,7 +14,7 @@ """Implementation of macro-half of py_binary rule.""" load(":py_binary_rule.bzl", py_binary_rule = "py_binary") -load(":py_executable.bzl", "convert_legacy_create_init_to_int") +load(":py_executable.bzl", "common_executable_macro_kwargs_setup") _PLATFORMS_OS_WINDOWS = str(Label("@platforms//os:windows")) @@ -22,12 +22,5 @@ def py_binary(**kwargs): py_binary_macro(py_binary_rule, **kwargs) def py_binary_macro(py_rule, **kwargs): - convert_legacy_create_init_to_int(kwargs) - kwargs["$build_runfiles_links"] = select({ - ##"@platforms//os:windows": "windows", - _PLATFORMS_OS_WINDOWS: "true", - "//conditions:default": "inherit", - }) - py_rule( - **kwargs - ) + common_executable_macro_kwargs_setup(kwargs) + py_rule(**kwargs) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index b456687001..8b366d01a8 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -169,9 +169,6 @@ Valid values are: target level. """, ), - "_build_runfiles_links": lambda: attrb.String( - default = "DEFAULT", - ), "python_version": lambda: attrb.String( # TODO(b/203567235): In the Java impl, the default comes from # --python_version. Not clear what the Starlark equivalent is. @@ -1774,6 +1771,27 @@ def _create_run_environment_info(ctx, inherited_environment): inherited_environment = inherited_environment, ) +def add_config_setting_default_build_runfiles_links(kwargs): + config_settings = kwargs.get("config_settings", None) + if config_settings == None: + config_settings = {} + + # NOTE: This code runs in loading phase within the context of the caller. + # Label() must be used to resolve repo names within rules_python's + # context to avoid unknown repo name errors. + default = select({ + labels.PLATFORMS_OS_WINDOWS: {labels.BUILD_RUNFILE_LINKS: "true"}, + "//conditions:default": { + labels.BUILD_RUNFILE_LINKS: "true", + }, + }) + config_settings = default | config_settings + kwargs["config_settings"] = config_settings + +def common_executable_macro_kwargs_setup(kwargs): + convert_legacy_create_init_to_int(kwargs) + add_config_setting_default_build_runfiles_links(kwargs) + def _transition_executable_impl(settings, attr): settings = dict(settings) apply_config_settings_attr(settings, attr) @@ -1784,11 +1802,6 @@ def _transition_executable_impl(settings, attr): if attr.stamp != -1: settings["//command_line_option:stamp"] = str(attr.stamp) - if attr._build_runfiles_links == "DEFAULT": - fail("build_runfiles_links not being passed: {}", attr.name) - if attr._build_runfiles_links and attr._build_runfiles_links != "inherit": - settings["//command_line_option:build_runfile_links"] = attr._build_runfiles_links - return settings def create_executable_rule(*, attrs, **kwargs): diff --git a/python/private/py_test_macro.bzl b/python/private/py_test_macro.bzl index 7e8456b979..bc58f859f8 100644 --- a/python/private/py_test_macro.bzl +++ b/python/private/py_test_macro.bzl @@ -13,19 +13,12 @@ # limitations under the License. """Implementation of macro-half of py_test rule.""" -load(":py_executable.bzl", "convert_legacy_create_init_to_int") +load(":py_executable.bzl", "common_executable_macro_kwargs_setup") load(":py_test_rule.bzl", py_test_rule = "py_test") -_PLATFORMS_OS_WINDOWS = str(Label("@platforms//os:windows")) - def py_test(**kwargs): py_test_macro(py_test_rule, **kwargs) def py_test_macro(py_rule, **kwargs): - convert_legacy_create_init_to_int(kwargs) - kwargs["$build_runfiles_links"] = select({ - ##"@platforms//os:windows": "windows", - _PLATFORMS_OS_WINDOWS: "true", - "//conditions:default": "inherit", - }) + common_executable_macro_kwargs_setup(kwargs) py_rule(**kwargs) From f81b664474ee5fb8386c5ecb9ca817ab5c01ecb8 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 14 Feb 2026 19:54:10 -0800 Subject: [PATCH 21/40] add missing command_line_option directory --- command_line_option/BUILD.bazel | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 command_line_option/BUILD.bazel diff --git a/command_line_option/BUILD.bazel b/command_line_option/BUILD.bazel new file mode 100644 index 0000000000..9ad05efc19 --- /dev/null +++ b/command_line_option/BUILD.bazel @@ -0,0 +1,13 @@ +# Aliases for Bazel builtin //command_line_option psuedo-targets +# +# These are alias to use with `py_binary.config_settings` that are treated +# as aliases for `//command_line_option:XXX` psuedo-targets. + +package( + default_visibility = ["//visibility:public"], +) + +alias( + name = "build_runfile_links", + actual = "//python:none", +) From 7e49984de14558c202e26f24f64288cd3d0b520b Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 14 Feb 2026 20:10:14 -0800 Subject: [PATCH 22/40] fix reconfig transition logic --- tests/support/py_reconfig.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/support/py_reconfig.bzl b/tests/support/py_reconfig.bzl index d0cb968466..9bbfdb1104 100644 --- a/tests/support/py_reconfig.bzl +++ b/tests/support/py_reconfig.bzl @@ -47,8 +47,6 @@ def _perform_transition_impl(input_settings, attr, base_impl): settings[labels.VENVS_USE_DECLARE_SYMLINK] = attr.venvs_use_declare_symlink if attr.venvs_site_packages: settings[labels.VENVS_SITE_PACKAGES] = attr.venvs_site_packages - for key, value in attr.config_settings.items(): - settings[str(key)] = value return settings _BUILTIN_BUILD_PYTHON_ZIP = [] if config.bazel_10_or_later else [ @@ -56,7 +54,9 @@ _BUILTIN_BUILD_PYTHON_ZIP = [] if config.bazel_10_or_later else [ ] _RECONFIG_INPUTS = [ + "//command_line_option:build_runfile_links", "//command_line_option:extra_toolchains", + "//command_line_option:stamp", CUSTOM_RUNTIME, labels.BOOTSTRAP_IMPL, labels.PYTHON_SRC, From 72671c9ccbf235759d32f14dbe93e369c2821db3 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sat, 14 Feb 2026 23:35:23 -0800 Subject: [PATCH 23/40] try enable_runfiles=true, add sys py test --- examples/pip_parse/.bazelrc | 5 ++++- tests/bootstrap_impls/BUILD.bazel | 10 ++++++++++ tests/bootstrap_impls/system_python_nodeps_test.py | 5 +++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/bootstrap_impls/system_python_nodeps_test.py diff --git a/examples/pip_parse/.bazelrc b/examples/pip_parse/.bazelrc index 1bdae98af6..f88c01a2cb 100644 --- a/examples/pip_parse/.bazelrc +++ b/examples/pip_parse/.bazelrc @@ -4,4 +4,7 @@ common --incompatible_python_disallow_native_rules # See issue 3567. Disable implicit python zip creation. common --build_python_zip=false -common --@rules_python//python/config_settings:build_python_zip=false \ No newline at end of file +common --@rules_python//python/config_settings:build_python_zip=false + +# Windows makes use of runfiles for some rules +common --enable_runfiles diff --git a/tests/bootstrap_impls/BUILD.bazel b/tests/bootstrap_impls/BUILD.bazel index e1f60f5b40..71b7c058d1 100644 --- a/tests/bootstrap_impls/BUILD.bazel +++ b/tests/bootstrap_impls/BUILD.bazel @@ -13,6 +13,7 @@ # limitations under the License. load("@rules_pkg//pkg:tar.bzl", "pkg_tar") load("@rules_shell//shell:sh_test.bzl", "sh_test") +load("//python:py_test.bzl", "py_test") load("//tests/support:py_reconfig.bzl", "py_reconfig_binary", "py_reconfig_test") load("//tests/support:sh_py_run_test.bzl", "sh_py_run_test") load("//tests/support:support.bzl", "SUPPORTS_BOOTSTRAP_SCRIPT") @@ -190,4 +191,13 @@ sh_test( }), ) +py_test( + name = "system_python_nodeps_test", + srcs = ["system_python_nodeps_test.py"], + config_settings = { + "//python/config_settings:bootstrap_impl": "system_python", + ##"//command_line_option:build_runfile_links": "false", + }, +) + relative_path_test_suite(name = "relative_path_tests") diff --git a/tests/bootstrap_impls/system_python_nodeps_test.py b/tests/bootstrap_impls/system_python_nodeps_test.py new file mode 100644 index 0000000000..cde2bcef38 --- /dev/null +++ b/tests/bootstrap_impls/system_python_nodeps_test.py @@ -0,0 +1,5 @@ +import os + +print("Hello, world") +for k, v in sorted(os.environ.items()): + print(k, v) From e08154850efcb265f3822f05a4195c3a95dbeef6 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 00:10:15 -0800 Subject: [PATCH 24/40] try tomake work with enable_runfiles=false --- command_line_option/BUILD.bazel | 9 +++++ examples/pip_parse/.bazelrc | 2 +- python/private/common_labels.bzl | 8 ++--- python/private/py_executable.bzl | 5 ++- python/private/python_bootstrap_template.txt | 38 +++++++++++--------- tests/bootstrap_impls/BUILD.bazel | 1 + 6 files changed, 41 insertions(+), 22 deletions(-) diff --git a/command_line_option/BUILD.bazel b/command_line_option/BUILD.bazel index 9ad05efc19..53cec5afc4 100644 --- a/command_line_option/BUILD.bazel +++ b/command_line_option/BUILD.bazel @@ -7,7 +7,16 @@ package( default_visibility = ["//visibility:public"], ) +# todo: add docs, xref with config_settings +# rules_python target, config_setting processing code converts it to the +# Bazel-builtin //command_line_label:build_runfile_links psuedo-target alias( name = "build_runfile_links", actual = "//python:none", ) + +# todo: add docs +alias( + name = "enable_runfiles", + actual = "//python:none", +) diff --git a/examples/pip_parse/.bazelrc b/examples/pip_parse/.bazelrc index f88c01a2cb..fe7156d840 100644 --- a/examples/pip_parse/.bazelrc +++ b/examples/pip_parse/.bazelrc @@ -7,4 +7,4 @@ common --build_python_zip=false common --@rules_python//python/config_settings:build_python_zip=false # Windows makes use of runfiles for some rules -common --enable_runfiles +##common --enable_runfiles diff --git a/python/private/common_labels.bzl b/python/private/common_labels.bzl index 31f05a57df..b6594cf0b9 100644 --- a/python/private/common_labels.bzl +++ b/python/private/common_labels.bzl @@ -8,11 +8,14 @@ labels = struct( ADD_SRCS_TO_RUNFILES = str(Label("//python/config_settings:add_srcs_to_runfiles")), BOOTSTRAP_IMPL = str(Label("//python/config_settings:bootstrap_impl")), BUILD_PYTHON_ZIP = str(Label("//python/config_settings:build_python_zip")), + # NOTE: Special target; see definition for details. BUILD_RUNFILE_LINKS = str(Label("//command_line_option:build_runfile_links")), DEBUGGER = str(Label("//python/config_settings:debugger")), + # NOTE: Special target; see definition for details. + ENABLE_RUNFILES = str(Label("//command_line_option:enable_runfiles")), EXEC_TOOLS_TOOLCHAIN = str(Label("//python/config_settings:exec_tools_toolchain")), - PIP_ENV_MARKER_CONFIG = str(Label("//python/config_settings:pip_env_marker_config")), NONE = str(Label("//python:none")), + PIP_ENV_MARKER_CONFIG = str(Label("//python/config_settings:pip_env_marker_config")), PIP_WHL = str(Label("//python/config_settings:pip_whl")), PIP_WHL_GLIBC_VERSION = str(Label("//python/config_settings:pip_whl_glibc_version")), PIP_WHL_MUSLC_VERSION = str(Label("//python/config_settings:pip_whl_muslc_version")), @@ -32,7 +35,4 @@ labels = struct( VENVS_SITE_PACKAGES = str(Label("//python/config_settings:venvs_site_packages")), VENVS_USE_DECLARE_SYMLINK = str(Label("//python/config_settings:venvs_use_declare_symlink")), VISIBLE_FOR_TESTING = str(Label("//python/private:visible_for_testing")), - # NOTE: This target is special. While this label resolves to the - # rules_python target, config_setting processing code converts it to the - # Bazel-builtin //command_line_label:build_runfile_links psuedo-target ) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 8b366d01a8..3acfa45cd8 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -1780,7 +1780,10 @@ def add_config_setting_default_build_runfiles_links(kwargs): # Label() must be used to resolve repo names within rules_python's # context to avoid unknown repo name errors. default = select({ - labels.PLATFORMS_OS_WINDOWS: {labels.BUILD_RUNFILE_LINKS: "true"}, + labels.PLATFORMS_OS_WINDOWS: { + labels.BUILD_RUNFILE_LINKS: "true", + ##labels.ENABLE_RUNFILES: "true", + }, "//conditions:default": { labels.BUILD_RUNFILE_LINKS: "true", }, diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 3b065c6ea5..c3ec27538b 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -233,6 +233,27 @@ def FindModuleSpace(main_rel_path): raise AssertionError('Cannot find .runfiles directory for %s' % sys.argv[0]) +def find_main_file(module_space, main_rel_path): + main_filename = os.path.join(module_space, main_rel_path) + main_filename = GetWindowsPathWithUNCPrefix(main_filename) + + if os.path.exists(main_filename): + return main_filename + + if (os.environ.get("RUNFILES_MANIFEST_FILE") + and os.path.exists(os.environ["RUNFILES_MANIFEST_FILE"])): + manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] + + # The manifest uses foo/bar, while main_rel_path is foo\bar + stage2_bytes_suffixed = STAGE2_BOOTSTRAP.encode("utf8") + b" " + # Use binary to avoid BOM issues + with open(manifest_path, 'b') as fp: + for line in fp: + if line.startswith(stage2_bytes_suffixed): + _, _, main_filename = line.partition(b" ") + return main_filename + raise AssertionError(f"Cannot find main filename: {main_rel_path}") + def ExtractZip(zip_path, dest_dir): """Extracts the contents of a zip file, preserving the unix file mode bits. @@ -420,22 +441,7 @@ def Main(): # See: https://docs.python.org/3.11/using/cmdline.html#envvar-PYTHONSAFEPATH new_env['PYTHONSAFEPATH'] = '1' - main_filename = os.path.join(module_space, main_rel_path) - main_filename = GetWindowsPathWithUNCPrefix(main_filename) - if not os.path.exists(main_filename): - import pathlib - - manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] - manifest = pathlib.Path(manifest_path) - manifest_text = manifest.read_text() - print_verbose("RUNFILES MANIFEST:\n", manifest_text) - - rf = pathlib.Path(module_space) - lines = [] - for x in rf.glob("**"): - lines.append(str(x)) - lines = "\n".join(lines) - raise Exception(f"{main_filename} not found, lines:\n{lines}") + main_filename = find_main_filename(module_space, main_rel_path) assert os.path.exists(main_filename), \ 'Cannot exec() %r: file not found.' % main_filename assert os.access(main_filename, os.R_OK), \ diff --git a/tests/bootstrap_impls/BUILD.bazel b/tests/bootstrap_impls/BUILD.bazel index 71b7c058d1..d9cfa665ae 100644 --- a/tests/bootstrap_impls/BUILD.bazel +++ b/tests/bootstrap_impls/BUILD.bazel @@ -13,6 +13,7 @@ # limitations under the License. load("@rules_pkg//pkg:tar.bzl", "pkg_tar") load("@rules_shell//shell:sh_test.bzl", "sh_test") +load("//python:py_binary.bzl", "py_binary") load("//python:py_test.bzl", "py_test") load("//tests/support:py_reconfig.bzl", "py_reconfig_binary", "py_reconfig_test") load("//tests/support:sh_py_run_test.bzl", "sh_py_run_test") From 2164bafc816eb1ecb4ba570b548f57bb5f07eefb Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 00:12:11 -0800 Subject: [PATCH 25/40] fix func name --- python/private/python_bootstrap_template.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index c3ec27538b..b3e44da92e 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -441,7 +441,7 @@ def Main(): # See: https://docs.python.org/3.11/using/cmdline.html#envvar-PYTHONSAFEPATH new_env['PYTHONSAFEPATH'] = '1' - main_filename = find_main_filename(module_space, main_rel_path) + main_filename = find_main_file(module_space, main_rel_path) assert os.path.exists(main_filename), \ 'Cannot exec() %r: file not found.' % main_filename assert os.access(main_filename, os.R_OK), \ From 04ef9affc0f1302ac1203f6ab2a4b71ef73071c3 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 00:25:29 -0800 Subject: [PATCH 26/40] add open r arg, add missing distribution target --- BUILD.bazel | 1 + command_line_option/BUILD.bazel | 6 ++++++ python/private/python_bootstrap_template.txt | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/BUILD.bazel b/BUILD.bazel index aa2642d43f..7da18ebaa4 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -43,6 +43,7 @@ filegroup( "internal_dev_deps.bzl", "internal_dev_setup.bzl", "version.bzl", + "//command_line_option:distribution", "//python:distribution", "//tools:distribution", ], diff --git a/command_line_option/BUILD.bazel b/command_line_option/BUILD.bazel index 53cec5afc4..1488f0b4e1 100644 --- a/command_line_option/BUILD.bazel +++ b/command_line_option/BUILD.bazel @@ -20,3 +20,9 @@ alias( name = "enable_runfiles", actual = "//python:none", ) + +filegroup( + name = "distribution", + srcs = glob(["**"]), + visibility = ["//:__subpackages__"], +) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index b3e44da92e..5aefc6232e 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -247,7 +247,7 @@ def find_main_file(module_space, main_rel_path): # The manifest uses foo/bar, while main_rel_path is foo\bar stage2_bytes_suffixed = STAGE2_BOOTSTRAP.encode("utf8") + b" " # Use binary to avoid BOM issues - with open(manifest_path, 'b') as fp: + with open(manifest_path, 'rb') as fp: for line in fp: if line.startswith(stage2_bytes_suffixed): _, _, main_filename = line.partition(b" ") From e6a57fd59066aceebc2de97dc377b9f54ca23471 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 00:30:35 -0800 Subject: [PATCH 27/40] add debug logic --- python/private/python_bootstrap_template.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 5aefc6232e..eabc8c773e 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -243,12 +243,14 @@ def find_main_file(module_space, main_rel_path): if (os.environ.get("RUNFILES_MANIFEST_FILE") and os.path.exists(os.environ["RUNFILES_MANIFEST_FILE"])): manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] + print_verbose("search for main in manifest:", manifest_path) # The manifest uses foo/bar, while main_rel_path is foo\bar stage2_bytes_suffixed = STAGE2_BOOTSTRAP.encode("utf8") + b" " # Use binary to avoid BOM issues with open(manifest_path, 'rb') as fp: for line in fp: + print_verbose("manifest line:", repr(line)) if line.startswith(stage2_bytes_suffixed): _, _, main_filename = line.partition(b" ") return main_filename From bb9dcdfd3e7b12852fbdd7c70493b976c9d92ba6 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 00:42:05 -0800 Subject: [PATCH 28/40] add utf8-sig logic, more debug --- python/private/stage2_bootstrap_template.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index 4eecb5b174..d9c8e0b9ad 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -70,11 +70,15 @@ def get_build_data(self): rlocation_path = rlocation_path.replace("/", "\\") path = runfiles.Create().Rlocation(rlocation_path) try: - with open(path) as fp: + # Use utf-8-sig to handle Windows BOM + with open(path, encoding='utf-8-sig') as fp: return fp.read() except Exception as exc: if hasattr(exc, "add_note"): exc.add_note(f"runfiles lookup path: {rlocation_path}") + exc.add_note(f"exists: {os.path.exists(path)}") + can_read = os.access(path, os.R_OK) + exc.add_note(f"readable: {can_read}") raise From 6d41092a22d1353dc3d7cd482ea7b404aea04caa Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 00:48:41 -0800 Subject: [PATCH 29/40] fix stage2 bootstrap path rendering --- python/private/py_executable.bzl | 5 +---- python/private/python_bootstrap_template.txt | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 3acfa45cd8..35f7a52785 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -768,10 +768,7 @@ def _create_stage1_bootstrap( } if stage2_bootstrap: - subs["%stage2_bootstrap%"] = "{}/{}".format( - ctx.workspace_name, - stage2_bootstrap.short_path, - ) + subs["%stage2_bootstrap%"] = runfiles_root_path(ctx, stage2_bootstrap.short_path) template = runtime.bootstrap_template subs["%shebang%"] = runtime.stub_shebang elif not ctx.files.srcs: diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index eabc8c773e..d3fafa9aa9 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -10,13 +10,13 @@ import sys import os import subprocess import uuid -# runfiles-relative path # NOTE: The sentinel strings are split (e.g., "%stage2" + "_bootstrap%") so that # the substitution logic won't replace them. This allows runtime detection of # unsubstituted placeholders, which occurs when native py_binary is used in # external repositories. In that case, we fall back to %main% which Bazel's # native rule does substitute. _STAGE2_BOOTSTRAP_SENTINEL = "%stage2" + "_bootstrap%" +# runfiles-root-relative path STAGE2_BOOTSTRAP="%stage2_bootstrap%" # NOTE: The fallback logic from stage2_bootstrap to main is only present From 0eda191cafae8ee105ff755417193285c8210dca Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:01:38 -0800 Subject: [PATCH 30/40] try to normpath, i guess? --- python/private/stage2_bootstrap_template.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index d9c8e0b9ad..5e0472ee1f 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -69,6 +69,8 @@ def get_build_data(self): if is_windows(): rlocation_path = rlocation_path.replace("/", "\\") path = runfiles.Create().Rlocation(rlocation_path) + if is_windows(): + path = os.path.normpath(path) try: # Use utf-8-sig to handle Windows BOM with open(path, encoding='utf-8-sig') as fp: From 060991c3541363a5c509b79431d5e69fb5c11feb Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:05:59 -0800 Subject: [PATCH 31/40] strip to remove trailing newline --- python/private/python_bootstrap_template.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index d3fafa9aa9..a465510a9a 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -253,7 +253,7 @@ def find_main_file(module_space, main_rel_path): print_verbose("manifest line:", repr(line)) if line.startswith(stage2_bytes_suffixed): _, _, main_filename = line.partition(b" ") - return main_filename + return main_filename.strip() raise AssertionError(f"Cannot find main filename: {main_rel_path}") def ExtractZip(zip_path, dest_dir): From daa87691f586aab2ba8e2420f3b714bcf9b47c71 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:07:16 -0800 Subject: [PATCH 32/40] encode to ut8 --- python/private/python_bootstrap_template.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index a465510a9a..2cba6a9ac9 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -253,7 +253,7 @@ def find_main_file(module_space, main_rel_path): print_verbose("manifest line:", repr(line)) if line.startswith(stage2_bytes_suffixed): _, _, main_filename = line.partition(b" ") - return main_filename.strip() + return main_filename.strip().encode("utf8") raise AssertionError(f"Cannot find main filename: {main_rel_path}") def ExtractZip(zip_path, dest_dir): From ebc6ed884ef5e25f415d9ea7b7edf2317037a8e2 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:11:52 -0800 Subject: [PATCH 33/40] use decode, not encode, on bytes object --- python/private/python_bootstrap_template.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 2cba6a9ac9..c9f991a323 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -246,14 +246,16 @@ def find_main_file(module_space, main_rel_path): print_verbose("search for main in manifest:", manifest_path) # The manifest uses foo/bar, while main_rel_path is foo\bar + # Add trailing space to avoid suffix-string matching stage2_bytes_suffixed = STAGE2_BOOTSTRAP.encode("utf8") + b" " # Use binary to avoid BOM issues with open(manifest_path, 'rb') as fp: for line in fp: print_verbose("manifest line:", repr(line)) + # NOTE: This doesn't handle escaped manifest lines if line.startswith(stage2_bytes_suffixed): _, _, main_filename = line.partition(b" ") - return main_filename.strip().encode("utf8") + return main_filename.strip().decode("utf8") raise AssertionError(f"Cannot find main filename: {main_rel_path}") def ExtractZip(zip_path, dest_dir): From dc97151e9f3336fa098694c0f02833b60d576386 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:24:15 -0800 Subject: [PATCH 34/40] manifest lookup of interpreter --- python/private/python_bootstrap_template.txt | 49 +++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index c9f991a323..a2a3cdc322 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -165,6 +165,28 @@ def print_verbose(*args, mapping=None, values=None): else: print("bootstrap: stage 1:", *args, file=sys.stderr, flush=True) +def maybe_find_in_manifest(rf_root_path): + if not os.environ.get("RUNFILES_MANIFEST_FILE"): + return None + if not os.path.exists(os.environ["RUNFILES_MANIFEST_FILE"]): + return None + manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] + print_verbose("search for main in manifest:", manifest_path) + + # Add trailing space to avoid suffix-string matching + search_for_prefix = rf_root_path.encode("utf8") + b" " + # Use binary to avoid BOM issues on Windows + with open(manifest_path, 'rb') as fp: + for line in fp: + print_verbose("manifest line:", repr(line)) + # NOTE: This doesn't handle escaped manifest lines + if line.startswith(search_for_prefix): + _, _, main_filename = line.partition(b" ") + return main_filename.strip().decode("utf8") + + return None + + def FindBinary(module_space, bin_name): """Finds the real binary if it's not a normal absolute path.""" if not bin_name: @@ -180,7 +202,12 @@ def FindBinary(module_space, bin_name): # Use normpath() to convert slashes to os.sep on Windows. elif os.sep in os.path.normpath(bin_name): # Case 3: Path is relative to the repo root. - return os.path.join(module_space, bin_name) + full_path = os.path.join(module_space, bin_name) + if os.path.exists(full_path): + return full_path + full_path = maybe_find_in_manifest(bin_name) + if not full_path: + raise AssertionError(f"Unable to find Python: {bin_name}") else: # Case 4: Path has to be looked up in the search path. return SearchPath(bin_name) @@ -240,24 +267,12 @@ def find_main_file(module_space, main_rel_path): if os.path.exists(main_filename): return main_filename - if (os.environ.get("RUNFILES_MANIFEST_FILE") - and os.path.exists(os.environ["RUNFILES_MANIFEST_FILE"])): - manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] - print_verbose("search for main in manifest:", manifest_path) - - # The manifest uses foo/bar, while main_rel_path is foo\bar - # Add trailing space to avoid suffix-string matching - stage2_bytes_suffixed = STAGE2_BOOTSTRAP.encode("utf8") + b" " - # Use binary to avoid BOM issues - with open(manifest_path, 'rb') as fp: - for line in fp: - print_verbose("manifest line:", repr(line)) - # NOTE: This doesn't handle escaped manifest lines - if line.startswith(stage2_bytes_suffixed): - _, _, main_filename = line.partition(b" ") - return main_filename.strip().decode("utf8") + main_filename = maybe_find_in_manifest(STAGE2_BOOTSTRAP) + if main_filename: + return main_filename raise AssertionError(f"Cannot find main filename: {main_rel_path}") + def ExtractZip(zip_path, dest_dir): """Extracts the contents of a zip file, preserving the unix file mode bits. From 254c712dffd3b5c005ab8796ba7f333286064167 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:27:37 -0800 Subject: [PATCH 35/40] less debugging --- python/private/python_bootstrap_template.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index a2a3cdc322..cab3565ee3 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -171,14 +171,13 @@ def maybe_find_in_manifest(rf_root_path): if not os.path.exists(os.environ["RUNFILES_MANIFEST_FILE"]): return None manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] - print_verbose("search for main in manifest:", manifest_path) + print_verbose(f"search for {rf_root_path} in manifest:", manifest_path) # Add trailing space to avoid suffix-string matching search_for_prefix = rf_root_path.encode("utf8") + b" " # Use binary to avoid BOM issues on Windows with open(manifest_path, 'rb') as fp: for line in fp: - print_verbose("manifest line:", repr(line)) # NOTE: This doesn't handle escaped manifest lines if line.startswith(search_for_prefix): _, _, main_filename = line.partition(b" ") From c66503bae1b72e16e365fcbe0abaf1d354c4088d Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:31:49 -0800 Subject: [PATCH 36/40] add some debugging --- python/private/python_bootstrap_template.txt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index cab3565ee3..1b2fadcd01 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -165,19 +165,21 @@ def print_verbose(*args, mapping=None, values=None): else: print("bootstrap: stage 1:", *args, file=sys.stderr, flush=True) -def maybe_find_in_manifest(rf_root_path): +def maybe_find_in_manifest(rf_root_path, show_lines=False): if not os.environ.get("RUNFILES_MANIFEST_FILE"): return None if not os.path.exists(os.environ["RUNFILES_MANIFEST_FILE"]): return None manifest_path = os.environ["RUNFILES_MANIFEST_FILE"] - print_verbose(f"search for {rf_root_path} in manifest:", manifest_path) + print_verbose("search manifest for:", rf_root_path) # Add trailing space to avoid suffix-string matching search_for_prefix = rf_root_path.encode("utf8") + b" " # Use binary to avoid BOM issues on Windows with open(manifest_path, 'rb') as fp: for line in fp: + if show_lines: + print_verbose("manifest line:", repr(line)) # NOTE: This doesn't handle escaped manifest lines if line.startswith(search_for_prefix): _, _, main_filename = line.partition(b" ") @@ -204,7 +206,7 @@ def FindBinary(module_space, bin_name): full_path = os.path.join(module_space, bin_name) if os.path.exists(full_path): return full_path - full_path = maybe_find_in_manifest(bin_name) + full_path = maybe_find_in_manifest(bin_name, True) if not full_path: raise AssertionError(f"Unable to find Python: {bin_name}") else: @@ -467,7 +469,10 @@ def Main(): program = python_program = FindPythonBinary(module_space) if python_program is None: - raise AssertionError('Could not find python binary: ' + repr(PYTHON_BINARY)) + raise AssertionError('Could not find python binary: {} or {}'.format( + repr(PYTHON_BINARY), + repr(PYTHON_BINARY_ACTUAL) + ) # Some older Python versions on macOS (namely Python 3.7) may unintentionally # leave this environment variable set after starting the interpreter, which From fa3d3fbacd6b9781dcb6b597b2add2847e541d61 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:34:04 -0800 Subject: [PATCH 37/40] fix syntax --- python/private/python_bootstrap_template.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index 1b2fadcd01..dd09fb2fc1 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -469,10 +469,10 @@ def Main(): program = python_program = FindPythonBinary(module_space) if python_program is None: - raise AssertionError('Could not find python binary: {} or {}'.format( + raise AssertionError("Could not find python binary: {} or {}".format( repr(PYTHON_BINARY), repr(PYTHON_BINARY_ACTUAL) - ) + )) # Some older Python versions on macOS (namely Python 3.7) may unintentionally # leave this environment variable set after starting the interpreter, which From bc954c22eb6ddea1a59e038689f6c351932f3b78 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:38:42 -0800 Subject: [PATCH 38/40] return the found value, duh --- python/private/python_bootstrap_template.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/python/private/python_bootstrap_template.txt b/python/private/python_bootstrap_template.txt index dd09fb2fc1..95fd1116eb 100644 --- a/python/private/python_bootstrap_template.txt +++ b/python/private/python_bootstrap_template.txt @@ -209,6 +209,7 @@ def FindBinary(module_space, bin_name): full_path = maybe_find_in_manifest(bin_name, True) if not full_path: raise AssertionError(f"Unable to find Python: {bin_name}") + return full_path else: # Case 4: Path has to be looked up in the search path. return SearchPath(bin_name) From a721a9efd5569da58132f0d93c94340c15c130eb Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:47:35 -0800 Subject: [PATCH 39/40] add notes --- python/private/stage2_bootstrap_template.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/private/stage2_bootstrap_template.py b/python/private/stage2_bootstrap_template.py index 5e0472ee1f..016b5fa7cb 100644 --- a/python/private/stage2_bootstrap_template.py +++ b/python/private/stage2_bootstrap_template.py @@ -209,6 +209,9 @@ def find_runfiles_root(main_rel_path): else: stub_filename = os.path.join(os.path.dirname(stub_filename), target) + # todo: this can happen if --enable_runfiles=false, which is the default + # on windows. + # To work around this, return None, then have callers handle the case raise AssertionError("Cannot find .runfiles directory for %s" % sys.argv[0]) @@ -454,6 +457,7 @@ def main(): else: runfiles_root = find_runfiles_root("") + # todo: handle missing runfiles root site_packages = os.path.join(runfiles_root, VENV_ROOT, VENV_SITE_PACKAGES) if site_packages not in sys.path and os.path.exists(site_packages): # This can happen in a few situations: @@ -473,6 +477,7 @@ def main(): print_verbose("runfiles root:", runfiles_root) + # todo: handle missing runfiles root runfiles_envkey, runfiles_envvalue = runfiles_envvar(runfiles_root) if runfiles_envkey: os.environ[runfiles_envkey] = runfiles_envvalue @@ -503,6 +508,7 @@ def main(): else: prepend_path_entries = [] + # todo: look in manifest if runfiles_root is None main_filename = os.path.join(runfiles_root, main_rel_path) main_filename = get_windows_path_with_unc_prefix(main_filename) assert os.path.exists(main_filename), ( From 1f7242550e4c31c7354898349bb778fa07d47b74 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 15 Feb 2026 01:53:01 -0800 Subject: [PATCH 40/40] transition enable_runfiles --- python/private/py_executable.bzl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 35f7a52785..0fc9dcc715 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -1779,7 +1779,7 @@ def add_config_setting_default_build_runfiles_links(kwargs): default = select({ labels.PLATFORMS_OS_WINDOWS: { labels.BUILD_RUNFILE_LINKS: "true", - ##labels.ENABLE_RUNFILES: "true", + labels.ENABLE_RUNFILES: "true", }, "//conditions:default": { labels.BUILD_RUNFILE_LINKS: "true", @@ -1856,11 +1856,13 @@ def create_executable_rule_builder(implementation, **kwargs): labels.PYTHON_VERSION, "//command_line_option:stamp", "//command_line_option:build_runfile_links", + "//command_line_option:enable_runfiles", ], outputs = TRANSITION_LABELS + [ labels.PYTHON_VERSION, "//command_line_option:stamp", "//command_line_option:build_runfile_links", + "//command_line_option:enable_runfiles", ], ), **kwargs