1*6777b538SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*6777b538SAndroid Build Coastguard Worker# 3*6777b538SAndroid Build Coastguard Worker# Copyright 2016 The Chromium Authors 4*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 5*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file. 6*6777b538SAndroid Build Coastguard Worker 7*6777b538SAndroid Build Coastguard Worker"""Processes an Android AAR file.""" 8*6777b538SAndroid Build Coastguard Worker 9*6777b538SAndroid Build Coastguard Workerimport argparse 10*6777b538SAndroid Build Coastguard Workerimport os 11*6777b538SAndroid Build Coastguard Workerimport posixpath 12*6777b538SAndroid Build Coastguard Workerimport re 13*6777b538SAndroid Build Coastguard Workerimport shutil 14*6777b538SAndroid Build Coastguard Workerimport sys 15*6777b538SAndroid Build Coastguard Workerfrom xml.etree import ElementTree 16*6777b538SAndroid Build Coastguard Workerimport zipfile 17*6777b538SAndroid Build Coastguard Worker 18*6777b538SAndroid Build Coastguard Workerfrom util import build_utils 19*6777b538SAndroid Build Coastguard Workerimport action_helpers # build_utils adds //build to sys.path. 20*6777b538SAndroid Build Coastguard Workerimport gn_helpers 21*6777b538SAndroid Build Coastguard Worker 22*6777b538SAndroid Build Coastguard Worker 23*6777b538SAndroid Build Coastguard Worker_PROGUARD_TXT = 'proguard.txt' 24*6777b538SAndroid Build Coastguard Worker 25*6777b538SAndroid Build Coastguard Worker 26*6777b538SAndroid Build Coastguard Workerdef _GetManifestPackage(doc): 27*6777b538SAndroid Build Coastguard Worker """Returns the package specified in the manifest. 28*6777b538SAndroid Build Coastguard Worker 29*6777b538SAndroid Build Coastguard Worker Args: 30*6777b538SAndroid Build Coastguard Worker doc: an XML tree parsed by ElementTree 31*6777b538SAndroid Build Coastguard Worker 32*6777b538SAndroid Build Coastguard Worker Returns: 33*6777b538SAndroid Build Coastguard Worker String representing the package name. 34*6777b538SAndroid Build Coastguard Worker """ 35*6777b538SAndroid Build Coastguard Worker return doc.attrib['package'] 36*6777b538SAndroid Build Coastguard Worker 37*6777b538SAndroid Build Coastguard Worker 38*6777b538SAndroid Build Coastguard Workerdef _IsManifestEmpty(doc): 39*6777b538SAndroid Build Coastguard Worker """Decides whether the given manifest has merge-worthy elements. 40*6777b538SAndroid Build Coastguard Worker 41*6777b538SAndroid Build Coastguard Worker E.g.: <activity>, <service>, etc. 42*6777b538SAndroid Build Coastguard Worker 43*6777b538SAndroid Build Coastguard Worker Args: 44*6777b538SAndroid Build Coastguard Worker doc: an XML tree parsed by ElementTree 45*6777b538SAndroid Build Coastguard Worker 46*6777b538SAndroid Build Coastguard Worker Returns: 47*6777b538SAndroid Build Coastguard Worker Whether the manifest has merge-worthy elements. 48*6777b538SAndroid Build Coastguard Worker """ 49*6777b538SAndroid Build Coastguard Worker for node in doc: 50*6777b538SAndroid Build Coastguard Worker if node.tag == 'application': 51*6777b538SAndroid Build Coastguard Worker if list(node): 52*6777b538SAndroid Build Coastguard Worker return False 53*6777b538SAndroid Build Coastguard Worker elif node.tag != 'uses-sdk': 54*6777b538SAndroid Build Coastguard Worker return False 55*6777b538SAndroid Build Coastguard Worker 56*6777b538SAndroid Build Coastguard Worker return True 57*6777b538SAndroid Build Coastguard Worker 58*6777b538SAndroid Build Coastguard Worker 59*6777b538SAndroid Build Coastguard Workerdef _CreateInfo(aar_file, resource_exclusion_globs): 60*6777b538SAndroid Build Coastguard Worker """Extracts and return .info data from an .aar file. 61*6777b538SAndroid Build Coastguard Worker 62*6777b538SAndroid Build Coastguard Worker Args: 63*6777b538SAndroid Build Coastguard Worker aar_file: Path to an input .aar file. 64*6777b538SAndroid Build Coastguard Worker resource_exclusion_globs: List of globs that exclude res/ files. 65*6777b538SAndroid Build Coastguard Worker 66*6777b538SAndroid Build Coastguard Worker Returns: 67*6777b538SAndroid Build Coastguard Worker A dict containing .info data. 68*6777b538SAndroid Build Coastguard Worker """ 69*6777b538SAndroid Build Coastguard Worker data = {} 70*6777b538SAndroid Build Coastguard Worker data['aidl'] = [] 71*6777b538SAndroid Build Coastguard Worker data['assets'] = [] 72*6777b538SAndroid Build Coastguard Worker data['resources'] = [] 73*6777b538SAndroid Build Coastguard Worker data['subjars'] = [] 74*6777b538SAndroid Build Coastguard Worker data['subjar_tuples'] = [] 75*6777b538SAndroid Build Coastguard Worker data['has_classes_jar'] = False 76*6777b538SAndroid Build Coastguard Worker data['has_proguard_flags'] = False 77*6777b538SAndroid Build Coastguard Worker data['has_native_libraries'] = False 78*6777b538SAndroid Build Coastguard Worker data['has_r_text_file'] = False 79*6777b538SAndroid Build Coastguard Worker with zipfile.ZipFile(aar_file) as z: 80*6777b538SAndroid Build Coastguard Worker manifest_xml = ElementTree.fromstring(z.read('AndroidManifest.xml')) 81*6777b538SAndroid Build Coastguard Worker data['is_manifest_empty'] = _IsManifestEmpty(manifest_xml) 82*6777b538SAndroid Build Coastguard Worker manifest_package = _GetManifestPackage(manifest_xml) 83*6777b538SAndroid Build Coastguard Worker if manifest_package: 84*6777b538SAndroid Build Coastguard Worker data['manifest_package'] = manifest_package 85*6777b538SAndroid Build Coastguard Worker 86*6777b538SAndroid Build Coastguard Worker for name in z.namelist(): 87*6777b538SAndroid Build Coastguard Worker if name.endswith('/'): 88*6777b538SAndroid Build Coastguard Worker continue 89*6777b538SAndroid Build Coastguard Worker if name.startswith('aidl/'): 90*6777b538SAndroid Build Coastguard Worker data['aidl'].append(name) 91*6777b538SAndroid Build Coastguard Worker elif name.startswith('res/'): 92*6777b538SAndroid Build Coastguard Worker if not build_utils.MatchesGlob(name, resource_exclusion_globs): 93*6777b538SAndroid Build Coastguard Worker data['resources'].append(name) 94*6777b538SAndroid Build Coastguard Worker elif name.startswith('libs/') and name.endswith('.jar'): 95*6777b538SAndroid Build Coastguard Worker label = posixpath.basename(name)[:-4] 96*6777b538SAndroid Build Coastguard Worker label = re.sub(r'[^a-zA-Z0-9._]', '_', label) 97*6777b538SAndroid Build Coastguard Worker data['subjars'].append(name) 98*6777b538SAndroid Build Coastguard Worker data['subjar_tuples'].append([label, name]) 99*6777b538SAndroid Build Coastguard Worker elif name.startswith('assets/'): 100*6777b538SAndroid Build Coastguard Worker data['assets'].append(name) 101*6777b538SAndroid Build Coastguard Worker elif name.startswith('jni/'): 102*6777b538SAndroid Build Coastguard Worker data['has_native_libraries'] = True 103*6777b538SAndroid Build Coastguard Worker if 'native_libraries' in data: 104*6777b538SAndroid Build Coastguard Worker data['native_libraries'].append(name) 105*6777b538SAndroid Build Coastguard Worker else: 106*6777b538SAndroid Build Coastguard Worker data['native_libraries'] = [name] 107*6777b538SAndroid Build Coastguard Worker elif name == 'classes.jar': 108*6777b538SAndroid Build Coastguard Worker data['has_classes_jar'] = True 109*6777b538SAndroid Build Coastguard Worker elif name == _PROGUARD_TXT: 110*6777b538SAndroid Build Coastguard Worker data['has_proguard_flags'] = True 111*6777b538SAndroid Build Coastguard Worker elif name == 'R.txt': 112*6777b538SAndroid Build Coastguard Worker # Some AARs, e.g. gvr_controller_java, have empty R.txt. Such AARs 113*6777b538SAndroid Build Coastguard Worker # have no resources as well. We treat empty R.txt as having no R.txt. 114*6777b538SAndroid Build Coastguard Worker data['has_r_text_file'] = bool(z.read('R.txt').strip()) 115*6777b538SAndroid Build Coastguard Worker 116*6777b538SAndroid Build Coastguard Worker return data 117*6777b538SAndroid Build Coastguard Worker 118*6777b538SAndroid Build Coastguard Worker 119*6777b538SAndroid Build Coastguard Workerdef _PerformExtract(aar_file, output_dir, name_allowlist): 120*6777b538SAndroid Build Coastguard Worker with build_utils.TempDir() as tmp_dir: 121*6777b538SAndroid Build Coastguard Worker tmp_dir = os.path.join(tmp_dir, 'staging') 122*6777b538SAndroid Build Coastguard Worker os.mkdir(tmp_dir) 123*6777b538SAndroid Build Coastguard Worker build_utils.ExtractAll( 124*6777b538SAndroid Build Coastguard Worker aar_file, path=tmp_dir, predicate=name_allowlist.__contains__) 125*6777b538SAndroid Build Coastguard Worker # Write a breadcrumb so that SuperSize can attribute files back to the .aar. 126*6777b538SAndroid Build Coastguard Worker with open(os.path.join(tmp_dir, 'source.info'), 'w') as f: 127*6777b538SAndroid Build Coastguard Worker f.write('source={}\n'.format(aar_file)) 128*6777b538SAndroid Build Coastguard Worker 129*6777b538SAndroid Build Coastguard Worker shutil.rmtree(output_dir, ignore_errors=True) 130*6777b538SAndroid Build Coastguard Worker shutil.move(tmp_dir, output_dir) 131*6777b538SAndroid Build Coastguard Worker 132*6777b538SAndroid Build Coastguard Worker 133*6777b538SAndroid Build Coastguard Workerdef _AddCommonArgs(parser): 134*6777b538SAndroid Build Coastguard Worker parser.add_argument( 135*6777b538SAndroid Build Coastguard Worker 'aar_file', help='Path to the AAR file.', type=os.path.normpath) 136*6777b538SAndroid Build Coastguard Worker parser.add_argument('--ignore-resources', 137*6777b538SAndroid Build Coastguard Worker action='store_true', 138*6777b538SAndroid Build Coastguard Worker help='Whether to skip extraction of res/') 139*6777b538SAndroid Build Coastguard Worker parser.add_argument('--resource-exclusion-globs', 140*6777b538SAndroid Build Coastguard Worker help='GN list of globs for res/ files to ignore') 141*6777b538SAndroid Build Coastguard Worker 142*6777b538SAndroid Build Coastguard Worker 143*6777b538SAndroid Build Coastguard Workerdef main(): 144*6777b538SAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description=__doc__) 145*6777b538SAndroid Build Coastguard Worker command_parsers = parser.add_subparsers(dest='command') 146*6777b538SAndroid Build Coastguard Worker subp = command_parsers.add_parser( 147*6777b538SAndroid Build Coastguard Worker 'list', help='Output a GN scope describing the contents of the .aar.') 148*6777b538SAndroid Build Coastguard Worker _AddCommonArgs(subp) 149*6777b538SAndroid Build Coastguard Worker subp.add_argument('--output', help='Output file.', default='-') 150*6777b538SAndroid Build Coastguard Worker 151*6777b538SAndroid Build Coastguard Worker subp = command_parsers.add_parser('extract', help='Extracts the .aar') 152*6777b538SAndroid Build Coastguard Worker _AddCommonArgs(subp) 153*6777b538SAndroid Build Coastguard Worker subp.add_argument( 154*6777b538SAndroid Build Coastguard Worker '--output-dir', 155*6777b538SAndroid Build Coastguard Worker help='Output directory for the extracted files.', 156*6777b538SAndroid Build Coastguard Worker required=True, 157*6777b538SAndroid Build Coastguard Worker type=os.path.normpath) 158*6777b538SAndroid Build Coastguard Worker subp.add_argument( 159*6777b538SAndroid Build Coastguard Worker '--assert-info-file', 160*6777b538SAndroid Build Coastguard Worker help='Path to .info file. Asserts that it matches what ' 161*6777b538SAndroid Build Coastguard Worker '"list" would output.', 162*6777b538SAndroid Build Coastguard Worker type=argparse.FileType('r')) 163*6777b538SAndroid Build Coastguard Worker 164*6777b538SAndroid Build Coastguard Worker args = parser.parse_args() 165*6777b538SAndroid Build Coastguard Worker 166*6777b538SAndroid Build Coastguard Worker args.resource_exclusion_globs = action_helpers.parse_gn_list( 167*6777b538SAndroid Build Coastguard Worker args.resource_exclusion_globs) 168*6777b538SAndroid Build Coastguard Worker if args.ignore_resources: 169*6777b538SAndroid Build Coastguard Worker args.resource_exclusion_globs.append('res/*') 170*6777b538SAndroid Build Coastguard Worker 171*6777b538SAndroid Build Coastguard Worker aar_info = _CreateInfo(args.aar_file, args.resource_exclusion_globs) 172*6777b538SAndroid Build Coastguard Worker formatted_info = """\ 173*6777b538SAndroid Build Coastguard Worker# Generated by //build/android/gyp/aar.py 174*6777b538SAndroid Build Coastguard Worker# To regenerate, use "update_android_aar_prebuilts = true" and run "gn gen". 175*6777b538SAndroid Build Coastguard Worker 176*6777b538SAndroid Build Coastguard Worker""" + gn_helpers.ToGNString(aar_info, pretty=True) 177*6777b538SAndroid Build Coastguard Worker 178*6777b538SAndroid Build Coastguard Worker if args.command == 'extract': 179*6777b538SAndroid Build Coastguard Worker if args.assert_info_file: 180*6777b538SAndroid Build Coastguard Worker cached_info = args.assert_info_file.read() 181*6777b538SAndroid Build Coastguard Worker if formatted_info != cached_info: 182*6777b538SAndroid Build Coastguard Worker raise Exception('android_aar_prebuilt() cached .info file is ' 183*6777b538SAndroid Build Coastguard Worker 'out-of-date. Run gn gen with ' 184*6777b538SAndroid Build Coastguard Worker 'update_android_aar_prebuilts=true to update it.') 185*6777b538SAndroid Build Coastguard Worker 186*6777b538SAndroid Build Coastguard Worker # Extract all files except for filtered res/ files. 187*6777b538SAndroid Build Coastguard Worker with zipfile.ZipFile(args.aar_file) as zf: 188*6777b538SAndroid Build Coastguard Worker names = {n for n in zf.namelist() if not n.startswith('res/')} 189*6777b538SAndroid Build Coastguard Worker names.update(aar_info['resources']) 190*6777b538SAndroid Build Coastguard Worker 191*6777b538SAndroid Build Coastguard Worker _PerformExtract(args.aar_file, args.output_dir, names) 192*6777b538SAndroid Build Coastguard Worker 193*6777b538SAndroid Build Coastguard Worker elif args.command == 'list': 194*6777b538SAndroid Build Coastguard Worker aar_output_present = args.output != '-' and os.path.isfile(args.output) 195*6777b538SAndroid Build Coastguard Worker if aar_output_present: 196*6777b538SAndroid Build Coastguard Worker # Some .info files are read-only, for examples the cipd-controlled ones 197*6777b538SAndroid Build Coastguard Worker # under third_party/android_deps/repository. To deal with these, first 198*6777b538SAndroid Build Coastguard Worker # that its content is correct, and if it is, exit without touching 199*6777b538SAndroid Build Coastguard Worker # the file system. 200*6777b538SAndroid Build Coastguard Worker file_info = open(args.output, 'r').read() 201*6777b538SAndroid Build Coastguard Worker if file_info == formatted_info: 202*6777b538SAndroid Build Coastguard Worker return 203*6777b538SAndroid Build Coastguard Worker 204*6777b538SAndroid Build Coastguard Worker # Try to write the file. This may fail for read-only ones that were 205*6777b538SAndroid Build Coastguard Worker # not updated. 206*6777b538SAndroid Build Coastguard Worker try: 207*6777b538SAndroid Build Coastguard Worker with open(args.output, 'w') as f: 208*6777b538SAndroid Build Coastguard Worker f.write(formatted_info) 209*6777b538SAndroid Build Coastguard Worker except IOError as e: 210*6777b538SAndroid Build Coastguard Worker if not aar_output_present: 211*6777b538SAndroid Build Coastguard Worker raise e 212*6777b538SAndroid Build Coastguard Worker raise Exception('Could not update output file: %s\n' % args.output) from e 213*6777b538SAndroid Build Coastguard Worker 214*6777b538SAndroid Build Coastguard Worker 215*6777b538SAndroid Build Coastguard Workerif __name__ == '__main__': 216*6777b538SAndroid Build Coastguard Worker sys.exit(main()) 217