Skip to content

Commit 12c96bc

Browse files
committed
perf: improve analysis performance by 95% for py_binary and py_test rules
1 parent 3c3e0be commit 12c96bc

File tree

3 files changed

+322
-57
lines changed

3 files changed

+322
-57
lines changed

python/private/BUILD.bazel

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
1616
load("@bazel_skylib//rules:common_settings.bzl", "bool_setting")
17+
load("@rules_cc//cc:defs.bzl", "cc_binary")
1718
load("//python:py_binary.bzl", "py_binary")
1819
load("//python:py_library.bzl", "py_library")
1920
load(":print_toolchain_checksums.bzl", "print_toolchains_checksums")
@@ -829,6 +830,19 @@ py_binary(
829830
],
830831
)
831832

833+
# Used for py_executable rule
834+
# C++ wrapper for zipper to process Python zip manifests
835+
cc_binary(
836+
name = "py_executable_zip_gen",
837+
srcs = ["py_executable_zip_gen.cc"],
838+
copts = select({
839+
"@rules_cc//cc/compiler:msvc-cl": ["/std:c++17"],
840+
"//conditions:default": ["-std=c++17"],
841+
}),
842+
data = ["@bazel_tools//tools/zip:zipper"],
843+
deps = ["@bazel_tools//tools/cpp/runfiles"],
844+
)
845+
832846
py_binary(
833847
name = "py_wheel_dist",
834848
srcs = ["py_wheel_dist.py"],

python/private/py_executable.bzl

Lines changed: 22 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ load(":transition_labels.bzl", "TRANSITION_LABELS")
6767
load(":venv_runfiles.bzl", "create_venv_app_files")
6868

6969
_py_builtins = py_internal
70-
_EXTERNAL_PATH_PREFIX = "external"
71-
_ZIP_RUNFILES_DIRECTORY_NAME = "runfiles"
7270

7371
# Non-Google-specific attributes for executables
7472
# These attributes are for rules that accept Python sources.
@@ -236,7 +234,7 @@ accepting arbitrary Python versions.
236234
"_zipper": lambda: attrb.Label(
237235
cfg = "exec",
238236
executable = True,
239-
default = "@bazel_tools//tools/zip:zipper",
237+
default = ":py_executable_zip_gen",
240238
),
241239
},
242240
)
@@ -380,9 +378,8 @@ def _create_executable(
380378
_create_zip_file(
381379
ctx,
382380
output = zip_file,
383-
original_nonzip_executable = executable,
384381
zip_main = zip_main,
385-
runfiles = runfiles_details.default_runfiles.merge(extra_runfiles),
382+
runfiles = runfiles_details.runfiles_without_exe.merge(extra_runfiles),
386383
)
387384

388385
extra_files_to_build = []
@@ -803,35 +800,16 @@ def _create_windows_exe_launcher(
803800
use_default_shell_env = True,
804801
)
805802

806-
def _create_zip_file(ctx, *, output, original_nonzip_executable, zip_main, runfiles):
803+
def _create_zip_file(ctx, *, output, zip_main, runfiles):
807804
"""Create a Python zipapp (zip with __main__.py entry point)."""
808-
workspace_name = ctx.workspace_name
809805
legacy_external_runfiles = _py_builtins.get_legacy_external_runfiles(ctx)
810806

811-
manifest = ctx.actions.args()
812-
manifest.use_param_file("@%s", use_always = True)
813-
manifest.set_param_file_format("multiline")
814-
815-
manifest.add("__main__.py={}".format(zip_main.path))
816-
manifest.add("__init__.py=")
817-
manifest.add(
818-
"{}=".format(
819-
_get_zip_runfiles_path("__init__.py", workspace_name, legacy_external_runfiles),
820-
),
821-
)
822-
for path in runfiles.empty_filenames.to_list():
823-
manifest.add("{}=".format(_get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles)))
824-
825-
def map_zip_runfiles(file):
826-
if file != original_nonzip_executable and file != output:
827-
return "{}={}".format(
828-
_get_zip_runfiles_path(file.short_path, workspace_name, legacy_external_runfiles),
829-
file.path,
830-
)
831-
else:
832-
return None
833-
834-
manifest.add_all(runfiles.files, map_each = map_zip_runfiles, allow_closure = True)
807+
args = ctx.actions.args()
808+
args.add("--output", output.path)
809+
args.add("--workspace-name", ctx.workspace_name)
810+
args.add("--main-file", zip_main.path)
811+
if legacy_external_runfiles:
812+
args.add("--legacy-external-runfiles")
835813

836814
inputs = [zip_main]
837815
if _py_builtins.is_bzlmod_enabled(ctx):
@@ -844,43 +822,30 @@ def _create_zip_file(ctx, *, output, original_nonzip_executable, zip_main, runfi
844822
runfiles = runfiles,
845823
output = zip_repo_mapping_manifest,
846824
)
847-
manifest.add("{}/_repo_mapping={}".format(
848-
_ZIP_RUNFILES_DIRECTORY_NAME,
849-
zip_repo_mapping_manifest.path,
850-
))
825+
args.add("--repo-mapping-manifest", zip_repo_mapping_manifest.path)
851826
inputs.append(zip_repo_mapping_manifest)
852827

853-
for artifact in runfiles.files.to_list():
854-
# Don't include the original executable because it isn't used by the
855-
# zip file, so no need to build it for the action.
856-
# Don't include the zipfile itself because it's an output.
857-
if artifact != original_nonzip_executable and artifact != output:
858-
inputs.append(artifact)
859-
860-
zip_cli_args = ctx.actions.args()
861-
zip_cli_args.add("cC")
862-
zip_cli_args.add(output)
828+
manifest = ctx.actions.args()
829+
manifest.use_param_file("%s", use_always = True)
830+
manifest.set_param_file_format("multiline")
831+
manifest.add_all(runfiles.empty_filenames, map_each = _get_zip_empty_path_arg)
832+
manifest.add_all(runfiles.files, map_each = _get_zip_path_arg)
863833

864834
ctx.actions.run(
865835
executable = ctx.executable._zipper,
866-
arguments = [zip_cli_args, manifest],
867-
inputs = depset(inputs),
836+
arguments = [args, manifest],
837+
inputs = depset(inputs, transitive = [runfiles.files]),
868838
outputs = [output],
869839
use_default_shell_env = True,
870840
mnemonic = "PythonZipper",
871841
progress_message = "Building Python zip: %{label}",
872842
)
873843

874-
def _get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles):
875-
if legacy_external_runfiles and path.startswith(_EXTERNAL_PATH_PREFIX):
876-
zip_runfiles_path = paths.relativize(path, _EXTERNAL_PATH_PREFIX)
877-
else:
878-
# NOTE: External runfiles (artifacts in other repos) will have a leading
879-
# path component of "../" so that they refer outside the main workspace
880-
# directory and into the runfiles root. By normalizing, we simplify e.g.
881-
# "workspace/../foo/bar" to simply "foo/bar".
882-
zip_runfiles_path = paths.normalize("{}/{}".format(workspace_name, path))
883-
return "{}/{}".format(_ZIP_RUNFILES_DIRECTORY_NAME, zip_runfiles_path)
844+
def _get_zip_empty_path_arg(file):
845+
return "{}=".format(file)
846+
847+
def _get_zip_path_arg(file):
848+
return "{}={}".format(file.short_path, file.path)
884849

885850
def _create_executable_zip_file(
886851
ctx,

0 commit comments

Comments
 (0)