# Copyright 2015 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 # # http://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. """Construct a dex manifest from a set of input .dex.zip files. Usage: %s * %s @ Input files must be either .zip files containing one or more .dex files or .dex files. A manifest file is written that contains one line for each input dex in the following form: or - """ import hashlib import os import shutil import sys import tempfile import zipfile class DexmanifestBuilder(object): """Implementation of the dex manifest builder.""" def __init__(self): self.manifest_lines = [] self.dir_counter = 1 self.output_dex_counter = 1 self.checksums = set() self.tmpdir = None def __enter__(self): self.tmpdir = tempfile.mkdtemp() return self def __exit__(self, unused_type, unused_value, unused_traceback): shutil.rmtree(self.tmpdir, True) def Checksum(self, filename): """Compute the SHA-256 checksum of a file.""" h = hashlib.sha256() with open(filename, "rb") as f: while True: data = f.read(65536) if not data: break h.update(data) return h.hexdigest() def AddDex(self, input_dex_or_zip, zippath, dex): """Adds a dex file to the output. Args: input_dex_or_zip: the input file written to the manifest zippath: the zip path written to the manifest or None if the input file is not a .zip . dex: the dex file to be added Returns: None. """ fs_checksum = self.Checksum(dex) if fs_checksum in self.checksums: return self.checksums.add(fs_checksum) zip_dex = "incremental_classes%d.dex" % self.output_dex_counter self.output_dex_counter += 1 self.manifest_lines.append("%s %s %s %s" %( input_dex_or_zip, zippath if zippath else "-", zip_dex, fs_checksum)) def Run(self, argv): """Creates a dex manifest.""" if len(argv) < 1: raise Exception("At least one argument expected") if argv[0][0] == "@": if len(argv) != 1: raise IOError("A parameter file should be the only argument") with open(argv[0][1:]) as param_file: argv = [a.strip() for a in param_file.readlines()] for input_filename in argv[1:]: input_filename = input_filename.strip() if input_filename.endswith(".zip"): with zipfile.ZipFile(input_filename, "r") as input_dex_zip: input_dex_dir = os.path.join(self.tmpdir, str(self.dir_counter)) os.makedirs(input_dex_dir) self.dir_counter += 1 for input_dex_dex in input_dex_zip.namelist(): if not input_dex_dex.endswith(".dex"): continue input_dex_zip.extract(input_dex_dex, input_dex_dir) fs_dex = input_dex_dir + "/" + input_dex_dex self.AddDex(input_filename, input_dex_dex, fs_dex) elif input_filename.endswith(".dex"): self.AddDex(input_filename, None, input_filename) with open(argv[0], "wb") as manifest: manifest.write(("\n".join(self.manifest_lines)).encode("utf-8")) def main(argv): with DexmanifestBuilder() as b: b.Run(argv[1:]) if __name__ == "__main__": main(sys.argv)