1# Copyright 2018 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Methods to process Android resources.""" 15 16load(":constants.bzl", "constants") 17load(":utils.bzl", "utils") 18 19# Android resource types, see https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/tools/aapt2/Resource.h 20res_types = [ 21 "anim", 22 "animator", 23 "array", 24 "attr", 25 "^attr-private", 26 "bool", 27 "color", 28 "configVarying", # Not really a type, but it shows up in some CTS tests 29 "dimen", 30 "drawable", 31 "font", 32 "fraction", 33 "id", 34 "integer", 35 "interpolator", 36 "layout", 37 "macro", 38 "menu", 39 "mipmap", 40 "navigation", 41 "plurals", 42 "raw", 43 "string", 44 "style", 45 "styleable", 46 "transition", 47 "xml", 48] 49 50def get_assets_dir(asset, base_dir): 51 """Build the full assets directory sanitizing the input first.""" 52 if base_dir == "": 53 # some targets specify assets files and set assets_dirs = "" 54 return asset.dirname 55 asset_path = asset.path.rstrip("/") 56 base_dir = base_dir.rstrip("/") 57 if asset_path.endswith("/" + base_dir): 58 return asset_path 59 return "%s/%s" % (asset_path.rpartition("/%s/" % base_dir)[0], base_dir) 60 61def compile_resources(ctx, lib_strategy = True): 62 """Compiles android resources using aapt2 63 64 Args: 65 ctx: The context. 66 lib_strategy: If to use library strategy or bucket. Default is lib strategy. 67 68 Returns: 69 A list of compiled android resource archives (.flata) files, otherwise None 70 if data is None or empty. 71 """ 72 res_dir_file_map = partition_by_res_dirs(ctx.rule.files.resource_files) 73 if lib_strategy: 74 return _compile_library_resouces(ctx, res_dir_file_map) 75 res_dir_buckets_map = _bucketize_resources(ctx, res_dir_file_map) 76 return _compile_bucketized_resources(ctx, res_dir_buckets_map) 77 78def partition_by_res_dirs(res_files): 79 """Partitions the resources by res directories. 80 81 Args: 82 res_files: A list of resource artifacts files. 83 84 Returns: 85 A map of "res" directories to files corresponding to the directory. 86 """ 87 if not res_files: 88 return None 89 90 # Given the fact that res directories can be named anything and 91 # its not possible to distinguish between directories and regular files 92 # during analysis time, we use file extensions as an heuristic to group 93 # resource files. All Android resource files have the following form 94 # res-dir/type-dir/res_file. When we see a regular file (by looking at 95 # the extesion) we use the directory two levels up as the grouping dir. 96 # Most of the resource directories will contain at least one file with 97 # and extension, so this heuristic will generally result in good groupings. 98 res_non_values_file_map = {} 99 res_value_file_map = {} 100 res_dir_map = {} 101 for res_file in res_files: 102 if res_file.is_directory: 103 res_dir_map.setdefault(res_file.path, []).append(res_file) 104 else: 105 path_segments = res_file.dirname.rsplit("/", 1) 106 root_dir = path_segments[0] 107 if path_segments[1].startswith("values"): 108 res_value_file_map.setdefault(root_dir, []).append(res_file) 109 else: 110 res_non_values_file_map.setdefault(root_dir, []).append(res_file) 111 return { 112 "values": res_value_file_map, 113 "non-values": res_non_values_file_map, 114 "res_dir": res_dir_map, 115 } 116 117def _bucketize_resources(ctx, data): 118 """Bucketizes resources by type. 119 120 Args: 121 ctx: The context. 122 data: A map of "res" directories to files corresponding to the directory. 123 124 Returns: 125 A map of "res" directories to "res" buckets, None when there no resource 126 files to compile. 127 """ 128 if not data: 129 return None 130 131 # Create backing files for the resource sharder. 132 res_dir_buckets_map = {} 133 for i, res_dir in enumerate(data.keys()): 134 res_buckets = [] 135 typed_outputs = [] 136 137 for r_type in res_types: 138 for idx in range(ctx.attr._mi_res_shards): 139 res_bucket = utils.isolated_declare_file( 140 ctx, 141 ctx.label.name + "_mi/resources/buckets/%d/%s_%s.zip" % (i, r_type, idx), 142 ) 143 res_buckets.append(res_bucket) 144 typed_outputs.append(r_type + ":" + res_bucket.path) 145 146 args = ctx.actions.args() 147 args.use_param_file(param_file_arg = "-flagfile=%s") 148 args.set_param_file_format("multiline") 149 args.add_joined("-typed_outputs", typed_outputs, join_with = ",") 150 if data[res_dir]: 151 args.add_joined("-res_paths", data[res_dir], join_with = ",") 152 153 ctx.actions.run( 154 executable = ctx.executable._android_kit, 155 arguments = ["bucketize", args], 156 inputs = data[res_dir], 157 outputs = res_buckets, 158 mnemonic = "BucketizeRes", 159 progress_message = "MI Bucketize resources for %s" % res_dir, 160 ) 161 res_dir_buckets_map[res_dir] = res_buckets 162 return res_dir_buckets_map 163 164def _compile_bucketized_resources(ctx, data): 165 """Compiles android resources using aapt2 166 167 Args: 168 ctx: The context. 169 data: A map of res directories to resource buckets. 170 171 Returns: 172 A list of compiled android resource archives (.flata) files, otherwise None 173 if data is None or empty. 174 """ 175 if not data: 176 return constants.EMPTY_LIST 177 178 # TODO(mauriciogg): use no-crunch. We are using crunch to process 9-patch 179 # pngs should be disabled in general either by having a more granular flag 180 # in aapt2 or bucketizing 9patch pngs. See (b/70578281) 181 compiled_res_buckets = [] 182 for res_dir, res_buckets in data.items(): 183 for res_bucket in res_buckets: 184 # Note that extension matters for aapt2. 185 out = utils.isolated_declare_file( 186 ctx, 187 res_bucket.basename + ".flata", 188 sibling = res_bucket, 189 ) 190 ctx.actions.run( 191 executable = ctx.executable._android_kit, 192 arguments = [ 193 "compile", 194 "--aapt2=" + utils.first(ctx.attr._aapt2.files).path, 195 "--in=" + res_bucket.path, 196 "--out=" + out.path, 197 ], 198 inputs = [res_bucket] + ctx.attr._aapt2.files.to_list(), 199 outputs = [out], 200 mnemonic = "CompileRes", 201 progress_message = "MI Compiling resources for %s" % res_dir, 202 ) 203 compiled_res_buckets.append(out) 204 205 return compiled_res_buckets 206 207def _compile_library_resouces(ctx, data): 208 """Compiles android resources using aapt2 209 210 Args: 211 ctx: The context. 212 data: A map of res directories to resource buckets. 213 214 Returns: 215 A list of compiled android resource archives (.flata) files, otherwise None 216 if data is None or empty. 217 """ 218 if not data: 219 return constants.EMPTY_LIST 220 221 # TODO(mauriciogg): use no-crunch. We are using crunch to process 9-patch 222 # pngs should be disabled in general either by having a more granular flag 223 # in aapt2 or bucketizing 9patch pngs. See (b/70578281) 224 compiled_res_dirs = [] 225 for res_type in data.keys(): 226 for res_dir in data[res_type].keys(): 227 # Note that extension matters for aapt2. 228 out = utils.isolated_declare_file( 229 ctx, 230 ctx.label.name + "_mi/resources/%s_%s.flata" % (res_dir.replace("/", "_"), res_type), 231 ) 232 compiled_res_dirs.append(out) 233 234 args = ctx.actions.args() 235 args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True) 236 args.set_param_file_format("multiline") 237 args.add("-aapt2", ctx.file._aapt2) 238 args.add("-in", res_dir) 239 args.add("-out", out) 240 ctx.actions.run( 241 executable = ctx.executable._android_kit, 242 arguments = ["compile", args], 243 inputs = data[res_type][res_dir] + ctx.attr._aapt2.files.to_list(), 244 outputs = [out], 245 mnemonic = "CompileRes", 246 progress_message = "MI Compiling resources for %s" % res_dir, 247 ) 248 return compiled_res_dirs 249 250def link_resources( 251 ctx, 252 manifest, 253 java_package, 254 android_jar, 255 resource_archives, 256 assets, 257 assets_dirs): 258 """Links android resources using aapt2 259 260 Args: 261 ctx: The context. 262 manifest: The AndroidManifest.xml file 263 java_package: The package to use to generate R.java 264 android_jar: The android jar 265 resource_archives: List of intermediate compiled android resource files. 266 assets: The list of assets. 267 assets_dirs: The list of directories for the assets. 268 269 Returns: 270 The resource apk and the R java file generated by aapt2. 271 """ 272 if not resource_archives: 273 return None 274 275 resource_apk = utils.isolated_declare_file(ctx, ctx.label.name + "_mi/resources/resource.apk") 276 rjava_zip = utils.isolated_declare_file(ctx, "R.zip", sibling = resource_apk) 277 278 args = ctx.actions.args() 279 args.use_param_file(param_file_arg = "-flagfile=%s", use_always = True) 280 args.add("-aapt2", ctx.executable._aapt2) 281 args.add("-sdk_jar", android_jar) 282 args.add("-manifest", manifest) 283 args.add("-pkg", java_package) 284 args.add("-src_jar", rjava_zip) 285 args.add("-out", resource_apk) 286 args.add_joined("-res_dirs", resource_archives, join_with = ",") 287 args.add_joined("-asset_dirs", assets_dirs, join_with = ",") 288 289 ctx.actions.run( 290 executable = ctx.executable._android_kit, 291 arguments = ["link", args], 292 inputs = depset( 293 [manifest, android_jar, ctx.executable._aapt2] + resource_archives, 294 transitive = [assets], 295 ), 296 outputs = [resource_apk, rjava_zip], 297 mnemonic = "LinkRes", 298 progress_message = "MI Linking resources for %s" % ctx.label, 299 ) 300 return resource_apk, rjava_zip 301 302def liteparse(ctx): 303 """Creates an R.pb which contains the resource ids gotten from a light parse. 304 305 Args: 306 ctx: The context. 307 308 Returns: 309 The resource pb file object. 310 """ 311 if not hasattr(ctx.rule.files, "resource_files"): 312 return None 313 314 r_pb = utils.isolated_declare_file(ctx, ctx.label.name + "_mi/resources/R.pb") 315 316 args = ctx.actions.args() 317 args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True) 318 args.set_param_file_format("multiline") 319 args.add_joined("--res_files", ctx.rule.files.resource_files, join_with = ",") 320 args.add("--out", r_pb) 321 322 ctx.actions.run( 323 executable = ctx.executable._android_kit, 324 arguments = ["liteparse", args], 325 inputs = ctx.rule.files.resource_files, 326 outputs = [r_pb], 327 mnemonic = "ResLiteParse", 328 progress_message = "MI Lite parse Android Resources %s" % ctx.label, 329 ) 330 return r_pb 331 332def compiletime_r_srcjar(ctx, output_srcjar, r_pbs, package): 333 """Create R.srcjar from the given R.pb files in the transitive closure. 334 335 Args: 336 ctx: The context. 337 output_srcjar: The output R source jar artifact. 338 r_pbs: Transitive set of resource pbs. 339 package: The package name of the compile-time R.java. 340 """ 341 args = ctx.actions.args() 342 args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True) 343 args.set_param_file_format("multiline") 344 args.add("-rJavaOutput", output_srcjar) 345 args.add("-packageForR", package) 346 args.add_joined("-resourcePbs", r_pbs, join_with = ",") 347 348 ctx.actions.run( 349 executable = ctx.executable._android_kit, 350 arguments = ["rstub", args], 351 inputs = r_pbs, 352 outputs = [output_srcjar], 353 mnemonic = "CompileTimeRSrcjar", 354 progress_message = "MI Make compile-time R.srcjar %s" % ctx.label, 355 ) 356