blob: b4936b865ed270fd582be9b7fd67ea624d0a7edb [file] [log] [blame]
Colin Cross72119102019-05-20 13:14:18 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""A tool for checking that a manifest agrees with the build system."""
18
19from __future__ import print_function
20
21import argparse
Ulya Trafimovich3c902e72021-03-04 18:06:27 +000022import json
Ulya Trafimovich0aba2522021-03-03 16:38:37 +000023import re
24import subprocess
Colin Cross72119102019-05-20 13:14:18 -070025import sys
26from xml.dom import minidom
27
Colin Cross72119102019-05-20 13:14:18 -070028from manifest import android_ns
29from manifest import get_children_with_tag
30from manifest import parse_manifest
31from manifest import write_xml
32
33
34class ManifestMismatchError(Exception):
Spandan Dasf8807422021-08-25 20:01:17 +000035 pass
Colin Cross72119102019-05-20 13:14:18 -070036
37
38def parse_args():
Spandan Dasf8807422021-08-25 20:01:17 +000039 """Parse commandline arguments."""
Colin Cross72119102019-05-20 13:14:18 -070040
Spandan Dasf8807422021-08-25 20:01:17 +000041 parser = argparse.ArgumentParser()
42 parser.add_argument(
43 '--uses-library',
44 dest='uses_libraries',
45 action='append',
46 help='specify uses-library entries known to the build system')
47 parser.add_argument(
48 '--optional-uses-library',
49 dest='optional_uses_libraries',
50 action='append',
51 help='specify uses-library entries known to the build system with '
52 'required:false'
53 )
54 parser.add_argument(
55 '--enforce-uses-libraries',
56 dest='enforce_uses_libraries',
57 action='store_true',
58 help='check the uses-library entries known to the build system against '
59 'the manifest'
60 )
61 parser.add_argument(
62 '--enforce-uses-libraries-relax',
63 dest='enforce_uses_libraries_relax',
64 action='store_true',
65 help='do not fail immediately, just save the error message to file')
66 parser.add_argument(
67 '--enforce-uses-libraries-status',
68 dest='enforce_uses_libraries_status',
69 help='output file to store check status (error message)')
70 parser.add_argument(
71 '--extract-target-sdk-version',
72 dest='extract_target_sdk_version',
73 action='store_true',
74 help='print the targetSdkVersion from the manifest')
75 parser.add_argument(
76 '--dexpreopt-config',
77 dest='dexpreopt_configs',
78 action='append',
79 help='a paths to a dexpreopt.config of some library')
80 parser.add_argument('--aapt', dest='aapt', help='path to aapt executable')
81 parser.add_argument(
82 '--output', '-o', dest='output', help='output AndroidManifest.xml file')
83 parser.add_argument('input', help='input AndroidManifest.xml file')
84 return parser.parse_args()
Colin Cross72119102019-05-20 13:14:18 -070085
86
Ulya Trafimovichbb7513d2021-03-30 17:15:16 +010087def enforce_uses_libraries(manifest, required, optional, relax, is_apk, path):
Spandan Dasf8807422021-08-25 20:01:17 +000088 """Verify that the <uses-library> tags in the manifest match those provided
89
Ulya Trafimovich0aba2522021-03-03 16:38:37 +000090 by the build system.
Colin Cross72119102019-05-20 13:14:18 -070091
92 Args:
Ulya Trafimovich0aba2522021-03-03 16:38:37 +000093 manifest: manifest (either parsed XML or aapt dump of APK)
94 required: required libs known to the build system
95 optional: optional libs known to the build system
96 relax: if true, suppress error on mismatch and just write it to file
97 is_apk: if the manifest comes from an APK or an XML file
Spandan Dasf8807422021-08-25 20:01:17 +000098 """
99 if is_apk:
100 manifest_required, manifest_optional, tags = extract_uses_libs_apk(
101 manifest)
102 else:
103 manifest_required, manifest_optional, tags = extract_uses_libs_xml(
104 manifest)
Colin Cross72119102019-05-20 13:14:18 -0700105
Spandan Dasf8807422021-08-25 20:01:17 +0000106 # Trim namespace component. Normally Soong does that automatically when it
107 # handles module names specified in Android.bp properties. However not all
108 # <uses-library> entries in the manifest correspond to real modules: some of
109 # the optional libraries may be missing at build time. Therefor this script
110 # accepts raw module names as spelled in Android.bp/Amdroid.mk and trims the
111 # optional namespace part manually.
112 required = trim_namespace_parts(required)
113 optional = trim_namespace_parts(optional)
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100114
Spandan Dasf8807422021-08-25 20:01:17 +0000115 if manifest_required == required and manifest_optional == optional:
116 return None
Colin Cross72119102019-05-20 13:14:18 -0700117
Spandan Dasf8807422021-08-25 20:01:17 +0000118 #pylint: disable=line-too-long
119 errmsg = ''.join([
120 'mismatch in the <uses-library> tags between the build system and the '
121 'manifest:\n',
122 '\t- required libraries in build system: [%s]\n' % ', '.join(required),
123 '\t vs. in the manifest: [%s]\n' %
124 ', '.join(manifest_required),
125 '\t- optional libraries in build system: [%s]\n' % ', '.join(optional),
126 '\t vs. in the manifest: [%s]\n' %
127 ', '.join(manifest_optional),
128 '\t- tags in the manifest (%s):\n' % path,
129 '\t\t%s\n' % '\t\t'.join(tags),
130 'note: the following options are available:\n',
131 '\t- to temporarily disable the check on command line, rebuild with ',
132 'RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" ',
133 'and disable AOT-compilation in dexpreopt)\n',
134 '\t- to temporarily disable the check for the whole product, set ',
135 'PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles\n',
136 '\t- to fix the check, make build system properties coherent with the '
137 'manifest\n', '\t- see build/make/Changes.md for details\n'
138 ])
139 #pylint: enable=line-too-long
Colin Cross72119102019-05-20 13:14:18 -0700140
Spandan Dasf8807422021-08-25 20:01:17 +0000141 if not relax:
142 raise ManifestMismatchError(errmsg)
Colin Cross72119102019-05-20 13:14:18 -0700143
Spandan Dasf8807422021-08-25 20:01:17 +0000144 return errmsg
Colin Cross72119102019-05-20 13:14:18 -0700145
Colin Cross72119102019-05-20 13:14:18 -0700146
Spandan Dasf8807422021-08-25 20:01:17 +0000147MODULE_NAMESPACE = re.compile('^//[^:]+:')
148
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100149
150def trim_namespace_parts(modules):
Spandan Dasf8807422021-08-25 20:01:17 +0000151 """Trim the namespace part of each module, if present.
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100152
Spandan Dasf8807422021-08-25 20:01:17 +0000153 Leave only the name.
154 """
155
156 trimmed = []
157 for module in modules:
158 trimmed.append(MODULE_NAMESPACE.sub('', module))
159 return trimmed
Ulya Trafimovich1b513452021-07-20 14:27:32 +0100160
161
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000162def extract_uses_libs_apk(badging):
Spandan Dasf8807422021-08-25 20:01:17 +0000163 """Extract <uses-library> tags from the manifest of an APK."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000164
Spandan Dasf8807422021-08-25 20:01:17 +0000165 pattern = re.compile("^uses-library(-not-required)?:'(.*)'$", re.MULTILINE)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000166
Spandan Dasf8807422021-08-25 20:01:17 +0000167 required = []
168 optional = []
169 lines = []
170 for match in re.finditer(pattern, badging):
171 lines.append(match.group(0))
172 libname = match.group(2)
173 if match.group(1) is None:
174 required.append(libname)
175 else:
176 optional.append(libname)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000177
Spandan Dasf8807422021-08-25 20:01:17 +0000178 required = first_unique_elements(required)
179 optional = first_unique_elements(optional)
180 tags = first_unique_elements(lines)
181 return required, optional, tags
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000182
183
Spandan Dasf8807422021-08-25 20:01:17 +0000184def extract_uses_libs_xml(xml): #pylint: disable=inconsistent-return-statements
185 """Extract <uses-library> tags from the manifest."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000186
Spandan Dasf8807422021-08-25 20:01:17 +0000187 manifest = parse_manifest(xml)
188 elems = get_children_with_tag(manifest, 'application')
189 application = elems[0] if len(elems) == 1 else None
190 if len(elems) > 1: #pylint: disable=no-else-raise
191 raise RuntimeError('found multiple <application> tags')
192 elif not elems:
193 if uses_libraries or optional_uses_libraries: #pylint: disable=undefined-variable
194 raise ManifestMismatchError('no <application> tag found')
195 return
Colin Cross72119102019-05-20 13:14:18 -0700196
Spandan Dasf8807422021-08-25 20:01:17 +0000197 libs = get_children_with_tag(application, 'uses-library')
Colin Cross72119102019-05-20 13:14:18 -0700198
Spandan Dasf8807422021-08-25 20:01:17 +0000199 required = [uses_library_name(x) for x in libs if uses_library_required(x)]
200 optional = [
201 uses_library_name(x) for x in libs if not uses_library_required(x)
202 ]
Colin Cross72119102019-05-20 13:14:18 -0700203
Spandan Dasf8807422021-08-25 20:01:17 +0000204 # render <uses-library> tags as XML for a pretty error message
205 tags = []
206 for lib in libs:
207 tags.append(lib.toprettyxml())
Ulya Trafimovichbb7513d2021-03-30 17:15:16 +0100208
Spandan Dasf8807422021-08-25 20:01:17 +0000209 required = first_unique_elements(required)
210 optional = first_unique_elements(optional)
211 tags = first_unique_elements(tags)
212 return required, optional, tags
Colin Cross72119102019-05-20 13:14:18 -0700213
214
215def first_unique_elements(l):
Spandan Dasf8807422021-08-25 20:01:17 +0000216 result = []
217 for x in l:
218 if x not in result:
219 result.append(x)
220 return result
Colin Cross72119102019-05-20 13:14:18 -0700221
222
223def uses_library_name(lib):
Spandan Dasf8807422021-08-25 20:01:17 +0000224 """Extract the name attribute of a uses-library tag.
Colin Cross72119102019-05-20 13:14:18 -0700225
226 Args:
227 lib: a <uses-library> tag.
Spandan Dasf8807422021-08-25 20:01:17 +0000228 """
229 name = lib.getAttributeNodeNS(android_ns, 'name')
230 return name.value if name is not None else ''
Colin Cross72119102019-05-20 13:14:18 -0700231
232
233def uses_library_required(lib):
Spandan Dasf8807422021-08-25 20:01:17 +0000234 """Extract the required attribute of a uses-library tag.
Colin Cross72119102019-05-20 13:14:18 -0700235
236 Args:
237 lib: a <uses-library> tag.
Spandan Dasf8807422021-08-25 20:01:17 +0000238 """
239 required = lib.getAttributeNodeNS(android_ns, 'required')
240 return (required.value == 'true') if required is not None else True
Colin Cross72119102019-05-20 13:14:18 -0700241
242
Spandan Dasf8807422021-08-25 20:01:17 +0000243def extract_target_sdk_version(manifest, is_apk=False):
244 """Returns the targetSdkVersion from the manifest.
Colin Cross72119102019-05-20 13:14:18 -0700245
246 Args:
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000247 manifest: manifest (either parsed XML or aapt dump of APK)
248 is_apk: if the manifest comes from an APK or an XML file
Spandan Dasf8807422021-08-25 20:01:17 +0000249 """
250 if is_apk: #pylint: disable=no-else-return
251 return extract_target_sdk_version_apk(manifest)
252 else:
253 return extract_target_sdk_version_xml(manifest)
Colin Cross72119102019-05-20 13:14:18 -0700254
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000255
256def extract_target_sdk_version_apk(badging):
Spandan Dasf8807422021-08-25 20:01:17 +0000257 """Extract targetSdkVersion tags from the manifest of an APK."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000258
Spandan Dasf8807422021-08-25 20:01:17 +0000259 pattern = re.compile("^targetSdkVersion?:'(.*)'$", re.MULTILINE)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000260
Spandan Dasf8807422021-08-25 20:01:17 +0000261 for match in re.finditer(pattern, badging):
262 return match.group(1)
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000263
Spandan Dasf8807422021-08-25 20:01:17 +0000264 raise RuntimeError('cannot find targetSdkVersion in the manifest')
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000265
266
267def extract_target_sdk_version_xml(xml):
Spandan Dasf8807422021-08-25 20:01:17 +0000268 """Extract targetSdkVersion tags from the manifest."""
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000269
Spandan Dasf8807422021-08-25 20:01:17 +0000270 manifest = parse_manifest(xml)
Colin Cross72119102019-05-20 13:14:18 -0700271
Spandan Dasf8807422021-08-25 20:01:17 +0000272 # Get or insert the uses-sdk element
273 uses_sdk = get_children_with_tag(manifest, 'uses-sdk')
274 if len(uses_sdk) > 1: #pylint: disable=no-else-raise
275 raise RuntimeError('found multiple uses-sdk elements')
276 elif len(uses_sdk) == 0:
277 raise RuntimeError('missing uses-sdk element')
Colin Cross72119102019-05-20 13:14:18 -0700278
Spandan Dasf8807422021-08-25 20:01:17 +0000279 uses_sdk = uses_sdk[0]
Colin Cross72119102019-05-20 13:14:18 -0700280
Spandan Dasf8807422021-08-25 20:01:17 +0000281 min_attr = uses_sdk.getAttributeNodeNS(android_ns, 'minSdkVersion')
282 if min_attr is None:
283 raise RuntimeError('minSdkVersion is not specified')
Colin Cross72119102019-05-20 13:14:18 -0700284
Spandan Dasf8807422021-08-25 20:01:17 +0000285 target_attr = uses_sdk.getAttributeNodeNS(android_ns, 'targetSdkVersion')
286 if target_attr is None:
287 target_attr = min_attr
Colin Cross72119102019-05-20 13:14:18 -0700288
Spandan Dasf8807422021-08-25 20:01:17 +0000289 return target_attr.value
Colin Cross72119102019-05-20 13:14:18 -0700290
291
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000292def load_dexpreopt_configs(configs):
Spandan Dasf8807422021-08-25 20:01:17 +0000293 """Load dexpreopt.config files and map module names to library names."""
294 module_to_libname = {}
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000295
Spandan Dasf8807422021-08-25 20:01:17 +0000296 if configs is None:
297 configs = []
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000298
Spandan Dasf8807422021-08-25 20:01:17 +0000299 for config in configs:
300 with open(config, 'r') as f:
301 contents = json.load(f)
302 module_to_libname[contents['Name']] = contents['ProvidesUsesLibrary']
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000303
Spandan Dasf8807422021-08-25 20:01:17 +0000304 return module_to_libname
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000305
306
307def translate_libnames(modules, module_to_libname):
Spandan Dasf8807422021-08-25 20:01:17 +0000308 """Translate module names into library names using the mapping."""
309 if modules is None:
310 modules = []
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000311
Spandan Dasf8807422021-08-25 20:01:17 +0000312 libnames = []
313 for name in modules:
314 if name in module_to_libname:
315 name = module_to_libname[name]
316 libnames.append(name)
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000317
Spandan Dasf8807422021-08-25 20:01:17 +0000318 return libnames
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000319
320
Colin Cross72119102019-05-20 13:14:18 -0700321def main():
Spandan Dasf8807422021-08-25 20:01:17 +0000322 """Program entry point."""
323 try:
324 args = parse_args()
Colin Cross72119102019-05-20 13:14:18 -0700325
Spandan Dasf8807422021-08-25 20:01:17 +0000326 # The input can be either an XML manifest or an APK, they are parsed and
327 # processed in different ways.
328 is_apk = args.input.endswith('.apk')
329 if is_apk:
330 aapt = args.aapt if args.aapt is not None else 'aapt'
331 manifest = subprocess.check_output(
332 [aapt, 'dump', 'badging', args.input])
333 else:
334 manifest = minidom.parse(args.input)
Colin Cross72119102019-05-20 13:14:18 -0700335
Spandan Dasf8807422021-08-25 20:01:17 +0000336 if args.enforce_uses_libraries:
337 # Load dexpreopt.config files and build a mapping from module
338 # names to library names. This is necessary because build system
339 # addresses libraries by their module name (`uses_libs`,
340 # `optional_uses_libs`, `LOCAL_USES_LIBRARIES`,
341 # `LOCAL_OPTIONAL_LIBRARY_NAMES` all contain module names), while
342 # the manifest addresses libraries by their name.
343 mod_to_lib = load_dexpreopt_configs(args.dexpreopt_configs)
344 required = translate_libnames(args.uses_libraries, mod_to_lib)
345 optional = translate_libnames(args.optional_uses_libraries,
346 mod_to_lib)
Ulya Trafimovich3c902e72021-03-04 18:06:27 +0000347
Spandan Dasf8807422021-08-25 20:01:17 +0000348 # Check if the <uses-library> lists in the build system agree with
349 # those in the manifest. Raise an exception on mismatch, unless the
350 # script was passed a special parameter to suppress exceptions.
351 errmsg = enforce_uses_libraries(manifest, required, optional,
352 args.enforce_uses_libraries_relax,
353 is_apk, args.input)
Ulya Trafimovich8c35fcf2021-02-17 16:23:28 +0000354
Spandan Dasf8807422021-08-25 20:01:17 +0000355 # Create a status file that is empty on success, or contains an
356 # error message on failure. When exceptions are suppressed,
357 # dexpreopt command command will check file size to determine if
358 # the check has failed.
359 if args.enforce_uses_libraries_status:
360 with open(args.enforce_uses_libraries_status, 'w') as f:
Spandan Das3d5cd4d2021-09-20 18:24:56 +0000361 if errmsg is not None:
Spandan Dasf8807422021-08-25 20:01:17 +0000362 f.write('%s\n' % errmsg)
Colin Cross72119102019-05-20 13:14:18 -0700363
Spandan Dasf8807422021-08-25 20:01:17 +0000364 if args.extract_target_sdk_version:
365 try:
366 print(extract_target_sdk_version(manifest, is_apk))
367 except: #pylint: disable=bare-except
368 # Failed; don't crash, return "any" SDK version. This will
369 # result in dexpreopt not adding any compatibility libraries.
370 print(10000)
Colin Cross72119102019-05-20 13:14:18 -0700371
Spandan Dasf8807422021-08-25 20:01:17 +0000372 if args.output:
373 # XML output is supposed to be written only when this script is
374 # invoked with XML input manifest, not with an APK.
375 if is_apk:
376 raise RuntimeError('cannot save APK manifest as XML')
Ulya Trafimovich0aba2522021-03-03 16:38:37 +0000377
Spandan Dasf8807422021-08-25 20:01:17 +0000378 with open(args.output, 'wb') as f:
379 write_xml(f, manifest)
Colin Cross72119102019-05-20 13:14:18 -0700380
Spandan Dasf8807422021-08-25 20:01:17 +0000381 # pylint: disable=broad-except
382 except Exception as err:
383 print('error: ' + str(err), file=sys.stderr)
384 sys.exit(-1)
385
Colin Cross72119102019-05-20 13:14:18 -0700386
387if __name__ == '__main__':
Spandan Dasf8807422021-08-25 20:01:17 +0000388 main()