| # 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. |
| |
| """Wrapper script to execute proguard and then normalize timestamps in the output jar file.""" |
| |
| import argparse |
| import datetime |
| import os |
| import platform |
| import subprocess |
| import tempfile |
| import zipfile |
| |
| from python.runfiles import Runfiles |
| |
| _PROGUARD_PATH = "_main/tools/build_defs/proguard/proguard_private" |
| |
| |
| def lookup_binary(r, path): |
| """Lookup the runfiles-adjusted path to a binary. |
| |
| Args: |
| r: The Runfiles object to use for the lookup. |
| path: The path of the binary being found. |
| |
| Returns: |
| The full path to the binary. |
| |
| Raises: |
| RuntimeError: If the path is not present in the runfiles, or if the adjusted |
| path does not |
| exist on the filesystem. |
| """ |
| |
| if platform.system() == "Windows": |
| path = path + ".exe" |
| binary = r.Rlocation(path) |
| if not binary: |
| raise RuntimeError(f"Runfiles failed to resolve {path}") |
| elif not os.path.exists(binary): |
| raise RuntimeError( |
| f"Runfiles resolved {path} to {binary} but the file does not exist" |
| ) |
| return binary |
| |
| |
| def apply_proguard(srcs, deps, proguard_spec, output_jar): |
| """Call proguard on the given source jars with the spec. |
| |
| Args: |
| srcs: The source jars to be modified. |
| deps: Dependency jars needed to resolve the source jars. |
| proguard_spec: The path to the proguard spec file describing what |
| modifications to make. |
| output_jar: The path to write the resulting modified jar file to. |
| |
| Raises: |
| RuntimeError: When the proguard binary fails, includes the stdout and |
| stderr. |
| """ |
| |
| # Set up runfiles and call the proguard binary. |
| r = Runfiles.Create() |
| proguard_path = lookup_binary(r, _PROGUARD_PATH) |
| |
| command = [ |
| proguard_path, |
| "-injars", |
| srcs, |
| "-libraryjars", |
| deps, |
| "-outjars", |
| output_jar, |
| "@" + proguard_spec, |
| ] |
| |
| env = os.environ.copy() |
| env.update(r.EnvVars()) |
| # print("Running proguard: %s" % " ".join(command)) |
| p = subprocess.run(command, capture_output=True, env=env, check=False) |
| |
| 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 RuntimeError(message) |
| |
| |
| def reset_timestamps(input_jar, output_jar, timestamp): |
| """Rewrite the given jar file to reset all timestamps to a known value. |
| |
| Args: |
| input_jar: The jar file to be modified. |
| output_jar: The path to write the destination jar to. |
| timestamp: The known timestamp to modify the output_jar with. |
| """ |
| # print("Resetting timestamps in %s to %s, writing to %s" % (input, |
| # timestamp, output)) |
| |
| with zipfile.ZipFile(input_jar, mode="r") as src: |
| with zipfile.ZipFile(output_jar, 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() |