blob: e0babcfb6115e5c5dcca23c4609fbf6486693186 [file] [log] [blame]
# 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,
)