Skip to content

Commit 149a840

Browse files
committed
basic impl
1 parent 5fe50fb commit 149a840

27 files changed

Lines changed: 1432 additions & 65 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ END_UNRELEASED_TEMPLATE
7676
* (binaries/tests) Build information is now included in binaries and tests.
7777
Use the `bazel_binary_info` module to access it. The {flag}`--stamp` flag will
7878
add {flag}`--workspace_status` information.
79+
* (zipapp) {obj}`py_zipapp_binary` and {obj}`py_zipapp_test` rules added. These
80+
will replace `--build_python_zip` and the zip output group of
81+
`py_binary/py_test`. The zipapp rules support more functionality, correctness,
82+
and have better build performance.
7983

8084
{#v1-8-0}
8185
## [1.8.0] - 2025-12-19

docs/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,13 @@ sphinx_stardocs(
125125
"//python/private/pypi:pkg_aliases_bzl",
126126
"//python/private/pypi:whl_config_setting_bzl",
127127
"//python/private/pypi:whl_library_bzl",
128+
"//python/private/zipapp:py_zipapp_rule_bzl",
128129
"//python/uv:lock_bzl",
129130
"//python/uv:uv_bzl",
130131
"//python/uv:uv_toolchain_bzl",
131132
"//python/uv:uv_toolchain_info_bzl",
133+
"//python/zipapp:py_zipapp_binary_bzl",
134+
"//python/zipapp:py_zipapp_test_bzl",
132135
] + ([
133136
# This depends on @pythons_hub, which is only created under bzlmod,
134137
"//python/extensions:pip_bzl",

python/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ filegroup(
4747
"//python/runfiles:distribution",
4848
"//python/runtime_env_toolchains:distribution",
4949
"//python/uv:distribution",
50+
"//python/zipapp:distribution",
5051
],
5152
visibility = ["//:__pkg__"],
5253
)

python/private/BUILD.bazel

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ filegroup(
3838
"//python/private/cc:distribution",
3939
"//python/private/pypi:distribution",
4040
"//python/private/whl_filegroup:distribution",
41+
"//python/private/zipapp:distribution",
4142
"//tools/build_defs/python/private:distribution",
4243
],
4344
visibility = ["//python:__pkg__"],
@@ -366,7 +367,7 @@ bzl_library(
366367
name = "py_cc_toolchain_rule_bzl",
367368
srcs = ["py_cc_toolchain_rule.bzl"],
368369
deps = [
369-
":common_labels.bzl",
370+
":common_labels_bzl",
370371
":py_cc_toolchain_info_bzl",
371372
":rules_cc_srcs_bzl",
372373
":sentinel_bzl",
@@ -460,7 +461,10 @@ bzl_library(
460461
bzl_library(
461462
name = "py_interpreter_program_bzl",
462463
srcs = ["py_interpreter_program.bzl"],
463-
deps = ["@bazel_skylib//rules:common_settings"],
464+
deps = [
465+
":sentinel_bzl",
466+
"@bazel_skylib//rules:common_settings",
467+
],
464468
)
465469

466470
bzl_library(
@@ -740,8 +744,8 @@ bzl_library(
740744
srcs = ["venv_runfiles.bzl"],
741745
deps = [
742746
":common_bzl",
743-
":py_info.bzl",
744-
":py_internal.bzl",
747+
":py_info_bzl",
748+
":py_internal_bzl",
745749
"@bazel_skylib//lib:paths",
746750
],
747751
)
@@ -786,14 +790,6 @@ filegroup(
786790
visibility = ["//visibility:public"],
787791
)
788792

789-
filegroup(
790-
name = "zip_main_template",
791-
srcs = ["zip_main_template.py"],
792-
# Not actually public. Only public because it's an implicit dependency of
793-
# py_runtime.
794-
visibility = ["//visibility:public"],
795-
)
796-
797793
filegroup(
798794
name = "site_init_template",
799795
srcs = ["site_init_template.py"],

python/private/common.bzl

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
load("@bazel_skylib//lib:paths.bzl", "paths")
1717
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
1818
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
19+
load("//python/private:py_interpreter_program.bzl", "PyInterpreterProgramInfo")
20+
load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE")
1921
load(":cc_helper.bzl", "cc_helper")
2022
load(":py_cc_link_params_info.bzl", "PyCcLinkParamsInfo")
2123
load(":py_info.bzl", "PyInfo", "PyInfoBuilder")
@@ -484,3 +486,120 @@ def collect_deps(ctx, extra_deps = []):
484486
deps = list(deps)
485487
deps.extend(extra_deps)
486488
return deps
489+
490+
def maybe_create_repo_mapping(ctx, *, runfiles):
491+
"""Creates a repo mapping manifest if bzlmod is enabled.
492+
493+
There isn't a way to reference the repo mapping Bazel implicitly
494+
creates, so we have to manually create it ourselves.
495+
496+
Args:
497+
ctx: rule ctx.
498+
runfiles: runfiles object to generate mapping for.
499+
500+
Returns:
501+
File object if the repo mapping manifest was created, None otherwise.
502+
"""
503+
if not py_internal.is_bzlmod_enabled(ctx):
504+
return None
505+
506+
# We have to add `.custom` because `{name}.repo_mapping` is used by Bazel
507+
# internally.
508+
repo_mapping_manifest = ctx.actions.declare_file(ctx.label.name + ".custom.repo_mapping")
509+
py_internal.create_repo_mapping_manifest(
510+
ctx = ctx,
511+
runfiles = runfiles,
512+
output = repo_mapping_manifest,
513+
)
514+
return repo_mapping_manifest
515+
516+
def actions_run(
517+
ctx,
518+
*,
519+
executable,
520+
toolchain = None,
521+
**kwargs):
522+
"""Runs a tool as an action, supporting py_interpreter_program targets.
523+
524+
This is wrapper around `ctx.actions.run()` that sets some useful defaults,
525+
supports handling `py_interpreter_program` targets, and some other features
526+
to let the target being run influence the action invocation.
527+
528+
Args:
529+
ctx: The rule context. The rule must have the
530+
`//python:exec_tools_toolchain_type` toolchain available.
531+
executable: The executable to run. This can be a target that provides
532+
`PyInterpreterProgramInfo` or a regular executable target. If it
533+
provides `testing.ExecutionInfo`, the requirements will be added to
534+
the execution requirements.
535+
toolchain: The toolchain type to use. Must be None or
536+
`//python:exec_tools_toolchain_type`.
537+
**kwargs: Additional arguments to pass to `ctx.actions.run()`.
538+
`mnemonic` and `progress_message` are required.
539+
"""
540+
mnemonic = kwargs.pop("mnemonic", None)
541+
if not mnemonic:
542+
fail("actions_run: missing required argument 'mnemonic'")
543+
544+
progress_message = kwargs.pop("progress_message", None)
545+
if not progress_message:
546+
fail("actions_run: missing required argument 'progress_message'")
547+
548+
tools = kwargs.pop("tools", None)
549+
tools = list(tools) if tools else []
550+
arguments = kwargs.pop("arguments", [])
551+
552+
action_arguments = []
553+
action_env = {
554+
"PYTHONHASHSEED": "0", # Helps avoid non-deterministic behavior
555+
"PYTHONNOUSERSITE": "1", # Helps avoid non-deterministic behavior
556+
"PYTHONSAFEPATH": "1", # Helps avoid incorrect import issues
557+
}
558+
default_info = executable[DefaultInfo]
559+
if PyInterpreterProgramInfo in executable:
560+
if toolchain and toolchain != EXEC_TOOLS_TOOLCHAIN_TYPE:
561+
fail(("Action {}: tool {} provides PyInterpreterProgramInfo, which " +
562+
"requires the `toolchain` arg be " +
563+
"None or {}, got: {}").format(
564+
mnemonic,
565+
executable,
566+
EXEC_TOOLS_TOOLCHAIN_TYPE,
567+
toolchain,
568+
))
569+
exec_tools = ctx.toolchains[EXEC_TOOLS_TOOLCHAIN_TYPE].exec_tools
570+
action_exe = exec_tools.exec_interpreter[DefaultInfo].files_to_run
571+
572+
program_info = executable[PyInterpreterProgramInfo]
573+
574+
interpreter_args = ctx.actions.args()
575+
interpreter_args.add_all(program_info.interpreter_args)
576+
interpreter_args.add(default_info.files_to_run.executable)
577+
action_arguments.append(interpreter_args)
578+
579+
action_env.update(program_info.env)
580+
tools.append(default_info.files_to_run)
581+
toolchain = EXEC_TOOLS_TOOLCHAIN_TYPE
582+
else:
583+
action_exe = executable[DefaultInfo].files_to_run
584+
585+
execution_requirements = {}
586+
if testing.ExecutionInfo in executable:
587+
execution_requirements.update(executable[testing.ExecutionInfo].requirements)
588+
589+
# Give precedence to caller's execution requirements.
590+
execution_requirements.update(kwargs.pop("execution_requirements", None) or {})
591+
592+
# Give precedence to caller's env.
593+
action_env.update(kwargs.pop("env", None) or {})
594+
action_arguments.extend(arguments)
595+
ctx.actions.run(
596+
executable = action_exe,
597+
arguments = action_arguments,
598+
tools = tools,
599+
env = action_env,
600+
execution_requirements = execution_requirements,
601+
toolchain = toolchain,
602+
mnemonic = mnemonic,
603+
progress_message = progress_message,
604+
**kwargs
605+
)

0 commit comments

Comments
 (0)