1#!/usr/bin/env python3 2 3# Copyright 2017 The Chromium Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Merges dependency Android manifests into a root manifest.""" 8 9import argparse 10import collections 11import contextlib 12import os 13import sys 14import tempfile 15import xml.etree.ElementTree as ElementTree 16 17from util import build_utils 18from util import manifest_utils 19import action_helpers # build_utils adds //build to sys.path. 20 21_MANIFEST_MERGER_MAIN_CLASS = 'com.android.manifmerger.Merger' 22 23 24@contextlib.contextmanager 25def _ProcessMainManifest(manifest_path, min_sdk_version, target_sdk_version, 26 max_sdk_version, manifest_package): 27 """Patches the main Android manifest""" 28 doc, manifest, _ = manifest_utils.ParseManifest(manifest_path) 29 manifest_utils.SetUsesSdk(manifest, target_sdk_version, min_sdk_version, 30 max_sdk_version) 31 assert manifest_utils.GetPackage(manifest) or manifest_package, \ 32 'Must set manifest package in GN or in AndroidManifest.xml' 33 if manifest_package: 34 manifest.set('package', manifest_package) 35 tmp_prefix = manifest_path.replace(os.path.sep, '-') 36 with tempfile.NamedTemporaryFile(prefix=tmp_prefix) as patched_manifest: 37 manifest_utils.SaveManifest(doc, patched_manifest.name) 38 yield patched_manifest.name, manifest_utils.GetPackage(manifest) 39 40 41@contextlib.contextmanager 42def _ProcessOtherManifest(manifest_path, min_sdk_version, target_sdk_version, 43 seen_package_names): 44 """Patches non-main AndroidManifest.xml if necessary.""" 45 # 1. Ensure targetSdkVersion is set to the expected value to avoid 46 # spurious permissions being added (b/222331337). 47 # 2. Ensure all manifests have a unique package name so that the merger 48 # does not fail when this happens. 49 doc, manifest, _ = manifest_utils.ParseManifest(manifest_path) 50 51 changed_api = manifest_utils.SetTargetApiIfUnset(manifest, target_sdk_version) 52 53 package_name = manifest_utils.GetPackage(manifest) 54 # Ignore minSdkVersion from androidx.pdf library. The client code will ensure 55 # not to call into the library API on older Android versions. 56 if package_name.startswith('androidx.pdf'): 57 manifest_utils.OverrideMinSdkVersionIfPresent(manifest, min_sdk_version) 58 changed_api = True 59 package_count = seen_package_names[package_name] 60 seen_package_names[package_name] += 1 61 if package_count > 0: 62 manifest.set('package', f'{package_name}_{package_count}') 63 64 if package_count > 0 or changed_api: 65 tmp_prefix = manifest_path.replace(os.path.sep, '-') 66 with tempfile.NamedTemporaryFile(prefix=tmp_prefix) as patched_manifest: 67 manifest_utils.SaveManifest(doc, patched_manifest.name) 68 yield patched_manifest.name 69 else: 70 yield manifest_path 71 72 73def main(argv): 74 argv = build_utils.ExpandFileArgs(argv) 75 parser = argparse.ArgumentParser(description=__doc__) 76 action_helpers.add_depfile_arg(parser) 77 parser.add_argument('--manifest-merger-jar', 78 help='Path to SDK\'s manifest merger jar.', 79 required=True) 80 parser.add_argument('--root-manifest', 81 help='Root manifest which to merge into', 82 required=True) 83 parser.add_argument('--output', help='Output manifest path', required=True) 84 parser.add_argument('--extras', 85 help='GN list of additional manifest to merge') 86 parser.add_argument( 87 '--min-sdk-version', 88 required=True, 89 help='android:minSdkVersion for merging.') 90 parser.add_argument( 91 '--target-sdk-version', 92 required=True, 93 help='android:targetSdkVersion for merging.') 94 parser.add_argument( 95 '--max-sdk-version', help='android:maxSdkVersion for merging.') 96 parser.add_argument( 97 '--manifest-package', 98 help='Package name of the merged AndroidManifest.xml.') 99 parser.add_argument('--warnings-as-errors', 100 action='store_true', 101 help='Treat all warnings as errors.') 102 args = parser.parse_args(argv) 103 104 with action_helpers.atomic_output(args.output) as output: 105 cmd = build_utils.JavaCmd() + [ 106 '-cp', 107 args.manifest_merger_jar, 108 _MANIFEST_MERGER_MAIN_CLASS, 109 '--out', 110 output.name, 111 '--property', 112 'MIN_SDK_VERSION=' + args.min_sdk_version, 113 '--property', 114 'TARGET_SDK_VERSION=' + args.target_sdk_version, 115 ] 116 117 if args.max_sdk_version: 118 cmd += [ 119 '--property', 120 'MAX_SDK_VERSION=' + args.max_sdk_version, 121 ] 122 123 extras = action_helpers.parse_gn_list(args.extras) 124 125 with contextlib.ExitStack() as stack: 126 root_manifest, package = stack.enter_context( 127 _ProcessMainManifest(args.root_manifest, args.min_sdk_version, 128 args.target_sdk_version, args.max_sdk_version, 129 args.manifest_package)) 130 if extras: 131 seen_package_names = collections.Counter() 132 extras_processed = [ 133 stack.enter_context( 134 _ProcessOtherManifest(e, args.min_sdk_version, 135 args.target_sdk_version, 136 seen_package_names)) for e in extras 137 ] 138 cmd += ['--libs', ':'.join(extras_processed)] 139 cmd += [ 140 '--main', 141 root_manifest, 142 '--property', 143 'PACKAGE=' + package, 144 '--remove-tools-declarations', 145 ] 146 build_utils.CheckOutput( 147 cmd, 148 # https://issuetracker.google.com/issues/63514300: 149 # The merger doesn't set a nonzero exit code for failures. 150 fail_func=lambda returncode, stderr: returncode != 0 or build_utils. 151 IsTimeStale(output.name, [root_manifest] + extras), 152 fail_on_output=args.warnings_as_errors) 153 154 if args.depfile: 155 action_helpers.write_depfile(args.depfile, args.output, inputs=extras) 156 157 158if __name__ == '__main__': 159 main(sys.argv[1:]) 160