summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/build/android/gyp/prepare_resources.py
blob: ba75afaee317400e0aec9dfaa4c07f11e3a61cca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env python3
#
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Process Android resource directories to generate .resources.zip and R.txt
files."""

import argparse
import os
import shutil
import sys
import zipfile

from util import build_utils
from util import jar_info_utils
from util import md5_check
from util import resources_parser
from util import resource_utils


def _ParseArgs(args):
  """Parses command line options.

  Returns:
    An options object as from argparse.ArgumentParser.parse_args()
  """
  parser = argparse.ArgumentParser(description=__doc__)
  build_utils.AddDepfileOption(parser)

  parser.add_argument('--res-sources-path',
                      required=True,
                      help='Path to a list of input resources for this target.')

  parser.add_argument(
      '--r-text-in',
      help='Path to pre-existing R.txt. Its resource IDs override those found '
      'in the generated R.txt when generating R.java.')

  parser.add_argument(
      '--allow-missing-resources',
      action='store_true',
      help='Do not fail if some resources exist in the res/ dir but are not '
      'listed in the sources.')

  parser.add_argument(
      '--resource-zip-out',
      help='Path to a zip archive containing all resources from '
      '--resource-dirs, merged into a single directory tree.')

  parser.add_argument('--r-text-out',
                      help='Path to store the generated R.txt file.')

  parser.add_argument('--strip-drawables',
                      action="store_true",
                      help='Remove drawables from the resources.')

  options = parser.parse_args(args)

  with open(options.res_sources_path) as f:
    options.sources = f.read().splitlines()
  options.resource_dirs = resource_utils.DeduceResourceDirsFromFileList(
      options.sources)

  return options


def _CheckAllFilesListed(resource_files, resource_dirs):
  resource_files = set(resource_files)
  missing_files = []
  for path, _ in resource_utils.IterResourceFilesInDirectories(resource_dirs):
    if path not in resource_files:
      missing_files.append(path)

  if missing_files:
    sys.stderr.write('Error: Found files not listed in the sources list of '
                     'the BUILD.gn target:\n')
    for path in missing_files:
      sys.stderr.write('{}\n'.format(path))
    sys.exit(1)


def _ZipResources(resource_dirs, zip_path, ignore_pattern):
  # ignore_pattern is a string of ':' delimited list of globs used to ignore
  # files that should not be part of the final resource zip.
  files_to_zip = []
  path_info = resource_utils.ResourceInfoFile()
  for index, resource_dir in enumerate(resource_dirs):
    attributed_aar = None
    if not resource_dir.startswith('..'):
      aar_source_info_path = os.path.join(
          os.path.dirname(resource_dir), 'source.info')
      if os.path.exists(aar_source_info_path):
        attributed_aar = jar_info_utils.ReadAarSourceInfo(aar_source_info_path)

    for path, archive_path in resource_utils.IterResourceFilesInDirectories(
        [resource_dir], ignore_pattern):
      attributed_path = path
      if attributed_aar:
        attributed_path = os.path.join(attributed_aar, 'res',
                                       path[len(resource_dir) + 1:])
      # Use the non-prefixed archive_path in the .info file.
      path_info.AddMapping(archive_path, attributed_path)

      resource_dir_name = os.path.basename(resource_dir)
      archive_path = '{}_{}/{}'.format(index, resource_dir_name, archive_path)
      files_to_zip.append((archive_path, path))

  path_info.Write(zip_path + '.info')

  with zipfile.ZipFile(zip_path, 'w') as z:
    # This magic comment signals to resource_utils.ExtractDeps that this zip is
    # not just the contents of a single res dir, without the encapsulating res/
    # (like the outputs of android_generated_resources targets), but instead has
    # the contents of possibly multiple res/ dirs each within an encapsulating
    # directory within the zip.
    z.comment = resource_utils.MULTIPLE_RES_MAGIC_STRING
    build_utils.DoZip(files_to_zip, z)


def _GenerateRTxt(options, r_txt_path):
  """Generate R.txt file.

  Args:
    options: The command-line options tuple.
    r_txt_path: Locates where the R.txt file goes.
  """
  ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN
  if options.strip_drawables:
    ignore_pattern += ':*drawable*'

  resources_parser.RTxtGenerator(options.resource_dirs,
                                 ignore_pattern).WriteRTxtFile(r_txt_path)


def _OnStaleMd5(options):
  with resource_utils.BuildContext() as build:
    if options.sources and not options.allow_missing_resources:
      _CheckAllFilesListed(options.sources, options.resource_dirs)
    if options.r_text_in:
      r_txt_path = options.r_text_in
    else:
      _GenerateRTxt(options, build.r_txt_path)
      r_txt_path = build.r_txt_path

    if options.r_text_out:
      shutil.copyfile(r_txt_path, options.r_text_out)

    if options.resource_zip_out:
      ignore_pattern = resource_utils.AAPT_IGNORE_PATTERN
      if options.strip_drawables:
        ignore_pattern += ':*drawable*'
      _ZipResources(options.resource_dirs, options.resource_zip_out,
                    ignore_pattern)


def main(args):
  args = build_utils.ExpandFileArgs(args)
  options = _ParseArgs(args)

  # Order of these must match order specified in GN so that the correct one
  # appears first in the depfile.
  output_paths = [
      options.resource_zip_out,
      options.resource_zip_out + '.info',
      options.r_text_out,
  ]

  input_paths = [options.res_sources_path]
  if options.r_text_in:
    input_paths += [options.r_text_in]

  # Resource files aren't explicitly listed in GN. Listing them in the depfile
  # ensures the target will be marked stale when resource files are removed.
  depfile_deps = []
  resource_names = []
  for resource_dir in options.resource_dirs:
    for resource_file in build_utils.FindInDirectory(resource_dir, '*'):
      # Don't list the empty .keep file in depfile. Since it doesn't end up
      # included in the .zip, it can lead to -w 'dupbuild=err' ninja errors
      # if ever moved.
      if not resource_file.endswith(os.path.join('empty', '.keep')):
        input_paths.append(resource_file)
        depfile_deps.append(resource_file)
      resource_names.append(os.path.relpath(resource_file, resource_dir))

  # Resource filenames matter to the output, so add them to strings as well.
  # This matters if a file is renamed but not changed (http://crbug.com/597126).
  input_strings = sorted(resource_names) + [
      options.strip_drawables,
  ]

  # Since android_resources targets like *__all_dfm_resources depend on java
  # targets that they do not need (in reality it only needs the transitive
  # resource targets that those java targets depend on), md5_check is used to
  # prevent outputs from being re-written when real inputs have not changed.
  md5_check.CallAndWriteDepfileIfStale(lambda: _OnStaleMd5(options),
                                       options,
                                       input_paths=input_paths,
                                       input_strings=input_strings,
                                       output_paths=output_paths,
                                       depfile_deps=depfile_deps)


if __name__ == '__main__':
  main(sys.argv[1:])