Example #1
0
def main():
  parser = optparse.OptionParser()
  parser.add_option('--zipalign-path', help='Path to the zipalign tool.')
  parser.add_option('--resource-packaged-apk-path',
      help='Base path to input .ap_s.')
  parser.add_option('--base-output-path',
      help='Path to output .apk, minus extension.')
  parser.add_option('--key-path', help='Path to keystore for signing.')
  parser.add_option('--key-passwd', help='Keystore password')
  parser.add_option('--key-name', help='Keystore name')
  parser.add_option('--densities',
      help='Comma separated list of densities finalize.')
  parser.add_option('--languages',
      help='GYP list of language splits to finalize.')

  options, _ = parser.parse_args()
  options.load_library_from_zip = 0

  if options.densities:
    for density in options.densities.split(','):
      options.unsigned_apk_path = ("%s_%s" %
          (options.resource_packaged_apk_path, density))
      options.final_apk_path = ("%s-density-%s.apk" %
          (options.base_output_path, density))
      finalize_apk.FinalizeApk(options)

  if options.languages:
    for lang in build_utils.ParseGnList(options.languages):
      options.unsigned_apk_path = ("%s_%s" %
          (options.resource_packaged_apk_path, lang))
      options.final_apk_path = ("%s-lang-%s.apk" %
          (options.base_output_path, lang))
      finalize_apk.FinalizeApk(options)
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', 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)
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('--component-build', action='store_true')
    group.add_argument('--shared_library')
    parser.add_argument(
        '--page-align-shared-libraries',
        action='store_true',
        help='Obsolete, but remains for backwards compatibility')
    parser.add_argument('--uncompress-shared-libraries', action='store_true')
    parser.add_argument('--debug', action='store_true')
    # This option shall only used in debug build, see http://crbug.com/631494.
    parser.add_argument('--ignore-classes-dex', action='store_true')
    parser.add_argument('--has-unwind-cfi',
                        action='store_true',
                        help='Specifies if the 32-bit apk has unwind_cfi file')
    args = parser.parse_args()

    if (args.zipalign_path is not None
            and not os.path.isfile(args.zipalign_path)):
        # If given an invalid path, fall back to try the default.
        logging.warning('zipalign path not found: %s', args.zipalign_path)
        logging.warning('falling back to: %s', DEFAULT_ZIPALIGN_PATH)
        args.zipalign_path = None

    if args.zipalign_path is None:
        # When no path given, try the default.
        if not os.path.isfile(DEFAULT_ZIPALIGN_PATH):
            return 'ERROR: zipalign path not found: %s' % DEFAULT_ZIPALIGN_PATH
        args.zipalign_path = DEFAULT_ZIPALIGN_PATH

    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')
    new_apk = args.out_apk

    try:
        MergeApk(args, tmp_apk, tmp_dir_32, tmp_dir_64)

        apksigner_path = os.path.join(os.path.dirname(args.zipalign_path),
                                      'apksigner')
        finalize_apk.FinalizeApk(apksigner_path, args.zipalign_path, tmp_apk,
                                 new_apk, args.keystore_path,
                                 args.key_password, args.key_name)
    finally:
        shutil.rmtree(tmp_dir)
    return 0
Example #3
0
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', 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)
  parser.add_argument('--uncompress-shared-libraries', action='store_true')
  parser.add_argument('--bundle', action='store_true')
  parser.add_argument('--debug', action='store_true')
  # This option shall only used in debug build, see http://crbug.com/631494.
  parser.add_argument('--ignore-classes-dex', action='store_true')
  parser.add_argument('--has-unwind-cfi', action='store_true',
                      help='Specifies if the 32-bit apk has unwind_cfi file')
  args = parser.parse_args()

  if (args.zipalign_path is not None and
      not os.path.isfile(args.zipalign_path)):
    # If given an invalid path, fall back to try the default.
    logging.warning('zipalign path not found: %s', args.zipalign_path)
    logging.warning('falling back to: %s', DEFAULT_ZIPALIGN_PATH)
    args.zipalign_path = None

  if args.zipalign_path is None:
    # When no path given, try the default.
    if not os.path.isfile(DEFAULT_ZIPALIGN_PATH):
      return 'ERROR: zipalign path not found: %s' % DEFAULT_ZIPALIGN_PATH
    args.zipalign_path = DEFAULT_ZIPALIGN_PATH

  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')
  new_apk = args.out_apk

  try:
    MergeApk(args, tmp_apk, tmp_dir_32, tmp_dir_64)

    apksigner_jar = os.path.join(
        os.path.dirname(args.zipalign_path), 'lib', 'apksigner.jar')
    # Official APKs are re-signed anyways, so it is not important to figure out
    # the correct min_sdk_version. Use 21 since that's the lowest supported
    # webview version.
    min_sdk_version = 21
    finalize_apk.FinalizeApk(apksigner_jar, args.zipalign_path, tmp_apk,
                             new_apk, args.keystore_path, args.key_password,
                             args.key_name, min_sdk_version)
  finally:
    shutil.rmtree(tmp_dir)
  return 0
