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