From e5593aab6f246c646479f4d224fce60269e3f561 Mon Sep 17 00:00:00 2001 From: katre Date: Mon, 25 May 2026 10:08:58 -0400 Subject: [PATCH 1/5] Extract proguard logic to new rules. - Timestamp clearing now uses a small hermetic python script instead of relying on non-hermetic `unzip` and `zip`. - Also split proguard and timestamp update actions for better caching. - Moved fastutil proguard spec into third_party where it is used. RELNOTES: None. --- third_party/BUILD | 48 ++++------------- {tools => third_party}/fastutil.proguard | 0 tools/BUILD | 2 - tools/build_defs/BUILD | 1 + tools/build_defs/proguard/BUILD | 33 ++++++++++++ tools/build_defs/proguard/proguard.bzl | 62 ++++++++++++++++++++++ tools/build_defs/proguard/zip_timestamp.py | 58 ++++++++++++++++++++ 7 files changed, 163 insertions(+), 41 deletions(-) rename {tools => third_party}/fastutil.proguard (100%) create mode 100644 tools/build_defs/proguard/BUILD create mode 100644 tools/build_defs/proguard/proguard.bzl create mode 100644 tools/build_defs/proguard/zip_timestamp.py diff --git a/third_party/BUILD b/third_party/BUILD index 575851fc725223..721184d004a090 100644 --- a/third_party/BUILD +++ b/third_party/BUILD @@ -1,7 +1,8 @@ -load("@rules_java//java:defs.bzl", "java_binary", "java_import", "java_library", "java_plugin") +load("@rules_java//java:defs.bzl", "java_import", "java_library", "java_plugin") load("@rules_license//rules:license.bzl", "license") load("//src/tools/bzlmod:utils.bzl", "get_repo_root") load("//tools/distributions:distribution_rules.bzl", "distrib_jar_filegroup", "distrib_java_import") +load("//tools/build_defs/proguard:proguard.bzl", "proguard_jar") package(default_visibility = ["//visibility:public"]) @@ -317,50 +318,19 @@ alias( # When using new classes from this dependency, make sure to update fastutil.proguard. java_import( name = "fastutil", - jars = [":fastutil_stripped_jar"], + jars = [":fastutil-stripped.jar"], ) -genrule( - name = "fastutil_stripped_jar", +proguard_jar( + name = "fastutil_proguard", srcs = [ "@maven//:it_unimi_dsi_fastutil_file", + ], + out = "fastutil-stripped.jar", + proguard_spec = "fastutil.proguard", + deps = [ "@rules_java//toolchains:platformclasspath_nostrip", ], - outs = ["fastutil-stripped.jar"], - # ProGuard output is silenced below because it prints - # ... - # Caused by: java.net.UnknownHostException: bk-docker-3gmr: Temporary failure in name resolution - # ... - # when running in the Bazel sandbox, which throws off bazel_determinism_test. - cmd = """ - $(location :proguard_bin) \ - -injars $(execpath @maven//:it_unimi_dsi_fastutil_file) \ - -outjars $@ \ - -libraryjars $(execpath @rules_java//toolchains:platformclasspath_nostrip) \ - @$(location //tools:fastutil.proguard) > /dev/null - # Null out the file times stored in the jar to make the output reproducible. - TMPDIR=$$(mktemp -d) - trap 'rm -rf $$TMPDIR' EXIT - unzip -q $@ -d $$TMPDIR - rm $@ - find $$TMPDIR -type f -print0 | xargs -0 touch -t 198001010000.00 - OUTPUT="$$(pwd)/$@" - (cd $$TMPDIR && find . -type f | LC_ALL=C sort | zip -qDX0r@ "$$OUTPUT") - """, - tools = [ - ":proguard_bin", - "//tools:fastutil.proguard", - ], -) - -java_binary( - name = "proguard_bin", - jvm_flags = [ - # Prevent ProGuard from calling out to the internet through log4j. - "-Dlog4j.rootLogger=OFF", - ], - main_class = "proguard.ProGuard", - runtime_deps = ["@maven//:com_guardsquare_proguard_base"], ) java_library( diff --git a/tools/fastutil.proguard b/third_party/fastutil.proguard similarity index 100% rename from tools/fastutil.proguard rename to third_party/fastutil.proguard diff --git a/tools/BUILD b/tools/BUILD index 3dd3b72439942c..b1aa4c23e8fde6 100644 --- a/tools/BUILD +++ b/tools/BUILD @@ -2,8 +2,6 @@ load("@rules_shell//shell:sh_binary.bzl", "sh_binary") package(default_visibility = ["//visibility:public"]) -exports_files(["fastutil.proguard"]) - filegroup( name = "srcs", srcs = glob(["**"]) + [ diff --git a/tools/build_defs/BUILD b/tools/build_defs/BUILD index 7feba2c110805b..1368ea471e46ba 100644 --- a/tools/build_defs/BUILD +++ b/tools/build_defs/BUILD @@ -10,6 +10,7 @@ filegroup( "//tools/build_defs/hash:srcs", "//tools/build_defs/inspect:srcs", "//tools/build_defs/pkg:srcs", + "//tools/build_defs/proguard:srcs", "//tools/build_defs/repo:srcs", ], visibility = ["//tools:__pkg__"], diff --git a/tools/build_defs/proguard/BUILD b/tools/build_defs/proguard/BUILD new file mode 100644 index 00000000000000..5732a5126b0b32 --- /dev/null +++ b/tools/build_defs/proguard/BUILD @@ -0,0 +1,33 @@ +load("@rules_java//java:defs.bzl", "java_binary") +load("@rules_python//python:py_binary.bzl", "py_binary") + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//tools:__subpackages__"], +) + +exports_files( + ["proguard.bzl"], + visibility = ["//visibility:public"], +) + +# Utility used in proguard_jar to fix timestamps. +py_binary( + name = "zip_timestamp_private", + srcs = ["zip_timestamp.py"], + main = "zip_timestamp.py", + visibility = ["//visibility:private"], +) + +java_binary( + name = "proguard_bin", + jvm_flags = [ + # Prevent ProGuard from calling out to the internet through log4j. + "-Dlog4j.rootLogger=OFF", + ], + main_class = "proguard.ProGuard", + runtime_deps = ["@maven//:com_guardsquare_proguard_base"], +) diff --git a/tools/build_defs/proguard/proguard.bzl b/tools/build_defs/proguard/proguard.bzl new file mode 100644 index 00000000000000..4072b187d24251 --- /dev/null +++ b/tools/build_defs/proguard/proguard.bzl @@ -0,0 +1,62 @@ +"""Apply proguard rules to a JAR file.""" + +def _run_proguard(ctx, output): + inputs = ctx.files.srcs + ctx.files.deps + [ctx.file.proguard_spec] + + ctx.actions.run( + inputs = inputs, + mnemonic = "ProguardJar", + outputs = [output], + executable = ctx.executable._proguard_bin, + arguments = [ + "-injars", + ",".join([src.path for src in ctx.files.srcs]), + "-libraryjars", + ",".join([dep.path for dep in ctx.files.deps]), + "-outjars", + output.path, + "@" + ctx.file.proguard_spec.path, + ], + ) + +def _fix_timestamps(ctx, input, output): + ctx.actions.run( + inputs = [input], + mnemonic = "ZipTimestamp", + outputs = [output], + executable = ctx.executable._zip_timestamp, + arguments = [ + "--input=" + input.path, + "--output=" + output.path, + "--timestamp=1980-01-01 00:00:00", + ], + ) + +def _proguard_jar_impl(ctx): + stripped = ctx.actions.declare_file("%s.stripped.jar" % ctx.label.name) + _run_proguard(ctx, stripped) + + output = ctx.outputs.out + _fix_timestamps(ctx, stripped, output) + + return DefaultInfo(files = depset([output])) + +proguard_jar = rule( + implementation = _proguard_jar_impl, + attrs = { + "srcs": attr.label_list(), + "deps": attr.label_list(), + "proguard_spec": attr.label(allow_single_file = True), + "out": attr.output(), + "_proguard_bin": attr.label( + cfg = "exec", + default = ":proguard_bin", + executable = True, + ), + "_zip_timestamp": attr.label( + cfg = "exec", + default = ":zip_timestamp_private", + executable = True, + ), + }, +) diff --git a/tools/build_defs/proguard/zip_timestamp.py b/tools/build_defs/proguard/zip_timestamp.py new file mode 100644 index 00000000000000..6743c581503497 --- /dev/null +++ b/tools/build_defs/proguard/zip_timestamp.py @@ -0,0 +1,58 @@ +# Copyright 2026 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import datetime +import zipfile + +def reset_timestamps(input, output, timestamp): + #print("Resetting timestamps in %s to %s, writing to %s" % (input, timestamp, output)) + + with zipfile.ZipFile(input, mode="r") as src: + with zipfile.ZipFile(output, mode="w") as dest: + for info in src.infolist(): + #print(f"Filename: {info.filename}") + #print(f" Modified: {datetime.datetime(*info.date_time)}") + data = src.read(info) + info.date_time = timestamp.timetuple()[:6] + dest.writestr(info, data) + +def main() -> None: + parser = argparse.ArgumentParser( + description="Resets timestamps in ZIP files", fromfile_prefix_chars="@" + ) + parser.add_argument( + "--output", + required=True, + help="The output file, mandatory." + ) + parser.add_argument( + "--input", + required=True, + help="The input file, mandatory.", + ) + parser.add_argument( + "--timestamp", + default = "1980-01-01 00:00:00", + type=datetime.datetime.fromisoformat, + help = "The timestamp (in ISO format) to set all files to.", + ) + opts = parser.parse_args() + + reset_timestamps(opts.input, opts.output, opts.timestamp) + + +if __name__ == "__main__": + main() + From 6689348cd613567a62700c052dfc4219eecb4c4b Mon Sep 17 00:00:00 2001 From: jcater Date: Thu, 28 May 2026 11:32:01 -0400 Subject: [PATCH 2/5] Use `ctx.action.args` --- tools/build_defs/proguard/proguard.bzl | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tools/build_defs/proguard/proguard.bzl b/tools/build_defs/proguard/proguard.bzl index 4072b187d24251..631a703c62e928 100644 --- a/tools/build_defs/proguard/proguard.bzl +++ b/tools/build_defs/proguard/proguard.bzl @@ -3,33 +3,31 @@ def _run_proguard(ctx, output): inputs = ctx.files.srcs + ctx.files.deps + [ctx.file.proguard_spec] + args = ctx.actions.args() + args.add_joined("-injars", ctx.files.srcs, join_with = ",") + args.add_joined("-libraryjars", ctx.files.deps, join_with = ",") + args.add("-outjars", output) + args.add("@" + ctx.file.proguard_spec.path) + ctx.actions.run( inputs = inputs, mnemonic = "ProguardJar", outputs = [output], executable = ctx.executable._proguard_bin, - arguments = [ - "-injars", - ",".join([src.path for src in ctx.files.srcs]), - "-libraryjars", - ",".join([dep.path for dep in ctx.files.deps]), - "-outjars", - output.path, - "@" + ctx.file.proguard_spec.path, - ], + arguments = [args], ) def _fix_timestamps(ctx, input, output): + args = ctx.actions.args() + args.add("--input", input) + args.add("--output", output) + args.add("--timestamp", "1980-01-01 00:00:00") ctx.actions.run( inputs = [input], mnemonic = "ZipTimestamp", outputs = [output], executable = ctx.executable._zip_timestamp, - arguments = [ - "--input=" + input.path, - "--output=" + output.path, - "--timestamp=1980-01-01 00:00:00", - ], + arguments = [args], ) def _proguard_jar_impl(ctx): From 3ef14b9bfb7950df17a69fe51e03212160a09a4e Mon Sep 17 00:00:00 2001 From: jcater Date: Fri, 29 May 2026 12:30:13 -0400 Subject: [PATCH 3/5] Combine proguard and timestamp fixes into a single wrapper python script. --- tools/build_defs/proguard/BUILD | 14 ++- tools/build_defs/proguard/proguard.bzl | 43 ++------ tools/build_defs/proguard/wrapper.py | 108 +++++++++++++++++++++ tools/build_defs/proguard/zip_timestamp.py | 58 ----------- 4 files changed, 128 insertions(+), 95 deletions(-) create mode 100644 tools/build_defs/proguard/wrapper.py delete mode 100644 tools/build_defs/proguard/zip_timestamp.py diff --git a/tools/build_defs/proguard/BUILD b/tools/build_defs/proguard/BUILD index 5732a5126b0b32..020d1956d04701 100644 --- a/tools/build_defs/proguard/BUILD +++ b/tools/build_defs/proguard/BUILD @@ -14,12 +14,18 @@ exports_files( visibility = ["//visibility:public"], ) -# Utility used in proguard_jar to fix timestamps. +# Utility used to run proguard and fix timestamps. py_binary( - name = "zip_timestamp_private", - srcs = ["zip_timestamp.py"], - main = "zip_timestamp.py", + name = "wrapper_private", + srcs = ["wrapper.py"], + data = [ + ":proguard_bin", + ], + main = "wrapper.py", visibility = ["//visibility:private"], + deps = [ + "@rules_python//python/runfiles", + ], ) java_binary( diff --git a/tools/build_defs/proguard/proguard.bzl b/tools/build_defs/proguard/proguard.bzl index 631a703c62e928..67abfe49862c5d 100644 --- a/tools/build_defs/proguard/proguard.bzl +++ b/tools/build_defs/proguard/proguard.bzl @@ -1,42 +1,24 @@ """Apply proguard rules to a JAR file.""" -def _run_proguard(ctx, output): +def _proguard_jar_impl(ctx): inputs = ctx.files.srcs + ctx.files.deps + [ctx.file.proguard_spec] + output = ctx.outputs.out args = ctx.actions.args() - args.add_joined("-injars", ctx.files.srcs, join_with = ",") - args.add_joined("-libraryjars", ctx.files.deps, join_with = ",") - args.add("-outjars", output) - args.add("@" + ctx.file.proguard_spec.path) + args.add_joined("--srcs", ctx.files.srcs, join_with = ",") + args.add_joined("--deps", ctx.files.deps, join_with = ",") + args.add("--proguard_spec", ctx.file.proguard_spec) + args.add("--output", output) + args.add("--timestamp", "1980-01-01 00:00:00") ctx.actions.run( inputs = inputs, mnemonic = "ProguardJar", outputs = [output], - executable = ctx.executable._proguard_bin, + executable = ctx.executable._wrapper, arguments = [args], ) -def _fix_timestamps(ctx, input, output): - args = ctx.actions.args() - args.add("--input", input) - args.add("--output", output) - args.add("--timestamp", "1980-01-01 00:00:00") - ctx.actions.run( - inputs = [input], - mnemonic = "ZipTimestamp", - outputs = [output], - executable = ctx.executable._zip_timestamp, - arguments = [args], - ) - -def _proguard_jar_impl(ctx): - stripped = ctx.actions.declare_file("%s.stripped.jar" % ctx.label.name) - _run_proguard(ctx, stripped) - - output = ctx.outputs.out - _fix_timestamps(ctx, stripped, output) - return DefaultInfo(files = depset([output])) proguard_jar = rule( @@ -46,14 +28,9 @@ proguard_jar = rule( "deps": attr.label_list(), "proguard_spec": attr.label(allow_single_file = True), "out": attr.output(), - "_proguard_bin": attr.label( - cfg = "exec", - default = ":proguard_bin", - executable = True, - ), - "_zip_timestamp": attr.label( + "_wrapper": attr.label( cfg = "exec", - default = ":zip_timestamp_private", + default = ":wrapper_private", executable = True, ), }, diff --git a/tools/build_defs/proguard/wrapper.py b/tools/build_defs/proguard/wrapper.py new file mode 100644 index 00000000000000..a25f4d71aed736 --- /dev/null +++ b/tools/build_defs/proguard/wrapper.py @@ -0,0 +1,108 @@ +# Copyright 2026 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import datetime +import os +import subprocess +import tempfile +import zipfile + +from python.runfiles import runfiles + +PROGUARD_BIN_PATH = "_main/tools/build_defs/proguard/proguard_bin" + +def apply_proguard(srcs, deps, proguard_spec, output_jar): + + rf = runfiles.Create() + proguard_binary = rf.Rlocation(PROGUARD_BIN_PATH) + if not proguard_binary: + raise Exception(f"Runfiles failed to resolve {PROGUARD_BIN_PATH}") + elif not os.path.exists(proguard_binary): + raise Exception(f"Runfiles resolved {PROGUARD_BIN_PATH} to {proguard_binary} but the file does not exist") + + command = [ + proguard_binary, + "-injars", srcs, + "-libraryjars", deps, + "-outjars", output_jar, + "@" + proguard_spec + ] + + #print("Running proguard: %s" % " ".join(command)) + p = subprocess.run(command, capture_output=True) + + if p.returncode != 0: + message = f"Proguard failed ({p.returncode})" + stdout = p.stdout.decode() + if stdout: + message += f"\n stdout:\n{stdout}" + stderr = p.stderr.decode() + if stdout: + message += f"\n stderr:\n{stderr}" + raise Exception(message) + +def reset_timestamps(input, output, timestamp): + #print("Resetting timestamps in %s to %s, writing to %s" % (input, timestamp, output)) + + with zipfile.ZipFile(input, mode="r") as src: + with zipfile.ZipFile(output, mode="w") as dest: + for info in src.infolist(): + #print(f"Filename: {info.filename}") + #print(f" Modified: {datetime.datetime(*info.date_time)}") + data = src.read(info) + info.date_time = timestamp.timetuple()[:6] + dest.writestr(info, data) + +def main() -> None: + parser = argparse.ArgumentParser( + description="Resets timestamps in ZIP files", fromfile_prefix_chars="@" + ) + parser.add_argument( + "--srcs", + required=True, + help="Input jar files, mandatory." + ) + parser.add_argument( + "--deps", + default=[], + help="Library jar files, optional." + ) + parser.add_argument( + "--proguard_spec", + required=True, + help="Proguard spec file, mandatory." + ) + parser.add_argument( + "--output", + required=True, + help="The output file, mandatory." + ) + parser.add_argument( + "--timestamp", + default = "1980-01-01 00:00:00", + type=datetime.datetime.fromisoformat, + help = "The timestamp (in ISO format) to set all files to.", + ) + opts = parser.parse_args() + + with tempfile.TemporaryDirectory() as wdir: + output_jar = os.path.join(wdir, "stripped.jar") + apply_proguard(opts.srcs, opts.deps, opts.proguard_spec, output_jar) + reset_timestamps(output_jar, opts.output, opts.timestamp) + + +if __name__ == "__main__": + main() + diff --git a/tools/build_defs/proguard/zip_timestamp.py b/tools/build_defs/proguard/zip_timestamp.py deleted file mode 100644 index 6743c581503497..00000000000000 --- a/tools/build_defs/proguard/zip_timestamp.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2026 The Bazel Authors. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import datetime -import zipfile - -def reset_timestamps(input, output, timestamp): - #print("Resetting timestamps in %s to %s, writing to %s" % (input, timestamp, output)) - - with zipfile.ZipFile(input, mode="r") as src: - with zipfile.ZipFile(output, mode="w") as dest: - for info in src.infolist(): - #print(f"Filename: {info.filename}") - #print(f" Modified: {datetime.datetime(*info.date_time)}") - data = src.read(info) - info.date_time = timestamp.timetuple()[:6] - dest.writestr(info, data) - -def main() -> None: - parser = argparse.ArgumentParser( - description="Resets timestamps in ZIP files", fromfile_prefix_chars="@" - ) - parser.add_argument( - "--output", - required=True, - help="The output file, mandatory." - ) - parser.add_argument( - "--input", - required=True, - help="The input file, mandatory.", - ) - parser.add_argument( - "--timestamp", - default = "1980-01-01 00:00:00", - type=datetime.datetime.fromisoformat, - help = "The timestamp (in ISO format) to set all files to.", - ) - opts = parser.parse_args() - - reset_timestamps(opts.input, opts.output, opts.timestamp) - - -if __name__ == "__main__": - main() - From c5a28f84bb834464ff3ac0601e7f9e6201f0ca00 Mon Sep 17 00:00:00 2001 From: jcater Date: Wed, 3 Jun 2026 14:53:41 -0400 Subject: [PATCH 4/5] Fix stderr output --- tools/build_defs/proguard/wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build_defs/proguard/wrapper.py b/tools/build_defs/proguard/wrapper.py index a25f4d71aed736..781e1eab36d4d2 100644 --- a/tools/build_defs/proguard/wrapper.py +++ b/tools/build_defs/proguard/wrapper.py @@ -49,7 +49,7 @@ def apply_proguard(srcs, deps, proguard_spec, output_jar): if stdout: message += f"\n stdout:\n{stdout}" stderr = p.stderr.decode() - if stdout: + if stderr: message += f"\n stderr:\n{stderr}" raise Exception(message) From e1b868d588c2ced6827de44003ec16dd24ff7600 Mon Sep 17 00:00:00 2001 From: jcater Date: Wed, 3 Jun 2026 15:02:37 -0400 Subject: [PATCH 5/5] Attempt to fix runfiles lookup for windows --- tools/build_defs/proguard/wrapper.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tools/build_defs/proguard/wrapper.py b/tools/build_defs/proguard/wrapper.py index 781e1eab36d4d2..6d6741eb2dd63a 100644 --- a/tools/build_defs/proguard/wrapper.py +++ b/tools/build_defs/proguard/wrapper.py @@ -15,6 +15,7 @@ import argparse import datetime import os +import platform import subprocess import tempfile import zipfile @@ -23,14 +24,20 @@ PROGUARD_BIN_PATH = "_main/tools/build_defs/proguard/proguard_bin" +def lookup_binary(path): + if platform.system() == "Windows": + path = path + ".exe" + rf = runfiles.Create() + binary = rf.Rlocation(path) + if not binary: + raise Exception(f"Runfiles failed to resolve {path}") + elif not os.path.exists(binary): + raise Exception(f"Runfiles resolved {path} to {binary} but the file does not exist") + return binary + def apply_proguard(srcs, deps, proguard_spec, output_jar): - rf = runfiles.Create() - proguard_binary = rf.Rlocation(PROGUARD_BIN_PATH) - if not proguard_binary: - raise Exception(f"Runfiles failed to resolve {PROGUARD_BIN_PATH}") - elif not os.path.exists(proguard_binary): - raise Exception(f"Runfiles resolved {PROGUARD_BIN_PATH} to {proguard_binary} but the file does not exist") + proguard_binary = lookup_binary(PROGUARD_BIN_PATH) command = [ proguard_binary,