| # 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 <output manifest> <input zip file>* | |
| %s @<params file> | |
| 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: | |
| <input zip> <path in input zip> <path in output zip> <MD5 checksum> | |
| or | |
| <input dex> - <path in output zip> <SHA-256 checksum> | |
| """ | |
| 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) |