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