Example #4
0
    def on_stale_md5():
        with tempfile.NamedTemporaryFile() as tmp_apk:
            tmp_file = tmp_apk.name
            with zipfile.ZipFile(options.resource_apk) as resource_apk, \
                 zipfile.ZipFile(tmp_file, 'w', zipfile.ZIP_DEFLATED) as out_apk:

                def copy_resource(zipinfo, out_dir=''):
                    compress = zipinfo.compress_type != zipfile.ZIP_STORED
                    build_utils.AddToZipHermetic(out_apk,
                                                 out_dir + zipinfo.filename,
                                                 data=resource_apk.read(
                                                     zipinfo.filename),
                                                 compress=compress)

                # Make assets come before resources in order to maintain the same file
                # ordering as GYP / aapt. http://crbug.com/561862
                resource_infos = resource_apk.infolist()

                # 1. AndroidManifest.xml
                assert resource_infos[0].filename == 'AndroidManifest.xml'
                copy_resource(resource_infos[0], out_dir=apk_manifest_dir)

                # 2. Assets
                if options.write_asset_list:
                    data = _CreateAssetsList(
                        itertools.chain(assets, uncompressed_assets))
                    build_utils.AddToZipHermetic(out_apk,
                                                 'assets/assets_list',
                                                 data=data)

                _AddAssets(out_apk, assets, disable_compression=False)
                _AddAssets(out_apk,
                           uncompressed_assets,
                           disable_compression=True)

                # 3. Dex files
                if options.dex_file and options.dex_file.endswith('.zip'):
                    with zipfile.ZipFile(options.dex_file, 'r') as dex_zip:
                        for dex in (d for d in dex_zip.namelist()
                                    if d.endswith('.dex')):
                            build_utils.AddToZipHermetic(
                                out_apk,
                                apk_dex_dir + dex,
                                data=dex_zip.read(dex))
                elif options.dex_file:
                    build_utils.AddToZipHermetic(out_apk,
                                                 apk_dex_dir + 'classes.dex',
                                                 src_path=options.dex_file)

                # 4. Native libraries.
                _AddNativeLibraries(out_apk, native_libs, options.android_abi,
                                    options.uncompress_shared_libraries)

                if options.secondary_android_abi:
                    _AddNativeLibraries(out_apk, secondary_native_libs,
                                        options.secondary_android_abi,
                                        options.uncompress_shared_libraries)

                for name in sorted(options.native_lib_placeholders):
                    # Note: Empty libs files are ignored by md5check (can cause issues
                    # with stale builds when the only change is adding/removing
                    # placeholders).
                    apk_path = 'lib/%s/%s' % (options.android_abi, name)
                    build_utils.AddToZipHermetic(out_apk, apk_path, data='')

                for name in sorted(options.secondary_native_lib_placeholders):
                    # Note: Empty libs files are ignored by md5check (can cause issues
                    # with stale builds when the only change is adding/removing
                    # placeholders).
                    apk_path = 'lib/%s/%s' % (options.secondary_android_abi,
                                              name)
                    build_utils.AddToZipHermetic(out_apk, apk_path, data='')

                # 5. Resources
                for info in resource_infos[1:]:
                    copy_resource(info)

                # 6. Java resources that should be accessible via
                # Class.getResourceAsStream(), in particular parts of Emma jar.
                # Prebuilt jars may contain class files which we shouldn't include.
                for java_resource in options.java_resources:
                    with zipfile.ZipFile(java_resource,
                                         'r') as java_resource_jar:
                        for apk_path in java_resource_jar.namelist():
                            apk_path_lower = apk_path.lower()

                            if apk_path_lower.startswith('meta-inf/'):
                                continue
                            if apk_path_lower.endswith('/'):
                                continue
                            if apk_path_lower.endswith('.class'):
                                continue

                            build_utils.AddToZipHermetic(
                                out_apk,
                                apk_root_dir + apk_path,
                                data=java_resource_jar.read(apk_path))

                if options.apk_pak_info_path:
                    _MergePakInfoFiles(options.apk_pak_info_path, pak_infos)
                if options.apk_res_info_path:
                    _MergeResInfoFiles(options.apk_res_info_path,
                                       options.resource_apk)

            if options.format == 'apk':
                finalize_apk.FinalizeApk(options.apksigner_path,
                                         options.zipalign_path, tmp_file,
                                         options.output_apk, options.key_path,
                                         options.key_passwd, options.key_name)
            else:
                shutil.move(tmp_file, options.output_apk)
                tmp_apk.delete = False
