Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
674e734
basic windows impl
rickeylev Feb 2, 2026
cbf5209
only run zipapp test on windows
rickeylev Feb 2, 2026
cfff697
remove shebang check for windows
rickeylev Feb 2, 2026
49ce65c
remove venv-symlink recreation logic
rickeylev Feb 3, 2026
ef18175
add debug to zip bootstrap
rickeylev Feb 3, 2026
bea94c2
use %python_binary% placeholder
rickeylev Feb 3, 2026
6c4036d
add dependency import without pip
rickeylev Feb 3, 2026
97116dd
make windows workspace just run zipapp tests
rickeylev Feb 3, 2026
9b0bb22
make venv test compatible with windows
rickeylev Feb 3, 2026
37c31d7
use some_dep, better error reporting
rickeylev Feb 4, 2026
f13c8da
rename var, look for .zip, not .pyz
rickeylev Feb 4, 2026
293b4f1
cleanaup open zipapp logic
rickeylev Feb 4, 2026
69f13a4
include namelist in error message
rickeylev Feb 4, 2026
54d3acf
skip venv zipapp test on windows, venv not supported on windows yet
rickeylev Feb 4, 2026
a34048c
re-add load for not_windows, remove from presubmit
rickeylev Feb 4, 2026
669cad2
cleanup zip main
rickeylev Feb 4, 2026
9c1293e
factor out windows exe creator function
rickeylev Feb 4, 2026
fb68364
Merge branch 'main' of https://github.com/bazel-contrib/rules_python …
rickeylev Feb 4, 2026
999688a
cleanup
rickeylev Feb 4, 2026
4c80945
fix imports path entry
rickeylev Feb 4, 2026
f25d758
make some_dep work with sys and venv
rickeylev Feb 4, 2026
70ccdc8
format
rickeylev Feb 4, 2026
03ec84f
cleanup
rickeylev Feb 5, 2026
47a347a
make external boot test work for windows
rickeylev Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion python/private/common.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -56,6 +56,51 @@ 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):
"""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")
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,
Expand Down
45 changes: 4 additions & 41 deletions python/private/py_executable.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -63,15 +64,14 @@ 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", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE")
load(":transition_labels.bzl", "TRANSITION_LABELS")
load(":venv_runfiles.bzl", "create_venv_app_files")

_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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 + [
Expand Down
1 change: 1 addition & 0 deletions python/private/toolchain_types.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
71 changes: 54 additions & 17 deletions python/private/zipapp/py_zipapp_rule.bzl
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""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", "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")

def _is_symlink(f):
Expand All @@ -18,13 +19,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)
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)
else:
python_exe_path = py_runtime.interpreter_path
venv_python_exe_path = ""

if py_runtime.interpreter:
python_binary_actual_path = runfiles_root_path(ctx, py_runtime.interpreter.short_path)
Expand All @@ -36,7 +35,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_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,
Expand Down Expand Up @@ -184,20 +183,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_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_outputs = [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_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 = depset([default_output]),
runfiles = ctx.runfiles(files = [default_output]),
files = depset(default_outputs),
runfiles = ctx.runfiles(files = default_outputs),
executable = executable,
),
]
Expand Down Expand Up @@ -277,6 +295,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,
Expand All @@ -285,8 +315,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 = """
Expand Down
Loading