xref: /aosp_15_r20/external/cronet/build/android/gyp/aar.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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