xref: /aosp_15_r20/external/angle/build/android/gyp/aar.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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