Example #5
0
def main(args):
    args = build_utils.ExpandFileArgs(args)
    options = _ParseArgs(args)

    native_libs = sorted(options.native_libs)

    # Include native libs in the depfile_deps since GN doesn't know about the
    # dependencies when is_component_build=true.
    depfile_deps = list(native_libs)

    # For targets that depend on static library APKs, dex paths are created by
    # the static library's dexsplitter target and GN doesn't know about these
    # paths.
    if options.dex_file:
        depfile_deps.append(options.dex_file)

    secondary_native_libs = []
    if options.secondary_native_libs:
        secondary_native_libs = sorted(options.secondary_native_libs)
        depfile_deps += secondary_native_libs

    if options.java_resources:
        # Included via .build_config, so need to write it to depfile.
        depfile_deps.extend(options.java_resources)

    assets = _ExpandPaths(options.assets)
    uncompressed_assets = _ExpandPaths(options.uncompressed_assets)

    # Included via .build_config, so need to write it to depfile.
    depfile_deps.extend(x[0] for x in assets)
    depfile_deps.extend(x[0] for x in uncompressed_assets)

    # Bundle modules have a structure similar to APKs, except that resources
    # are compiled in protobuf format (instead of binary xml), and that some
    # files are located into different top-level directories, e.g.:
    #  AndroidManifest.xml -> manifest/AndroidManifest.xml
    #  classes.dex -> dex/classes.dex
    #  res/ -> res/  (unchanged)
    #  assets/ -> assets/  (unchanged)
    #  <other-file> -> root/<other-file>
    #
    # Hence, the following variables are used to control the location of files in
    # the final archive.
    if options.format == 'bundle-module':
        apk_manifest_dir = 'manifest/'
        apk_root_dir = 'root/'
        apk_dex_dir = 'dex/'
    else:
        apk_manifest_dir = ''
        apk_root_dir = ''
        apk_dex_dir = ''

    # Targets generally do not depend on apks, so no need for only_if_changed.
    with build_utils.AtomicOutput(options.output_apk,
                                  only_if_changed=False) as f:
        with zipfile.ZipFile(options.resource_apk) as resource_apk, \
             zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED) as out_apk:

            def copy_resource(zipinfo, out_dir=''):
                compress = zipinfo.compress_type != zipfile.ZIP_STORED
                build_utils.AddToZipHermetic(out_apk,
                                             out_dir + zipinfo.filename,
                                             data=resource_apk.read(
                                                 zipinfo.filename),
                                             compress=compress)

            # Make assets come before resources in order to maintain the same file
            # ordering as GYP / aapt. http://crbug.com/561862
            resource_infos = resource_apk.infolist()

            # 1. AndroidManifest.xml
            copy_resource(resource_apk.getinfo('AndroidManifest.xml'),
                          out_dir=apk_manifest_dir)

            # 2. Assets
            if options.write_asset_list:
                data = _CreateAssetsList(
                    itertools.chain(assets, uncompressed_assets))
                build_utils.AddToZipHermetic(out_apk,
                                             'assets/assets_list',
                                             data=data)

            _AddAssets(out_apk, assets, disable_compression=False)
            _AddAssets(out_apk, uncompressed_assets, disable_compression=True)

            # 3. Dex files
            if options.dex_file and options.dex_file.endswith('.zip'):
                with zipfile.ZipFile(options.dex_file, 'r') as dex_zip:
                    for dex in (d for d in dex_zip.namelist()
                                if d.endswith('.dex')):
                        build_utils.AddToZipHermetic(
                            out_apk,
                            apk_dex_dir + dex,
                            data=dex_zip.read(dex),
                            compress=not options.uncompress_dex)
            elif options.dex_file:
                build_utils.AddToZipHermetic(
                    out_apk,
                    apk_dex_dir + 'classes.dex',
                    src_path=options.dex_file,
                    compress=not options.uncompress_dex)

            # 4. Native libraries.
            _AddNativeLibraries(out_apk, native_libs, options.android_abi,
                                options.uncompress_shared_libraries)

            if options.secondary_android_abi:
                _AddNativeLibraries(out_apk, secondary_native_libs,
                                    options.secondary_android_abi,
                                    options.uncompress_shared_libraries)

            for name in sorted(options.native_lib_placeholders):
                # Note: Empty libs files are ignored by md5check (can cause issues
                # with stale builds when the only change is adding/removing
                # placeholders).
                apk_path = 'lib/%s/%s' % (options.android_abi, name)
                build_utils.AddToZipHermetic(out_apk, apk_path, data='')

            for name in sorted(options.secondary_native_lib_placeholders):
                # Note: Empty libs files are ignored by md5check (can cause issues
                # with stale builds when the only change is adding/removing
                # placeholders).
                apk_path = 'lib/%s/%s' % (options.secondary_android_abi, name)
                build_utils.AddToZipHermetic(out_apk, apk_path, data='')

            # 5. Resources
            for info in sorted(resource_infos, key=lambda i: i.filename):
                if info.filename != 'AndroidManifest.xml':
                    copy_resource(info)

            # 6. Java resources that should be accessible via
            # Class.getResourceAsStream(), in particular parts of Emma jar.
            # Prebuilt jars may contain class files which we shouldn't include.
            for java_resource in options.java_resources:
                with zipfile.ZipFile(java_resource, 'r') as java_resource_jar:
                    for apk_path in sorted(java_resource_jar.namelist()):
                        apk_path_lower = apk_path.lower()

                        if apk_path_lower.startswith('meta-inf/'):
                            continue
                        if apk_path_lower.endswith('/'):
                            continue
                        if apk_path_lower.endswith('.class'):
                            continue

                        build_utils.AddToZipHermetic(
                            out_apk,
                            apk_root_dir + apk_path,
                            data=java_resource_jar.read(apk_path))

        if options.format == 'apk':
            finalize_apk.FinalizeApk(options.apksigner_path,
                                     options.zipalign_path, f.name, f.name,
                                     options.key_path, options.key_passwd,
                                     options.key_name)

    if options.depfile:
        build_utils.WriteDepfile(options.depfile,
                                 options.output_apk,
                                 inputs=depfile_deps,
                                 add_pydeps=False)
Example #6
0
def main(args):
  build_utils.InitLogging('APKBUILDER_DEBUG')
  args = build_utils.ExpandFileArgs(args)
  options = _ParseArgs(args)

  # Until Python 3.7, there's no better way to set compression level.
  # The default is 6.
  if options.best_compression:
    # Compresses about twice as slow as the default.
    zlib.Z_DEFAULT_COMPRESSION = 9
  else:
    # Compresses about twice as fast as the default.
    zlib.Z_DEFAULT_COMPRESSION = 1

  # Manually align only when alignment is necessary.
  # Python's zip implementation duplicates file comments in the central
  # directory, whereas zipalign does not, so use zipalign for official builds.
  fast_align = options.format == 'apk' and not options.best_compression

  native_libs = sorted(options.native_libs)

  # Include native libs in the depfile_deps since GN doesn't know about the
  # dependencies when is_component_build=true.
  depfile_deps = list(native_libs)

  # For targets that depend on static library APKs, dex paths are created by
  # the static library's dexsplitter target and GN doesn't know about these
  # paths.
  if options.dex_file:
    depfile_deps.append(options.dex_file)

  secondary_native_libs = []
  if options.secondary_native_libs:
    secondary_native_libs = sorted(options.secondary_native_libs)
    depfile_deps += secondary_native_libs

  if options.java_resources:
    # Included via .build_config.json, so need to write it to depfile.
    depfile_deps.extend(options.java_resources)

  assets = _ExpandPaths(options.assets)
  uncompressed_assets = _ExpandPaths(options.uncompressed_assets)

  # Included via .build_config.json, so need to write it to depfile.
  depfile_deps.extend(x[0] for x in assets)
  depfile_deps.extend(x[0] for x in uncompressed_assets)
  depfile_deps.append(options.resource_apk)

  # Bundle modules have a structure similar to APKs, except that resources
  # are compiled in protobuf format (instead of binary xml), and that some
  # files are located into different top-level directories, e.g.:
  #  AndroidManifest.xml -> manifest/AndroidManifest.xml
  #  classes.dex -> dex/classes.dex
  #  res/ -> res/  (unchanged)
  #  assets/ -> assets/  (unchanged)
  #  <other-file> -> root/<other-file>
  #
  # Hence, the following variables are used to control the location of files in
  # the final archive.
  if options.format == 'bundle-module':
    apk_manifest_dir = 'manifest/'
    apk_root_dir = 'root/'
    apk_dex_dir = 'dex/'
  else:
    apk_manifest_dir = ''
    apk_root_dir = ''
    apk_dex_dir = ''

  def _GetAssetDetails(assets, uncompressed_assets, fast_align, allow_reads):
    ret = _GetAssetsToAdd(assets,
                          fast_align,
                          disable_compression=False,
                          allow_reads=allow_reads)
    ret.extend(
        _GetAssetsToAdd(uncompressed_assets,
                        fast_align,
                        disable_compression=True,
                        allow_reads=allow_reads))
    return ret

  libs_to_add = _GetNativeLibrariesToAdd(
      native_libs, options.android_abi, options.uncompress_shared_libraries,
      fast_align, options.library_always_compress, options.library_renames)
  if options.secondary_android_abi:
    libs_to_add.extend(
        _GetNativeLibrariesToAdd(
            secondary_native_libs, options.secondary_android_abi,
            options.uncompress_shared_libraries, fast_align,
            options.library_always_compress, options.library_renames))

  if options.expected_file:
    # We compute expectations without reading the files. This allows us to check
    # expectations for different targets by just generating their build_configs
    # and not have to first generate all the actual files and all their
    # dependencies (for example by just passing --only-verify-expectations).
    asset_details = _GetAssetDetails(assets,
                                     uncompressed_assets,
                                     fast_align,
                                     allow_reads=False)

    actual_data = _CreateExpectationsData(libs_to_add, asset_details)
    diff_utils.CheckExpectations(actual_data, options)

    if options.only_verify_expectations:
      if options.depfile:
        build_utils.WriteDepfile(options.depfile,
                                 options.actual_file,
                                 inputs=depfile_deps)
      return

  # If we are past this point, we are going to actually create the final apk so
  # we should recompute asset details again but maybe perform some optimizations
  # based on the size of the files on disk.
  assets_to_add = _GetAssetDetails(
      assets, uncompressed_assets, fast_align, allow_reads=True)

  # Targets generally do not depend on apks, so no need for only_if_changed.
  with build_utils.AtomicOutput(options.output_apk, only_if_changed=False) as f:
    with zipfile.ZipFile(options.resource_apk) as resource_apk, \
         zipfile.ZipFile(f, 'w') as out_apk:

      def add_to_zip(zip_path, data, compress=True, alignment=4):
        zipalign.AddToZipHermetic(
            out_apk,
            zip_path,
            data=data,
            compress=compress,
            alignment=0 if compress and not fast_align else alignment)

      def copy_resource(zipinfo, out_dir=''):
        add_to_zip(
            out_dir + zipinfo.filename,
            resource_apk.read(zipinfo.filename),
            compress=zipinfo.compress_type != zipfile.ZIP_STORED)

      # Make assets come before resources in order to maintain the same file
      # ordering as GYP / aapt. http://crbug.com/561862
      resource_infos = resource_apk.infolist()

      # 1. AndroidManifest.xml
      logging.debug('Adding AndroidManifest.xml')
      copy_resource(
          resource_apk.getinfo('AndroidManifest.xml'), out_dir=apk_manifest_dir)

      # 2. Assets
      logging.debug('Adding assets/')
      _AddFiles(out_apk, assets_to_add)

      # 3. Dex files
      logging.debug('Adding classes.dex')
      if options.dex_file:
        with open(options.dex_file, 'rb') as dex_file_obj:
          if options.dex_file.endswith('.dex'):
            max_dex_number = 1
            # This is the case for incremental_install=true.
            add_to_zip(
                apk_dex_dir + 'classes.dex',
                dex_file_obj.read(),
                compress=not options.uncompress_dex)
          else:
            max_dex_number = 0
            with zipfile.ZipFile(dex_file_obj) as dex_zip:
              for dex in (d for d in dex_zip.namelist() if d.endswith('.dex')):
                max_dex_number += 1
                add_to_zip(
                    apk_dex_dir + dex,
                    dex_zip.read(dex),
                    compress=not options.uncompress_dex)

      if options.jdk_libs_dex_file:
        with open(options.jdk_libs_dex_file, 'rb') as dex_file_obj:
          add_to_zip(
              apk_dex_dir + 'classes{}.dex'.format(max_dex_number + 1),
              dex_file_obj.read(),
              compress=not options.uncompress_dex)

      # 4. Native libraries.
      logging.debug('Adding lib/')
      _AddFiles(out_apk, libs_to_add)

      # Add a placeholder lib if the APK should be multi ABI but is missing libs
      # for one of the ABIs.
      native_lib_placeholders = options.native_lib_placeholders
      secondary_native_lib_placeholders = (
          options.secondary_native_lib_placeholders)
      if options.is_multi_abi:
        if ((secondary_native_libs or secondary_native_lib_placeholders)
            and not native_libs and not native_lib_placeholders):
          native_lib_placeholders += ['libplaceholder.so']
        if ((native_libs or native_lib_placeholders)
            and not secondary_native_libs
            and not secondary_native_lib_placeholders):
          secondary_native_lib_placeholders += ['libplaceholder.so']

      # Add placeholder libs.
      for name in sorted(native_lib_placeholders):
        # Note: Empty libs files are ignored by md5check (can cause issues
        # with stale builds when the only change is adding/removing
        # placeholders).
        apk_path = 'lib/%s/%s' % (options.android_abi, name)
        add_to_zip(apk_path, '', alignment=0x1000)

      for name in sorted(secondary_native_lib_placeholders):
        # Note: Empty libs files are ignored by md5check (can cause issues
        # with stale builds when the only change is adding/removing
        # placeholders).
        apk_path = 'lib/%s/%s' % (options.secondary_android_abi, name)
        add_to_zip(apk_path, '', alignment=0x1000)

      # 5. Resources
      logging.debug('Adding res/')
      for info in sorted(resource_infos, key=lambda i: i.filename):
        if info.filename != 'AndroidManifest.xml':
          copy_resource(info)

      # 6. Java resources that should be accessible via
      # Class.getResourceAsStream(), in particular parts of Emma jar.
      # Prebuilt jars may contain class files which we shouldn't include.
      logging.debug('Adding Java resources')
      for java_resource in options.java_resources:
        with zipfile.ZipFile(java_resource, 'r') as java_resource_jar:
          for apk_path in sorted(java_resource_jar.namelist()):
            apk_path_lower = apk_path.lower()

            if apk_path_lower.startswith('meta-inf/'):
              continue
            if apk_path_lower.endswith('/'):
              continue
            if apk_path_lower.endswith('.class'):
              continue

            add_to_zip(apk_root_dir + apk_path,
                       java_resource_jar.read(apk_path))

    if options.format == 'apk':
      zipalign_path = None if fast_align else options.zipalign_path
      finalize_apk.FinalizeApk(options.apksigner_jar,
                               zipalign_path,
                               f.name,
                               f.name,
                               options.key_path,
                               options.key_passwd,
                               options.key_name,
                               int(options.min_sdk_version),
                               warnings_as_errors=options.warnings_as_errors)
    logging.debug('Moving file into place')

    if options.depfile:
      build_utils.WriteDepfile(options.depfile,
                               options.output_apk,
                               inputs=depfile_deps)