• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3# Copyright 2018 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"""Creates size-info/*.info files used by SuperSize."""
8
9import argparse
10import collections
11import os
12import re
13import sys
14import zipfile
15
16from util import build_utils
17from util import jar_info_utils
18import action_helpers  # build_utils adds //build to sys.path.
19
20
21_AAR_VERSION_PATTERN = re.compile(r'/[^/]*?(\.aar/|\.jar/)')
22
23
24def _RemoveDuplicatesFromList(source_list):
25  return collections.OrderedDict.fromkeys(source_list).keys()
26
27
28def _TransformAarPaths(path):
29  # .aar files within //third_party/android_deps have a version suffix.
30  # The suffix changes each time .aar files are updated, which makes size diffs
31  # hard to compare (since the before/after have different source paths).
32  # Rather than changing how android_deps works, we employ this work-around
33  # to normalize the paths.
34  # From: .../androidx_appcompat_appcompat/appcompat-1.1.0.aar/res/...
35  #   To: .../androidx_appcompat_appcompat.aar/res/...
36  # https://crbug.com/1056455
37  if 'android_deps' not in path:
38    return path
39  return _AAR_VERSION_PATTERN.sub(r'\1', path)
40
41
42def _MergeResInfoFiles(res_info_path, info_paths):
43  # Concatenate them all.
44  with action_helpers.atomic_output(res_info_path, 'w+') as dst:
45    for p in info_paths:
46      with open(p) as src:
47        dst.writelines(_TransformAarPaths(l) for l in src)
48
49
50def _PakInfoPathsForAssets(assets):
51  # Use "in" rather than "endswith" due to suffix. https://crbug.com/357131361
52  return [f.split(':')[0] + '.info' for f in assets if '.pak' in f]
53
54
55def _MergePakInfoFiles(merged_path, pak_infos):
56  info_lines = set()
57  for pak_info_path in pak_infos:
58    with open(pak_info_path, 'r') as src_info_file:
59      info_lines.update(_TransformAarPaths(x) for x in src_info_file)
60  # only_if_changed=False since no build rules depend on this as an input.
61  with action_helpers.atomic_output(merged_path,
62                                    only_if_changed=False,
63                                    mode='w+') as f:
64    f.writelines(sorted(info_lines))
65
66
67def _FullJavaNameFromClassFilePath(path):
68  # Input:  base/android/java/src/org/chromium/Foo.class
69  # Output: base.android.java.src.org.chromium.Foo
70  if not path.endswith('.class'):
71    return ''
72  path = os.path.splitext(path)[0]
73  parts = []
74  while path:
75    # Use split to be platform independent.
76    head, tail = os.path.split(path)
77    path = head
78    parts.append(tail)
79  parts.reverse()  # Package comes first
80  return '.'.join(parts)
81
82
83def _MergeJarInfoFiles(output, inputs):
84  """Merge several .jar.info files to generate an .apk.jar.info.
85
86  Args:
87    output: output file path.
88    inputs: List of .jar.info or .jar files.
89  """
90  info_data = dict()
91  for path in inputs:
92    # For non-prebuilts: .jar.info files are written by compile_java.py and map
93    # .class files to .java source paths.
94    #
95    # For prebuilts: No .jar.info file exists, we scan the .jar files here and
96    # map .class files to the .jar.
97    #
98    # For .aar files: We look for a "source.info" file in the containing
99    # directory in order to map classes back to the .aar (rather than mapping
100    # them to the extracted .jar file).
101    if path.endswith('.info'):
102      info_data.update(jar_info_utils.ParseJarInfoFile(path))
103    else:
104      attributed_path = path
105      if not path.startswith('..'):
106        parent_path = os.path.dirname(path)
107        # See if it's an sub-jar within the .aar.
108        if os.path.basename(parent_path) == 'libs':
109          parent_path = os.path.dirname(parent_path)
110        aar_source_info_path = os.path.join(parent_path, 'source.info')
111        # source.info files exist only for jars from android_aar_prebuilt().
112        # E.g. Could have an java_prebuilt() pointing to a generated .jar.
113        if os.path.exists(aar_source_info_path):
114          attributed_path = jar_info_utils.ReadAarSourceInfo(
115              aar_source_info_path)
116
117      with zipfile.ZipFile(path) as zip_info:
118        for name in zip_info.namelist():
119          fully_qualified_name = _FullJavaNameFromClassFilePath(name)
120          if fully_qualified_name:
121            info_data[fully_qualified_name] = _TransformAarPaths('{}/{}'.format(
122                attributed_path, name))
123
124  # only_if_changed=False since no build rules depend on this as an input.
125  with action_helpers.atomic_output(output, only_if_changed=False) as f:
126    jar_info_utils.WriteJarInfoFile(f, info_data)
127
128
129def _FindJarInputs(jar_paths):
130  ret = []
131  for jar_path in jar_paths:
132    jar_info_path = jar_path + '.info'
133    if os.path.exists(jar_info_path):
134      ret.append(jar_info_path)
135    else:
136      ret.append(jar_path)
137  return ret
138
139
140def main(args):
141  args = build_utils.ExpandFileArgs(args)
142  parser = argparse.ArgumentParser(description=__doc__)
143  action_helpers.add_depfile_arg(parser)
144  parser.add_argument(
145      '--jar-info-path', required=True, help='Output .jar.info file')
146  parser.add_argument(
147      '--pak-info-path', required=True, help='Output .pak.info file')
148  parser.add_argument(
149      '--res-info-path', required=True, help='Output .res.info file')
150  parser.add_argument(
151      '--jar-files',
152      required=True,
153      action='append',
154      help='GN-list of .jar file paths')
155  parser.add_argument(
156      '--assets',
157      required=True,
158      action='append',
159      help='GN-list of files to add as assets in the form '
160      '"srcPath:zipPath", where ":zipPath" is optional.')
161  parser.add_argument(
162      '--uncompressed-assets',
163      required=True,
164      action='append',
165      help='Same as --assets, except disables compression.')
166  parser.add_argument(
167      '--in-res-info-path',
168      required=True,
169      action='append',
170      help='Paths to .ap_.info files')
171
172  options = parser.parse_args(args)
173
174  options.jar_files = action_helpers.parse_gn_list(options.jar_files)
175  options.assets = action_helpers.parse_gn_list(options.assets)
176  options.uncompressed_assets = action_helpers.parse_gn_list(
177      options.uncompressed_assets)
178
179  jar_inputs = _FindJarInputs(_RemoveDuplicatesFromList(options.jar_files))
180  pak_inputs = _PakInfoPathsForAssets(options.assets +
181                                      options.uncompressed_assets)
182  res_inputs = options.in_res_info_path
183
184  # Just create the info files every time. See https://crbug.com/1045024
185  _MergeJarInfoFiles(options.jar_info_path, jar_inputs)
186  _MergePakInfoFiles(options.pak_info_path, pak_inputs)
187  _MergeResInfoFiles(options.res_info_path, res_inputs)
188
189  all_inputs = jar_inputs + pak_inputs + res_inputs
190  action_helpers.write_depfile(options.depfile,
191                               options.jar_info_path,
192                               inputs=all_inputs)
193
194
195if __name__ == '__main__':
196  main(sys.argv[1:])
197