From 674e7343173ec0b9f5389cb64208f623e135edc5 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 1 Feb 2026 20:25:03 -0800 Subject: [PATCH 01/21] basic windows impl --- python/private/zipapp/py_zipapp_rule.bzl | 96 +++++++++++++++++++++--- tests/py_zipapp/BUILD.bazel | 14 ++-- 2 files changed, 94 insertions(+), 16 deletions(-) diff --git a/python/private/zipapp/py_zipapp_rule.bzl b/python/private/zipapp/py_zipapp_rule.bzl index cc399064ad..884902012c 100644 --- a/python/private/zipapp/py_zipapp_rule.bzl +++ b/python/private/zipapp/py_zipapp_rule.bzl @@ -1,9 +1,10 @@ """Implementation of the zipapp rules.""" load("@bazel_skylib//lib:paths.bzl", "paths") +load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config") load("//python/private:attributes.bzl", "apply_config_settings_attr") load("//python/private:builders.bzl", "builders") -load("//python/private:common.bzl", "BUILTIN_BUILD_PYTHON_ZIP", "actions_run", "maybe_builtin_build_python_zip", "maybe_create_repo_mapping", "runfiles_root_path") +load("//python/private:common.bzl", "BUILTIN_BUILD_PYTHON_ZIP", "actions_run", "maybe_builtin_build_python_zip", "maybe_create_repo_mapping", "runfiles_root_path", "target_platform_has_any_constraint") load("//python/private:common_labels.bzl", "labels") load("//python/private:py_executable_info.bzl", "PyExecutableInfo") load("//python/private:py_internal.bzl", "py_internal") @@ -11,6 +12,45 @@ load("//python/private:py_runtime_info.bzl", "PyRuntimeInfo") load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE") load("//python/private:transition_labels.bzl", "TRANSITION_LABELS") +_LAUNCHER_MAKER_TOOLCHAIN_TYPE = "@bazel_tools//tools/launcher:launcher_maker_toolchain_type" + +def _find_launcher_maker(ctx): + if rp_config.bazel_9_or_later: + return (ctx.toolchains[_LAUNCHER_MAKER_TOOLCHAIN_TYPE].binary, _LAUNCHER_MAKER_TOOLCHAIN_TYPE) + return (ctx.executable._windows_launcher_maker, None) + +def _create_windows_exe_launcher( + ctx, + *, + output, + python_binary_path, + use_zip_file): + launch_info = ctx.actions.args() + launch_info.use_param_file("%s", use_always = True) + launch_info.set_param_file_format("multiline") + launch_info.add("binary_type=Python") + launch_info.add(ctx.workspace_name, format = "workspace_name=%s") + launch_info.add( + "1" if py_internal.runfiles_enabled(ctx) else "0", + format = "symlink_runfiles_enabled=%s", + ) + launch_info.add(python_binary_path, format = "python_bin_path=%s") + launch_info.add("1" if use_zip_file else "0", format = "use_zip_file=%s") + + launcher = ctx.attr._launcher[DefaultInfo].files_to_run.executable + executable, toolchain = _find_launcher_maker(ctx) + ctx.actions.run( + executable = executable, + arguments = [launcher.path, launch_info, output.path], + inputs = [launcher], + outputs = [output], + mnemonic = "PyBuildLauncher", + progress_message = "Creating launcher for %{label}", + # Needed to inherit PATH when using non-MSVC compilers like MinGW + use_default_shell_env = True, + toolchain = toolchain, + ) + def _is_symlink(f): if hasattr(f, "is_symlink"): return str(int(f.is_symlink)) @@ -184,20 +224,39 @@ def _py_zipapp_executable_impl(ctx): zip_file = _create_zip(ctx, py_runtime, py_executable, stage2_bootstrap) if ctx.attr.executable: - preamble = _create_shell_bootstrap(ctx, py_runtime, py_executable, stage2_bootstrap) - executable = _create_self_executable_zip(ctx, preamble, zip_file) - default_output = executable + if target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints): + executable = ctx.actions.declare_file(ctx.label.name + ".exe") + + python_exe = py_executable.venv_python_exe + if python_exe: + python_exe_path = runfiles_root_path(ctx, python_exe.short_path) + elif py_runtime.interpreter: + python_exe_path = runfiles_root_path(ctx, py_runtime.interpreter.short_path) + else: + python_exe_path = py_runtime.interpreter_path + + _create_windows_exe_launcher( + ctx, + output = executable, + python_binary_path = python_exe_path, + use_zip_file = True, + ) + default_output = depset([executable, zip_file]) + else: + preamble = _create_shell_bootstrap(ctx, py_runtime, py_executable, stage2_bootstrap) + executable = _create_self_executable_zip(ctx, preamble, zip_file) + default_output = depset([executable]) else: # Bazel requires executable=True rules to have an executable given, so give # a fake one to satisfy that. - default_output = zip_file + default_output = depset([zip_file]) executable = ctx.actions.declare_file(ctx.label.name + "-not-executable") ctx.actions.write(executable, "echo 'ERROR: Non executable zip file'; exit 1") return [ DefaultInfo( - files = depset([default_output]), - runfiles = ctx.runfiles(files = [default_output]), + files = default_output, + runfiles = ctx.runfiles(files = default_output.to_list()), executable = executable, ), ] @@ -277,6 +336,18 @@ Whether the output should be an executable zip file. cfg = "exec", default = "//tools/private/zipapp:exe_zip_maker", ), + "_launcher": attr.label( + cfg = "target", + # NOTE: This is an executable, but is only used for Windows. It + # can't have executable=True because the backing target is an + # empty target for other platforms. + default = "//tools/launcher:launcher", + ), + "_windows_constraints": attr.label_list( + default = [ + "@platforms//os:windows", + ], + ), "_zip_shell_template": attr.label( default = ":zip_shell_template", allow_single_file = True, @@ -285,8 +356,15 @@ Whether the output should be an executable zip file. cfg = "exec", default = "//tools/private/zipapp:zipper", ), -} -_TOOLCHAINS = [EXEC_TOOLS_TOOLCHAIN_TYPE] +} | ({ + "_windows_launcher_maker": attr.label( + default = "@bazel_tools//tools/launcher:launcher_maker", + cfg = "exec", + executable = True, + ), +} if not rp_config.bazel_9_or_later else {}) + +_TOOLCHAINS = [EXEC_TOOLS_TOOLCHAIN_TYPE] + ([_LAUNCHER_MAKER_TOOLCHAIN_TYPE] if rp_config.bazel_9_or_later else []) py_zipapp_binary = rule( doc = """ diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index 74df4aa04d..b41c51fe5a 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -16,14 +16,14 @@ py_binary( "//python/config_settings:venvs_site_packages": "yes", }, main = "main.py", - target_compatible_with = NOT_WINDOWS, + ##target_compatible_with = NOT_WINDOWS, deps = ["@dev_pip//absl_py"], ) py_zipapp_binary( name = "venv_zipapp", binary = ":venv_bin", - target_compatible_with = NOT_WINDOWS, + ##target_compatible_with = NOT_WINDOWS, ) py_test( @@ -34,7 +34,7 @@ py_test( "BZLMOD_ENABLED": str(int(BZLMOD_ENABLED)), "TEST_ZIPAPP": "$(location :venv_zipapp)", }, - target_compatible_with = NOT_WINDOWS, + ##target_compatible_with = NOT_WINDOWS, ) py_binary( @@ -46,14 +46,14 @@ py_binary( }, main = "main.py", # TODO: #2586 - Add windows support - target_compatible_with = NOT_WINDOWS, - deps = ["@dev_pip//absl_py"], + ##target_compatible_with = NOT_WINDOWS, + ##deps = ["@dev_pip//absl_py"], ) py_zipapp_binary( name = "system_python_zipapp", binary = ":system_python_bin", - target_compatible_with = NOT_WINDOWS, + ##target_compatible_with = NOT_WINDOWS, ) py_test( @@ -63,5 +63,5 @@ py_test( env = { "TEST_ZIPAPP": "$(location :system_python_zipapp)", }, - target_compatible_with = NOT_WINDOWS, + ##target_compatible_with = NOT_WINDOWS, ) From cbf520991cc2e9e01773739ed3e068f4b4827b0e Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 1 Feb 2026 20:40:16 -0800 Subject: [PATCH 02/21] only run zipapp test on windows --- .bazelci/presubmit.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 630638ee80..d38a9c8c49 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -256,8 +256,14 @@ tasks: <<: *reusable_config name: "Default: Windows" platform: windows + build_targets: + - "--" + - "//tests/py_zipapp:system_python_zipapp_test" test_flags: - "--test_tag_filters=-integration-test,-fix-windows" + test_targets: + - "--" + - "//tests/py_zipapp:system_python_zipapp_test" rbe_min: <<: *minimum_supported_version <<: *reusable_config From cfff6979afa1a3229d53368efae868d37002509c Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 1 Feb 2026 21:02:27 -0800 Subject: [PATCH 03/21] remove shebang check for windows --- tests/py_zipapp/system_python_zipapp_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/py_zipapp/system_python_zipapp_test.py b/tests/py_zipapp/system_python_zipapp_test.py index 297a574f16..1ddb9794ca 100644 --- a/tests/py_zipapp/system_python_zipapp_test.py +++ b/tests/py_zipapp/system_python_zipapp_test.py @@ -11,10 +11,10 @@ def test_zipapp_contents(self): self.assertTrue(os.path.exists(zipapp_path)) self.assertTrue(os.path.isfile(zipapp_path)) - # The zipapp itself is a shell script prepended to the zip file. - with open(zipapp_path, "rb") as f: - content = f.read() - self.assertTrue(content.startswith(b"#!/usr/bin/env bash")) + ### The zipapp itself is a shell script prepended to the zip file. + ##with open(zipapp_path, "rb") as f: + ## content = f.read() + ##self.assertTrue(content.startswith(b"#!/usr/bin/env bash")) output = subprocess.check_output([zipapp_path]).decode("utf-8").strip() self.assertIn("Hello from zipapp", output) From 49ce65cb5c6aed9c91f5d6dae2b8d08d48cdd9ee Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 2 Feb 2026 19:49:55 -0800 Subject: [PATCH 04/21] remove venv-symlink recreation logic --- .bazelci/presubmit.yml | 2 + python/private/zipapp/py_zipapp_rule.bzl | 14 ++-- python/private/zipapp/zip_main_template.py | 75 +++++++++++----------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index d38a9c8c49..0f22821d95 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -259,11 +259,13 @@ tasks: build_targets: - "--" - "//tests/py_zipapp:system_python_zipapp_test" + ##- "//tests/py_zipapp:venv_zipapp_test" test_flags: - "--test_tag_filters=-integration-test,-fix-windows" test_targets: - "--" - "//tests/py_zipapp:system_python_zipapp_test" + ##- "//tests/py_zipapp:venv_zipapp_test" rbe_min: <<: *minimum_supported_version <<: *reusable_config diff --git a/python/private/zipapp/py_zipapp_rule.bzl b/python/private/zipapp/py_zipapp_rule.bzl index 884902012c..9aac1cba07 100644 --- a/python/private/zipapp/py_zipapp_rule.bzl +++ b/python/private/zipapp/py_zipapp_rule.bzl @@ -58,13 +58,11 @@ def _is_symlink(f): return "-1" def _create_zipapp_main_py(ctx, py_runtime, py_executable, stage2_bootstrap): - python_exe = py_executable.venv_python_exe - if python_exe: - python_exe_path = runfiles_root_path(ctx, python_exe.short_path) - elif py_runtime.interpreter: - python_exe_path = runfiles_root_path(ctx, py_runtime.interpreter.short_path) - else: - python_exe_path = py_runtime.interpreter_path + venv_python_exe = py_executable.venv_python_exe + if venv_python_exe: + venv_python_exe_path = runfiles_root_path(ctx, venv_python_exe.short_path) + if not py_executable.venv_python_exe: + venv_python_exe_path = "" if py_runtime.interpreter: python_binary_actual_path = runfiles_root_path(ctx, py_runtime.interpreter.short_path) @@ -76,7 +74,7 @@ def _create_zipapp_main_py(ctx, py_runtime, py_executable, stage2_bootstrap): template = py_runtime.zip_main_template, output = zip_main_py, substitutions = { - "%python_binary%": python_exe_path, + "%python_binary_venv%": venv_python_exe_path, "%python_binary_actual%": python_binary_actual_path, "%stage2_bootstrap%": runfiles_root_path(ctx, stage2_bootstrap.short_path), "%workspace_name%": ctx.workspace_name, diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index 35db1645bc..9f3c23e7a2 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py @@ -30,7 +30,7 @@ # runfiles-root-relative path _STAGE2_BOOTSTRAP = "%stage2_bootstrap%" # runfiles-root-relative path to venv's bin/python3. Empty if venv not being used. -_PYTHON_BINARY = "%python_binary%" +_PYTHON_BINARY_VENV = "%python_binary_venv%" # runfiles-root-relative path, absolute path, or single word. The actual Python # executable to use. _PYTHON_BINARY_ACTUAL = "%python_binary_actual%" @@ -106,11 +106,11 @@ def has_windows_executable_extension(path): if ( - _PYTHON_BINARY + _PYTHON_BINARY_VENV and is_windows() - and not has_windows_executable_extension(_PYTHON_BINARY) + and not has_windows_executable_extension(_PYTHON_BINARY_VENV) ): - _PYTHON_BINARY = _PYTHON_BINARY + ".exe" + _PYTHON_BINARY_VENV = _PYTHON_BINARY_VENV + ".exe" def search_path(name): @@ -126,8 +126,9 @@ def search_path(name): def find_python_binary(module_space): """Finds the real Python binary if it's not a normal absolute path.""" - if _PYTHON_BINARY: - return find_binary(module_space, _PYTHON_BINARY) + if _PYTHON_BINARY_VENV: + # todo: do join(module_space, PYTHON_BINARY_VENV) instead + return find_binary(module_space, _PYTHON_BINARY_VENV) else: return find_binary(module_space, _PYTHON_BINARY_ACTUAL) @@ -221,7 +222,7 @@ def execute_file( # - On Windows, os.execv doesn't handle arguments with spaces # correctly, and it actually starts a subprocess just like # subprocess.call. - # - When running in a workspace or zip file, we need to clean up the + # - When running in a zip file, we need to clean up the # workspace after the process finishes so control must return here. try: subprocess_argv = [python_program, main_filename] + args @@ -275,36 +276,36 @@ def main(): python_program = find_python_binary(module_space) if python_program is None: - raise AssertionError("Could not find python binary: " + _PYTHON_BINARY) - - # When a venv is used, the `bin/python3` symlink has to be recreated. - if _PYTHON_BINARY: - # The venv bin/python3 interpreter should always be under runfiles, but - # double check. We don't want to accidentally create symlinks elsewhere. - if not python_program.startswith(module_space): - raise AssertionError( - "Program's venv binary not under runfiles: {python_program}" - ) - - if os.path.isabs(_PYTHON_BINARY_ACTUAL): - symlink_to = _PYTHON_BINARY_ACTUAL - elif "/" in _PYTHON_BINARY_ACTUAL: - symlink_to = os.path.join(module_space, _PYTHON_BINARY_ACTUAL) - else: - symlink_to = search_path(_PYTHON_BINARY_ACTUAL) - if not symlink_to: - raise AssertionError( - f"Python interpreter to use not found on PATH: {_PYTHON_BINARY_ACTUAL}" - ) - - # The bin/ directory may not exist if it is empty. - os.makedirs(os.path.dirname(python_program), exist_ok=True) - try: - os.symlink(symlink_to, python_program) - except OSError as e: - raise Exception( - f"Unable to create venv python interpreter symlink: {python_program} -> {symlink_to}" - ) from e + raise AssertionError("Could not find python binary: " + _PYTHON_BINARY_VENV) + + ### When a venv is used, the `bin/python3` symlink may need to be created. + ##if _PYTHON_BINARY_VENV: + ## # The venv bin/python3 interpreter should always be under runfiles, but + ## # double check. We don't want to accidentally create symlinks elsewhere. + ## if not python_program.startswith(module_space): + ## raise AssertionError( + ## "Program's venv binary not under runfiles: {python_program}" + ## ) + + ## if os.path.isabs(_PYTHON_BINARY_ACTUAL): + ## symlink_to = _PYTHON_BINARY_ACTUAL + ## elif "/" in _PYTHON_BINARY_ACTUAL: + ## symlink_to = os.path.join(module_space, _PYTHON_BINARY_ACTUAL) + ## else: + ## symlink_to = search_path(_PYTHON_BINARY_ACTUAL) + ## if not symlink_to: + ## raise AssertionError( + ## f"Python interpreter to use not found on PATH: {_PYTHON_BINARY_ACTUAL}" + ## ) + + ## # The bin/ directory may not exist if it is empty. + ## os.makedirs(os.path.dirname(python_program), exist_ok=True) + ## try: + ## os.symlink(symlink_to, python_program) + ## except OSError as e: + ## raise Exception( + ## f"Unable to create venv python interpreter symlink: {python_program} -> {symlink_to}" + ## ) from e # Some older Python versions on macOS (namely Python 3.7) may unintentionally # leave this environment variable set after starting the interpreter, which From ef18175fb21dcad9138fa4adf680b70bb840d0dc Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 2 Feb 2026 20:09:42 -0800 Subject: [PATCH 05/21] add debug to zip bootstrap --- python/private/zipapp/zip_main_template.py | 34 +++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index 9f3c23e7a2..066a8506ed 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py @@ -38,7 +38,7 @@ def print_verbose(*args, mapping=None, values=None): - if bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")): + if bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")) or True: if mapping is not None: for key, value in sorted((mapping or {}).items()): print( @@ -124,6 +124,7 @@ def search_path(name): return None +# todo: remove this function def find_python_binary(module_space): """Finds the real Python binary if it's not a normal absolute path.""" if _PYTHON_BINARY_VENV: @@ -140,7 +141,7 @@ def find_binary(module_space, bin_name): if bin_name.startswith("//"): # Case 1: Path is a label. Not supported yet. raise AssertionError( - "Bazel does not support execution of Python interpreters via labels yet" + "Bazel does not support execution of Python interpreters via labels" ) elif os.path.isabs(bin_name): # Case 2: Absolute path. @@ -242,8 +243,12 @@ def main(): print_verbose("running zip main bootstrap") print_verbose("initial argv:", values=sys.argv) print_verbose("initial environ:", mapping=os.environ) - print_verbose("initial sys.executable", sys.executable) - print_verbose("initial sys.version", sys.version) + print_verbose("initial sys.executable:", sys.executable) + print_verbose("initial sys.version:", sys.version) + print_verbose("stage2_bootstrap:", _STAGE2_BOOTSTRAP) + print_verbose("python_binary_venv:", _PYTHON_BINARY_VENV) + print_verbose("python_binary_actual:", _PYTHON_BINARY_ACTUAL) + print_verbose("workspace_name:", _WORKSPACE_NAME) args = sys.argv[1:] @@ -274,9 +279,24 @@ def main(): "Cannot exec() %r: file not readable." % main_filename ) - python_program = find_python_binary(module_space) - if python_program is None: - raise AssertionError("Could not find python binary: " + _PYTHON_BINARY_VENV) + if _PYTHON_BINARY_VENV: + python_program = os.path.join(module_space, _PYTHON_BINARY_VENV) + ##if not os.path.exists(python_program): + ## symlink_to = find_binary(module_space, _PYTHON_BINARY_ACTUAL) + ## os.makedirs(os.path.dirname(python_program), exist_ok=True) + ## if os.path.lexists(python_program): + ## os.unlink(python_program) + ## try: + ## os.symlink(symlink_to, python_program) + ## except OSError as e: + ## raise Exception( + ## f"Unable to create venv python interpreter symlink: {python_program} -> {symlink_to}" + ## ) from e + + else: + python_program = find_binary(module_space, _PYTHON_BINARY_ACTUAL) + if python_program is None: + raise AssertionError("Could not find python binary: " + _PYTHON_BINARY_ACTUAL) ### When a venv is used, the `bin/python3` symlink may need to be created. ##if _PYTHON_BINARY_VENV: From bea94c287b9c5990dbe6da9cff2961013ebefe95 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 2 Feb 2026 20:15:07 -0800 Subject: [PATCH 06/21] use %python_binary% placeholder --- python/private/zipapp/py_zipapp_rule.bzl | 2 +- python/private/zipapp/zip_main_template.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/private/zipapp/py_zipapp_rule.bzl b/python/private/zipapp/py_zipapp_rule.bzl index 9aac1cba07..925e257bf7 100644 --- a/python/private/zipapp/py_zipapp_rule.bzl +++ b/python/private/zipapp/py_zipapp_rule.bzl @@ -74,7 +74,7 @@ def _create_zipapp_main_py(ctx, py_runtime, py_executable, stage2_bootstrap): template = py_runtime.zip_main_template, output = zip_main_py, substitutions = { - "%python_binary_venv%": venv_python_exe_path, + "%python_binary%": venv_python_exe_path, "%python_binary_actual%": python_binary_actual_path, "%stage2_bootstrap%": runfiles_root_path(ctx, stage2_bootstrap.short_path), "%workspace_name%": ctx.workspace_name, diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index 066a8506ed..89af27d27f 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py @@ -30,7 +30,7 @@ # runfiles-root-relative path _STAGE2_BOOTSTRAP = "%stage2_bootstrap%" # runfiles-root-relative path to venv's bin/python3. Empty if venv not being used. -_PYTHON_BINARY_VENV = "%python_binary_venv%" +_PYTHON_BINARY_VENV = "%python_binary%" # runfiles-root-relative path, absolute path, or single word. The actual Python # executable to use. _PYTHON_BINARY_ACTUAL = "%python_binary_actual%" From 6c4036d30eb6ca4f12eb9de38fa14ad6bc7c1220 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 2 Feb 2026 20:48:01 -0800 Subject: [PATCH 07/21] add dependency import without pip --- python/private/zipapp/zip_main_template.py | 4 +++- tests/py_zipapp/BUILD.bazel | 21 ++++++++++++++++++- tests/py_zipapp/main.py | 13 +++++++++--- tests/py_zipapp/some_dep.py | 1 + .../py_zipapp/system_python_zipapp_sh_test.sh | 15 +++++++++++++ 5 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 tests/py_zipapp/some_dep.py create mode 100755 tests/py_zipapp/system_python_zipapp_sh_test.sh diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index 89af27d27f..69be5cbc8c 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py @@ -233,10 +233,12 @@ def execute_file( ret_code = subprocess.call(subprocess_argv, env=env, cwd=workspace) sys.exit(ret_code) finally: + pass # NOTE: dirname() is called because create_module_space() creates a # sub-directory within a temporary directory, and we want to remove the # whole temporary directory. - shutil.rmtree(os.path.dirname(module_space), True) + ## todo: re-enable this + ##shutil.rmtree(os.path.dirname(module_space), True) def main(): diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index b41c51fe5a..d2f660bddc 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -1,4 +1,6 @@ +load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//python:py_binary.bzl", "py_binary") +load("//python:py_library.bzl", "py_library") load("//python:py_test.bzl", "py_test") load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") # buildifier: disable=bzl-visibility load("//python/zipapp:py_zipapp_binary.bzl", "py_zipapp_binary") @@ -47,7 +49,7 @@ py_binary( main = "main.py", # TODO: #2586 - Add windows support ##target_compatible_with = NOT_WINDOWS, - ##deps = ["@dev_pip//absl_py"], + deps = [":some_dep"], ) py_zipapp_binary( @@ -65,3 +67,20 @@ py_test( }, ##target_compatible_with = NOT_WINDOWS, ) + +sh_test( + name = "system_python_zipapp_sh_test", + srcs = ["system_python_zipapp_sh_test.sh"], + data = [":system_python_zipapp"], + env = { + "ZIPAPP": "$(location :system_python_zipapp)", + }, + ##target_compatible_with = NOT_WINDOWS, +) + +# todo: add venv_site_packages settings to this +py_library( + name = "some_dep", + srcs = ["some_dep.py"], + imports = ["."], +) diff --git a/tests/py_zipapp/main.py b/tests/py_zipapp/main.py index b8fdbe365e..e06d8a96fb 100644 --- a/tests/py_zipapp/main.py +++ b/tests/py_zipapp/main.py @@ -3,9 +3,16 @@ def main(): print("Hello from zipapp") - import absl - - print(f"absl: {absl}") + try: + import some_dep + print(f"absl: {some_dep}") + except ImportError: + import sys + print("Failed to import dependency", file=sys.stderr) + print("sys.path:", file=sys.stderr) + for i, x in enumerate(sys.path): + print(i, x, file=sys.stderr) + raise if __name__ == "__main__": diff --git a/tests/py_zipapp/some_dep.py b/tests/py_zipapp/some_dep.py new file mode 100644 index 0000000000..b64ecfb84a --- /dev/null +++ b/tests/py_zipapp/some_dep.py @@ -0,0 +1 @@ +"""empty module""" diff --git a/tests/py_zipapp/system_python_zipapp_sh_test.sh b/tests/py_zipapp/system_python_zipapp_sh_test.sh new file mode 100755 index 0000000000..1d2b090304 --- /dev/null +++ b/tests/py_zipapp/system_python_zipapp_sh_test.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# This test expects ZIPAPP env var to point to the zipapp file. +if [[ -z "${ZIPAPP:-}" ]]; then + echo "ZIPAPP env var not set" + exit 1 +fi + +echo "Running zipapp: $ZIPAPP" +# We use python3 to run the zipapp. +# This ensures that the zipapp (which is a zip file with __main__.py) +# is valid and executable by python. +python3 "$ZIPAPP" From 97116dda57ab87587dd11591ece5fc41e5a044bf Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 09:01:40 -0800 Subject: [PATCH 08/21] make windows workspace just run zipapp tests --- .bazelci/presubmit.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 0f22821d95..b43be82e5f 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -227,10 +227,12 @@ tasks: # for now until we can look into it. build_targets: - "--" - - "..." - # As a regression test for #225, check that wheel targets still build when - # their package path is qualified with the repo name. - - "@rules_python//examples/wheel/..." + ##- "..." + ### As a regression test for #225, check that wheel targets still build when + ### their package path is qualified with the repo name. + ##- "@rules_python//examples/wheel/..." + - "//tests/py_zipapp:system_python_zipapp_test" + - "//tests/py_zipapp:venv_zipapp_test" build_flags: - "--noenable_bzlmod" - "--enable_workspace" @@ -238,7 +240,9 @@ tasks: - "--build_tag_filters=-integration-test" test_targets: - "--" - - "..." + ##- "..." + - "//tests/py_zipapp:system_python_zipapp_test" + - "//tests/py_zipapp:venv_zipapp_test" test_flags: - "--noenable_bzlmod" - "--enable_workspace" @@ -259,13 +263,13 @@ tasks: build_targets: - "--" - "//tests/py_zipapp:system_python_zipapp_test" - ##- "//tests/py_zipapp:venv_zipapp_test" + - "//tests/py_zipapp:venv_zipapp_test" test_flags: - "--test_tag_filters=-integration-test,-fix-windows" test_targets: - "--" - "//tests/py_zipapp:system_python_zipapp_test" - ##- "//tests/py_zipapp:venv_zipapp_test" + - "//tests/py_zipapp:venv_zipapp_test" rbe_min: <<: *minimum_supported_version <<: *reusable_config From 9b0bb22580a71415c9c92d679b7cf66e431331e2 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 09:35:10 -0800 Subject: [PATCH 09/21] make venv test compatible with windows --- tests/py_zipapp/BUILD.bazel | 11 ++++- tests/py_zipapp/main.py | 4 +- .../py_zipapp/system_python_zipapp_sh_test.sh | 8 ++-- tests/py_zipapp/venv_zipapp_test.py | 43 +++++++++++++++---- 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index d2f660bddc..f47b85c92f 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -19,7 +19,7 @@ py_binary( }, main = "main.py", ##target_compatible_with = NOT_WINDOWS, - deps = ["@dev_pip//absl_py"], + deps = [":some_dep"], ) py_zipapp_binary( @@ -34,6 +34,7 @@ py_test( data = [":venv_zipapp"], env = { "BZLMOD_ENABLED": str(int(BZLMOD_ENABLED)), + # todo: for windows, the output is (launcher.exe, zipapp.pyz) "TEST_ZIPAPP": "$(location :venv_zipapp)", }, ##target_compatible_with = NOT_WINDOWS, @@ -68,13 +69,19 @@ py_test( ##target_compatible_with = NOT_WINDOWS, ) +# todo: rename to system_python_zipapp_external_bootstrap_test sh_test( name = "system_python_zipapp_sh_test", srcs = ["system_python_zipapp_sh_test.sh"], - data = [":system_python_zipapp"], + data = [ + ":system_python_zipapp", + "//python:current_py_toolchain", + ], env = { "ZIPAPP": "$(location :system_python_zipapp)", + "PYTHON": "$(PYTHON3_ROOTPATH)", }, + toolchains = ["//python:current_py_toolchain"], ##target_compatible_with = NOT_WINDOWS, ) diff --git a/tests/py_zipapp/main.py b/tests/py_zipapp/main.py index e06d8a96fb..4321d84f91 100644 --- a/tests/py_zipapp/main.py +++ b/tests/py_zipapp/main.py @@ -5,10 +5,10 @@ def main(): print("Hello from zipapp") try: import some_dep - print(f"absl: {some_dep}") + print(f"dep: {some_dep}") except ImportError: import sys - print("Failed to import dependency", file=sys.stderr) + print("Failed to import `some_dep`", file=sys.stderr) print("sys.path:", file=sys.stderr) for i, x in enumerate(sys.path): print(i, x, file=sys.stderr) diff --git a/tests/py_zipapp/system_python_zipapp_sh_test.sh b/tests/py_zipapp/system_python_zipapp_sh_test.sh index 1d2b090304..9546dae059 100755 --- a/tests/py_zipapp/system_python_zipapp_sh_test.sh +++ b/tests/py_zipapp/system_python_zipapp_sh_test.sh @@ -8,8 +8,6 @@ if [[ -z "${ZIPAPP:-}" ]]; then exit 1 fi -echo "Running zipapp: $ZIPAPP" -# We use python3 to run the zipapp. -# This ensures that the zipapp (which is a zip file with __main__.py) -# is valid and executable by python. -python3 "$ZIPAPP" +# We're testing the invocation of `__main__.py`, so we have to +# manually pass the zipapp to python. +"$PYTHON" "$ZIPAPP" diff --git a/tests/py_zipapp/venv_zipapp_test.py b/tests/py_zipapp/venv_zipapp_test.py index 40d20fedb4..d976645f6c 100644 --- a/tests/py_zipapp/venv_zipapp_test.py +++ b/tests/py_zipapp/venv_zipapp_test.py @@ -2,23 +2,33 @@ import subprocess import unittest import zipfile +import contextlib class PyZipAppTest(unittest.TestCase): - def test_zipapp_contents(self): + def test_zipapp_runnable(self): zipapp_path = os.environ["TEST_ZIPAPP"] - self.assertTrue(os.path.exists(zipapp_path)) - self.assertTrue(os.path.isfile(zipapp_path)) + ##self.assertTrue(os.path.exists(zipapp_path)) + ##self.assertTrue(os.path.isfile(zipapp_path)) # The zipapp itself is a shell script prepended to the zip file. - with open(zipapp_path, "rb") as f: - content = f.read() - self.assertTrue(content.startswith(b"#!/usr/bin/env bash")) + ##with open(zipapp_path, "rb") as f: + ## content = f.read() + ##self.assertTrue(content.startswith(b"#!/usr/bin/env bash")) - output = subprocess.check_output([zipapp_path]).decode("utf-8").strip() + try: + output = subprocess.check_output( + [zipapp_path], stderr=subprocess.STDOUT + ).decode("utf-8").strip() + except subprocess.CalledProcessError as e: + self.fail( + "Zipapp execution failed with return code {}:\n{}".format( + e.returncode, e.output.decode("utf-8") + ) + ) self.assertIn("Hello from zipapp", output) - self.assertIn("absl", output) + self.assertIn("dep:", output) def assertHasPathMatchingSuffix(self, namelist, suffix, msg=None): if not any(name.endswith(suffix) for name in namelist): @@ -39,10 +49,25 @@ def assertZipEntryIsSymlink(self, zip_file, path, msg=None): def _is_bzlmod_enabled(self): return os.environ["BZLMOD_ENABLED"] == "1" + @contextlib.contextmanager + def _open_zipapp(self, path): + # On windows, the main output is the launcher .exe file, and the zip + # file is a sibling file. + try: + zf = zipfile.ZipFile(path, "r") + except zipfile.BadZipFile: + path = path.replace(".exe", ".pyz") + zf = zipfile.ZipFile(path, "r") + yield zf + else: + yield zf + finally: + zf.close() + def test_zipapp_structure(self): zipapp_path = os.environ["TEST_ZIPAPP"] - with zipfile.ZipFile(zipapp_path, "r") as zf: + with self._open_zipapp(zipapp_path) as zf: namelist = zf.namelist() if self._is_bzlmod_enabled(): From 37c31d77a103b24272825d55fb4f6334fc45c573 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 16:17:00 -0800 Subject: [PATCH 10/21] use some_dep, better error reporting --- python/private/zipapp/py_zipapp_rule.bzl | 2 +- python/private/zipapp/zip_main_template.py | 4 +++- tests/py_zipapp/BUILD.bazel | 3 +-- tests/py_zipapp/main.py | 2 ++ tests/py_zipapp/system_python_zipapp_test.py | 2 +- tests/py_zipapp/venv_zipapp_test.py | 19 ++++++++++++------- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/python/private/zipapp/py_zipapp_rule.bzl b/python/private/zipapp/py_zipapp_rule.bzl index 925e257bf7..de0747b4a9 100644 --- a/python/private/zipapp/py_zipapp_rule.bzl +++ b/python/private/zipapp/py_zipapp_rule.bzl @@ -61,7 +61,7 @@ def _create_zipapp_main_py(ctx, py_runtime, py_executable, stage2_bootstrap): venv_python_exe = py_executable.venv_python_exe if venv_python_exe: venv_python_exe_path = runfiles_root_path(ctx, venv_python_exe.short_path) - if not py_executable.venv_python_exe: + else: venv_python_exe_path = "" if py_runtime.interpreter: diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index 69be5cbc8c..73c5ee1d6c 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py @@ -298,7 +298,9 @@ def main(): else: python_program = find_binary(module_space, _PYTHON_BINARY_ACTUAL) if python_program is None: - raise AssertionError("Could not find python binary: " + _PYTHON_BINARY_ACTUAL) + raise AssertionError( + "Could not find python binary: " + _PYTHON_BINARY_ACTUAL + ) ### When a venv is used, the `bin/python3` symlink may need to be created. ##if _PYTHON_BINARY_VENV: diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index f47b85c92f..0aa164c53d 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -4,7 +4,6 @@ load("//python:py_library.bzl", "py_library") load("//python:py_test.bzl", "py_test") load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") # buildifier: disable=bzl-visibility load("//python/zipapp:py_zipapp_binary.bzl", "py_zipapp_binary") -load("//tests/support:support.bzl", "NOT_WINDOWS") # todo: add windows support. Windows support will be a bit odd. # It previously worked by having special logic in the exe launcher @@ -78,8 +77,8 @@ sh_test( "//python:current_py_toolchain", ], env = { - "ZIPAPP": "$(location :system_python_zipapp)", "PYTHON": "$(PYTHON3_ROOTPATH)", + "ZIPAPP": "$(location :system_python_zipapp)", }, toolchains = ["//python:current_py_toolchain"], ##target_compatible_with = NOT_WINDOWS, diff --git a/tests/py_zipapp/main.py b/tests/py_zipapp/main.py index 4321d84f91..8e67ec9fae 100644 --- a/tests/py_zipapp/main.py +++ b/tests/py_zipapp/main.py @@ -5,9 +5,11 @@ def main(): print("Hello from zipapp") try: import some_dep + print(f"dep: {some_dep}") except ImportError: import sys + print("Failed to import `some_dep`", file=sys.stderr) print("sys.path:", file=sys.stderr) for i, x in enumerate(sys.path): diff --git a/tests/py_zipapp/system_python_zipapp_test.py b/tests/py_zipapp/system_python_zipapp_test.py index 1ddb9794ca..96d0cde22c 100644 --- a/tests/py_zipapp/system_python_zipapp_test.py +++ b/tests/py_zipapp/system_python_zipapp_test.py @@ -18,7 +18,7 @@ def test_zipapp_contents(self): output = subprocess.check_output([zipapp_path]).decode("utf-8").strip() self.assertIn("Hello from zipapp", output) - self.assertIn("absl", output) + self.assertIn("dep:", output) if __name__ == "__main__": diff --git a/tests/py_zipapp/venv_zipapp_test.py b/tests/py_zipapp/venv_zipapp_test.py index d976645f6c..77968841d7 100644 --- a/tests/py_zipapp/venv_zipapp_test.py +++ b/tests/py_zipapp/venv_zipapp_test.py @@ -1,8 +1,8 @@ +import contextlib import os import subprocess import unittest import zipfile -import contextlib class PyZipAppTest(unittest.TestCase): @@ -18,14 +18,19 @@ def test_zipapp_runnable(self): ##self.assertTrue(content.startswith(b"#!/usr/bin/env bash")) try: - output = subprocess.check_output( - [zipapp_path], stderr=subprocess.STDOUT - ).decode("utf-8").strip() + output = ( + subprocess.check_output([zipapp_path], stderr=subprocess.STDOUT) + .decode("utf-8") + .strip() + ) except subprocess.CalledProcessError as e: self.fail( - "Zipapp execution failed with return code {}:\n{}".format( - e.returncode, e.output.decode("utf-8") - ) + ( + "exec failed: {}\n" + + "exit code: {}\n" + + "=== stdout/stderr start ===\n" + "{}\n" + "=== stdout/stderr end ===" + ).format(zipapp_path, e.returncode, e.output.decode("utf-8")) ) self.assertIn("Hello from zipapp", output) self.assertIn("dep:", output) From f13c8da6c6dccd4c063f898224e8cd376e81e2fc Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 17:24:22 -0800 Subject: [PATCH 11/21] rename var, look for .zip, not .pyz --- python/private/zipapp/py_zipapp_rule.bzl | 10 +++++----- tests/py_zipapp/venv_zipapp_test.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/private/zipapp/py_zipapp_rule.bzl b/python/private/zipapp/py_zipapp_rule.bzl index de0747b4a9..0967cc33f5 100644 --- a/python/private/zipapp/py_zipapp_rule.bzl +++ b/python/private/zipapp/py_zipapp_rule.bzl @@ -239,22 +239,22 @@ def _py_zipapp_executable_impl(ctx): python_binary_path = python_exe_path, use_zip_file = True, ) - default_output = depset([executable, zip_file]) + default_outputs = [executable, zip_file] else: preamble = _create_shell_bootstrap(ctx, py_runtime, py_executable, stage2_bootstrap) executable = _create_self_executable_zip(ctx, preamble, zip_file) - default_output = depset([executable]) + default_outputs = [executable] else: # Bazel requires executable=True rules to have an executable given, so give # a fake one to satisfy that. - default_output = depset([zip_file]) + default_outputs = [zip_file] executable = ctx.actions.declare_file(ctx.label.name + "-not-executable") ctx.actions.write(executable, "echo 'ERROR: Non executable zip file'; exit 1") return [ DefaultInfo( - files = default_output, - runfiles = ctx.runfiles(files = default_output.to_list()), + files = depset(default_outputs), + runfiles = ctx.runfiles(files = default_outputs), executable = executable, ), ] diff --git a/tests/py_zipapp/venv_zipapp_test.py b/tests/py_zipapp/venv_zipapp_test.py index 77968841d7..9fd18b1cda 100644 --- a/tests/py_zipapp/venv_zipapp_test.py +++ b/tests/py_zipapp/venv_zipapp_test.py @@ -61,7 +61,7 @@ def _open_zipapp(self, path): try: zf = zipfile.ZipFile(path, "r") except zipfile.BadZipFile: - path = path.replace(".exe", ".pyz") + path = path.replace(".exe", ".zip") zf = zipfile.ZipFile(path, "r") yield zf else: From 293b4f107532f5ea3f4fb50b0a23df77e68399b5 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 17:31:01 -0800 Subject: [PATCH 12/21] cleanaup open zipapp logic --- tests/py_zipapp/venv_zipapp_test.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/py_zipapp/venv_zipapp_test.py b/tests/py_zipapp/venv_zipapp_test.py index 9fd18b1cda..9d7bb2fbd8 100644 --- a/tests/py_zipapp/venv_zipapp_test.py +++ b/tests/py_zipapp/venv_zipapp_test.py @@ -58,16 +58,18 @@ def _is_bzlmod_enabled(self): def _open_zipapp(self, path): # On windows, the main output is the launcher .exe file, and the zip # file is a sibling file. + zf = None try: - zf = zipfile.ZipFile(path, "r") - except zipfile.BadZipFile: - path = path.replace(".exe", ".zip") - zf = zipfile.ZipFile(path, "r") - yield zf - else: - yield zf + try: + zf = zipfile.ZipFile(path, "r") + except zipfile.BadZipFile: + path = path.replace(".exe", ".zip") + zf = zipfile.ZipFile(path, "r") + if zf: + yield zf finally: - zf.close() + if zf: + zf.close() def test_zipapp_structure(self): zipapp_path = os.environ["TEST_ZIPAPP"] From 69f13a47685f77aef689cc09c5483cf278c29fda Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 17:34:43 -0800 Subject: [PATCH 13/21] include namelist in error message --- tests/py_zipapp/venv_zipapp_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/py_zipapp/venv_zipapp_test.py b/tests/py_zipapp/venv_zipapp_test.py index 9d7bb2fbd8..7605d2ca1c 100644 --- a/tests/py_zipapp/venv_zipapp_test.py +++ b/tests/py_zipapp/venv_zipapp_test.py @@ -37,7 +37,11 @@ def test_zipapp_runnable(self): def assertHasPathMatchingSuffix(self, namelist, suffix, msg=None): if not any(name.endswith(suffix) for name in namelist): - self.fail(msg or f"No path in zipapp matching suffix '{suffix}'") + self.fail( + (msg or f"No path in zipapp matching suffix '{suffix}'") + + "\nAvailable paths:\n" + + "\n".join(namelist) + ) def assertZipEntryIsSymlink(self, zip_file, path, msg=None): try: From 54d3acff9b39acbc4605f319e1a4a08db24937bc Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 18:47:53 -0800 Subject: [PATCH 14/21] skip venv zipapp test on windows, venv not supported on windows yet --- tests/py_zipapp/BUILD.bazel | 7 +++---- tests/py_zipapp/venv_zipapp_test.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index 0aa164c53d..2c0abf0af1 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -17,14 +17,14 @@ py_binary( "//python/config_settings:venvs_site_packages": "yes", }, main = "main.py", - ##target_compatible_with = NOT_WINDOWS, + target_compatible_with = NOT_WINDOWS, deps = [":some_dep"], ) py_zipapp_binary( name = "venv_zipapp", binary = ":venv_bin", - ##target_compatible_with = NOT_WINDOWS, + target_compatible_with = NOT_WINDOWS, ) py_test( @@ -33,10 +33,9 @@ py_test( data = [":venv_zipapp"], env = { "BZLMOD_ENABLED": str(int(BZLMOD_ENABLED)), - # todo: for windows, the output is (launcher.exe, zipapp.pyz) "TEST_ZIPAPP": "$(location :venv_zipapp)", }, - ##target_compatible_with = NOT_WINDOWS, + target_compatible_with = NOT_WINDOWS, ) py_binary( diff --git a/tests/py_zipapp/venv_zipapp_test.py b/tests/py_zipapp/venv_zipapp_test.py index 7605d2ca1c..5524d36766 100644 --- a/tests/py_zipapp/venv_zipapp_test.py +++ b/tests/py_zipapp/venv_zipapp_test.py @@ -60,13 +60,13 @@ def _is_bzlmod_enabled(self): @contextlib.contextmanager def _open_zipapp(self, path): - # On windows, the main output is the launcher .exe file, and the zip - # file is a sibling file. zf = None try: try: zf = zipfile.ZipFile(path, "r") except zipfile.BadZipFile: + # On windows, the main output is the launcher .exe file, and the + # zip file is a sibling file. path = path.replace(".exe", ".zip") zf = zipfile.ZipFile(path, "r") if zf: From a34048c26ba939c44e3e2d6b17d111256e11c2ce Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 19:21:01 -0800 Subject: [PATCH 15/21] re-add load for not_windows, remove from presubmit --- .bazelci/presubmit.yml | 4 ---- tests/py_zipapp/BUILD.bazel | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index b43be82e5f..92a9b233a8 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -232,7 +232,6 @@ tasks: ### their package path is qualified with the repo name. ##- "@rules_python//examples/wheel/..." - "//tests/py_zipapp:system_python_zipapp_test" - - "//tests/py_zipapp:venv_zipapp_test" build_flags: - "--noenable_bzlmod" - "--enable_workspace" @@ -242,7 +241,6 @@ tasks: - "--" ##- "..." - "//tests/py_zipapp:system_python_zipapp_test" - - "//tests/py_zipapp:venv_zipapp_test" test_flags: - "--noenable_bzlmod" - "--enable_workspace" @@ -263,13 +261,11 @@ tasks: build_targets: - "--" - "//tests/py_zipapp:system_python_zipapp_test" - - "//tests/py_zipapp:venv_zipapp_test" test_flags: - "--test_tag_filters=-integration-test,-fix-windows" test_targets: - "--" - "//tests/py_zipapp:system_python_zipapp_test" - - "//tests/py_zipapp:venv_zipapp_test" rbe_min: <<: *minimum_supported_version <<: *reusable_config diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index 2c0abf0af1..1c004def23 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -4,7 +4,7 @@ load("//python:py_library.bzl", "py_library") load("//python:py_test.bzl", "py_test") load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") # buildifier: disable=bzl-visibility load("//python/zipapp:py_zipapp_binary.bzl", "py_zipapp_binary") - +load("//tests/support:support.bzl", "NOT_WINDOWS") # todo: add windows support. Windows support will be a bit odd. # It previously worked by having special logic in the exe launcher # that knew to look for .zip and running that through python From 669cad28a3fe2f7cc5c7d8345d8a4b4d6085208b Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 19:38:14 -0800 Subject: [PATCH 16/21] cleanup zip main --- python/private/zipapp/zip_main_template.py | 73 ++++++---------------- 1 file changed, 20 insertions(+), 53 deletions(-) diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index 73c5ee1d6c..6f3c9ddd1f 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py @@ -38,7 +38,7 @@ def print_verbose(*args, mapping=None, values=None): - if bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")) or True: + if bool(os.environ.get("RULES_PYTHON_BOOTSTRAP_VERBOSE")): if mapping is not None: for key, value in sorted((mapping or {}).items()): print( @@ -124,16 +124,6 @@ def search_path(name): return None -# todo: remove this function -def find_python_binary(module_space): - """Finds the real Python binary if it's not a normal absolute path.""" - if _PYTHON_BINARY_VENV: - # todo: do join(module_space, PYTHON_BINARY_VENV) instead - return find_binary(module_space, _PYTHON_BINARY_VENV) - else: - return find_binary(module_space, _PYTHON_BINARY_ACTUAL) - - def find_binary(module_space, bin_name): """Finds the real binary if it's not a normal absolute path.""" if not bin_name: @@ -257,8 +247,6 @@ def main(): new_env = {} # The main Python source file. - # The magic string percent-main-percent is replaced with the runfiles-relative - # filename of the main file of the Python binary in BazelPythonSemantics.java. main_rel_path = _STAGE2_BOOTSTRAP if is_windows(): main_rel_path = main_rel_path.replace("/", os.sep) @@ -283,17 +271,25 @@ def main(): if _PYTHON_BINARY_VENV: python_program = os.path.join(module_space, _PYTHON_BINARY_VENV) - ##if not os.path.exists(python_program): - ## symlink_to = find_binary(module_space, _PYTHON_BINARY_ACTUAL) - ## os.makedirs(os.path.dirname(python_program), exist_ok=True) - ## if os.path.lexists(python_program): - ## os.unlink(python_program) - ## try: - ## os.symlink(symlink_to, python_program) - ## except OSError as e: - ## raise Exception( - ## f"Unable to create venv python interpreter symlink: {python_program} -> {symlink_to}" - ## ) from e + # When a venv is used, the `bin/python3` symlink may need to be created. + # This case occurs when "create venv at runtime" or "resolve python at + # runtime" modes are enabled. + if not os.path.lexists(python_program): + # The venv bin/python3 interpreter should always be under runfiles, but + # double check. We don't want to accidentally create symlinks elsewhere + # or unlink outside our tree. + if not python_program.startswith(module_space): + raise AssertionError( + "Program's venv binary not under runfiles: {python_program}" + ) + symlink_to = find_binary(module_space, _PYTHON_BINARY_ACTUAL) + os.makedirs(os.path.dirname(python_program), exist_ok=True) + try: + os.symlink(symlink_to, python_program) + except OSError as e: + raise Exception( + f"Unable to create venv python interpreter symlink: {python_program} -> {symlink_to}" + ) from e else: python_program = find_binary(module_space, _PYTHON_BINARY_ACTUAL) @@ -302,35 +298,6 @@ def main(): "Could not find python binary: " + _PYTHON_BINARY_ACTUAL ) - ### When a venv is used, the `bin/python3` symlink may need to be created. - ##if _PYTHON_BINARY_VENV: - ## # The venv bin/python3 interpreter should always be under runfiles, but - ## # double check. We don't want to accidentally create symlinks elsewhere. - ## if not python_program.startswith(module_space): - ## raise AssertionError( - ## "Program's venv binary not under runfiles: {python_program}" - ## ) - - ## if os.path.isabs(_PYTHON_BINARY_ACTUAL): - ## symlink_to = _PYTHON_BINARY_ACTUAL - ## elif "/" in _PYTHON_BINARY_ACTUAL: - ## symlink_to = os.path.join(module_space, _PYTHON_BINARY_ACTUAL) - ## else: - ## symlink_to = search_path(_PYTHON_BINARY_ACTUAL) - ## if not symlink_to: - ## raise AssertionError( - ## f"Python interpreter to use not found on PATH: {_PYTHON_BINARY_ACTUAL}" - ## ) - - ## # The bin/ directory may not exist if it is empty. - ## os.makedirs(os.path.dirname(python_program), exist_ok=True) - ## try: - ## os.symlink(symlink_to, python_program) - ## except OSError as e: - ## raise Exception( - ## f"Unable to create venv python interpreter symlink: {python_program} -> {symlink_to}" - ## ) from e - # Some older Python versions on macOS (namely Python 3.7) may unintentionally # leave this environment variable set after starting the interpreter, which # causes problems with Python subprocesses correctly locating sys.executable, From 9c1293eaa9febc5ecb284eb4ed9baa315d1069a9 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 19:55:10 -0800 Subject: [PATCH 17/21] factor out windows exe creator function --- python/private/common.bzl | 39 +++++++++++++++++++- python/private/py_executable.bzl | 45 ++--------------------- python/private/toolchain_types.bzl | 1 + python/private/zipapp/py_zipapp_rule.bzl | 47 ++---------------------- 4 files changed, 47 insertions(+), 85 deletions(-) diff --git a/python/private/common.bzl b/python/private/common.bzl index ca201cb10f..4df633a3f7 100644 --- a/python/private/common.bzl +++ b/python/private/common.bzl @@ -18,7 +18,7 @@ load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") load("@rules_python_internal//:rules_python_config.bzl", "config") load("//python/private:py_interpreter_program.bzl", "PyInterpreterProgramInfo") -load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE") +load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE") load(":builders.bzl", "builders") load(":cc_helper.bzl", "cc_helper") load(":py_cc_link_params_info.bzl", "PyCcLinkParamsInfo") @@ -56,6 +56,43 @@ def maybe_builtin_build_python_zip(value, settings = None): return settings +def _find_launcher_maker(ctx): + if config.bazel_9_or_later: + return (ctx.toolchains[LAUNCHER_MAKER_TOOLCHAIN_TYPE].binary, LAUNCHER_MAKER_TOOLCHAIN_TYPE) + return (ctx.executable._windows_launcher_maker, None) + +def create_windows_exe_launcher( + ctx, + *, + output, + python_binary_path, + use_zip_file): + launch_info = ctx.actions.args() + launch_info.use_param_file("%s", use_always = True) + launch_info.set_param_file_format("multiline") + launch_info.add("binary_type=Python") + launch_info.add(ctx.workspace_name, format = "workspace_name=%s") + launch_info.add( + "1" if py_internal.runfiles_enabled(ctx) else "0", + format = "symlink_runfiles_enabled=%s", + ) + launch_info.add(python_binary_path, format = "python_bin_path=%s") + launch_info.add("1" if use_zip_file else "0", format = "use_zip_file=%s") + + launcher = ctx.attr._launcher[DefaultInfo].files_to_run.executable + executable, toolchain = _find_launcher_maker(ctx) + ctx.actions.run( + executable = executable, + arguments = [launcher.path, launch_info, output.path], + inputs = [launcher], + outputs = [output], + mnemonic = "PyBuildLauncher", + progress_message = "Creating launcher for %{label}", + # Needed to inherit PATH when using non-MSVC compilers like MinGW + use_default_shell_env = True, + toolchain = toolchain, + ) + def create_binary_semantics_struct( *, get_native_deps_dso_name, diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 5c3cb77c91..f3e3354a0f 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -45,6 +45,7 @@ load( "create_instrumented_files_info", "create_output_group_info", "create_py_info", + "create_windows_exe_launcher", "csv", "filter_to_py_srcs", "is_bool", @@ -63,7 +64,7 @@ load(":py_internal.bzl", "py_internal") load(":py_runtime_info.bzl", "DEFAULT_STUB_SHEBANG") load(":reexports.bzl", "BuiltinPyInfo", "BuiltinPyRuntimeInfo") load(":rule_builders.bzl", "ruleb") -load(":toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE") +load(":toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE", "TARGET_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE") load(":transition_labels.bzl", "TRANSITION_LABELS") load(":venv_runfiles.bzl", "create_venv_app_files") @@ -71,7 +72,6 @@ _py_builtins = py_internal _EXTERNAL_PATH_PREFIX = "external" _ZIP_RUNFILES_DIRECTORY_NAME = "runfiles" _INIT_PY = "__init__.py" -_LAUNCHER_MAKER_TOOLCHAIN_TYPE = "@bazel_tools//tools/launcher:launcher_maker_toolchain_type" # Non-Google-specific attributes for executables # These attributes are for rules that accept Python sources. @@ -398,7 +398,7 @@ def _create_executable( else: bootstrap_output = executable else: - _create_windows_exe_launcher( + create_windows_exe_launcher( ctx, output = executable, use_zip_file = build_zip_enabled, @@ -789,43 +789,6 @@ def _create_stage1_bootstrap( is_executable = True, ) -def _find_launcher_maker(ctx): - if rp_config.bazel_9_or_later: - return (ctx.toolchains[_LAUNCHER_MAKER_TOOLCHAIN_TYPE].binary, _LAUNCHER_MAKER_TOOLCHAIN_TYPE) - return (ctx.executable._windows_launcher_maker, None) - -def _create_windows_exe_launcher( - ctx, - *, - output, - python_binary_path, - use_zip_file): - launch_info = ctx.actions.args() - launch_info.use_param_file("%s", use_always = True) - launch_info.set_param_file_format("multiline") - launch_info.add("binary_type=Python") - launch_info.add(ctx.workspace_name, format = "workspace_name=%s") - launch_info.add( - "1" if py_internal.runfiles_enabled(ctx) else "0", - format = "symlink_runfiles_enabled=%s", - ) - launch_info.add(python_binary_path, format = "python_bin_path=%s") - launch_info.add("1" if use_zip_file else "0", format = "use_zip_file=%s") - - launcher = ctx.attr._launcher[DefaultInfo].files_to_run.executable - executable, toolchain = _find_launcher_maker(ctx) - ctx.actions.run( - executable = executable, - arguments = [launcher.path, launch_info, output.path], - inputs = [launcher], - outputs = [output], - mnemonic = "PyBuildLauncher", - progress_message = "Creating launcher for %{label}", - # Needed to inherit PATH when using non-MSVC compilers like MinGW - use_default_shell_env = True, - toolchain = toolchain, - ) - def _create_zip_file(ctx, *, output, zip_main, runfiles): """Create a Python zipapp (zip with __main__.py entry point).""" workspace_name = ctx.workspace_name @@ -1848,7 +1811,7 @@ def create_executable_rule_builder(implementation, **kwargs): ruleb.ToolchainType(TOOLCHAIN_TYPE), ruleb.ToolchainType(EXEC_TOOLS_TOOLCHAIN_TYPE, mandatory = False), ruleb.ToolchainType("@bazel_tools//tools/cpp:toolchain_type", mandatory = False), - ] + ([ruleb.ToolchainType(_LAUNCHER_MAKER_TOOLCHAIN_TYPE)] if rp_config.bazel_9_or_later else []), + ] + ([ruleb.ToolchainType(LAUNCHER_MAKER_TOOLCHAIN_TYPE)] if rp_config.bazel_9_or_later else []), cfg = dict( implementation = _transition_executable_impl, inputs = TRANSITION_LABELS + [ diff --git a/python/private/toolchain_types.bzl b/python/private/toolchain_types.bzl index ef81bf3bd4..5b5cce90ee 100644 --- a/python/private/toolchain_types.bzl +++ b/python/private/toolchain_types.bzl @@ -21,3 +21,4 @@ implementation of the toolchain. TARGET_TOOLCHAIN_TYPE = Label("//python:toolchain_type") EXEC_TOOLS_TOOLCHAIN_TYPE = Label("//python:exec_tools_toolchain_type") PY_CC_TOOLCHAIN_TYPE = Label("//python/cc:toolchain_type") +LAUNCHER_MAKER_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/launcher:launcher_maker_toolchain_type") diff --git a/python/private/zipapp/py_zipapp_rule.bzl b/python/private/zipapp/py_zipapp_rule.bzl index 0967cc33f5..a0f0df943f 100644 --- a/python/private/zipapp/py_zipapp_rule.bzl +++ b/python/private/zipapp/py_zipapp_rule.bzl @@ -4,53 +4,14 @@ load("@bazel_skylib//lib:paths.bzl", "paths") load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config") load("//python/private:attributes.bzl", "apply_config_settings_attr") load("//python/private:builders.bzl", "builders") -load("//python/private:common.bzl", "BUILTIN_BUILD_PYTHON_ZIP", "actions_run", "maybe_builtin_build_python_zip", "maybe_create_repo_mapping", "runfiles_root_path", "target_platform_has_any_constraint") +load("//python/private:common.bzl", "BUILTIN_BUILD_PYTHON_ZIP", "actions_run", "create_windows_exe_launcher", "maybe_builtin_build_python_zip", "maybe_create_repo_mapping", "runfiles_root_path", "target_platform_has_any_constraint") load("//python/private:common_labels.bzl", "labels") load("//python/private:py_executable_info.bzl", "PyExecutableInfo") load("//python/private:py_internal.bzl", "py_internal") load("//python/private:py_runtime_info.bzl", "PyRuntimeInfo") -load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE") +load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE") load("//python/private:transition_labels.bzl", "TRANSITION_LABELS") -_LAUNCHER_MAKER_TOOLCHAIN_TYPE = "@bazel_tools//tools/launcher:launcher_maker_toolchain_type" - -def _find_launcher_maker(ctx): - if rp_config.bazel_9_or_later: - return (ctx.toolchains[_LAUNCHER_MAKER_TOOLCHAIN_TYPE].binary, _LAUNCHER_MAKER_TOOLCHAIN_TYPE) - return (ctx.executable._windows_launcher_maker, None) - -def _create_windows_exe_launcher( - ctx, - *, - output, - python_binary_path, - use_zip_file): - launch_info = ctx.actions.args() - launch_info.use_param_file("%s", use_always = True) - launch_info.set_param_file_format("multiline") - launch_info.add("binary_type=Python") - launch_info.add(ctx.workspace_name, format = "workspace_name=%s") - launch_info.add( - "1" if py_internal.runfiles_enabled(ctx) else "0", - format = "symlink_runfiles_enabled=%s", - ) - launch_info.add(python_binary_path, format = "python_bin_path=%s") - launch_info.add("1" if use_zip_file else "0", format = "use_zip_file=%s") - - launcher = ctx.attr._launcher[DefaultInfo].files_to_run.executable - executable, toolchain = _find_launcher_maker(ctx) - ctx.actions.run( - executable = executable, - arguments = [launcher.path, launch_info, output.path], - inputs = [launcher], - outputs = [output], - mnemonic = "PyBuildLauncher", - progress_message = "Creating launcher for %{label}", - # Needed to inherit PATH when using non-MSVC compilers like MinGW - use_default_shell_env = True, - toolchain = toolchain, - ) - def _is_symlink(f): if hasattr(f, "is_symlink"): return str(int(f.is_symlink)) @@ -233,7 +194,7 @@ def _py_zipapp_executable_impl(ctx): else: python_exe_path = py_runtime.interpreter_path - _create_windows_exe_launcher( + create_windows_exe_launcher( ctx, output = executable, python_binary_path = python_exe_path, @@ -362,7 +323,7 @@ Whether the output should be an executable zip file. ), } if not rp_config.bazel_9_or_later else {}) -_TOOLCHAINS = [EXEC_TOOLS_TOOLCHAIN_TYPE] + ([_LAUNCHER_MAKER_TOOLCHAIN_TYPE] if rp_config.bazel_9_or_later else []) +_TOOLCHAINS = [EXEC_TOOLS_TOOLCHAIN_TYPE] + ([LAUNCHER_MAKER_TOOLCHAIN_TYPE] if rp_config.bazel_9_or_later else []) py_zipapp_binary = rule( doc = """ From 999688a4a16130333db96d03d5c66ca6abfb6b0a Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 20:01:46 -0800 Subject: [PATCH 18/21] cleanup --- python/private/common.bzl | 8 ++++++++ python/private/zipapp/zip_main_template.py | 4 +--- tests/py_zipapp/BUILD.bazel | 11 +++-------- ...> system_python_zipapp_external_bootstrap_test.sh} | 0 tests/py_zipapp/system_python_zipapp_test.py | 7 +------ tests/py_zipapp/venv_zipapp_test.py | 8 -------- 6 files changed, 13 insertions(+), 25 deletions(-) rename tests/py_zipapp/{system_python_zipapp_sh_test.sh => system_python_zipapp_external_bootstrap_test.sh} (100%) diff --git a/python/private/common.bzl b/python/private/common.bzl index 4df633a3f7..6d9e0e3c84 100644 --- a/python/private/common.bzl +++ b/python/private/common.bzl @@ -67,6 +67,14 @@ def create_windows_exe_launcher( output, python_binary_path, use_zip_file): + """Creates a Windows exe launcher. + + Args: + ctx: The rule context. + output: The output file for the launcher. + python_binary_path: The path to the Python binary. + use_zip_file: Whether to use a zip file. + """ launch_info = ctx.actions.args() launch_info.use_param_file("%s", use_always = True) launch_info.set_param_file_format("multiline") diff --git a/python/private/zipapp/zip_main_template.py b/python/private/zipapp/zip_main_template.py index 6f3c9ddd1f..2c26842e6c 100644 --- a/python/private/zipapp/zip_main_template.py +++ b/python/private/zipapp/zip_main_template.py @@ -223,12 +223,10 @@ def execute_file( ret_code = subprocess.call(subprocess_argv, env=env, cwd=workspace) sys.exit(ret_code) finally: - pass # NOTE: dirname() is called because create_module_space() creates a # sub-directory within a temporary directory, and we want to remove the # whole temporary directory. - ## todo: re-enable this - ##shutil.rmtree(os.path.dirname(module_space), True) + shutil.rmtree(os.path.dirname(module_space), True) def main(): diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index 1c004def23..2b33c5e34d 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -46,15 +46,12 @@ py_binary( "//python/config_settings:venvs_site_packages": "no", }, main = "main.py", - # TODO: #2586 - Add windows support - ##target_compatible_with = NOT_WINDOWS, deps = [":some_dep"], ) py_zipapp_binary( name = "system_python_zipapp", binary = ":system_python_bin", - ##target_compatible_with = NOT_WINDOWS, ) py_test( @@ -64,13 +61,11 @@ py_test( env = { "TEST_ZIPAPP": "$(location :system_python_zipapp)", }, - ##target_compatible_with = NOT_WINDOWS, ) -# todo: rename to system_python_zipapp_external_bootstrap_test sh_test( - name = "system_python_zipapp_sh_test", - srcs = ["system_python_zipapp_sh_test.sh"], + name = "system_python_zipapp_external_bootstrap_test", + srcs = ["system_python_zipapp_external_bootstrap_test.sh"], data = [ ":system_python_zipapp", "//python:current_py_toolchain", @@ -80,12 +75,12 @@ sh_test( "ZIPAPP": "$(location :system_python_zipapp)", }, toolchains = ["//python:current_py_toolchain"], - ##target_compatible_with = NOT_WINDOWS, ) # todo: add venv_site_packages settings to this py_library( name = "some_dep", srcs = ["some_dep.py"], + experimental_venvs_site_packages = "//python/config_settings:venvs_site_packages", imports = ["."], ) diff --git a/tests/py_zipapp/system_python_zipapp_sh_test.sh b/tests/py_zipapp/system_python_zipapp_external_bootstrap_test.sh similarity index 100% rename from tests/py_zipapp/system_python_zipapp_sh_test.sh rename to tests/py_zipapp/system_python_zipapp_external_bootstrap_test.sh diff --git a/tests/py_zipapp/system_python_zipapp_test.py b/tests/py_zipapp/system_python_zipapp_test.py index 96d0cde22c..ec0f837135 100644 --- a/tests/py_zipapp/system_python_zipapp_test.py +++ b/tests/py_zipapp/system_python_zipapp_test.py @@ -5,17 +5,12 @@ class SystemPythonZipAppTest(unittest.TestCase): - def test_zipapp_contents(self): + def test_zipapp_runnable(self): zipapp_path = os.environ["TEST_ZIPAPP"] self.assertTrue(os.path.exists(zipapp_path)) self.assertTrue(os.path.isfile(zipapp_path)) - ### The zipapp itself is a shell script prepended to the zip file. - ##with open(zipapp_path, "rb") as f: - ## content = f.read() - ##self.assertTrue(content.startswith(b"#!/usr/bin/env bash")) - output = subprocess.check_output([zipapp_path]).decode("utf-8").strip() self.assertIn("Hello from zipapp", output) self.assertIn("dep:", output) diff --git a/tests/py_zipapp/venv_zipapp_test.py b/tests/py_zipapp/venv_zipapp_test.py index 5524d36766..9bb917156f 100644 --- a/tests/py_zipapp/venv_zipapp_test.py +++ b/tests/py_zipapp/venv_zipapp_test.py @@ -9,14 +9,6 @@ class PyZipAppTest(unittest.TestCase): def test_zipapp_runnable(self): zipapp_path = os.environ["TEST_ZIPAPP"] - ##self.assertTrue(os.path.exists(zipapp_path)) - ##self.assertTrue(os.path.isfile(zipapp_path)) - - # The zipapp itself is a shell script prepended to the zip file. - ##with open(zipapp_path, "rb") as f: - ## content = f.read() - ##self.assertTrue(content.startswith(b"#!/usr/bin/env bash")) - try: output = ( subprocess.check_output([zipapp_path], stderr=subprocess.STDOUT) From 4c80945118e2f6ec9977ddbb28accd7630e15b38 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 20:20:21 -0800 Subject: [PATCH 19/21] fix imports path entry --- tests/py_zipapp/BUILD.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index 2b33c5e34d..bc1b823a80 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -82,5 +82,5 @@ py_library( name = "some_dep", srcs = ["some_dep.py"], experimental_venvs_site_packages = "//python/config_settings:venvs_site_packages", - imports = ["."], + imports = ["tests/py_zipapp"], ) From f25d7588cb2542351c7fd22da396630d6d7eb5b0 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 20:38:39 -0800 Subject: [PATCH 20/21] make some_dep work with sys and venv --- tests/py_zipapp/BUILD.bazel | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/py_zipapp/BUILD.bazel b/tests/py_zipapp/BUILD.bazel index bc1b823a80..da42567656 100644 --- a/tests/py_zipapp/BUILD.bazel +++ b/tests/py_zipapp/BUILD.bazel @@ -77,10 +77,19 @@ sh_test( toolchains = ["//python:current_py_toolchain"], ) -# todo: add venv_site_packages settings to this py_library( name = "some_dep", srcs = ["some_dep.py"], experimental_venvs_site_packages = "//python/config_settings:venvs_site_packages", - imports = ["tests/py_zipapp"], + imports = select({ + ":is_venvs_site_packages_enabled": ["tests/py_zipapp"], + "//conditions:default": ["."], + }), +) + +config_setting( + name = "is_venvs_site_packages_enabled", + flag_values = { + "//python/config_settings:venvs_site_packages": "yes", + }, ) From 70ccdc89510f23ae2d7c21ae57e65aeb1b206eaf Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Tue, 3 Feb 2026 22:27:23 -0800 Subject: [PATCH 21/21] format --- python/private/py_executable.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index f3e3354a0f..9f1113d9f6 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -64,7 +64,7 @@ load(":py_internal.bzl", "py_internal") load(":py_runtime_info.bzl", "DEFAULT_STUB_SHEBANG") load(":reexports.bzl", "BuiltinPyInfo", "BuiltinPyRuntimeInfo") load(":rule_builders.bzl", "ruleb") -load(":toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE", "TARGET_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE") +load(":toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE") load(":transition_labels.bzl", "TRANSITION_LABELS") load(":venv_runfiles.bzl", "create_venv_app_files")