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