blob: e912d62a939d593caa642c131466e61eefcd6f79 [file] [log] [blame]
# 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)