xref: /aosp_15_r20/external/angle/build/android/gyp/compile_resources.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*8975f5c5SAndroid Build Coastguard Worker#
3*8975f5c5SAndroid Build Coastguard Worker# Copyright 2012 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"""Compile Android resources into an intermediate APK.
8*8975f5c5SAndroid Build Coastguard Worker
9*8975f5c5SAndroid Build Coastguard WorkerThis can also generate an R.txt, and an .srcjar file containing the proper
10*8975f5c5SAndroid Build Coastguard Workerfinal R.java class for all resource packages the APK depends on.
11*8975f5c5SAndroid Build Coastguard Worker
12*8975f5c5SAndroid Build Coastguard WorkerThis will crunch images with aapt2.
13*8975f5c5SAndroid Build Coastguard Worker"""
14*8975f5c5SAndroid Build Coastguard Worker
15*8975f5c5SAndroid Build Coastguard Workerimport argparse
16*8975f5c5SAndroid Build Coastguard Workerimport collections
17*8975f5c5SAndroid Build Coastguard Workerimport contextlib
18*8975f5c5SAndroid Build Coastguard Workerimport filecmp
19*8975f5c5SAndroid Build Coastguard Workerimport hashlib
20*8975f5c5SAndroid Build Coastguard Workerimport logging
21*8975f5c5SAndroid Build Coastguard Workerimport os
22*8975f5c5SAndroid Build Coastguard Workerimport pathlib
23*8975f5c5SAndroid Build Coastguard Workerimport re
24*8975f5c5SAndroid Build Coastguard Workerimport shutil
25*8975f5c5SAndroid Build Coastguard Workerimport subprocess
26*8975f5c5SAndroid Build Coastguard Workerimport sys
27*8975f5c5SAndroid Build Coastguard Workerimport textwrap
28*8975f5c5SAndroid Build Coastguard Workerfrom xml.etree import ElementTree
29*8975f5c5SAndroid Build Coastguard Worker
30*8975f5c5SAndroid Build Coastguard Workerfrom util import build_utils
31*8975f5c5SAndroid Build Coastguard Workerfrom util import diff_utils
32*8975f5c5SAndroid Build Coastguard Workerfrom util import manifest_utils
33*8975f5c5SAndroid Build Coastguard Workerfrom util import parallel
34*8975f5c5SAndroid Build Coastguard Workerfrom util import protoresources
35*8975f5c5SAndroid Build Coastguard Workerfrom util import resource_utils
36*8975f5c5SAndroid Build Coastguard Workerimport action_helpers  # build_utils adds //build to sys.path.
37*8975f5c5SAndroid Build Coastguard Workerimport zip_helpers
38*8975f5c5SAndroid Build Coastguard Worker
39*8975f5c5SAndroid Build Coastguard Worker
40*8975f5c5SAndroid Build Coastguard Worker# Pngs that we shouldn't convert to webp. Please add rationale when updating.
41*8975f5c5SAndroid Build Coastguard Worker_PNG_WEBP_EXCLUSION_PATTERN = re.compile('|'.join([
42*8975f5c5SAndroid Build Coastguard Worker    # Android requires pngs for 9-patch images.
43*8975f5c5SAndroid Build Coastguard Worker    r'.*\.9\.png',
44*8975f5c5SAndroid Build Coastguard Worker    # Daydream requires pngs for icon files.
45*8975f5c5SAndroid Build Coastguard Worker    r'.*daydream_icon_.*\.png'
46*8975f5c5SAndroid Build Coastguard Worker]))
47*8975f5c5SAndroid Build Coastguard Worker
48*8975f5c5SAndroid Build Coastguard Worker
49*8975f5c5SAndroid Build Coastguard Workerdef _ParseArgs(args):
50*8975f5c5SAndroid Build Coastguard Worker  """Parses command line options.
51*8975f5c5SAndroid Build Coastguard Worker
52*8975f5c5SAndroid Build Coastguard Worker  Returns:
53*8975f5c5SAndroid Build Coastguard Worker    An options object as from argparse.ArgumentParser.parse_args()
54*8975f5c5SAndroid Build Coastguard Worker  """
55*8975f5c5SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(description=__doc__)
56*8975f5c5SAndroid Build Coastguard Worker
57*8975f5c5SAndroid Build Coastguard Worker  input_opts = parser.add_argument_group('Input options')
58*8975f5c5SAndroid Build Coastguard Worker  output_opts = parser.add_argument_group('Output options')
59*8975f5c5SAndroid Build Coastguard Worker
60*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument('--include-resources',
61*8975f5c5SAndroid Build Coastguard Worker                          action='append',
62*8975f5c5SAndroid Build Coastguard Worker                          required=True,
63*8975f5c5SAndroid Build Coastguard Worker                          help='Paths to arsc resource files used to link '
64*8975f5c5SAndroid Build Coastguard Worker                          'against. Can be specified multiple times.')
65*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
66*8975f5c5SAndroid Build Coastguard Worker      '--dependencies-res-zips',
67*8975f5c5SAndroid Build Coastguard Worker      default=[],
68*8975f5c5SAndroid Build Coastguard Worker      help='Resources zip archives from dependents. Required to '
69*8975f5c5SAndroid Build Coastguard Worker      'resolve @type/foo references into dependent libraries.')
70*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
71*8975f5c5SAndroid Build Coastguard Worker      '--extra-res-packages',
72*8975f5c5SAndroid Build Coastguard Worker      help='Additional package names to generate R.java files for.')
73*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
74*8975f5c5SAndroid Build Coastguard Worker      '--aapt2-path', required=True, help='Path to the Android aapt2 tool.')
75*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
76*8975f5c5SAndroid Build Coastguard Worker      '--android-manifest', required=True, help='AndroidManifest.xml path.')
77*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
78*8975f5c5SAndroid Build Coastguard Worker      '--r-java-root-package-name',
79*8975f5c5SAndroid Build Coastguard Worker      default='base',
80*8975f5c5SAndroid Build Coastguard Worker      help='Short package name for this target\'s root R java file (ex. '
81*8975f5c5SAndroid Build Coastguard Worker      'input of "base" would become gen.base_module). Defaults to "base".')
82*8975f5c5SAndroid Build Coastguard Worker  group = input_opts.add_mutually_exclusive_group()
83*8975f5c5SAndroid Build Coastguard Worker  group.add_argument(
84*8975f5c5SAndroid Build Coastguard Worker      '--shared-resources',
85*8975f5c5SAndroid Build Coastguard Worker      action='store_true',
86*8975f5c5SAndroid Build Coastguard Worker      help='Make all resources in R.java non-final and allow the resource IDs '
87*8975f5c5SAndroid Build Coastguard Worker      'to be reset to a different package index when the apk is loaded by '
88*8975f5c5SAndroid Build Coastguard Worker      'another application at runtime.')
89*8975f5c5SAndroid Build Coastguard Worker  group.add_argument(
90*8975f5c5SAndroid Build Coastguard Worker      '--app-as-shared-lib',
91*8975f5c5SAndroid Build Coastguard Worker      action='store_true',
92*8975f5c5SAndroid Build Coastguard Worker      help='Same as --shared-resources, but also ensures all resource IDs are '
93*8975f5c5SAndroid Build Coastguard Worker      'directly usable from the APK loaded as an application.')
94*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
95*8975f5c5SAndroid Build Coastguard Worker      '--package-id',
96*8975f5c5SAndroid Build Coastguard Worker      type=int,
97*8975f5c5SAndroid Build Coastguard Worker      help='Decimal integer representing custom package ID for resources '
98*8975f5c5SAndroid Build Coastguard Worker      '(instead of 127==0x7f). Cannot be used with --shared-resources.')
99*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
100*8975f5c5SAndroid Build Coastguard Worker      '--package-name',
101*8975f5c5SAndroid Build Coastguard Worker      help='Package name that will be used to create R class.')
102*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
103*8975f5c5SAndroid Build Coastguard Worker      '--rename-manifest-package', help='Package name to force AAPT to use.')
104*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
105*8975f5c5SAndroid Build Coastguard Worker      '--arsc-package-name',
106*8975f5c5SAndroid Build Coastguard Worker      help='Package name to set in manifest of resources.arsc file. This is '
107*8975f5c5SAndroid Build Coastguard Worker      'only used for apks under test.')
108*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
109*8975f5c5SAndroid Build Coastguard Worker      '--shared-resources-allowlist',
110*8975f5c5SAndroid Build Coastguard Worker      help='An R.txt file acting as a allowlist for resources that should be '
111*8975f5c5SAndroid Build Coastguard Worker      'non-final and have their package ID changed at runtime in R.java. '
112*8975f5c5SAndroid Build Coastguard Worker      'Implies and overrides --shared-resources.')
113*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
114*8975f5c5SAndroid Build Coastguard Worker      '--shared-resources-allowlist-locales',
115*8975f5c5SAndroid Build Coastguard Worker      default='[]',
116*8975f5c5SAndroid Build Coastguard Worker      help='Optional GN-list of locales. If provided, all strings corresponding'
117*8975f5c5SAndroid Build Coastguard Worker      ' to this locale list will be kept in the final output for the '
118*8975f5c5SAndroid Build Coastguard Worker      'resources identified through --shared-resources-allowlist, even '
119*8975f5c5SAndroid Build Coastguard Worker      'if --locale-allowlist is being used.')
120*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
121*8975f5c5SAndroid Build Coastguard Worker      '--use-resource-ids-path',
122*8975f5c5SAndroid Build Coastguard Worker      help='Use resource IDs generated by aapt --emit-ids.')
123*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
124*8975f5c5SAndroid Build Coastguard Worker      '--debuggable',
125*8975f5c5SAndroid Build Coastguard Worker      action='store_true',
126*8975f5c5SAndroid Build Coastguard Worker      help='Whether to add android:debuggable="true".')
127*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument('--static-library-version',
128*8975f5c5SAndroid Build Coastguard Worker                          help='Version code for static library.')
129*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument('--version-code', help='Version code for apk.')
130*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument('--version-name', help='Version name for apk.')
131*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
132*8975f5c5SAndroid Build Coastguard Worker      '--min-sdk-version', required=True, help='android:minSdkVersion for APK.')
133*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
134*8975f5c5SAndroid Build Coastguard Worker      '--target-sdk-version',
135*8975f5c5SAndroid Build Coastguard Worker      required=True,
136*8975f5c5SAndroid Build Coastguard Worker      help="android:targetSdkVersion for APK.")
137*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
138*8975f5c5SAndroid Build Coastguard Worker      '--max-sdk-version',
139*8975f5c5SAndroid Build Coastguard Worker      help="android:maxSdkVersion expected in AndroidManifest.xml.")
140*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
141*8975f5c5SAndroid Build Coastguard Worker      '--manifest-package', help='Package name of the AndroidManifest.xml.')
142*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
143*8975f5c5SAndroid Build Coastguard Worker      '--locale-allowlist',
144*8975f5c5SAndroid Build Coastguard Worker      default='[]',
145*8975f5c5SAndroid Build Coastguard Worker      help='GN list of languages to include. All other language configs will '
146*8975f5c5SAndroid Build Coastguard Worker      'be stripped out. List may include a combination of Android locales '
147*8975f5c5SAndroid Build Coastguard Worker      'or Chrome locales.')
148*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
149*8975f5c5SAndroid Build Coastguard Worker      '--resource-exclusion-regex',
150*8975f5c5SAndroid Build Coastguard Worker      default='',
151*8975f5c5SAndroid Build Coastguard Worker      help='File-based filter for resources (applied before compiling)')
152*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
153*8975f5c5SAndroid Build Coastguard Worker      '--resource-exclusion-exceptions',
154*8975f5c5SAndroid Build Coastguard Worker      default='[]',
155*8975f5c5SAndroid Build Coastguard Worker      help='GN list of globs that say which files to include even '
156*8975f5c5SAndroid Build Coastguard Worker      'when --resource-exclusion-regex is set.')
157*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
158*8975f5c5SAndroid Build Coastguard Worker      '--dependencies-res-zip-overlays',
159*8975f5c5SAndroid Build Coastguard Worker      help='GN list with subset of --dependencies-res-zips to use overlay '
160*8975f5c5SAndroid Build Coastguard Worker      'semantics for.')
161*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
162*8975f5c5SAndroid Build Coastguard Worker      '--values-filter-rules',
163*8975f5c5SAndroid Build Coastguard Worker      help='GN list of source_glob:regex for filtering resources after they '
164*8975f5c5SAndroid Build Coastguard Worker      'are compiled. Use this to filter out entries within values/ files.')
165*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument('--png-to-webp', action='store_true',
166*8975f5c5SAndroid Build Coastguard Worker                          help='Convert png files to webp format.')
167*8975f5c5SAndroid Build Coastguard Worker
168*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument('--webp-binary', default='',
169*8975f5c5SAndroid Build Coastguard Worker                          help='Path to the cwebp binary.')
170*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
171*8975f5c5SAndroid Build Coastguard Worker      '--webp-cache-dir', help='The directory to store webp image cache.')
172*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
173*8975f5c5SAndroid Build Coastguard Worker      '--is-bundle-module',
174*8975f5c5SAndroid Build Coastguard Worker      action='store_true',
175*8975f5c5SAndroid Build Coastguard Worker      help='Whether resources are being generated for a bundle module.')
176*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
177*8975f5c5SAndroid Build Coastguard Worker      '--uses-split',
178*8975f5c5SAndroid Build Coastguard Worker      help='Value to set uses-split to in the AndroidManifest.xml.')
179*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
180*8975f5c5SAndroid Build Coastguard Worker      '--verification-version-code-offset',
181*8975f5c5SAndroid Build Coastguard Worker      help='Subtract this from versionCode for expectation files')
182*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument(
183*8975f5c5SAndroid Build Coastguard Worker      '--verification-library-version-offset',
184*8975f5c5SAndroid Build Coastguard Worker      help='Subtract this from static-library version for expectation files')
185*8975f5c5SAndroid Build Coastguard Worker  input_opts.add_argument('--xml-namespaces',
186*8975f5c5SAndroid Build Coastguard Worker                          action='store_true',
187*8975f5c5SAndroid Build Coastguard Worker                          help='Do not pass --no-xml-namespaces')
188*8975f5c5SAndroid Build Coastguard Worker
189*8975f5c5SAndroid Build Coastguard Worker  action_helpers.add_depfile_arg(output_opts)
190*8975f5c5SAndroid Build Coastguard Worker  output_opts.add_argument('--arsc-path', help='Apk output for arsc format.')
191*8975f5c5SAndroid Build Coastguard Worker  output_opts.add_argument('--proto-path', help='Apk output for proto format.')
192*8975f5c5SAndroid Build Coastguard Worker  output_opts.add_argument(
193*8975f5c5SAndroid Build Coastguard Worker      '--info-path', help='Path to output info file for the partial apk.')
194*8975f5c5SAndroid Build Coastguard Worker  output_opts.add_argument(
195*8975f5c5SAndroid Build Coastguard Worker      '--srcjar-out',
196*8975f5c5SAndroid Build Coastguard Worker      help='Path to srcjar to contain generated R.java.')
197*8975f5c5SAndroid Build Coastguard Worker  output_opts.add_argument('--r-text-out',
198*8975f5c5SAndroid Build Coastguard Worker                           help='Path to store the generated R.txt file.')
199*8975f5c5SAndroid Build Coastguard Worker  output_opts.add_argument(
200*8975f5c5SAndroid Build Coastguard Worker      '--proguard-file', help='Path to proguard.txt generated file.')
201*8975f5c5SAndroid Build Coastguard Worker  output_opts.add_argument(
202*8975f5c5SAndroid Build Coastguard Worker      '--emit-ids-out', help='Path to file produced by aapt2 --emit-ids.')
203*8975f5c5SAndroid Build Coastguard Worker
204*8975f5c5SAndroid Build Coastguard Worker  diff_utils.AddCommandLineFlags(parser)
205*8975f5c5SAndroid Build Coastguard Worker  options = parser.parse_args(args)
206*8975f5c5SAndroid Build Coastguard Worker
207*8975f5c5SAndroid Build Coastguard Worker  options.include_resources = action_helpers.parse_gn_list(
208*8975f5c5SAndroid Build Coastguard Worker      options.include_resources)
209*8975f5c5SAndroid Build Coastguard Worker  options.dependencies_res_zips = action_helpers.parse_gn_list(
210*8975f5c5SAndroid Build Coastguard Worker      options.dependencies_res_zips)
211*8975f5c5SAndroid Build Coastguard Worker  options.extra_res_packages = action_helpers.parse_gn_list(
212*8975f5c5SAndroid Build Coastguard Worker      options.extra_res_packages)
213*8975f5c5SAndroid Build Coastguard Worker  options.locale_allowlist = action_helpers.parse_gn_list(
214*8975f5c5SAndroid Build Coastguard Worker      options.locale_allowlist)
215*8975f5c5SAndroid Build Coastguard Worker  options.shared_resources_allowlist_locales = action_helpers.parse_gn_list(
216*8975f5c5SAndroid Build Coastguard Worker      options.shared_resources_allowlist_locales)
217*8975f5c5SAndroid Build Coastguard Worker  options.resource_exclusion_exceptions = action_helpers.parse_gn_list(
218*8975f5c5SAndroid Build Coastguard Worker      options.resource_exclusion_exceptions)
219*8975f5c5SAndroid Build Coastguard Worker  options.dependencies_res_zip_overlays = action_helpers.parse_gn_list(
220*8975f5c5SAndroid Build Coastguard Worker      options.dependencies_res_zip_overlays)
221*8975f5c5SAndroid Build Coastguard Worker  options.values_filter_rules = action_helpers.parse_gn_list(
222*8975f5c5SAndroid Build Coastguard Worker      options.values_filter_rules)
223*8975f5c5SAndroid Build Coastguard Worker
224*8975f5c5SAndroid Build Coastguard Worker  if not options.arsc_path and not options.proto_path:
225*8975f5c5SAndroid Build Coastguard Worker    parser.error('One of --arsc-path or --proto-path is required.')
226*8975f5c5SAndroid Build Coastguard Worker
227*8975f5c5SAndroid Build Coastguard Worker  if options.package_id and options.shared_resources:
228*8975f5c5SAndroid Build Coastguard Worker    parser.error('--package-id and --shared-resources are mutually exclusive')
229*8975f5c5SAndroid Build Coastguard Worker
230*8975f5c5SAndroid Build Coastguard Worker  if options.static_library_version and (options.static_library_version !=
231*8975f5c5SAndroid Build Coastguard Worker                                         options.version_code):
232*8975f5c5SAndroid Build Coastguard Worker    assert options.static_library_version == options.version_code, (
233*8975f5c5SAndroid Build Coastguard Worker        f'static_library_version={options.static_library_version} must equal '
234*8975f5c5SAndroid Build Coastguard Worker        f'version_code={options.version_code}. Please verify the version code '
235*8975f5c5SAndroid Build Coastguard Worker        'map for this target is defined correctly.')
236*8975f5c5SAndroid Build Coastguard Worker
237*8975f5c5SAndroid Build Coastguard Worker  return options
238*8975f5c5SAndroid Build Coastguard Worker
239*8975f5c5SAndroid Build Coastguard Worker
240*8975f5c5SAndroid Build Coastguard Workerdef _IterFiles(root_dir):
241*8975f5c5SAndroid Build Coastguard Worker  for root, _, files in os.walk(root_dir):
242*8975f5c5SAndroid Build Coastguard Worker    for f in files:
243*8975f5c5SAndroid Build Coastguard Worker      yield os.path.join(root, f)
244*8975f5c5SAndroid Build Coastguard Worker
245*8975f5c5SAndroid Build Coastguard Worker
246*8975f5c5SAndroid Build Coastguard Workerdef _RenameLocaleResourceDirs(resource_dirs, path_info):
247*8975f5c5SAndroid Build Coastguard Worker  """Rename locale resource directories into standard names when necessary.
248*8975f5c5SAndroid Build Coastguard Worker
249*8975f5c5SAndroid Build Coastguard Worker  This is necessary to deal with the fact that older Android releases only
250*8975f5c5SAndroid Build Coastguard Worker  support ISO 639-1 two-letter codes, and sometimes even obsolete versions
251*8975f5c5SAndroid Build Coastguard Worker  of them.
252*8975f5c5SAndroid Build Coastguard Worker
253*8975f5c5SAndroid Build Coastguard Worker  In practice it means:
254*8975f5c5SAndroid Build Coastguard Worker    * 3-letter ISO 639-2 qualifiers are renamed under a corresponding
255*8975f5c5SAndroid Build Coastguard Worker      2-letter one. E.g. for Filipino, strings under values-fil/ will be moved
256*8975f5c5SAndroid Build Coastguard Worker      to a new corresponding values-tl/ sub-directory.
257*8975f5c5SAndroid Build Coastguard Worker
258*8975f5c5SAndroid Build Coastguard Worker    * Modern ISO 639-1 codes will be renamed to their obsolete variant
259*8975f5c5SAndroid Build Coastguard Worker      for Indonesian, Hebrew and Yiddish (e.g. 'values-in/ -> values-id/).
260*8975f5c5SAndroid Build Coastguard Worker
261*8975f5c5SAndroid Build Coastguard Worker    * Norwegian macrolanguage strings will be renamed to Bokmal (main
262*8975f5c5SAndroid Build Coastguard Worker      Norway language). See http://crbug.com/920960. In practice this
263*8975f5c5SAndroid Build Coastguard Worker      means that 'values-no/ -> values-nb/' unless 'values-nb/' already
264*8975f5c5SAndroid Build Coastguard Worker      exists.
265*8975f5c5SAndroid Build Coastguard Worker
266*8975f5c5SAndroid Build Coastguard Worker    * BCP 47 langauge tags will be renamed to an equivalent ISO 639-1
267*8975f5c5SAndroid Build Coastguard Worker      locale qualifier if possible (e.g. 'values-b+en+US/ -> values-en-rUS').
268*8975f5c5SAndroid Build Coastguard Worker
269*8975f5c5SAndroid Build Coastguard Worker  Args:
270*8975f5c5SAndroid Build Coastguard Worker    resource_dirs: list of top-level resource directories.
271*8975f5c5SAndroid Build Coastguard Worker  """
272*8975f5c5SAndroid Build Coastguard Worker  for resource_dir in resource_dirs:
273*8975f5c5SAndroid Build Coastguard Worker    ignore_dirs = {}
274*8975f5c5SAndroid Build Coastguard Worker    for path in _IterFiles(resource_dir):
275*8975f5c5SAndroid Build Coastguard Worker      locale = resource_utils.FindLocaleInStringResourceFilePath(path)
276*8975f5c5SAndroid Build Coastguard Worker      if not locale:
277*8975f5c5SAndroid Build Coastguard Worker        continue
278*8975f5c5SAndroid Build Coastguard Worker      cr_locale = resource_utils.ToChromiumLocaleName(locale)
279*8975f5c5SAndroid Build Coastguard Worker      if not cr_locale:
280*8975f5c5SAndroid Build Coastguard Worker        continue  # Unsupported Android locale qualifier!?
281*8975f5c5SAndroid Build Coastguard Worker      locale2 = resource_utils.ToAndroidLocaleName(cr_locale)
282*8975f5c5SAndroid Build Coastguard Worker      if locale != locale2:
283*8975f5c5SAndroid Build Coastguard Worker        path2 = path.replace('/values-%s/' % locale, '/values-%s/' % locale2)
284*8975f5c5SAndroid Build Coastguard Worker        if path == path2:
285*8975f5c5SAndroid Build Coastguard Worker          raise Exception('Could not substitute locale %s for %s in %s' %
286*8975f5c5SAndroid Build Coastguard Worker                          (locale, locale2, path))
287*8975f5c5SAndroid Build Coastguard Worker
288*8975f5c5SAndroid Build Coastguard Worker        # Ignore rather than rename when the destination resources config
289*8975f5c5SAndroid Build Coastguard Worker        # already exists.
290*8975f5c5SAndroid Build Coastguard Worker        # e.g. some libraries provide both values-nb/ and values-no/.
291*8975f5c5SAndroid Build Coastguard Worker        # e.g. material design provides:
292*8975f5c5SAndroid Build Coastguard Worker        # * res/values-rUS/values-rUS.xml
293*8975f5c5SAndroid Build Coastguard Worker        # * res/values-b+es+419/values-b+es+419.xml
294*8975f5c5SAndroid Build Coastguard Worker        config_dir = os.path.dirname(path2)
295*8975f5c5SAndroid Build Coastguard Worker        already_has_renamed_config = ignore_dirs.get(config_dir)
296*8975f5c5SAndroid Build Coastguard Worker        if already_has_renamed_config is None:
297*8975f5c5SAndroid Build Coastguard Worker          # Cache the result of the first time the directory is encountered
298*8975f5c5SAndroid Build Coastguard Worker          # since subsequent encounters will find the directory already exists
299*8975f5c5SAndroid Build Coastguard Worker          # (due to the rename).
300*8975f5c5SAndroid Build Coastguard Worker          already_has_renamed_config = os.path.exists(config_dir)
301*8975f5c5SAndroid Build Coastguard Worker          ignore_dirs[config_dir] = already_has_renamed_config
302*8975f5c5SAndroid Build Coastguard Worker        if already_has_renamed_config:
303*8975f5c5SAndroid Build Coastguard Worker          continue
304*8975f5c5SAndroid Build Coastguard Worker
305*8975f5c5SAndroid Build Coastguard Worker        build_utils.MakeDirectory(os.path.dirname(path2))
306*8975f5c5SAndroid Build Coastguard Worker        shutil.move(path, path2)
307*8975f5c5SAndroid Build Coastguard Worker        path_info.RegisterRename(
308*8975f5c5SAndroid Build Coastguard Worker            os.path.relpath(path, resource_dir),
309*8975f5c5SAndroid Build Coastguard Worker            os.path.relpath(path2, resource_dir))
310*8975f5c5SAndroid Build Coastguard Worker
311*8975f5c5SAndroid Build Coastguard Worker
312*8975f5c5SAndroid Build Coastguard Workerdef _ToAndroidLocales(locale_allowlist):
313*8975f5c5SAndroid Build Coastguard Worker  """Converts the list of Chrome locales to Android config locale qualifiers.
314*8975f5c5SAndroid Build Coastguard Worker
315*8975f5c5SAndroid Build Coastguard Worker  Args:
316*8975f5c5SAndroid Build Coastguard Worker    locale_allowlist: A list of Chromium locale names.
317*8975f5c5SAndroid Build Coastguard Worker  Returns:
318*8975f5c5SAndroid Build Coastguard Worker    A set of matching Android config locale qualifier names.
319*8975f5c5SAndroid Build Coastguard Worker  """
320*8975f5c5SAndroid Build Coastguard Worker  ret = set()
321*8975f5c5SAndroid Build Coastguard Worker  for locale in locale_allowlist:
322*8975f5c5SAndroid Build Coastguard Worker    locale = resource_utils.ToAndroidLocaleName(locale)
323*8975f5c5SAndroid Build Coastguard Worker    if locale is None or ('-' in locale and '-r' not in locale):
324*8975f5c5SAndroid Build Coastguard Worker      raise Exception('Unsupported Chromium locale name: %s' % locale)
325*8975f5c5SAndroid Build Coastguard Worker    ret.add(locale)
326*8975f5c5SAndroid Build Coastguard Worker    # Always keep non-regional fall-backs.
327*8975f5c5SAndroid Build Coastguard Worker    language = locale.split('-')[0]
328*8975f5c5SAndroid Build Coastguard Worker    ret.add(language)
329*8975f5c5SAndroid Build Coastguard Worker
330*8975f5c5SAndroid Build Coastguard Worker  return ret
331*8975f5c5SAndroid Build Coastguard Worker
332*8975f5c5SAndroid Build Coastguard Worker
333*8975f5c5SAndroid Build Coastguard Workerdef _MoveImagesToNonMdpiFolders(res_root, path_info):
334*8975f5c5SAndroid Build Coastguard Worker  """Move images from drawable-*-mdpi-* folders to drawable-* folders.
335*8975f5c5SAndroid Build Coastguard Worker
336*8975f5c5SAndroid Build Coastguard Worker  Why? http://crbug.com/289843
337*8975f5c5SAndroid Build Coastguard Worker  """
338*8975f5c5SAndroid Build Coastguard Worker  for src_dir_name in os.listdir(res_root):
339*8975f5c5SAndroid Build Coastguard Worker    src_components = src_dir_name.split('-')
340*8975f5c5SAndroid Build Coastguard Worker    if src_components[0] != 'drawable' or 'mdpi' not in src_components:
341*8975f5c5SAndroid Build Coastguard Worker      continue
342*8975f5c5SAndroid Build Coastguard Worker    src_dir = os.path.join(res_root, src_dir_name)
343*8975f5c5SAndroid Build Coastguard Worker    if not os.path.isdir(src_dir):
344*8975f5c5SAndroid Build Coastguard Worker      continue
345*8975f5c5SAndroid Build Coastguard Worker    dst_components = [c for c in src_components if c != 'mdpi']
346*8975f5c5SAndroid Build Coastguard Worker    assert dst_components != src_components
347*8975f5c5SAndroid Build Coastguard Worker    dst_dir_name = '-'.join(dst_components)
348*8975f5c5SAndroid Build Coastguard Worker    dst_dir = os.path.join(res_root, dst_dir_name)
349*8975f5c5SAndroid Build Coastguard Worker    build_utils.MakeDirectory(dst_dir)
350*8975f5c5SAndroid Build Coastguard Worker    for src_file_name in os.listdir(src_dir):
351*8975f5c5SAndroid Build Coastguard Worker      src_file = os.path.join(src_dir, src_file_name)
352*8975f5c5SAndroid Build Coastguard Worker      dst_file = os.path.join(dst_dir, src_file_name)
353*8975f5c5SAndroid Build Coastguard Worker      assert not os.path.lexists(dst_file)
354*8975f5c5SAndroid Build Coastguard Worker      shutil.move(src_file, dst_file)
355*8975f5c5SAndroid Build Coastguard Worker      path_info.RegisterRename(
356*8975f5c5SAndroid Build Coastguard Worker          os.path.relpath(src_file, res_root),
357*8975f5c5SAndroid Build Coastguard Worker          os.path.relpath(dst_file, res_root))
358*8975f5c5SAndroid Build Coastguard Worker
359*8975f5c5SAndroid Build Coastguard Worker
360*8975f5c5SAndroid Build Coastguard Workerdef _DeterminePlatformVersion(aapt2_path, jar_candidates):
361*8975f5c5SAndroid Build Coastguard Worker  def maybe_extract_version(j):
362*8975f5c5SAndroid Build Coastguard Worker    try:
363*8975f5c5SAndroid Build Coastguard Worker      return resource_utils.ExtractBinaryManifestValues(aapt2_path, j)
364*8975f5c5SAndroid Build Coastguard Worker    except build_utils.CalledProcessError:
365*8975f5c5SAndroid Build Coastguard Worker      return None
366*8975f5c5SAndroid Build Coastguard Worker
367*8975f5c5SAndroid Build Coastguard Worker  def is_sdk_jar(jar_name):
368*8975f5c5SAndroid Build Coastguard Worker    if jar_name in ('android.jar', 'android_system.jar'):
369*8975f5c5SAndroid Build Coastguard Worker      return True
370*8975f5c5SAndroid Build Coastguard Worker    # Robolectric jar looks a bit different.
371*8975f5c5SAndroid Build Coastguard Worker    return 'android-all' in jar_name and 'robolectric' in jar_name
372*8975f5c5SAndroid Build Coastguard Worker
373*8975f5c5SAndroid Build Coastguard Worker  android_sdk_jars = [
374*8975f5c5SAndroid Build Coastguard Worker      j for j in jar_candidates if is_sdk_jar(os.path.basename(j))
375*8975f5c5SAndroid Build Coastguard Worker  ]
376*8975f5c5SAndroid Build Coastguard Worker  extract_all = [maybe_extract_version(j) for j in android_sdk_jars]
377*8975f5c5SAndroid Build Coastguard Worker  extract_all = [x for x in extract_all if x]
378*8975f5c5SAndroid Build Coastguard Worker  if len(extract_all) == 0:
379*8975f5c5SAndroid Build Coastguard Worker    raise Exception(
380*8975f5c5SAndroid Build Coastguard Worker        'Unable to find android SDK jar among candidates: %s'
381*8975f5c5SAndroid Build Coastguard Worker            % ', '.join(android_sdk_jars))
382*8975f5c5SAndroid Build Coastguard Worker  if len(extract_all) > 1:
383*8975f5c5SAndroid Build Coastguard Worker    raise Exception(
384*8975f5c5SAndroid Build Coastguard Worker        'Found multiple android SDK jars among candidates: %s'
385*8975f5c5SAndroid Build Coastguard Worker            % ', '.join(android_sdk_jars))
386*8975f5c5SAndroid Build Coastguard Worker  platform_version_code, platform_version_name = extract_all.pop()[:2]
387*8975f5c5SAndroid Build Coastguard Worker  return platform_version_code, platform_version_name
388*8975f5c5SAndroid Build Coastguard Worker
389*8975f5c5SAndroid Build Coastguard Worker
390*8975f5c5SAndroid Build Coastguard Workerdef _FixManifest(options, temp_dir):
391*8975f5c5SAndroid Build Coastguard Worker  """Fix the APK's AndroidManifest.xml.
392*8975f5c5SAndroid Build Coastguard Worker
393*8975f5c5SAndroid Build Coastguard Worker  This adds any missing namespaces for 'android' and 'tools', and
394*8975f5c5SAndroid Build Coastguard Worker  sets certains elements like 'platformBuildVersionCode' or
395*8975f5c5SAndroid Build Coastguard Worker  'android:debuggable' depending on the content of |options|.
396*8975f5c5SAndroid Build Coastguard Worker
397*8975f5c5SAndroid Build Coastguard Worker  Args:
398*8975f5c5SAndroid Build Coastguard Worker    options: The command-line arguments tuple.
399*8975f5c5SAndroid Build Coastguard Worker    temp_dir: A temporary directory where the fixed manifest will be written to.
400*8975f5c5SAndroid Build Coastguard Worker  Returns:
401*8975f5c5SAndroid Build Coastguard Worker    Tuple of:
402*8975f5c5SAndroid Build Coastguard Worker     * Manifest path within |temp_dir|.
403*8975f5c5SAndroid Build Coastguard Worker     * Original package_name.
404*8975f5c5SAndroid Build Coastguard Worker     * Manifest package name.
405*8975f5c5SAndroid Build Coastguard Worker  """
406*8975f5c5SAndroid Build Coastguard Worker  doc, manifest_node, app_node = manifest_utils.ParseManifest(
407*8975f5c5SAndroid Build Coastguard Worker      options.android_manifest)
408*8975f5c5SAndroid Build Coastguard Worker
409*8975f5c5SAndroid Build Coastguard Worker  # merge_manifest.py also sets package & <uses-sdk>. We may want to ensure
410*8975f5c5SAndroid Build Coastguard Worker  # manifest merger is always enabled and remove these command-line arguments.
411*8975f5c5SAndroid Build Coastguard Worker  manifest_utils.SetUsesSdk(manifest_node, options.target_sdk_version,
412*8975f5c5SAndroid Build Coastguard Worker                            options.min_sdk_version, options.max_sdk_version)
413*8975f5c5SAndroid Build Coastguard Worker  orig_package = manifest_node.get('package') or options.manifest_package
414*8975f5c5SAndroid Build Coastguard Worker  fixed_package = (options.arsc_package_name or options.manifest_package
415*8975f5c5SAndroid Build Coastguard Worker                   or orig_package)
416*8975f5c5SAndroid Build Coastguard Worker  manifest_node.set('package', fixed_package)
417*8975f5c5SAndroid Build Coastguard Worker
418*8975f5c5SAndroid Build Coastguard Worker  platform_version_code, platform_version_name = _DeterminePlatformVersion(
419*8975f5c5SAndroid Build Coastguard Worker      options.aapt2_path, options.include_resources)
420*8975f5c5SAndroid Build Coastguard Worker  manifest_node.set('platformBuildVersionCode', platform_version_code)
421*8975f5c5SAndroid Build Coastguard Worker  manifest_node.set('platformBuildVersionName', platform_version_name)
422*8975f5c5SAndroid Build Coastguard Worker  if options.version_code:
423*8975f5c5SAndroid Build Coastguard Worker    manifest_utils.NamespacedSet(manifest_node, 'versionCode',
424*8975f5c5SAndroid Build Coastguard Worker                                 options.version_code)
425*8975f5c5SAndroid Build Coastguard Worker  if options.version_name:
426*8975f5c5SAndroid Build Coastguard Worker    manifest_utils.NamespacedSet(manifest_node, 'versionName',
427*8975f5c5SAndroid Build Coastguard Worker                                 options.version_name)
428*8975f5c5SAndroid Build Coastguard Worker  if options.debuggable:
429*8975f5c5SAndroid Build Coastguard Worker    manifest_utils.NamespacedSet(app_node, 'debuggable', 'true')
430*8975f5c5SAndroid Build Coastguard Worker
431*8975f5c5SAndroid Build Coastguard Worker  if options.uses_split:
432*8975f5c5SAndroid Build Coastguard Worker    uses_split = ElementTree.SubElement(manifest_node, 'uses-split')
433*8975f5c5SAndroid Build Coastguard Worker    manifest_utils.NamespacedSet(uses_split, 'name', options.uses_split)
434*8975f5c5SAndroid Build Coastguard Worker
435*8975f5c5SAndroid Build Coastguard Worker  # Make sure the min-sdk condition is not less than the min-sdk of the bundle.
436*8975f5c5SAndroid Build Coastguard Worker  for min_sdk_node in manifest_node.iter('{%s}min-sdk' %
437*8975f5c5SAndroid Build Coastguard Worker                                         manifest_utils.DIST_NAMESPACE):
438*8975f5c5SAndroid Build Coastguard Worker    dist_value = '{%s}value' % manifest_utils.DIST_NAMESPACE
439*8975f5c5SAndroid Build Coastguard Worker    if int(min_sdk_node.get(dist_value)) < int(options.min_sdk_version):
440*8975f5c5SAndroid Build Coastguard Worker      min_sdk_node.set(dist_value, options.min_sdk_version)
441*8975f5c5SAndroid Build Coastguard Worker
442*8975f5c5SAndroid Build Coastguard Worker  debug_manifest_path = os.path.join(temp_dir, 'AndroidManifest.xml')
443*8975f5c5SAndroid Build Coastguard Worker  manifest_utils.SaveManifest(doc, debug_manifest_path)
444*8975f5c5SAndroid Build Coastguard Worker  return debug_manifest_path, orig_package, fixed_package
445*8975f5c5SAndroid Build Coastguard Worker
446*8975f5c5SAndroid Build Coastguard Worker
447*8975f5c5SAndroid Build Coastguard Workerdef _CreateKeepPredicate(resource_exclusion_regex,
448*8975f5c5SAndroid Build Coastguard Worker                         resource_exclusion_exceptions):
449*8975f5c5SAndroid Build Coastguard Worker  """Return a predicate lambda to determine which resource files to keep.
450*8975f5c5SAndroid Build Coastguard Worker
451*8975f5c5SAndroid Build Coastguard Worker  Args:
452*8975f5c5SAndroid Build Coastguard Worker    resource_exclusion_regex: A regular expression describing all resources
453*8975f5c5SAndroid Build Coastguard Worker      to exclude, except if they are mip-maps, or if they are listed
454*8975f5c5SAndroid Build Coastguard Worker      in |resource_exclusion_exceptions|.
455*8975f5c5SAndroid Build Coastguard Worker    resource_exclusion_exceptions: A list of glob patterns corresponding
456*8975f5c5SAndroid Build Coastguard Worker      to exceptions to the |resource_exclusion_regex|.
457*8975f5c5SAndroid Build Coastguard Worker  Returns:
458*8975f5c5SAndroid Build Coastguard Worker    A lambda that takes a path, and returns true if the corresponding file
459*8975f5c5SAndroid Build Coastguard Worker    must be kept.
460*8975f5c5SAndroid Build Coastguard Worker  """
461*8975f5c5SAndroid Build Coastguard Worker  predicate = lambda path: os.path.basename(path)[0] != '.'
462*8975f5c5SAndroid Build Coastguard Worker  if resource_exclusion_regex == '':
463*8975f5c5SAndroid Build Coastguard Worker    # Do not extract dotfiles (e.g. ".gitkeep"). aapt ignores them anyways.
464*8975f5c5SAndroid Build Coastguard Worker    return predicate
465*8975f5c5SAndroid Build Coastguard Worker
466*8975f5c5SAndroid Build Coastguard Worker  # A simple predicate that only removes (returns False for) paths covered by
467*8975f5c5SAndroid Build Coastguard Worker  # the exclusion regex or listed as exceptions.
468*8975f5c5SAndroid Build Coastguard Worker  return lambda path: (
469*8975f5c5SAndroid Build Coastguard Worker      not re.search(resource_exclusion_regex, path) or
470*8975f5c5SAndroid Build Coastguard Worker      build_utils.MatchesGlob(path, resource_exclusion_exceptions))
471*8975f5c5SAndroid Build Coastguard Worker
472*8975f5c5SAndroid Build Coastguard Worker
473*8975f5c5SAndroid Build Coastguard Workerdef _ComputeSha1(path):
474*8975f5c5SAndroid Build Coastguard Worker  with open(path, 'rb') as f:
475*8975f5c5SAndroid Build Coastguard Worker    data = f.read()
476*8975f5c5SAndroid Build Coastguard Worker  return hashlib.sha1(data).hexdigest()
477*8975f5c5SAndroid Build Coastguard Worker
478*8975f5c5SAndroid Build Coastguard Worker
479*8975f5c5SAndroid Build Coastguard Workerdef _ConvertToWebPSingle(png_path, cwebp_binary, cwebp_version, webp_cache_dir):
480*8975f5c5SAndroid Build Coastguard Worker  sha1_hash = _ComputeSha1(png_path)
481*8975f5c5SAndroid Build Coastguard Worker
482*8975f5c5SAndroid Build Coastguard Worker  # The set of arguments that will appear in the cache key.
483*8975f5c5SAndroid Build Coastguard Worker  quality_args = ['-m', '6', '-q', '100', '-lossless']
484*8975f5c5SAndroid Build Coastguard Worker
485*8975f5c5SAndroid Build Coastguard Worker  webp_cache_path = os.path.join(
486*8975f5c5SAndroid Build Coastguard Worker      webp_cache_dir, '{}-{}-{}'.format(sha1_hash, cwebp_version,
487*8975f5c5SAndroid Build Coastguard Worker                                        ''.join(quality_args)))
488*8975f5c5SAndroid Build Coastguard Worker  # No need to add .webp. Android can load images fine without them.
489*8975f5c5SAndroid Build Coastguard Worker  webp_path = os.path.splitext(png_path)[0]
490*8975f5c5SAndroid Build Coastguard Worker
491*8975f5c5SAndroid Build Coastguard Worker  cache_hit = os.path.exists(webp_cache_path)
492*8975f5c5SAndroid Build Coastguard Worker  if cache_hit:
493*8975f5c5SAndroid Build Coastguard Worker    os.link(webp_cache_path, webp_path)
494*8975f5c5SAndroid Build Coastguard Worker  else:
495*8975f5c5SAndroid Build Coastguard Worker    # We place the generated webp image to webp_path, instead of in the
496*8975f5c5SAndroid Build Coastguard Worker    # webp_cache_dir to avoid concurrency issues.
497*8975f5c5SAndroid Build Coastguard Worker    args = [cwebp_binary, png_path, '-o', webp_path, '-quiet'] + quality_args
498*8975f5c5SAndroid Build Coastguard Worker    subprocess.check_call(args)
499*8975f5c5SAndroid Build Coastguard Worker
500*8975f5c5SAndroid Build Coastguard Worker    try:
501*8975f5c5SAndroid Build Coastguard Worker      os.link(webp_path, webp_cache_path)
502*8975f5c5SAndroid Build Coastguard Worker    except OSError:
503*8975f5c5SAndroid Build Coastguard Worker      # Because of concurrent run, a webp image may already exists in
504*8975f5c5SAndroid Build Coastguard Worker      # webp_cache_path.
505*8975f5c5SAndroid Build Coastguard Worker      pass
506*8975f5c5SAndroid Build Coastguard Worker
507*8975f5c5SAndroid Build Coastguard Worker  os.remove(png_path)
508*8975f5c5SAndroid Build Coastguard Worker  original_dir = os.path.dirname(os.path.dirname(png_path))
509*8975f5c5SAndroid Build Coastguard Worker  rename_tuple = (os.path.relpath(png_path, original_dir),
510*8975f5c5SAndroid Build Coastguard Worker                  os.path.relpath(webp_path, original_dir))
511*8975f5c5SAndroid Build Coastguard Worker  return rename_tuple, cache_hit
512*8975f5c5SAndroid Build Coastguard Worker
513*8975f5c5SAndroid Build Coastguard Worker
514*8975f5c5SAndroid Build Coastguard Workerdef _ConvertToWebP(cwebp_binary, png_paths, path_info, webp_cache_dir):
515*8975f5c5SAndroid Build Coastguard Worker  cwebp_version = subprocess.check_output([cwebp_binary, '-version']).rstrip()
516*8975f5c5SAndroid Build Coastguard Worker  shard_args = [(f, ) for f in png_paths
517*8975f5c5SAndroid Build Coastguard Worker                if not _PNG_WEBP_EXCLUSION_PATTERN.match(f)]
518*8975f5c5SAndroid Build Coastguard Worker
519*8975f5c5SAndroid Build Coastguard Worker  build_utils.MakeDirectory(webp_cache_dir)
520*8975f5c5SAndroid Build Coastguard Worker  results = parallel.BulkForkAndCall(_ConvertToWebPSingle,
521*8975f5c5SAndroid Build Coastguard Worker                                     shard_args,
522*8975f5c5SAndroid Build Coastguard Worker                                     cwebp_binary=cwebp_binary,
523*8975f5c5SAndroid Build Coastguard Worker                                     cwebp_version=cwebp_version,
524*8975f5c5SAndroid Build Coastguard Worker                                     webp_cache_dir=webp_cache_dir)
525*8975f5c5SAndroid Build Coastguard Worker  total_cache_hits = 0
526*8975f5c5SAndroid Build Coastguard Worker  for rename_tuple, cache_hit in results:
527*8975f5c5SAndroid Build Coastguard Worker    path_info.RegisterRename(*rename_tuple)
528*8975f5c5SAndroid Build Coastguard Worker    total_cache_hits += int(cache_hit)
529*8975f5c5SAndroid Build Coastguard Worker
530*8975f5c5SAndroid Build Coastguard Worker  logging.debug('png->webp cache: %d/%d', total_cache_hits, len(shard_args))
531*8975f5c5SAndroid Build Coastguard Worker
532*8975f5c5SAndroid Build Coastguard Worker
533*8975f5c5SAndroid Build Coastguard Workerdef _RemoveImageExtensions(directory, path_info):
534*8975f5c5SAndroid Build Coastguard Worker  """Remove extensions from image files in the passed directory.
535*8975f5c5SAndroid Build Coastguard Worker
536*8975f5c5SAndroid Build Coastguard Worker  This reduces binary size but does not affect android's ability to load the
537*8975f5c5SAndroid Build Coastguard Worker  images.
538*8975f5c5SAndroid Build Coastguard Worker  """
539*8975f5c5SAndroid Build Coastguard Worker  for f in _IterFiles(directory):
540*8975f5c5SAndroid Build Coastguard Worker    if (f.endswith('.png') or f.endswith('.webp')) and not f.endswith('.9.png'):
541*8975f5c5SAndroid Build Coastguard Worker      path_with_extension = f
542*8975f5c5SAndroid Build Coastguard Worker      path_no_extension = os.path.splitext(path_with_extension)[0]
543*8975f5c5SAndroid Build Coastguard Worker      if path_no_extension != path_with_extension:
544*8975f5c5SAndroid Build Coastguard Worker        shutil.move(path_with_extension, path_no_extension)
545*8975f5c5SAndroid Build Coastguard Worker        path_info.RegisterRename(
546*8975f5c5SAndroid Build Coastguard Worker            os.path.relpath(path_with_extension, directory),
547*8975f5c5SAndroid Build Coastguard Worker            os.path.relpath(path_no_extension, directory))
548*8975f5c5SAndroid Build Coastguard Worker
549*8975f5c5SAndroid Build Coastguard Worker
550*8975f5c5SAndroid Build Coastguard Workerdef _CompileSingleDep(index, dep_subdir, keep_predicate, aapt2_path,
551*8975f5c5SAndroid Build Coastguard Worker                      partials_dir):
552*8975f5c5SAndroid Build Coastguard Worker  unique_name = '{}_{}'.format(index, os.path.basename(dep_subdir))
553*8975f5c5SAndroid Build Coastguard Worker  partial_path = os.path.join(partials_dir, '{}.zip'.format(unique_name))
554*8975f5c5SAndroid Build Coastguard Worker
555*8975f5c5SAndroid Build Coastguard Worker  compile_command = [
556*8975f5c5SAndroid Build Coastguard Worker      aapt2_path,
557*8975f5c5SAndroid Build Coastguard Worker      'compile',
558*8975f5c5SAndroid Build Coastguard Worker      # TODO(wnwen): Turn this on once aapt2 forces 9-patch to be crunched.
559*8975f5c5SAndroid Build Coastguard Worker      # '--no-crunch',
560*8975f5c5SAndroid Build Coastguard Worker      '--dir',
561*8975f5c5SAndroid Build Coastguard Worker      dep_subdir,
562*8975f5c5SAndroid Build Coastguard Worker      '-o',
563*8975f5c5SAndroid Build Coastguard Worker      partial_path
564*8975f5c5SAndroid Build Coastguard Worker  ]
565*8975f5c5SAndroid Build Coastguard Worker
566*8975f5c5SAndroid Build Coastguard Worker  # There are resources targeting API-versions lower than our minapi. For
567*8975f5c5SAndroid Build Coastguard Worker  # various reasons it's easier to let aapt2 ignore these than for us to
568*8975f5c5SAndroid Build Coastguard Worker  # remove them from our build (e.g. it's from a 3rd party library).
569*8975f5c5SAndroid Build Coastguard Worker  build_utils.CheckOutput(
570*8975f5c5SAndroid Build Coastguard Worker      compile_command,
571*8975f5c5SAndroid Build Coastguard Worker      stderr_filter=lambda output: build_utils.FilterLines(
572*8975f5c5SAndroid Build Coastguard Worker          output, r'ignoring configuration .* for (styleable|attribute)'))
573*8975f5c5SAndroid Build Coastguard Worker
574*8975f5c5SAndroid Build Coastguard Worker  # Filtering these files is expensive, so only apply filters to the partials
575*8975f5c5SAndroid Build Coastguard Worker  # that have been explicitly targeted.
576*8975f5c5SAndroid Build Coastguard Worker  if keep_predicate:
577*8975f5c5SAndroid Build Coastguard Worker    logging.debug('Applying .arsc filtering to %s', dep_subdir)
578*8975f5c5SAndroid Build Coastguard Worker    protoresources.StripUnwantedResources(partial_path, keep_predicate)
579*8975f5c5SAndroid Build Coastguard Worker  return partial_path
580*8975f5c5SAndroid Build Coastguard Worker
581*8975f5c5SAndroid Build Coastguard Worker
582*8975f5c5SAndroid Build Coastguard Workerdef _CreateValuesKeepPredicate(exclusion_rules, dep_subdir):
583*8975f5c5SAndroid Build Coastguard Worker  patterns = [
584*8975f5c5SAndroid Build Coastguard Worker      x[1] for x in exclusion_rules
585*8975f5c5SAndroid Build Coastguard Worker      if build_utils.MatchesGlob(dep_subdir, [x[0]])
586*8975f5c5SAndroid Build Coastguard Worker  ]
587*8975f5c5SAndroid Build Coastguard Worker  if not patterns:
588*8975f5c5SAndroid Build Coastguard Worker    return None
589*8975f5c5SAndroid Build Coastguard Worker
590*8975f5c5SAndroid Build Coastguard Worker  regexes = [re.compile(p) for p in patterns]
591*8975f5c5SAndroid Build Coastguard Worker  return lambda x: not any(r.search(x) for r in regexes)
592*8975f5c5SAndroid Build Coastguard Worker
593*8975f5c5SAndroid Build Coastguard Worker
594*8975f5c5SAndroid Build Coastguard Workerdef _CompileDeps(aapt2_path, dep_subdirs, dep_subdir_overlay_set, temp_dir,
595*8975f5c5SAndroid Build Coastguard Worker                 exclusion_rules):
596*8975f5c5SAndroid Build Coastguard Worker  partials_dir = os.path.join(temp_dir, 'partials')
597*8975f5c5SAndroid Build Coastguard Worker  build_utils.MakeDirectory(partials_dir)
598*8975f5c5SAndroid Build Coastguard Worker
599*8975f5c5SAndroid Build Coastguard Worker  job_params = [(i, dep_subdir,
600*8975f5c5SAndroid Build Coastguard Worker                 _CreateValuesKeepPredicate(exclusion_rules, dep_subdir))
601*8975f5c5SAndroid Build Coastguard Worker                for i, dep_subdir in enumerate(dep_subdirs)]
602*8975f5c5SAndroid Build Coastguard Worker
603*8975f5c5SAndroid Build Coastguard Worker  # Filtering is slow, so ensure jobs with keep_predicate are started first.
604*8975f5c5SAndroid Build Coastguard Worker  job_params.sort(key=lambda x: not x[2])
605*8975f5c5SAndroid Build Coastguard Worker  partials = list(
606*8975f5c5SAndroid Build Coastguard Worker      parallel.BulkForkAndCall(_CompileSingleDep,
607*8975f5c5SAndroid Build Coastguard Worker                               job_params,
608*8975f5c5SAndroid Build Coastguard Worker                               aapt2_path=aapt2_path,
609*8975f5c5SAndroid Build Coastguard Worker                               partials_dir=partials_dir))
610*8975f5c5SAndroid Build Coastguard Worker
611*8975f5c5SAndroid Build Coastguard Worker  partials_cmd = list()
612*8975f5c5SAndroid Build Coastguard Worker  for i, partial in enumerate(partials):
613*8975f5c5SAndroid Build Coastguard Worker    dep_subdir = job_params[i][1]
614*8975f5c5SAndroid Build Coastguard Worker    if dep_subdir in dep_subdir_overlay_set:
615*8975f5c5SAndroid Build Coastguard Worker      partials_cmd += ['-R']
616*8975f5c5SAndroid Build Coastguard Worker    partials_cmd += [partial]
617*8975f5c5SAndroid Build Coastguard Worker  return partials_cmd
618*8975f5c5SAndroid Build Coastguard Worker
619*8975f5c5SAndroid Build Coastguard Worker
620*8975f5c5SAndroid Build Coastguard Workerdef _CreateResourceInfoFile(path_info, info_path, dependencies_res_zips):
621*8975f5c5SAndroid Build Coastguard Worker  for zip_file in dependencies_res_zips:
622*8975f5c5SAndroid Build Coastguard Worker    zip_info_file_path = zip_file + '.info'
623*8975f5c5SAndroid Build Coastguard Worker    if os.path.exists(zip_info_file_path):
624*8975f5c5SAndroid Build Coastguard Worker      path_info.MergeInfoFile(zip_info_file_path)
625*8975f5c5SAndroid Build Coastguard Worker  path_info.Write(info_path)
626*8975f5c5SAndroid Build Coastguard Worker
627*8975f5c5SAndroid Build Coastguard Worker
628*8975f5c5SAndroid Build Coastguard Workerdef _RemoveUnwantedLocalizedStrings(dep_subdirs, options):
629*8975f5c5SAndroid Build Coastguard Worker  """Remove localized strings that should not go into the final output.
630*8975f5c5SAndroid Build Coastguard Worker
631*8975f5c5SAndroid Build Coastguard Worker  Args:
632*8975f5c5SAndroid Build Coastguard Worker    dep_subdirs: List of resource dependency directories.
633*8975f5c5SAndroid Build Coastguard Worker    options: Command-line options namespace.
634*8975f5c5SAndroid Build Coastguard Worker  """
635*8975f5c5SAndroid Build Coastguard Worker  # Collect locale and file paths from the existing subdirs.
636*8975f5c5SAndroid Build Coastguard Worker  # The following variable maps Android locale names to
637*8975f5c5SAndroid Build Coastguard Worker  # sets of corresponding xml file paths.
638*8975f5c5SAndroid Build Coastguard Worker  locale_to_files_map = collections.defaultdict(set)
639*8975f5c5SAndroid Build Coastguard Worker  for directory in dep_subdirs:
640*8975f5c5SAndroid Build Coastguard Worker    for f in _IterFiles(directory):
641*8975f5c5SAndroid Build Coastguard Worker      locale = resource_utils.FindLocaleInStringResourceFilePath(f)
642*8975f5c5SAndroid Build Coastguard Worker      if locale:
643*8975f5c5SAndroid Build Coastguard Worker        locale_to_files_map[locale].add(f)
644*8975f5c5SAndroid Build Coastguard Worker
645*8975f5c5SAndroid Build Coastguard Worker  all_locales = set(locale_to_files_map)
646*8975f5c5SAndroid Build Coastguard Worker
647*8975f5c5SAndroid Build Coastguard Worker  # Set A: wanted locales, either all of them or the
648*8975f5c5SAndroid Build Coastguard Worker  # list provided by --locale-allowlist.
649*8975f5c5SAndroid Build Coastguard Worker  wanted_locales = all_locales
650*8975f5c5SAndroid Build Coastguard Worker  if options.locale_allowlist:
651*8975f5c5SAndroid Build Coastguard Worker    wanted_locales = _ToAndroidLocales(options.locale_allowlist)
652*8975f5c5SAndroid Build Coastguard Worker
653*8975f5c5SAndroid Build Coastguard Worker  # Set B: shared resources locales, which is either set A
654*8975f5c5SAndroid Build Coastguard Worker  # or the list provided by --shared-resources-allowlist-locales
655*8975f5c5SAndroid Build Coastguard Worker  shared_resources_locales = wanted_locales
656*8975f5c5SAndroid Build Coastguard Worker  shared_names_allowlist = set()
657*8975f5c5SAndroid Build Coastguard Worker  if options.shared_resources_allowlist_locales:
658*8975f5c5SAndroid Build Coastguard Worker    shared_names_allowlist = set(
659*8975f5c5SAndroid Build Coastguard Worker        resource_utils.GetRTxtStringResourceNames(
660*8975f5c5SAndroid Build Coastguard Worker            options.shared_resources_allowlist))
661*8975f5c5SAndroid Build Coastguard Worker
662*8975f5c5SAndroid Build Coastguard Worker    shared_resources_locales = _ToAndroidLocales(
663*8975f5c5SAndroid Build Coastguard Worker        options.shared_resources_allowlist_locales)
664*8975f5c5SAndroid Build Coastguard Worker
665*8975f5c5SAndroid Build Coastguard Worker  # Remove any file that belongs to a locale not covered by
666*8975f5c5SAndroid Build Coastguard Worker  # either A or B.
667*8975f5c5SAndroid Build Coastguard Worker  removable_locales = (all_locales - wanted_locales - shared_resources_locales)
668*8975f5c5SAndroid Build Coastguard Worker  for locale in removable_locales:
669*8975f5c5SAndroid Build Coastguard Worker    for path in locale_to_files_map[locale]:
670*8975f5c5SAndroid Build Coastguard Worker      os.remove(path)
671*8975f5c5SAndroid Build Coastguard Worker
672*8975f5c5SAndroid Build Coastguard Worker  # For any locale in B but not in A, only keep the shared
673*8975f5c5SAndroid Build Coastguard Worker  # resource strings in each file.
674*8975f5c5SAndroid Build Coastguard Worker  for locale in shared_resources_locales - wanted_locales:
675*8975f5c5SAndroid Build Coastguard Worker    for path in locale_to_files_map[locale]:
676*8975f5c5SAndroid Build Coastguard Worker      resource_utils.FilterAndroidResourceStringsXml(
677*8975f5c5SAndroid Build Coastguard Worker          path, lambda x: x in shared_names_allowlist)
678*8975f5c5SAndroid Build Coastguard Worker
679*8975f5c5SAndroid Build Coastguard Worker  # For any locale in A but not in B, only keep the strings
680*8975f5c5SAndroid Build Coastguard Worker  # that are _not_ from shared resources in the file.
681*8975f5c5SAndroid Build Coastguard Worker  for locale in wanted_locales - shared_resources_locales:
682*8975f5c5SAndroid Build Coastguard Worker    for path in locale_to_files_map[locale]:
683*8975f5c5SAndroid Build Coastguard Worker      resource_utils.FilterAndroidResourceStringsXml(
684*8975f5c5SAndroid Build Coastguard Worker          path, lambda x: x not in shared_names_allowlist)
685*8975f5c5SAndroid Build Coastguard Worker
686*8975f5c5SAndroid Build Coastguard Worker
687*8975f5c5SAndroid Build Coastguard Workerdef _FilterResourceFiles(dep_subdirs, keep_predicate):
688*8975f5c5SAndroid Build Coastguard Worker  # Create a function that selects which resource files should be packaged
689*8975f5c5SAndroid Build Coastguard Worker  # into the final output. Any file that does not pass the predicate will
690*8975f5c5SAndroid Build Coastguard Worker  # be removed below.
691*8975f5c5SAndroid Build Coastguard Worker  png_paths = []
692*8975f5c5SAndroid Build Coastguard Worker  for directory in dep_subdirs:
693*8975f5c5SAndroid Build Coastguard Worker    for f in _IterFiles(directory):
694*8975f5c5SAndroid Build Coastguard Worker      if not keep_predicate(f):
695*8975f5c5SAndroid Build Coastguard Worker        os.remove(f)
696*8975f5c5SAndroid Build Coastguard Worker      elif f.endswith('.png'):
697*8975f5c5SAndroid Build Coastguard Worker        png_paths.append(f)
698*8975f5c5SAndroid Build Coastguard Worker
699*8975f5c5SAndroid Build Coastguard Worker  return png_paths
700*8975f5c5SAndroid Build Coastguard Worker
701*8975f5c5SAndroid Build Coastguard Worker
702*8975f5c5SAndroid Build Coastguard Workerdef _PackageApk(options, build):
703*8975f5c5SAndroid Build Coastguard Worker  """Compile and link resources with aapt2.
704*8975f5c5SAndroid Build Coastguard Worker
705*8975f5c5SAndroid Build Coastguard Worker  Args:
706*8975f5c5SAndroid Build Coastguard Worker    options: The command-line options.
707*8975f5c5SAndroid Build Coastguard Worker    build: BuildContext object.
708*8975f5c5SAndroid Build Coastguard Worker  Returns:
709*8975f5c5SAndroid Build Coastguard Worker    The manifest package name for the APK.
710*8975f5c5SAndroid Build Coastguard Worker  """
711*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Extracting resource .zips')
712*8975f5c5SAndroid Build Coastguard Worker  dep_subdirs = []
713*8975f5c5SAndroid Build Coastguard Worker  dep_subdir_overlay_set = set()
714*8975f5c5SAndroid Build Coastguard Worker  for dependency_res_zip in options.dependencies_res_zips:
715*8975f5c5SAndroid Build Coastguard Worker    extracted_dep_subdirs = resource_utils.ExtractDeps([dependency_res_zip],
716*8975f5c5SAndroid Build Coastguard Worker                                                       build.deps_dir)
717*8975f5c5SAndroid Build Coastguard Worker    dep_subdirs += extracted_dep_subdirs
718*8975f5c5SAndroid Build Coastguard Worker    if dependency_res_zip in options.dependencies_res_zip_overlays:
719*8975f5c5SAndroid Build Coastguard Worker      dep_subdir_overlay_set.update(extracted_dep_subdirs)
720*8975f5c5SAndroid Build Coastguard Worker
721*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Applying locale transformations')
722*8975f5c5SAndroid Build Coastguard Worker  path_info = resource_utils.ResourceInfoFile()
723*8975f5c5SAndroid Build Coastguard Worker  _RenameLocaleResourceDirs(dep_subdirs, path_info)
724*8975f5c5SAndroid Build Coastguard Worker
725*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Applying file-based exclusions')
726*8975f5c5SAndroid Build Coastguard Worker  keep_predicate = _CreateKeepPredicate(options.resource_exclusion_regex,
727*8975f5c5SAndroid Build Coastguard Worker                                        options.resource_exclusion_exceptions)
728*8975f5c5SAndroid Build Coastguard Worker  png_paths = _FilterResourceFiles(dep_subdirs, keep_predicate)
729*8975f5c5SAndroid Build Coastguard Worker
730*8975f5c5SAndroid Build Coastguard Worker  if options.locale_allowlist or options.shared_resources_allowlist_locales:
731*8975f5c5SAndroid Build Coastguard Worker    logging.debug('Applying locale-based string exclusions')
732*8975f5c5SAndroid Build Coastguard Worker    _RemoveUnwantedLocalizedStrings(dep_subdirs, options)
733*8975f5c5SAndroid Build Coastguard Worker
734*8975f5c5SAndroid Build Coastguard Worker  if png_paths and options.png_to_webp:
735*8975f5c5SAndroid Build Coastguard Worker    logging.debug('Converting png->webp')
736*8975f5c5SAndroid Build Coastguard Worker    _ConvertToWebP(options.webp_binary, png_paths, path_info,
737*8975f5c5SAndroid Build Coastguard Worker                   options.webp_cache_dir)
738*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Applying drawable transformations')
739*8975f5c5SAndroid Build Coastguard Worker  for directory in dep_subdirs:
740*8975f5c5SAndroid Build Coastguard Worker    _MoveImagesToNonMdpiFolders(directory, path_info)
741*8975f5c5SAndroid Build Coastguard Worker    _RemoveImageExtensions(directory, path_info)
742*8975f5c5SAndroid Build Coastguard Worker
743*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Running aapt2 compile')
744*8975f5c5SAndroid Build Coastguard Worker  exclusion_rules = [x.split(':', 1) for x in options.values_filter_rules]
745*8975f5c5SAndroid Build Coastguard Worker  partials = _CompileDeps(options.aapt2_path, dep_subdirs,
746*8975f5c5SAndroid Build Coastguard Worker                          dep_subdir_overlay_set, build.temp_dir,
747*8975f5c5SAndroid Build Coastguard Worker                          exclusion_rules)
748*8975f5c5SAndroid Build Coastguard Worker
749*8975f5c5SAndroid Build Coastguard Worker  link_command = [
750*8975f5c5SAndroid Build Coastguard Worker      options.aapt2_path,
751*8975f5c5SAndroid Build Coastguard Worker      'link',
752*8975f5c5SAndroid Build Coastguard Worker      '--auto-add-overlay',
753*8975f5c5SAndroid Build Coastguard Worker      '--no-version-vectors',
754*8975f5c5SAndroid Build Coastguard Worker      '--output-text-symbols',
755*8975f5c5SAndroid Build Coastguard Worker      build.r_txt_path,
756*8975f5c5SAndroid Build Coastguard Worker  ]
757*8975f5c5SAndroid Build Coastguard Worker
758*8975f5c5SAndroid Build Coastguard Worker  for j in options.include_resources:
759*8975f5c5SAndroid Build Coastguard Worker    link_command += ['-I', j]
760*8975f5c5SAndroid Build Coastguard Worker  if options.proguard_file:
761*8975f5c5SAndroid Build Coastguard Worker    link_command += ['--proguard', build.proguard_path]
762*8975f5c5SAndroid Build Coastguard Worker    link_command += ['--proguard-minimal-keep-rules']
763*8975f5c5SAndroid Build Coastguard Worker  if options.emit_ids_out:
764*8975f5c5SAndroid Build Coastguard Worker    link_command += ['--emit-ids', build.emit_ids_path]
765*8975f5c5SAndroid Build Coastguard Worker
766*8975f5c5SAndroid Build Coastguard Worker  # Note: only one of --proto-format, --shared-lib or --app-as-shared-lib
767*8975f5c5SAndroid Build Coastguard Worker  #       can be used with recent versions of aapt2.
768*8975f5c5SAndroid Build Coastguard Worker  if options.shared_resources:
769*8975f5c5SAndroid Build Coastguard Worker    link_command.append('--shared-lib')
770*8975f5c5SAndroid Build Coastguard Worker
771*8975f5c5SAndroid Build Coastguard Worker  if int(options.min_sdk_version) > 21 and not options.xml_namespaces:
772*8975f5c5SAndroid Build Coastguard Worker    link_command.append('--no-xml-namespaces')
773*8975f5c5SAndroid Build Coastguard Worker
774*8975f5c5SAndroid Build Coastguard Worker  if options.package_id:
775*8975f5c5SAndroid Build Coastguard Worker    link_command += [
776*8975f5c5SAndroid Build Coastguard Worker        '--package-id',
777*8975f5c5SAndroid Build Coastguard Worker        '0x%02x' % options.package_id,
778*8975f5c5SAndroid Build Coastguard Worker        '--allow-reserved-package-id',
779*8975f5c5SAndroid Build Coastguard Worker    ]
780*8975f5c5SAndroid Build Coastguard Worker
781*8975f5c5SAndroid Build Coastguard Worker  fixed_manifest, desired_manifest_package_name, fixed_manifest_package = (
782*8975f5c5SAndroid Build Coastguard Worker      _FixManifest(options, build.temp_dir))
783*8975f5c5SAndroid Build Coastguard Worker  if options.rename_manifest_package:
784*8975f5c5SAndroid Build Coastguard Worker    desired_manifest_package_name = options.rename_manifest_package
785*8975f5c5SAndroid Build Coastguard Worker
786*8975f5c5SAndroid Build Coastguard Worker  link_command += [
787*8975f5c5SAndroid Build Coastguard Worker      '--manifest', fixed_manifest, '--rename-manifest-package',
788*8975f5c5SAndroid Build Coastguard Worker      desired_manifest_package_name
789*8975f5c5SAndroid Build Coastguard Worker  ]
790*8975f5c5SAndroid Build Coastguard Worker
791*8975f5c5SAndroid Build Coastguard Worker  if options.package_id is not None:
792*8975f5c5SAndroid Build Coastguard Worker    package_id = options.package_id
793*8975f5c5SAndroid Build Coastguard Worker  elif options.shared_resources:
794*8975f5c5SAndroid Build Coastguard Worker    package_id = 0
795*8975f5c5SAndroid Build Coastguard Worker  else:
796*8975f5c5SAndroid Build Coastguard Worker    package_id = 0x7f
797*8975f5c5SAndroid Build Coastguard Worker  _CreateStableIdsFile(options.use_resource_ids_path, build.stable_ids_path,
798*8975f5c5SAndroid Build Coastguard Worker                       fixed_manifest_package, package_id)
799*8975f5c5SAndroid Build Coastguard Worker  link_command += ['--stable-ids', build.stable_ids_path]
800*8975f5c5SAndroid Build Coastguard Worker
801*8975f5c5SAndroid Build Coastguard Worker  link_command += partials
802*8975f5c5SAndroid Build Coastguard Worker
803*8975f5c5SAndroid Build Coastguard Worker  # We always create a binary arsc file first, then convert to proto, so flags
804*8975f5c5SAndroid Build Coastguard Worker  # such as --shared-lib can be supported.
805*8975f5c5SAndroid Build Coastguard Worker  link_command += ['-o', build.arsc_path]
806*8975f5c5SAndroid Build Coastguard Worker
807*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Starting: aapt2 link')
808*8975f5c5SAndroid Build Coastguard Worker  link_proc = subprocess.Popen(link_command)
809*8975f5c5SAndroid Build Coastguard Worker
810*8975f5c5SAndroid Build Coastguard Worker  # Create .res.info file in parallel.
811*8975f5c5SAndroid Build Coastguard Worker  if options.info_path:
812*8975f5c5SAndroid Build Coastguard Worker    logging.debug('Creating .res.info file')
813*8975f5c5SAndroid Build Coastguard Worker    _CreateResourceInfoFile(path_info, build.info_path,
814*8975f5c5SAndroid Build Coastguard Worker                            options.dependencies_res_zips)
815*8975f5c5SAndroid Build Coastguard Worker
816*8975f5c5SAndroid Build Coastguard Worker  exit_code = link_proc.wait()
817*8975f5c5SAndroid Build Coastguard Worker  assert exit_code == 0, f'aapt2 link cmd failed with {exit_code=}'
818*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Finished: aapt2 link')
819*8975f5c5SAndroid Build Coastguard Worker
820*8975f5c5SAndroid Build Coastguard Worker  if options.shared_resources:
821*8975f5c5SAndroid Build Coastguard Worker    logging.debug('Resolving styleables in R.txt')
822*8975f5c5SAndroid Build Coastguard Worker    # Need to resolve references because unused resource removal tool does not
823*8975f5c5SAndroid Build Coastguard Worker    # support references in R.txt files.
824*8975f5c5SAndroid Build Coastguard Worker    resource_utils.ResolveStyleableReferences(build.r_txt_path)
825*8975f5c5SAndroid Build Coastguard Worker
826*8975f5c5SAndroid Build Coastguard Worker  if exit_code:
827*8975f5c5SAndroid Build Coastguard Worker    raise subprocess.CalledProcessError(exit_code, link_command)
828*8975f5c5SAndroid Build Coastguard Worker
829*8975f5c5SAndroid Build Coastguard Worker  if options.proguard_file and (options.shared_resources
830*8975f5c5SAndroid Build Coastguard Worker                                or options.app_as_shared_lib):
831*8975f5c5SAndroid Build Coastguard Worker    # Make sure the R class associated with the manifest package does not have
832*8975f5c5SAndroid Build Coastguard Worker    # its onResourcesLoaded method obfuscated or removed, so that the framework
833*8975f5c5SAndroid Build Coastguard Worker    # can call it in the case where the APK is being loaded as a library.
834*8975f5c5SAndroid Build Coastguard Worker    with open(build.proguard_path, 'a') as proguard_file:
835*8975f5c5SAndroid Build Coastguard Worker      keep_rule = '''
836*8975f5c5SAndroid Build Coastguard Worker                  -keep,allowoptimization class {package}.R {{
837*8975f5c5SAndroid Build Coastguard Worker                    public static void onResourcesLoaded(int);
838*8975f5c5SAndroid Build Coastguard Worker                  }}
839*8975f5c5SAndroid Build Coastguard Worker                  '''.format(package=desired_manifest_package_name)
840*8975f5c5SAndroid Build Coastguard Worker      proguard_file.write(textwrap.dedent(keep_rule))
841*8975f5c5SAndroid Build Coastguard Worker
842*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Running aapt2 convert')
843*8975f5c5SAndroid Build Coastguard Worker  build_utils.CheckOutput([
844*8975f5c5SAndroid Build Coastguard Worker      options.aapt2_path, 'convert', '--output-format', 'proto', '-o',
845*8975f5c5SAndroid Build Coastguard Worker      build.proto_path, build.arsc_path
846*8975f5c5SAndroid Build Coastguard Worker  ])
847*8975f5c5SAndroid Build Coastguard Worker
848*8975f5c5SAndroid Build Coastguard Worker  # Workaround for b/147674078. This is only needed for WebLayer and does not
849*8975f5c5SAndroid Build Coastguard Worker  # affect WebView usage, since WebView does not used dynamic attributes.
850*8975f5c5SAndroid Build Coastguard Worker  if options.shared_resources:
851*8975f5c5SAndroid Build Coastguard Worker    logging.debug('Hardcoding dynamic attributes')
852*8975f5c5SAndroid Build Coastguard Worker    protoresources.HardcodeSharedLibraryDynamicAttributes(
853*8975f5c5SAndroid Build Coastguard Worker        build.proto_path, options.is_bundle_module,
854*8975f5c5SAndroid Build Coastguard Worker        options.shared_resources_allowlist)
855*8975f5c5SAndroid Build Coastguard Worker
856*8975f5c5SAndroid Build Coastguard Worker    build_utils.CheckOutput([
857*8975f5c5SAndroid Build Coastguard Worker        options.aapt2_path, 'convert', '--output-format', 'binary', '-o',
858*8975f5c5SAndroid Build Coastguard Worker        build.arsc_path, build.proto_path
859*8975f5c5SAndroid Build Coastguard Worker    ])
860*8975f5c5SAndroid Build Coastguard Worker
861*8975f5c5SAndroid Build Coastguard Worker  # Sanity check that the created resources have the expected package ID.
862*8975f5c5SAndroid Build Coastguard Worker  logging.debug('Performing sanity check')
863*8975f5c5SAndroid Build Coastguard Worker  _, actual_package_id = resource_utils.ExtractArscPackage(
864*8975f5c5SAndroid Build Coastguard Worker      options.aapt2_path,
865*8975f5c5SAndroid Build Coastguard Worker      build.arsc_path if options.arsc_path else build.proto_path)
866*8975f5c5SAndroid Build Coastguard Worker  # When there are no resources, ExtractArscPackage returns (None, None), in
867*8975f5c5SAndroid Build Coastguard Worker  # this case there is no need to check for matching package ID.
868*8975f5c5SAndroid Build Coastguard Worker  if actual_package_id is not None and actual_package_id != package_id:
869*8975f5c5SAndroid Build Coastguard Worker    raise Exception('Invalid package ID 0x%x (expected 0x%x)' %
870*8975f5c5SAndroid Build Coastguard Worker                    (actual_package_id, package_id))
871*8975f5c5SAndroid Build Coastguard Worker
872*8975f5c5SAndroid Build Coastguard Worker  return desired_manifest_package_name
873*8975f5c5SAndroid Build Coastguard Worker
874*8975f5c5SAndroid Build Coastguard Worker
875*8975f5c5SAndroid Build Coastguard Workerdef _CreateStableIdsFile(in_path, out_path, package_name, package_id):
876*8975f5c5SAndroid Build Coastguard Worker  """Transforms a file generated by --emit-ids from another package.
877*8975f5c5SAndroid Build Coastguard Worker
878*8975f5c5SAndroid Build Coastguard Worker  --stable-ids is generally meant to be used by different versions of the same
879*8975f5c5SAndroid Build Coastguard Worker  package. To make it work for other packages, we need to transform the package
880*8975f5c5SAndroid Build Coastguard Worker  name references to match the package that resources are being generated for.
881*8975f5c5SAndroid Build Coastguard Worker  """
882*8975f5c5SAndroid Build Coastguard Worker  if in_path:
883*8975f5c5SAndroid Build Coastguard Worker    data = pathlib.Path(in_path).read_text()
884*8975f5c5SAndroid Build Coastguard Worker  else:
885*8975f5c5SAndroid Build Coastguard Worker    # Force IDs to use 0x01 for the type byte in order to ensure they are
886*8975f5c5SAndroid Build Coastguard Worker    # different from IDs generated by other apps. https://crbug.com/1293336
887*8975f5c5SAndroid Build Coastguard Worker    data = 'pkg:id/fake_resource_id = 0x7f010000\n'
888*8975f5c5SAndroid Build Coastguard Worker  # Replace "pkg:" with correct package name.
889*8975f5c5SAndroid Build Coastguard Worker  data = re.sub(r'^.*?:', package_name + ':', data, flags=re.MULTILINE)
890*8975f5c5SAndroid Build Coastguard Worker  # Replace "0x7f" with correct package id.
891*8975f5c5SAndroid Build Coastguard Worker  data = re.sub(r'0x..', '0x%02x' % package_id, data)
892*8975f5c5SAndroid Build Coastguard Worker  pathlib.Path(out_path).write_text(data)
893*8975f5c5SAndroid Build Coastguard Worker
894*8975f5c5SAndroid Build Coastguard Worker
895*8975f5c5SAndroid Build Coastguard Workerdef _WriteOutputs(options, build):
896*8975f5c5SAndroid Build Coastguard Worker  possible_outputs = [
897*8975f5c5SAndroid Build Coastguard Worker      (options.srcjar_out, build.srcjar_path),
898*8975f5c5SAndroid Build Coastguard Worker      (options.r_text_out, build.r_txt_path),
899*8975f5c5SAndroid Build Coastguard Worker      (options.arsc_path, build.arsc_path),
900*8975f5c5SAndroid Build Coastguard Worker      (options.proto_path, build.proto_path),
901*8975f5c5SAndroid Build Coastguard Worker      (options.proguard_file, build.proguard_path),
902*8975f5c5SAndroid Build Coastguard Worker      (options.emit_ids_out, build.emit_ids_path),
903*8975f5c5SAndroid Build Coastguard Worker      (options.info_path, build.info_path),
904*8975f5c5SAndroid Build Coastguard Worker  ]
905*8975f5c5SAndroid Build Coastguard Worker
906*8975f5c5SAndroid Build Coastguard Worker  for final, temp in possible_outputs:
907*8975f5c5SAndroid Build Coastguard Worker    # Write file only if it's changed.
908*8975f5c5SAndroid Build Coastguard Worker    if final and not (os.path.exists(final) and filecmp.cmp(final, temp)):
909*8975f5c5SAndroid Build Coastguard Worker      shutil.move(temp, final)
910*8975f5c5SAndroid Build Coastguard Worker
911*8975f5c5SAndroid Build Coastguard Worker
912*8975f5c5SAndroid Build Coastguard Workerdef _CreateNormalizedManifestForVerification(options):
913*8975f5c5SAndroid Build Coastguard Worker  with build_utils.TempDir() as tempdir:
914*8975f5c5SAndroid Build Coastguard Worker    fixed_manifest, _, _ = _FixManifest(options, tempdir)
915*8975f5c5SAndroid Build Coastguard Worker    with open(fixed_manifest) as f:
916*8975f5c5SAndroid Build Coastguard Worker      return manifest_utils.NormalizeManifest(
917*8975f5c5SAndroid Build Coastguard Worker          f.read(), options.verification_version_code_offset,
918*8975f5c5SAndroid Build Coastguard Worker          options.verification_library_version_offset)
919*8975f5c5SAndroid Build Coastguard Worker
920*8975f5c5SAndroid Build Coastguard Worker
921*8975f5c5SAndroid Build Coastguard Workerdef main(args):
922*8975f5c5SAndroid Build Coastguard Worker  build_utils.InitLogging('RESOURCE_DEBUG')
923*8975f5c5SAndroid Build Coastguard Worker  args = build_utils.ExpandFileArgs(args)
924*8975f5c5SAndroid Build Coastguard Worker  options = _ParseArgs(args)
925*8975f5c5SAndroid Build Coastguard Worker
926*8975f5c5SAndroid Build Coastguard Worker  if options.expected_file:
927*8975f5c5SAndroid Build Coastguard Worker    actual_data = _CreateNormalizedManifestForVerification(options)
928*8975f5c5SAndroid Build Coastguard Worker    diff_utils.CheckExpectations(actual_data, options)
929*8975f5c5SAndroid Build Coastguard Worker    if options.only_verify_expectations:
930*8975f5c5SAndroid Build Coastguard Worker      return
931*8975f5c5SAndroid Build Coastguard Worker
932*8975f5c5SAndroid Build Coastguard Worker  path = options.arsc_path or options.proto_path
933*8975f5c5SAndroid Build Coastguard Worker  debug_temp_resources_dir = os.environ.get('TEMP_RESOURCES_DIR')
934*8975f5c5SAndroid Build Coastguard Worker  if debug_temp_resources_dir:
935*8975f5c5SAndroid Build Coastguard Worker    path = os.path.join(debug_temp_resources_dir, os.path.basename(path))
936*8975f5c5SAndroid Build Coastguard Worker  else:
937*8975f5c5SAndroid Build Coastguard Worker    # Use a deterministic temp directory since .pb files embed the absolute
938*8975f5c5SAndroid Build Coastguard Worker    # path of resources: crbug.com/939984
939*8975f5c5SAndroid Build Coastguard Worker    path = path + '.tmpdir'
940*8975f5c5SAndroid Build Coastguard Worker  build_utils.DeleteDirectory(path)
941*8975f5c5SAndroid Build Coastguard Worker
942*8975f5c5SAndroid Build Coastguard Worker  with resource_utils.BuildContext(
943*8975f5c5SAndroid Build Coastguard Worker      temp_dir=path, keep_files=bool(debug_temp_resources_dir)) as build:
944*8975f5c5SAndroid Build Coastguard Worker
945*8975f5c5SAndroid Build Coastguard Worker    manifest_package_name = _PackageApk(options, build)
946*8975f5c5SAndroid Build Coastguard Worker
947*8975f5c5SAndroid Build Coastguard Worker    # If --shared-resources-allowlist is used, all the resources listed in the
948*8975f5c5SAndroid Build Coastguard Worker    # corresponding R.txt file will be non-final, and an onResourcesLoaded()
949*8975f5c5SAndroid Build Coastguard Worker    # will be generated to adjust them at runtime.
950*8975f5c5SAndroid Build Coastguard Worker    #
951*8975f5c5SAndroid Build Coastguard Worker    # Otherwise, if --shared-resources is used, the all resources will be
952*8975f5c5SAndroid Build Coastguard Worker    # non-final, and an onResourcesLoaded() method will be generated too.
953*8975f5c5SAndroid Build Coastguard Worker    #
954*8975f5c5SAndroid Build Coastguard Worker    # Otherwise, all resources will be final, and no method will be generated.
955*8975f5c5SAndroid Build Coastguard Worker    #
956*8975f5c5SAndroid Build Coastguard Worker    rjava_build_options = resource_utils.RJavaBuildOptions()
957*8975f5c5SAndroid Build Coastguard Worker    if options.shared_resources_allowlist:
958*8975f5c5SAndroid Build Coastguard Worker      rjava_build_options.ExportSomeResources(
959*8975f5c5SAndroid Build Coastguard Worker          options.shared_resources_allowlist)
960*8975f5c5SAndroid Build Coastguard Worker      rjava_build_options.GenerateOnResourcesLoaded()
961*8975f5c5SAndroid Build Coastguard Worker      if options.shared_resources:
962*8975f5c5SAndroid Build Coastguard Worker        # The final resources will only be used in WebLayer, so hardcode the
963*8975f5c5SAndroid Build Coastguard Worker        # package ID to be what WebLayer expects.
964*8975f5c5SAndroid Build Coastguard Worker        rjava_build_options.SetFinalPackageId(
965*8975f5c5SAndroid Build Coastguard Worker            protoresources.SHARED_LIBRARY_HARDCODED_ID)
966*8975f5c5SAndroid Build Coastguard Worker    elif options.shared_resources or options.app_as_shared_lib:
967*8975f5c5SAndroid Build Coastguard Worker      rjava_build_options.ExportAllResources()
968*8975f5c5SAndroid Build Coastguard Worker      rjava_build_options.GenerateOnResourcesLoaded()
969*8975f5c5SAndroid Build Coastguard Worker
970*8975f5c5SAndroid Build Coastguard Worker    custom_root_package_name = options.r_java_root_package_name
971*8975f5c5SAndroid Build Coastguard Worker    grandparent_custom_package_name = None
972*8975f5c5SAndroid Build Coastguard Worker
973*8975f5c5SAndroid Build Coastguard Worker    # Always generate an R.java file for the package listed in
974*8975f5c5SAndroid Build Coastguard Worker    # AndroidManifest.xml because this is where Android framework looks to find
975*8975f5c5SAndroid Build Coastguard Worker    # onResourcesLoaded() for shared library apks. While not actually necessary
976*8975f5c5SAndroid Build Coastguard Worker    # for application apks, it also doesn't hurt.
977*8975f5c5SAndroid Build Coastguard Worker    apk_package_name = manifest_package_name
978*8975f5c5SAndroid Build Coastguard Worker
979*8975f5c5SAndroid Build Coastguard Worker    if options.package_name and not options.arsc_package_name:
980*8975f5c5SAndroid Build Coastguard Worker      # Feature modules have their own custom root package name and should
981*8975f5c5SAndroid Build Coastguard Worker      # inherit from the appropriate base module package. This behaviour should
982*8975f5c5SAndroid Build Coastguard Worker      # not be present for test apks with an apk under test. Thus,
983*8975f5c5SAndroid Build Coastguard Worker      # arsc_package_name is used as it is only defined for test apks with an
984*8975f5c5SAndroid Build Coastguard Worker      # apk under test.
985*8975f5c5SAndroid Build Coastguard Worker      custom_root_package_name = options.package_name
986*8975f5c5SAndroid Build Coastguard Worker      grandparent_custom_package_name = options.r_java_root_package_name
987*8975f5c5SAndroid Build Coastguard Worker      # Feature modules have the same manifest package as the base module but
988*8975f5c5SAndroid Build Coastguard Worker      # they should not create an R.java for said manifest package because it
989*8975f5c5SAndroid Build Coastguard Worker      # will be created in the base module.
990*8975f5c5SAndroid Build Coastguard Worker      apk_package_name = None
991*8975f5c5SAndroid Build Coastguard Worker
992*8975f5c5SAndroid Build Coastguard Worker    if options.srcjar_out:
993*8975f5c5SAndroid Build Coastguard Worker      logging.debug('Creating R.srcjar')
994*8975f5c5SAndroid Build Coastguard Worker      resource_utils.CreateRJavaFiles(build.srcjar_dir, apk_package_name,
995*8975f5c5SAndroid Build Coastguard Worker                                      build.r_txt_path,
996*8975f5c5SAndroid Build Coastguard Worker                                      options.extra_res_packages,
997*8975f5c5SAndroid Build Coastguard Worker                                      rjava_build_options, options.srcjar_out,
998*8975f5c5SAndroid Build Coastguard Worker                                      custom_root_package_name,
999*8975f5c5SAndroid Build Coastguard Worker                                      grandparent_custom_package_name)
1000*8975f5c5SAndroid Build Coastguard Worker      with action_helpers.atomic_output(build.srcjar_path) as f:
1001*8975f5c5SAndroid Build Coastguard Worker        zip_helpers.zip_directory(f, build.srcjar_dir)
1002*8975f5c5SAndroid Build Coastguard Worker
1003*8975f5c5SAndroid Build Coastguard Worker    logging.debug('Copying outputs')
1004*8975f5c5SAndroid Build Coastguard Worker    _WriteOutputs(options, build)
1005*8975f5c5SAndroid Build Coastguard Worker
1006*8975f5c5SAndroid Build Coastguard Worker  if options.depfile:
1007*8975f5c5SAndroid Build Coastguard Worker    assert options.srcjar_out, 'Update first output below and remove assert.'
1008*8975f5c5SAndroid Build Coastguard Worker    depfile_deps = (options.dependencies_res_zips +
1009*8975f5c5SAndroid Build Coastguard Worker                    options.dependencies_res_zip_overlays +
1010*8975f5c5SAndroid Build Coastguard Worker                    options.include_resources)
1011*8975f5c5SAndroid Build Coastguard Worker    action_helpers.write_depfile(options.depfile, options.srcjar_out,
1012*8975f5c5SAndroid Build Coastguard Worker                                 depfile_deps)
1013*8975f5c5SAndroid Build Coastguard Worker
1014*8975f5c5SAndroid Build Coastguard Worker
1015*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__':
1016*8975f5c5SAndroid Build Coastguard Worker  main(sys.argv[1:])
1017