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..020d1956d04701 --- /dev/null +++ b/tools/build_defs/proguard/BUILD @@ -0,0 +1,39 @@ +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 to run proguard and fix timestamps. +py_binary( + name = "wrapper_private", + srcs = ["wrapper.py"], + data = [ + ":proguard_bin", + ], + main = "wrapper.py", + visibility = ["//visibility:private"], + deps = [ + "@rules_python//python/runfiles", + ], +) + +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..67abfe49862c5d --- /dev/null +++ b/tools/build_defs/proguard/proguard.bzl @@ -0,0 +1,37 @@ +"""Apply proguard rules to a JAR file.""" + +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("--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._wrapper, + arguments = [args], + ) + + 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(), + "_wrapper": attr.label( + cfg = "exec", + 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..6d6741eb2dd63a --- /dev/null +++ b/tools/build_defs/proguard/wrapper.py @@ -0,0 +1,115 @@ +# 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 platform +import subprocess +import tempfile +import zipfile + +from python.runfiles import runfiles + +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): + + proguard_binary = lookup_binary(PROGUARD_BIN_PATH) + + 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 stderr: + 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() +