| # Copyright 2018 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Methods to process Android resources.""" |
| |
| load("//rules:visibility.bzl", "PROJECT_VISIBILITY") |
| load(":constants.bzl", "constants") |
| load(":utils.bzl", "utils") |
| |
| visibility(PROJECT_VISIBILITY) |
| |
| # Android resource types, see https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/tools/aapt2/Resource.h |
| res_types = [ |
| "anim", |
| "animator", |
| "array", |
| "attr", |
| "^attr-private", |
| "bool", |
| "color", |
| "configVarying", # Not really a type, but it shows up in some CTS tests |
| "dimen", |
| "drawable", |
| "font", |
| "fraction", |
| "id", |
| "integer", |
| "interpolator", |
| "layout", |
| "macro", |
| "menu", |
| "mipmap", |
| "navigation", |
| "plurals", |
| "raw", |
| "string", |
| "style", |
| "styleable", |
| "transition", |
| "xml", |
| ] |
| |
| def get_assets_dir(asset, base_dir): |
| """Build the full assets directory sanitizing the input first.""" |
| if base_dir == "": |
| # some targets specify assets files and set assets_dirs = "" |
| return asset.dirname |
| asset_path = asset.path.rstrip("/") |
| base_dir = base_dir.rstrip("/") |
| if asset_path.endswith("/" + base_dir): |
| return asset_path |
| return "%s/%s" % (asset_path.rpartition("/%s/" % base_dir)[0], base_dir) |
| |
| def compile_resources(ctx, lib_strategy = True): |
| """Compiles android resources using aapt2 |
| |
| Args: |
| ctx: The context. |
| lib_strategy: If to use library strategy or bucket. Default is lib strategy. |
| |
| Returns: |
| A list of compiled android resource archives (.flata) files, otherwise None |
| if data is None or empty. |
| """ |
| res_dir_file_map = partition_by_res_dirs(ctx.rule.files.resource_files) |
| if lib_strategy: |
| return _compile_library_resouces(ctx, res_dir_file_map) |
| res_dir_buckets_map = _bucketize_resources(ctx, res_dir_file_map) |
| return _compile_bucketized_resources(ctx, res_dir_buckets_map) |
| |
| def partition_by_res_dirs(res_files): |
| """Partitions the resources by res directories. |
| |
| Args: |
| res_files: A list of resource artifacts files. |
| |
| Returns: |
| A map of "res" directories to files corresponding to the directory. |
| """ |
| if not res_files: |
| return None |
| |
| # Given the fact that res directories can be named anything and |
| # its not possible to distinguish between directories and regular files |
| # during analysis time, we use file extensions as an heuristic to group |
| # resource files. All Android resource files have the following form |
| # res-dir/type-dir/res_file. When we see a regular file (by looking at |
| # the extesion) we use the directory two levels up as the grouping dir. |
| # Most of the resource directories will contain at least one file with |
| # and extension, so this heuristic will generally result in good groupings. |
| res_non_values_file_map = {} |
| res_value_file_map = {} |
| res_dir_map = {} |
| for res_file in res_files: |
| if res_file.is_directory: |
| res_dir_map.setdefault(res_file.path, []).append(res_file) |
| else: |
| path_segments = res_file.dirname.rsplit("/", 1) |
| root_dir = path_segments[0] |
| if path_segments[1].startswith("values"): |
| res_value_file_map.setdefault(root_dir, []).append(res_file) |
| else: |
| res_non_values_file_map.setdefault(root_dir, []).append(res_file) |
| return { |
| "values": res_value_file_map, |
| "non-values": res_non_values_file_map, |
| "res_dir": res_dir_map, |
| } |
| |
| def _bucketize_resources(ctx, data): |
| """Bucketizes resources by type. |
| |
| Args: |
| ctx: The context. |
| data: A map of "res" directories to files corresponding to the directory. |
| |
| Returns: |
| A map of "res" directories to "res" buckets, None when there no resource |
| files to compile. |
| """ |
| if not data: |
| return None |
| |
| # Create backing files for the resource sharder. |
| res_dir_buckets_map = {} |
| for i, res_dir in enumerate(data.keys()): |
| res_buckets = [] |
| typed_outputs = [] |
| |
| for r_type in res_types: |
| for idx in range(ctx.attr._mi_res_shards): |
| res_bucket = utils.isolated_declare_file( |
| ctx, |
| ctx.label.name + "_mi/resources/buckets/%d/%s_%s.zip" % (i, r_type, idx), |
| ) |
| res_buckets.append(res_bucket) |
| typed_outputs.append(r_type + ":" + res_bucket.path) |
| |
| args = ctx.actions.args() |
| args.use_param_file(param_file_arg = "-flagfile=%s") |
| args.set_param_file_format("multiline") |
| args.add_joined("-typed_outputs", typed_outputs, join_with = ",") |
| if data[res_dir]: |
| args.add_joined("-res_paths", data[res_dir], join_with = ",") |
| |
| ctx.actions.run( |
| executable = ctx.executable._android_kit, |
| arguments = ["bucketize", args], |
| inputs = data[res_dir], |
| outputs = res_buckets, |
| mnemonic = "BucketizeRes", |
| progress_message = "MI Bucketize resources for %s" % res_dir, |
| ) |
| res_dir_buckets_map[res_dir] = res_buckets |
| return res_dir_buckets_map |
| |
| def _compile_bucketized_resources(ctx, data): |
| """Compiles android resources using aapt2 |
| |
| Args: |
| ctx: The context. |
| data: A map of res directories to resource buckets. |
| |
| Returns: |
| A list of compiled android resource archives (.flata) files, otherwise None |
| if data is None or empty. |
| """ |
| if not data: |
| return constants.EMPTY_LIST |
| |
| # TODO(mauriciogg): use no-crunch. We are using crunch to process 9-patch |
| # pngs should be disabled in general either by having a more granular flag |
| # in aapt2 or bucketizing 9patch pngs. See (b/70578281) |
| compiled_res_buckets = [] |
| for res_dir, res_buckets in data.items(): |
| for res_bucket in res_buckets: |
| # Note that extension matters for aapt2. |
| out = utils.isolated_declare_file( |
| ctx, |
| res_bucket.basename + ".flata", |
| sibling = res_bucket, |
| ) |
| ctx.actions.run( |
| executable = ctx.executable._android_kit, |
| arguments = [ |
| "compile", |
| "--aapt2=" + utils.first(ctx.attr._aapt2[DefaultInfo].files).path, |
| "--in=" + res_bucket.path, |
| "--out=" + out.path, |
| ], |
| inputs = [res_bucket] + ctx.attr._aapt2[DefaultInfo].files.to_list(), |
| outputs = [out], |
| mnemonic = "CompileRes", |
| progress_message = "MI Compiling resources for %s" % res_dir, |
| ) |
| compiled_res_buckets.append(out) |
| |
| return compiled_res_buckets |
| |
| def _compile_library_resouces(ctx, data): |
| """Compiles android resources using aapt2 |
| |
| Args: |
| ctx: The context. |
| data: A map of res directories to resource buckets. |
| |
| Returns: |
| A list of compiled android resource archives (.flata) files, otherwise None |
| if data is None or empty. |
| """ |
| if not data: |
| return constants.EMPTY_LIST |
| |
| # TODO(mauriciogg): use no-crunch. We are using crunch to process 9-patch |
| # pngs should be disabled in general either by having a more granular flag |
| # in aapt2 or bucketizing 9patch pngs. See (b/70578281) |
| compiled_res_dirs = [] |
| for res_type in data.keys(): |
| for res_dir in data[res_type].keys(): |
| # Note that extension matters for aapt2. |
| out = utils.isolated_declare_file( |
| ctx, |
| ctx.label.name + "_mi/resources/%s_%s.flata" % (res_dir.replace("/", "_"), res_type), |
| ) |
| compiled_res_dirs.append(out) |
| |
| args = ctx.actions.args() |
| args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True) |
| args.set_param_file_format("multiline") |
| args.add("-aapt2", ctx.file._aapt2) |
| args.add("-in", res_dir) |
| args.add("-out", out) |
| ctx.actions.run( |
| executable = ctx.executable._android_kit, |
| arguments = ["compile", args], |
| inputs = data[res_type][res_dir] + ctx.attr._aapt2[DefaultInfo].files.to_list(), |
| outputs = [out], |
| mnemonic = "CompileRes", |
| progress_message = "MI Compiling resources for %s" % res_dir, |
| ) |
| return compiled_res_dirs |
| |
| def link_resources( |
| ctx, |
| manifest, |
| java_package, |
| android_jar, |
| resource_archives, |
| assets, |
| assets_dirs): |
| """Links android resources using aapt2 |
| |
| Args: |
| ctx: The context. |
| manifest: The AndroidManifest.xml file |
| java_package: The package to use to generate R.java |
| android_jar: The android jar |
| resource_archives: List of intermediate compiled android resource files. |
| assets: The list of assets. |
| assets_dirs: The list of directories for the assets. |
| |
| Returns: |
| The resource apk and the R java file generated by aapt2. |
| """ |
| if not resource_archives: |
| return None |
| |
| resource_apk = utils.isolated_declare_file(ctx, ctx.label.name + "_mi/resources/resource.apk") |
| rjava_zip = utils.isolated_declare_file(ctx, "R.zip", sibling = resource_apk) |
| |
| args = ctx.actions.args() |
| args.use_param_file(param_file_arg = "-flagfile=%s", use_always = True) |
| args.add("-aapt2", ctx.executable._aapt2) |
| args.add("-sdk_jar", android_jar) |
| args.add("-manifest", manifest) |
| args.add("-pkg", java_package) |
| args.add("-src_jar", rjava_zip) |
| args.add("-out", resource_apk) |
| args.add_joined("-res_dirs", resource_archives, join_with = ",") |
| args.add_joined("-asset_dirs", assets_dirs, join_with = ",") |
| |
| ctx.actions.run( |
| executable = ctx.executable._android_kit, |
| arguments = ["link", args], |
| inputs = depset( |
| [manifest, android_jar, ctx.executable._aapt2] + resource_archives, |
| transitive = [assets], |
| ), |
| outputs = [resource_apk, rjava_zip], |
| mnemonic = "LinkRes", |
| progress_message = "MI Linking resources for %s" % ctx.label, |
| ) |
| return resource_apk, rjava_zip |
| |
| def liteparse(ctx): |
| """Creates an R.pb which contains the resource ids gotten from a light parse. |
| |
| Args: |
| ctx: The context. |
| |
| Returns: |
| The resource pb file object. |
| """ |
| if not hasattr(ctx.rule.files, "resource_files"): |
| return None |
| |
| r_pb = utils.isolated_declare_file(ctx, ctx.label.name + "_mi/resources/R.pb") |
| |
| args = ctx.actions.args() |
| args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True) |
| args.set_param_file_format("multiline") |
| args.add_joined("--res_files", ctx.rule.files.resource_files, join_with = ",") |
| args.add("--out", r_pb) |
| |
| ctx.actions.run( |
| executable = ctx.executable._android_kit, |
| arguments = ["liteparse", args], |
| inputs = ctx.rule.files.resource_files, |
| outputs = [r_pb], |
| mnemonic = "ResLiteParse", |
| progress_message = "MI Lite parse Android Resources %s" % ctx.label, |
| ) |
| return r_pb |
| |
| def compiletime_r_srcjar(ctx, output_srcjar, r_pbs, package): |
| """Create R.srcjar from the given R.pb files in the transitive closure. |
| |
| Args: |
| ctx: The context. |
| output_srcjar: The output R source jar artifact. |
| r_pbs: Transitive set of resource pbs. |
| package: The package name of the compile-time R.java. |
| """ |
| args = ctx.actions.args() |
| args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True) |
| args.set_param_file_format("multiline") |
| args.add("-rJavaOutput", output_srcjar) |
| args.add("-packageForR", package) |
| args.add_joined("-resourcePbs", r_pbs, join_with = ",") |
| |
| ctx.actions.run( |
| executable = ctx.executable._android_kit, |
| arguments = ["rstub", args], |
| inputs = r_pbs, |
| outputs = [output_srcjar], |
| mnemonic = "CompileTimeRSrcjar", |
| progress_message = "MI Make compile-time R.srcjar %s" % ctx.label, |
| ) |