Index: android_webview/tools/apk_merger.py |
diff --git a/android_webview/tools/apk_merger.py b/android_webview/tools/apk_merger.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..a8aa44d015159e9458af98bae24c54661b22030a |
--- /dev/null |
+++ b/android_webview/tools/apk_merger.py |
@@ -0,0 +1,202 @@ |
+#!/usr/bin/python |
+# Copyright 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+""" Merges a 64-bit and a 32-bit APK into a single APK |
+ |
+""" |
+ |
+import os |
+import sys |
+import shutil |
+import zipfile |
+import filecmp |
+import tempfile |
+import argparse |
+import subprocess |
+ |
+SRC_DIR = os.path.join(os.path.dirname(__file__), '..', '..') |
+SRC_DIR = os.path.abspath(SRC_DIR) |
+BUILD_ANDROID_GYP_DIR = os.path.join(SRC_DIR, 'build/android/gyp') |
+sys.path.append(BUILD_ANDROID_GYP_DIR) |
+ |
+import finalize_apk |
+from util import build_utils |
+ |
+class ApkMergeFailure(Exception): |
+ pass |
+ |
+ |
+def UnpackApk(file_name, dst): |
+ zippy = zipfile.ZipFile(file_name) |
+ zippy.extractall(dst) |
+ |
+ |
+def GetNonDirFiles(top, base_dir): |
+ """ Return a list containing all (non-directory) files in tree with top as |
+ root. |
+ |
+ Each file is represented by the relative path from base_dir to that file. |
+ If top is a file (not a directory) then a list containing only top is |
+ returned. |
+ """ |
+ if os.path.isdir(top): |
+ ret = [] |
+ for dirpath, _, filenames in os.walk(top): |
+ for filename in filenames: |
+ ret.append( |
+ os.path.relpath(os.path.join(dirpath, filename), base_dir)) |
+ return ret |
+ else: |
+ return [os.path.relpath(top, base_dir)] |
+ |
+ |
+def GetDiffFiles(dcmp, base_dir): |
+ """ Return the list of files contained only in the right directory of dcmp. |
+ |
+ The files returned are represented by relative paths from base_dir. |
+ """ |
+ copy_files = [] |
+ for file_name in dcmp.right_only: |
+ copy_files.extend( |
+ GetNonDirFiles(os.path.join(dcmp.right, file_name), base_dir)) |
+ |
+ # we cannot merge APKs with files with similar names but different contents |
+ if len(dcmp.diff_files) > 0: |
+ raise ApkMergeFailure('found differing files: %s in %s and %s' % |
+ (dcmp.diff_files, dcmp.left, dcmp.right)) |
+ |
+ if len(dcmp.funny_files) > 0: |
+ ApkMergeFailure('found uncomparable files: %s in %s and %s' % |
+ (dcmp.funny_files, dcmp.left, dcmp.right)) |
+ |
+ for sub_dcmp in dcmp.subdirs.itervalues(): |
+ copy_files.extend(GetDiffFiles(sub_dcmp, base_dir)) |
+ return copy_files |
+ |
+ |
+def CheckFilesExpected(actual_files, expected_files): |
+ """ Check that the lists of actual and expected files are the same. """ |
+ file_set = set() |
+ for file_name in actual_files: |
+ base_name = os.path.basename(file_name) |
+ if base_name not in expected_files: |
+ raise ApkMergeFailure('Found unexpected file named %s.' % |
+ file_name) |
+ if base_name in file_set: |
+ raise ApkMergeFailure('Duplicate file %s to add to APK!' % |
+ file_name) |
+ file_set.add(base_name) |
+ |
+ if len(file_set) != len(expected_files): |
+ raise ApkMergeFailure('Missing expected files to add to APK!') |
+ |
+ |
+def AddDiffFiles(diff_files, tmp_dir_32, tmp_apk, expected_files): |
+ """ Insert files only present in 32-bit APK into 64-bit APK (tmp_apk). """ |
+ old_dir = os.getcwd() |
+ # Move into 32-bit directory to make sure the files we insert have correct |
+ # relative paths. |
+ os.chdir(tmp_dir_32) |
+ try: |
+ for diff_file in diff_files: |
+ extra_flags = expected_files[os.path.basename(diff_file)] |
+ build_utils.CheckOutput(['zip', '-r', '-X', '--no-dir-entries', |
+ tmp_apk, diff_file] + extra_flags) |
+ except build_utils.CalledProcessError as e: |
+ raise ApkMergeFailure( |
+ 'Failed to add file %s to APK: %s' % (diff_file, e.output)) |
+ finally: |
+ # Move out of 32-bit directory when done |
+ os.chdir(old_dir) |
+ |
+ |
+def RemoveMetafiles(tmp_apk): |
+ """ Remove all meta info to avoid certificate clashes """ |
+ try: |
+ build_utils.CheckOutput(['zip', '-d', tmp_apk, 'META-INF/*']) |
+ except build_utils.CalledProcessError as e: |
+ raise ApkMergeFailure('Failed to delete Meta folder: ' + e.output) |
+ |
+ |
+def SignAndAlignApk(tmp_apk, signed_tmp_apk, new_apk, zipalign_path, |
+ keystore_path, key_name, key_password): |
+ try: |
+ finalize_apk.JarSigner( |
+ keystore_path, |
+ key_name, |
+ key_password, |
+ tmp_apk, |
+ signed_tmp_apk) |
+ except build_utils.CalledProcessError as e: |
+ raise ApkMergeFailure('Failed to sign APK: ' + e.output) |
+ |
+ try: |
+ finalize_apk.AlignApk(zipalign_path, signed_tmp_apk, new_apk) |
+ except build_utils.CalledProcessError as e: |
+ raise ApkMergeFailure('Failed to align APK: ' + e.output) |
+ |
+ |
+def main(): |
+ parser = argparse.ArgumentParser( |
+ description='Merge a 32-bit APK into a 64-bit APK') |
+ # Using type=os.path.abspath converts file paths to absolute paths so that |
+ # we can change working directory without affecting these paths |
+ parser.add_argument('--apk_32bit', required=True, type=os.path.abspath) |
+ parser.add_argument('--apk_64bit', required=True, type=os.path.abspath) |
+ parser.add_argument('--out_apk', required=True, type=os.path.abspath) |
+ parser.add_argument('--zipalign_path', required=True, type=os.path.abspath) |
+ parser.add_argument('--keystore_path', required=True, type=os.path.abspath) |
+ parser.add_argument('--key_name', required=True) |
+ parser.add_argument('--key_password', required=True) |
+ args = parser.parse_args() |
+ |
+ tmp_dir = tempfile.mkdtemp() |
+ tmp_dir_64 = os.path.join(tmp_dir, '64_bit') |
+ tmp_dir_32 = os.path.join(tmp_dir, '32_bit') |
+ tmp_apk = os.path.join(tmp_dir, 'tmp.apk') |
+ signed_tmp_apk = os.path.join(tmp_dir, 'signed.apk') |
+ new_apk = args.out_apk |
+ |
+ # Expected files to copy from 32- to 64-bit APK together with an extra flag |
+ # setting the compression level of the file |
+ expected_files = {'snapshot_blob_32.bin': ['-0'], |
+ 'natives_blob_32.bin': ['-0'], |
+ 'libwebviewchromium.so': []} |
+ |
+ try: |
+ shutil.copyfile(args.apk_64bit, tmp_apk) |
+ |
+ # need to unpack APKs to compare their contents |
+ UnpackApk(args.apk_64bit, tmp_dir_64) |
+ UnpackApk(args.apk_32bit, tmp_dir_32) |
+ |
+ dcmp = filecmp.dircmp( |
+ tmp_dir_64, |
+ tmp_dir_32, |
+ ignore=['META-INF', 'AndroidManifest.xml']) |
+ |
+ diff_files = GetDiffFiles(dcmp, tmp_dir_32) |
+ |
+ # Check that diff_files match exactly those files we want to insert into |
+ # the 64-bit APK. |
+ CheckFilesExpected(diff_files, expected_files) |
+ |
+ RemoveMetafiles(tmp_apk) |
+ |
+ AddDiffFiles(diff_files, tmp_dir_32, tmp_apk, expected_files) |
+ |
+ SignAndAlignApk(tmp_apk, signed_tmp_apk, new_apk, args.zipalign_path, |
+ args.keystore_path, args.key_name, args.key_password) |
+ |
+ except ApkMergeFailure as e: |
+ print e |
+ return 1 |
+ finally: |
+ shutil.rmtree(tmp_dir) |
+ return 0 |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |