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 return [f.split(':')[0] + '.info' for f in assets if f.endswith('.pak')] 52 53 54def _MergePakInfoFiles(merged_path, pak_infos): 55 info_lines = set() 56 for pak_info_path in pak_infos: 57 with open(pak_info_path, 'r') as src_info_file: 58 info_lines.update(_TransformAarPaths(x) for x in src_info_file) 59 # only_if_changed=False since no build rules depend on this as an input. 60 with action_helpers.atomic_output(merged_path, 61 only_if_changed=False, 62 mode='w+') as f: 63 f.writelines(sorted(info_lines)) 64 65 66def _FullJavaNameFromClassFilePath(path): 67 # Input: base/android/java/src/org/chromium/Foo.class 68 # Output: base.android.java.src.org.chromium.Foo 69 if not path.endswith('.class'): 70 return '' 71 path = os.path.splitext(path)[0] 72 parts = [] 73 while path: 74 # Use split to be platform independent. 75 head, tail = os.path.split(path) 76 path = head 77 parts.append(tail) 78 parts.reverse() # Package comes first 79 return '.'.join(parts) 80 81 82def _MergeJarInfoFiles(output, inputs): 83 """Merge several .jar.info files to generate an .apk.jar.info. 84 85 Args: 86 output: output file path. 87 inputs: List of .jar.info or .jar files. 88 """ 89 info_data = dict() 90 for path in inputs: 91 # For non-prebuilts: .jar.info files are written by compile_java.py and map 92 # .class files to .java source paths. 93 # 94 # For prebuilts: No .jar.info file exists, we scan the .jar files here and 95 # map .class files to the .jar. 96 # 97 # For .aar files: We look for a "source.info" file in the containing 98 # directory in order to map classes back to the .aar (rather than mapping 99 # them to the extracted .jar file). 100 if path.endswith('.info'): 101 info_data.update(jar_info_utils.ParseJarInfoFile(path)) 102 else: 103 attributed_path = path 104 if not path.startswith('..'): 105 parent_path = os.path.dirname(path) 106 # See if it's an sub-jar within the .aar. 107 if os.path.basename(parent_path) == 'libs': 108 parent_path = os.path.dirname(parent_path) 109 aar_source_info_path = os.path.join(parent_path, 'source.info') 110 # source.info files exist only for jars from android_aar_prebuilt(). 111 # E.g. Could have an java_prebuilt() pointing to a generated .jar. 112 if os.path.exists(aar_source_info_path): 113 attributed_path = jar_info_utils.ReadAarSourceInfo( 114 aar_source_info_path) 115 116 with zipfile.ZipFile(path) as zip_info: 117 for name in zip_info.namelist(): 118 fully_qualified_name = _FullJavaNameFromClassFilePath(name) 119 if fully_qualified_name: 120 info_data[fully_qualified_name] = _TransformAarPaths('{}/{}'.format( 121 attributed_path, name)) 122 123 # only_if_changed=False since no build rules depend on this as an input. 124 with action_helpers.atomic_output(output, only_if_changed=False) as f: 125 jar_info_utils.WriteJarInfoFile(f, info_data) 126 127 128def _FindJarInputs(jar_paths): 129 ret = [] 130 for jar_path in jar_paths: 131 jar_info_path = jar_path + '.info' 132 if os.path.exists(jar_info_path): 133 ret.append(jar_info_path) 134 else: 135 ret.append(jar_path) 136 return ret 137 138 139def main(args): 140 args = build_utils.ExpandFileArgs(args) 141 parser = argparse.ArgumentParser(description=__doc__) 142 action_helpers.add_depfile_arg(parser) 143 parser.add_argument( 144 '--jar-info-path', required=True, help='Output .jar.info file') 145 parser.add_argument( 146 '--pak-info-path', required=True, help='Output .pak.info file') 147 parser.add_argument( 148 '--res-info-path', required=True, help='Output .res.info file') 149 parser.add_argument( 150 '--jar-files', 151 required=True, 152 action='append', 153 help='GN-list of .jar file paths') 154 parser.add_argument( 155 '--assets', 156 required=True, 157 action='append', 158 help='GN-list of files to add as assets in the form ' 159 '"srcPath:zipPath", where ":zipPath" is optional.') 160 parser.add_argument( 161 '--uncompressed-assets', 162 required=True, 163 action='append', 164 help='Same as --assets, except disables compression.') 165 parser.add_argument( 166 '--in-res-info-path', 167 required=True, 168 action='append', 169 help='Paths to .ap_.info files') 170 171 options = parser.parse_args(args) 172 173 options.jar_files = action_helpers.parse_gn_list(options.jar_files) 174 options.assets = action_helpers.parse_gn_list(options.assets) 175 options.uncompressed_assets = action_helpers.parse_gn_list( 176 options.uncompressed_assets) 177 178 jar_inputs = _FindJarInputs(_RemoveDuplicatesFromList(options.jar_files)) 179 pak_inputs = _PakInfoPathsForAssets(options.assets + 180 options.uncompressed_assets) 181 res_inputs = options.in_res_info_path 182 183 # Just create the info files every time. See https://crbug.com/1045024 184 _MergeJarInfoFiles(options.jar_info_path, jar_inputs) 185 _MergePakInfoFiles(options.pak_info_path, pak_inputs) 186 _MergeResInfoFiles(options.res_info_path, res_inputs) 187 188 all_inputs = jar_inputs + pak_inputs + res_inputs 189 action_helpers.write_depfile(options.depfile, 190 options.jar_info_path, 191 inputs=all_inputs) 192 193 194if __name__ == '__main__': 195 main(sys.argv[1:]) 196