From 6ada47bc7950dd870a3a82458790a0cc0b7ba649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ingemar=20=C3=85dahl?= Date: Mon, 10 Jul 2017 17:14:01 +0000 Subject: [PATCH] Merge Android manifests when assembling apk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge all resource dependency manifests using the manifest merger from the Android SDK, providing the functionality described in https://developer.android.com/studio/build/manifest-merge.html. Removing the nontrivial manifest guard in the android_aar_prebuilt() template will be done in a follow-up change, as well as removing pre-merged manifest tags, such as "com.google.android.gms.version" meta-data. Bug: 643967 Change-Id: Ifdf9f3f76f5c80f1a2326dcd47045d032556936f Reviewed-on: https://chromium-review.googlesource.com/558296 Reviewed-by: Bo Liu Reviewed-by: Andrew Grieve Commit-Queue: Ingemar Ã…dahl Cr-Original-Commit-Position: refs/heads/master@{#485303} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: f3efbd5b8ec69ef49dbdbf8945f546815039efe3 --- android/BUILD.gn | 4 +- android/gradle/generate_gradle.py | 2 +- android/gyp/merge_manifest.py | 100 ++++++++++++++++++++++++++++++ android/gyp/util/build_utils.py | 9 +++ android/gyp/write_build_config.py | 6 +- android/resource_sizes.py | 7 +-- config/android/config.gni | 5 ++ config/android/rules.gni | 66 +++++++++++++++----- 8 files changed, 176 insertions(+), 23 deletions(-) create mode 100755 android/gyp/merge_manifest.py diff --git a/android/BUILD.gn b/android/BUILD.gn index 4720c8a1b..bb3a5aae6 100644 --- a/android/BUILD.gn +++ b/android/BUILD.gn @@ -49,12 +49,14 @@ if (enable_java_templates) { _data += "android_sdk_build_tools=" + rebase_path(android_sdk_build_tools, root_build_dir) + CR _data += "android_sdk_build_tools_version=$android_sdk_build_tools_version$CR" + _data += + "android_sdk_tools_version_suffix=$android_sdk_tools_version_suffix$CR" _data += "android_sdk_root=" + rebase_path(android_sdk_root, root_build_dir) + CR _data += "android_sdk_version=$android_sdk_version$CR" _data += "android_tool_prefix=" + rebase_path(android_tool_prefix, root_build_dir) + CR - write_file("$root_build_dir/build_vars.txt", _data) + write_file(android_build_vars, _data) } # Copy to the lib.unstripped directory so that gdb can easily find it. diff --git a/android/gradle/generate_gradle.py b/android/gradle/generate_gradle.py index ba2311ae5..537a26ade 100755 --- a/android/gradle/generate_gradle.py +++ b/android/gradle/generate_gradle.py @@ -364,7 +364,7 @@ class _ProjectContextGenerator(object): res_dirs.add( os.path.join(self.EntryOutputDir(root_entry), _RES_SUBDIR)) variables['res_dirs'] = self._Relativize(root_entry, res_dirs) - android_manifest = root_entry.Gradle().get('android_manifest') + android_manifest = root_entry.DepsInfo().get('android_manifest') if not android_manifest: android_manifest = self._GenCustomManifest(root_entry) variables['android_manifest'] = self._Relativize( diff --git a/android/gyp/merge_manifest.py b/android/gyp/merge_manifest.py new file mode 100755 index 000000000..93769e19d --- /dev/null +++ b/android/gyp/merge_manifest.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +# Copyright 2017 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 dependency Android manifests into a root manifest.""" + +import argparse +import contextlib +import os +import sys +import tempfile +import xml.dom.minidom as minidom + +from util import build_utils + +# Tools library directory - relative to Android SDK root +SDK_TOOLS_LIB_DIR = os.path.join('tools', 'lib') + +MANIFEST_MERGER_MAIN_CLASS = 'com.android.manifmerger.Merger' +MANIFEST_MERGER_JARS = [ + 'common{suffix}.jar', + 'manifest-merger{suffix}.jar', + 'sdk-common{suffix}.jar', + 'sdklib{suffix}.jar', +] + +TOOLS_NAMESPACE_PREFIX = 'tools' +TOOLS_NAMESPACE = 'http://schemas.android.com/tools' + + +@contextlib.contextmanager +def _PatchedManifest(manifest_path): + """Patches an Android manifest to always include the 'tools' namespace + declaration, as it is not propagated by the manifest merger from the SDK. + + See https://issuetracker.google.com/issues/63411481 + """ + doc = minidom.parse(manifest_path) + manifests = doc.getElementsByTagName('manifest') + assert len(manifests) == 1 + manifest = manifests[0] + + manifest.setAttribute('xmlns:%s' % TOOLS_NAMESPACE_PREFIX, TOOLS_NAMESPACE) + + tmp_prefix = os.path.basename(manifest_path) + with tempfile.NamedTemporaryFile(prefix=tmp_prefix) as patched_manifest: + doc.writexml(patched_manifest) + patched_manifest.flush() + yield patched_manifest.name + + +def _BuildManifestMergerClasspath(build_vars): + return ':'.join([ + os.path.join( + build_vars['android_sdk_root'], + SDK_TOOLS_LIB_DIR, + jar.format(suffix=build_vars['android_sdk_tools_version_suffix'])) + for jar in MANIFEST_MERGER_JARS + ]) + + +def main(argv): + argv = build_utils.ExpandFileArgs(argv) + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--build-vars', + help='Path to GN build vars file', + required=True) + parser.add_argument('--root-manifest', + help='Root manifest which to merge into', + required=True) + parser.add_argument('--output', help='Output manifest path', required=True) + parser.add_argument('--extras', + help='GN list of additional manifest to merge') + args = parser.parse_args(argv) + + cmd = [ + 'java', + '-cp', + _BuildManifestMergerClasspath(build_utils.ReadBuildVars(args.build_vars)), + MANIFEST_MERGER_MAIN_CLASS, + '--out', args.output, + ] + + extras = build_utils.ParseGnList(args.extras) + if extras: + cmd += ['--libs', ':'.join(extras)] + + with _PatchedManifest(args.root_manifest) as root_manifest: + cmd += ['--main', root_manifest] + build_utils.CheckOutput(cmd, + # https://issuetracker.google.com/issues/63514300: The merger doesn't set + # a nonzero exit code for failures. + fail_func=lambda returncode, stderr: returncode != 0 or + build_utils.IsTimeStale(args.output, [root_manifest] + extras)) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/android/gyp/util/build_utils.py b/android/gyp/util/build_utils.py index 7b2f48dd5..422bfee33 100644 --- a/android/gyp/util/build_utils.py +++ b/android/gyp/util/build_utils.py @@ -21,6 +21,7 @@ import zipfile import md5_check # pylint: disable=relative-import sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +from pylib import constants from pylib.constants import host_paths sys.path.append(os.path.join(os.path.dirname(__file__), @@ -81,6 +82,14 @@ def FindInDirectories(directories, filename_filter): return all_files +def ReadBuildVars(build_vars_path=None): + if not build_vars_path: + build_vars_path = os.path.join(constants.GetOutDirectory(), + "build_vars.txt") + with open(build_vars_path) as f: + return dict(l.rstrip().split('=', 1) for l in f) + + def ParseGnList(gn_string): """Converts a command-line parameter into a list. diff --git a/android/gyp/write_build_config.py b/android/gyp/write_build_config.py index 6eab4b27b..c2dee9a51 100755 --- a/android/gyp/write_build_config.py +++ b/android/gyp/write_build_config.py @@ -410,7 +410,8 @@ def main(argv): deps_info['gradle_treat_as_prebuilt'] = options.gradle_treat_as_prebuilt if options.android_manifest: - gradle['android_manifest'] = options.android_manifest + deps_info['android_manifest'] = options.android_manifest + if options.type in ('java_binary', 'java_library', 'android_apk'): if options.java_sources_file: deps_info['java_sources_file'] = options.java_sources_file @@ -706,6 +707,9 @@ def main(argv): config['uncompressed_locales_java_list'] = ( _CreateLocalePaksAssetJavaList(config['uncompressed_assets'])) + config['extra_android_manifests'] = filter(None, ( + d.get('android_manifest') for d in all_resources_deps)) + # Collect java resources java_resources_jars = [d['java_resources_jar'] for d in all_library_deps if 'java_resources_jar' in d] diff --git a/android/resource_sizes.py b/android/resource_sizes.py index c93f6758c..76f03fb4e 100755 --- a/android/resource_sizes.py +++ b/android/resource_sizes.py @@ -771,11 +771,6 @@ def _VerifyLibBuildIdsMatch(tools_prefix, *so_files): 'Your output directory is likely stale.') -def _ReadBuildVars(output_dir): - with open(os.path.join(output_dir, 'build_vars.txt')) as f: - return dict(l.replace('//', '').rstrip().split('=', 1) for l in f) - - def main(): argparser = argparse.ArgumentParser(description='Print APK size metrics.') argparser.add_argument('--min-pak-resource-size', type=int, default=20*1024, @@ -814,7 +809,7 @@ def main(): if not args.no_output_dir: constants.CheckOutputDirectory() devil_chromium.Initialize() - build_vars = _ReadBuildVars(constants.GetOutDirectory()) + build_vars = build_utils.ReadBuildVars() tools_prefix = os.path.join(constants.GetOutDirectory(), build_vars['android_tool_prefix']) else: diff --git a/config/android/config.gni b/config/android/config.gni index 141d39419..b47c723c6 100644 --- a/config/android/config.gni +++ b/config/android/config.gni @@ -45,6 +45,7 @@ if (is_android) { default_android_sdk_root = "//third_party/android_tools/sdk" default_android_sdk_version = "26" default_android_sdk_build_tools_version = "26.0.0" + default_android_sdk_tools_version_suffix = "-25.3.2" } if (!defined(default_lint_android_sdk_root)) { @@ -104,6 +105,7 @@ if (is_android) { android_sdk_root = default_android_sdk_root android_sdk_version = default_android_sdk_version android_sdk_build_tools_version = default_android_sdk_build_tools_version + android_sdk_tools_version_suffix = default_android_sdk_tools_version_suffix lint_android_sdk_root = default_lint_android_sdk_root lint_android_sdk_version = default_lint_android_sdk_version @@ -194,6 +196,9 @@ if (is_android) { assert(!(enable_incremental_dx && !is_java_debug)) assert(!(enable_incremental_javac && !is_java_debug)) + # Path to where selected build variables are written to. + android_build_vars = "$root_build_dir/build_vars.txt" + # Host stuff ----------------------------------------------------------------- # Defines the name the Android build gives to the current host CPU diff --git a/config/android/rules.gni b/config/android/rules.gni index 7ae86ac4a..e7a2c5b53 100644 --- a/config/android/rules.gni +++ b/config/android/rules.gni @@ -1777,11 +1777,11 @@ if (enable_java_templates) { } sources = [] } - _android_manifest_deps = [] + _android_root_manifest_deps = [] if (defined(invoker.android_manifest_dep)) { - _android_manifest_deps = [ invoker.android_manifest_dep ] + _android_root_manifest_deps = [ invoker.android_manifest_dep ] } - _android_manifest = invoker.android_manifest + _android_root_manifest = invoker.android_manifest _rebased_build_config = rebase_path(_build_config, root_build_dir) _create_abi_split = @@ -1820,13 +1820,13 @@ if (enable_java_templates) { incremental_install_script_path = _incremental_install_script_path resources_zip = resources_zip_path build_config = _build_config - android_manifest = _android_manifest + android_manifest = _android_root_manifest if (defined(_java_sources_file)) { java_sources_file = _java_sources_file } - deps = _android_manifest_deps + deps = _android_root_manifest_deps if (defined(invoker.deps)) { possible_config_deps = invoker.deps @@ -1856,6 +1856,34 @@ if (enable_java_templates) { } } + _android_manifest = + "$target_gen_dir/${_template_name}_manifest/AndroidManifest.xml" + android_manifest_target = "${_template_name}__merge_manifests" + action(android_manifest_target) { + script = "//build/android/gyp/merge_manifest.py" + + sources = [ + _android_root_manifest, + ] + + outputs = [ + _android_manifest, + ] + + args = [ + "--build-vars", + rebase_path(android_build_vars, root_build_dir), + "--root-manifest", + rebase_path(_android_root_manifest, root_build_dir), + "--output", + rebase_path(_android_manifest, root_build_dir), + "--extras", + "@FileArg($_rebased_build_config:extra_android_manifests)", + ] + + deps = _android_root_manifest_deps + [ ":$build_config_target" ] + } + _final_deps = [] if (enable_multidex) { @@ -1886,7 +1914,10 @@ if (enable_java_templates) { } build_config = _build_config - deps = _android_manifest_deps + [ ":$build_config_target" ] + deps = [ + ":$android_manifest_target", + ":$build_config_target", + ] if (defined(invoker.deps)) { deps += invoker.deps } @@ -1992,7 +2023,10 @@ if (enable_java_templates) { supports_android = true requires_android = true override_build_config = _build_config - deps = _android_manifest_deps + [ ":$build_config_target" ] + deps = [ + ":$android_manifest_target", + ":$build_config_target", + ] android_manifest = _android_manifest srcjar_deps = _srcjar_deps @@ -2268,17 +2302,19 @@ if (enable_java_templates) { keystore_password = _keystore_password # Incremental apk does not use native libs nor final dex. - incremental_deps = deps + _android_manifest_deps + [ + incremental_deps = deps + [ + ":$android_manifest_target", ":$build_config_target", ":$process_resources_target", ] # This target generates the input file _all_resources_zip_path. - deps += _android_manifest_deps + [ - ":$build_config_target", - ":$process_resources_target", - ":$final_dex_target_name", - ] + deps += [ + ":$android_manifest_target", + ":$build_config_target", + ":$final_dex_target_name", + ":$process_resources_target", + ] if ((_native_libs_deps != [] || _extra_native_libs_even_when_incremental != []) && @@ -2311,7 +2347,9 @@ if (enable_java_templates) { out_manifest = "$gen_dir/split-manifests/${android_app_abi}/AndroidManifest.xml" split_name = "abi_${android_app_abi}" - deps = _android_manifest_deps + deps = [ + ":$android_manifest_target", + ] } _apk_rule = "${_template_name}__split_apk_abi_${android_app_abi}"