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