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