xref: /aosp_15_r20/external/angle/build/android/gyp/prepare_resources.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#!/usr/bin/env python3
2#
3# Copyright 2012 The Chromium Authors
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Process Android resource directories to generate .resources.zip and R.txt
8files."""
9
10import argparse
11import os
12import shutil
13import sys
14import zipfile
15
16from util import build_utils
17from util import jar_info_utils
18from util import md5_check
19from util import resources_parser
20from util import resource_utils
21import action_helpers  # build_utils adds //build to sys.path.
22import zip_helpers
23
24
25def _ParseArgs(args):
26  """Parses command line options.
27
28  Returns:
29    An options object as from argparse.ArgumentParser.parse_args()
30  """
31  parser = argparse.ArgumentParser(description=__doc__)
32  action_helpers.add_depfile_arg(parser)
33
34  parser.add_argument('--res-sources-path',
35                      required=True,
36                      help='Path to a list of input resources for this target.')
37
38  parser.add_argument(
39      '--r-text-in',
40      help='Path to pre-existing R.txt. Its resource IDs override those found '
41      'in the generated R.txt when generating R.java.')
42
43  parser.add_argument(
44      '--allow-missing-resources',
45      action='store_true',
46      help='Do not fail if some resources exist in the res/ dir but are not '
47      'listed in the sources.')
48
49  parser.add_argument(
50      '--resource-zip-out',
51      help='Path to a zip archive containing all resources from '
52      '--resource-dirs, merged into a single directory tree.')
53
54  parser.add_argument('--r-text-out',
55                      help='Path to store the generated R.txt file.')
56
57  parser.add_argument('--strip-drawables',
58                      action="store_true",
59                      help='Remove drawables from the resources.')
60
61  options = parser.parse_args(args)
62
63  with open(options.res_sources_path) as f:
64    options.sources = f.read().splitlines()
65  options.resource_dirs = resource_utils.DeduceResourceDirsFromFileList(
66      options.sources)
67
68  return options
69
70
71def _CheckAllFilesListed(resource_files, resource_dirs):
72  resource_files = set(resource_files)
73  missing_files = []
74  for path, _ in resource_utils.IterResourceFilesInDirectories(resource_dirs):
75    if path not in resource_files:
76      missing_files.append(path)
77
78  if missing_files:
79    sys.stderr.write('Error: Found files not listed in the sources list of '
80                     'the BUILD.gn target:\n')
81    for path in missing_files:
82      sys.stderr.write('{}\n'.format(path))
83    sys.exit(1)
84
85
86def _ZipResources(resource_dirs, zip_path, ignore_pattern):
87  # ignore_pattern is a string of ':' delimited list of globs used to ignore
88  # files that should not be part of the final resource zip.
89  files_to_zip = []
90  path_info = resource_utils.ResourceInfoFile()
91  for index, resource_dir in enumerate(resource_dirs):
92    attributed_aar = None
93    if not resource_dir.startswith('..'):
94      aar_source_info_path = os.path.join(
95          os.path.dirname(resource_dir), 'source.info')
96      if os.path.exists(aar_source_info_path):
97        attributed_aar = jar_info_utils.ReadAarSourceInfo(aar_source_info_path)
98
99    for path, archive_path in resource_utils.IterResourceFilesInDirectories(
100        [resource_dir], ignore_pattern):
101      attributed_path = path
102      if attributed_aar:
103        attributed_path = os.path.join(attributed_aar, 'res',
104                                       path[len(resource_dir) + 1:])
105      # Use the non-prefixed archive_path in the .info file.
106      path_info.AddMapping(archive_path, attributed_path)
107
108      resource_dir_name = os.path.basename(resource_dir)
109      archive_path = '{}_{}/{}'.format(index, resource_dir_name, archive_path)
110      files_to_zip.append((archive_path, path))
111
112  path_info.Write(zip_path + '.info')
113
114  with zipfile.ZipFile(zip_path, 'w') as z:
115    # This magic comment signals to resource_utils.ExtractDeps that this zip is
116    # not just the contents of a single res dir, without the encapsulating res/
117    # (like the outputs of android_generated_resources targets), but instead has
118    # the contents of possibly multiple res/ dirs each within an encapsulating
119    # directory within the zip.
120    z.comment = resource_utils.MULTIPLE_RES_MAGIC_STRING
121    zip_helpers.add_files_to_zip(files_to_zip, z)
122
123
124def _GenerateRTxt(options, r_txt_path):
125  """Generate R.txt file.
126
127  Args:
128    options: The command-line options tuple.
129    r_txt_path: Locates where the R.txt file goes.
130  """
131  ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN
132  if options.strip_drawables:
133    ignore_pattern += ':*drawable*'
134
135  resources_parser.RTxtGenerator(options.resource_dirs,
136                                 ignore_pattern).WriteRTxtFile(r_txt_path)
137
138
139def _OnStaleMd5(options):
140  with resource_utils.BuildContext() as build:
141    if options.sources and not options.allow_missing_resources:
142      _CheckAllFilesListed(options.sources, options.resource_dirs)
143    if options.r_text_in:
144      r_txt_path = options.r_text_in
145    else:
146      _GenerateRTxt(options, build.r_txt_path)
147      r_txt_path = build.r_txt_path
148
149    if options.r_text_out:
150      shutil.copyfile(r_txt_path, options.r_text_out)
151
152    if options.resource_zip_out:
153      ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN
154      if options.strip_drawables:
155        ignore_pattern += ':*drawable*'
156      _ZipResources(options.resource_dirs, options.resource_zip_out,
157                    ignore_pattern)
158
159
160def main(args):
161  args = build_utils.ExpandFileArgs(args)
162  options = _ParseArgs(args)
163
164  # Order of these must match order specified in GN so that the correct one
165  # appears first in the depfile.
166  output_paths = [
167      options.resource_zip_out,
168      options.resource_zip_out + '.info',
169      options.r_text_out,
170  ]
171
172  input_paths = [options.res_sources_path]
173  if options.r_text_in:
174    input_paths += [options.r_text_in]
175
176  # Resource files aren't explicitly listed in GN. Listing them in the depfile
177  # ensures the target will be marked stale when resource files are removed.
178  depfile_deps = []
179  resource_names = []
180  for resource_dir in options.resource_dirs:
181    for resource_file in build_utils.FindInDirectory(resource_dir, '*'):
182      # Don't list the empty .keep file in depfile. Since it doesn't end up
183      # included in the .zip, it can lead to -w 'dupbuild=err' ninja errors
184      # if ever moved.
185      if not resource_file.endswith(os.path.join('empty', '.keep')):
186        input_paths.append(resource_file)
187        depfile_deps.append(resource_file)
188      resource_names.append(os.path.relpath(resource_file, resource_dir))
189
190  # Resource filenames matter to the output, so add them to strings as well.
191  # This matters if a file is renamed but not changed (http://crbug.com/597126).
192  input_strings = sorted(resource_names) + [
193      options.strip_drawables,
194  ]
195
196  # Since android_resources targets like *__all_dfm_resources depend on java
197  # targets that they do not need (in reality it only needs the transitive
198  # resource targets that those java targets depend on), md5_check is used to
199  # prevent outputs from being re-written when real inputs have not changed.
200  md5_check.CallAndWriteDepfileIfStale(lambda: _OnStaleMd5(options),
201                                       options,
202                                       input_paths=input_paths,
203                                       input_strings=input_strings,
204                                       output_paths=output_paths,
205                                       depfile_deps=depfile_deps)
206
207
208if __name__ == '__main__':
209  main(sys.argv[1:])
210