1#!/usr/bin/env python
2
3# Copyright 2017 The Chromium Authors. All rights reserved.
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 contextlib
11import os
12import sys
13import tempfile
14import xml.etree.ElementTree as ElementTree
15
16from util import build_utils
17from util import manifest_utils
18
19_MANIFEST_MERGER_MAIN_CLASS = 'com.android.manifmerger.Merger'
20_MANIFEST_MERGER_JARS = [
21    os.path.join('build-system', 'manifest-merger.jar'),
22    os.path.join('common', 'common.jar'),
23    os.path.join('sdk-common', 'sdk-common.jar'),
24    os.path.join('sdklib', 'sdklib.jar'),
25    os.path.join('external', 'com', 'google', 'guava', 'guava', '27.1-jre',
26                 'guava-27.1-jre.jar'),
27    os.path.join('external', 'kotlin-plugin-ij', 'Kotlin', 'kotlinc', 'lib',
28                 'kotlin-stdlib.jar'),
29    os.path.join('external', 'com', 'google', 'code', 'gson', 'gson', '2.8.5',
30                 'gson-2.8.5.jar'),
31]
32
33
34@contextlib.contextmanager
35def _ProcessManifest(manifest_path, min_sdk_version, target_sdk_version,
36                     max_sdk_version, manifest_package):
37  """Patches an Android manifest's package and performs assertions to ensure
38  correctness for the manifest.
39  """
40  doc, manifest, _ = manifest_utils.ParseManifest(manifest_path)
41  manifest_utils.AssertUsesSdk(manifest, min_sdk_version, target_sdk_version,
42                               max_sdk_version)
43  assert manifest_utils.GetPackage(manifest) or manifest_package, \
44            'Must set manifest package in GN or in AndroidManifest.xml'
45  manifest_utils.AssertPackage(manifest, manifest_package)
46  if manifest_package:
47    manifest.set('package', manifest_package)
48  tmp_prefix = os.path.basename(manifest_path)
49  with tempfile.NamedTemporaryFile(prefix=tmp_prefix) as patched_manifest:
50    manifest_utils.SaveManifest(doc, patched_manifest.name)
51    yield patched_manifest.name, manifest_utils.GetPackage(manifest)
52
53
54def _BuildManifestMergerClasspath(android_sdk_cmdline_tools):
55  return ':'.join([
56      os.path.join(android_sdk_cmdline_tools, 'lib', jar)
57      for jar in _MANIFEST_MERGER_JARS
58  ])
59
60
61def main(argv):
62  argv = build_utils.ExpandFileArgs(argv)
63  parser = argparse.ArgumentParser(description=__doc__)
64  build_utils.AddDepfileOption(parser)
65  parser.add_argument(
66      '--android-sdk-cmdline-tools',
67      help='Path to SDK\'s cmdline-tools folder.',
68      required=True)
69  parser.add_argument('--root-manifest',
70                      help='Root manifest which to merge into',
71                      required=True)
72  parser.add_argument('--output', help='Output manifest path', required=True)
73  parser.add_argument('--extras',
74                      help='GN list of additional manifest to merge')
75  parser.add_argument(
76      '--min-sdk-version',
77      required=True,
78      help='android:minSdkVersion for merging.')
79  parser.add_argument(
80      '--target-sdk-version',
81      required=True,
82      help='android:targetSdkVersion for merging.')
83  parser.add_argument(
84      '--max-sdk-version', help='android:maxSdkVersion for merging.')
85  parser.add_argument(
86      '--manifest-package',
87      help='Package name of the merged AndroidManifest.xml.')
88  args = parser.parse_args(argv)
89
90  classpath = _BuildManifestMergerClasspath(args.android_sdk_cmdline_tools)
91
92  with build_utils.AtomicOutput(args.output) as output:
93    cmd = [
94        build_utils.JAVA_PATH,
95        '-cp',
96        classpath,
97        _MANIFEST_MERGER_MAIN_CLASS,
98        '--out',
99        output.name,
100        '--property',
101        'MIN_SDK_VERSION=' + args.min_sdk_version,
102        '--property',
103        'TARGET_SDK_VERSION=' + args.target_sdk_version,
104    ]
105
106    if args.max_sdk_version:
107      cmd += [
108          '--property',
109          'MAX_SDK_VERSION=' + args.max_sdk_version,
110      ]
111
112    extras = build_utils.ParseGnList(args.extras)
113    if extras:
114      cmd += ['--libs', ':'.join(extras)]
115
116    with _ProcessManifest(args.root_manifest, args.min_sdk_version,
117                          args.target_sdk_version, args.max_sdk_version,
118                          args.manifest_package) as tup:
119      root_manifest, package = tup
120      cmd += [
121          '--main',
122          root_manifest,
123          '--property',
124          'PACKAGE=' + package,
125      ]
126      build_utils.CheckOutput(cmd,
127        # https://issuetracker.google.com/issues/63514300:
128        # The merger doesn't set a nonzero exit code for failures.
129        fail_func=lambda returncode, stderr: returncode != 0 or
130          build_utils.IsTimeStale(output.name, [root_manifest] + extras))
131
132    # Check for correct output.
133    _, manifest, _ = manifest_utils.ParseManifest(output.name)
134    manifest_utils.AssertUsesSdk(manifest, args.min_sdk_version,
135                                 args.target_sdk_version)
136    manifest_utils.AssertPackage(manifest, package)
137
138  if args.depfile:
139    inputs = extras + classpath.split(':')
140    build_utils.WriteDepfile(args.depfile, args.output, inputs=inputs,
141                             add_pydeps=False)
142
143
144if __name__ == '__main__':
145  main(sys.argv[1:])
146