blob: 8e231b3299bb2b9b20ba35ebf1918f38f4e631b8 [file] [log] [blame]
# Copyright (C) 2023 The Android Open Source Project
#
# 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.
# framework-res is a highly customized android_app module in Soong.
# Direct translation to an android_binary rule (as is done for other
# android_app modules) is made difficult due to Soong code name checking
# for this specific module, e.g. to:
# - Skip java compilation and dexing of R.java generated from resources
# - Provide custom aapt linking flags that are exclusive to this module,
# some of which depend on product configuration.
# - Provide custom output groups exclusively used by reverse dependencies
# of this module.
# A separate rule, implemented below is preferred over implementing a similar
# customization within android_binary.
load(":debug_signing_key.bzl", "debug_signing_key")
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@rules_android//rules/android_binary_internal:rule.bzl", "sanitize_attrs")
load("@rules_android//rules/android_binary_internal:attrs.bzl", _BASE_ATTRS = "ATTRS")
load("@rules_android//rules:busybox.bzl", _busybox = "busybox")
load("@rules_android//rules:common.bzl", "common")
load("@rules_android//rules:utils.bzl", "get_android_toolchain")
load("//build/bazel/rules/android:manifest_fixer.bzl", "manifest_fixer")
load("//build/bazel/rules/common:api.bzl", "api")
load("//build/bazel/rules/common:config.bzl", "has_unbundled_build_apps")
def _fix_manifest(ctx):
fixed_manifest = ctx.actions.declare_file(
paths.join(ctx.label.name, "AndroidManifest.xml"),
)
target_sdk_version = manifest_fixer.target_sdk_version_for_manifest_fixer(
target_sdk_version = "current",
platform_sdk_final = ctx.attr._platform_sdk_final[BuildSettingInfo].value,
has_unbundled_build_apps = has_unbundled_build_apps(ctx.attr._unbundled_build_apps),
)
manifest_fixer.fix(
ctx,
manifest_fixer = ctx.executable._manifest_fixer,
in_manifest = ctx.file.manifest,
out_manifest = fixed_manifest,
min_sdk_version = api.effective_version_string("current"),
target_sdk_version = target_sdk_version,
)
return fixed_manifest
def _compile_resources(ctx):
host_javabase = common.get_host_javabase(ctx)
aapt = get_android_toolchain(ctx).aapt2.files_to_run
busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run
# Unzip resource zips so they can be compiled by aapt and packaged with the
# proper directory structure at linking.
unzip = get_android_toolchain(ctx).unzip_tool
# TODO: b/301457407 - support declare_directory in mixed builds or don't use it here
resource_unzip_dir = ctx.actions.declare_directory(ctx.label.name + "_resource_zips")
zip_args = ctx.actions.args()
zip_args.add("-qq")
zip_args.add_all(ctx.files.resource_zips)
zip_args.add("-d", resource_unzip_dir.path)
ctx.actions.run(
inputs = ctx.files.resource_zips,
outputs = [resource_unzip_dir],
executable = unzip.files_to_run,
arguments = [zip_args],
toolchain = None,
mnemonic = "UnzipResourceZips",
)
compiled_resources = ctx.actions.declare_file(
paths.join(ctx.label.name + "_symbols", "symbols.zip"),
)
_busybox.compile(
ctx,
out_file = compiled_resources,
resource_files = ctx.files.resource_files + [resource_unzip_dir],
aapt = aapt,
busybox = busybox,
host_javabase = host_javabase,
)
# The resource processor busybox runs the same aapt2 compile command with
# and without --pseudo-localize, and places the output in the "default" and
# "generated" top-level folders of symbol.zip, respectively. This results in
# duplicated resources under "default" and "generated", which would normally
# be resolved by resource merging (when using the android rules). Resource
# merging, however, does not properly handle product tags, and should not be
# needed to build framework resources as they have no dependencies. As Soong
# always calls aapt2 with --pseudo-localize, this is resolved by deleting
# the "default" top-level directory from the symbols.zip output of the
# compile step.
merged_resources = ctx.actions.declare_file(
paths.join(ctx.label.name + "_symbols", "symbols_merged.zip"),
)
merge_args = ctx.actions.args()
merge_args.add("-i", compiled_resources)
merge_args.add("-o", merged_resources)
merge_args.add("-x", "default/**/*")
ctx.actions.run(
inputs = [compiled_resources],
outputs = [merged_resources],
executable = ctx.executable._zip2zip,
arguments = [merge_args],
toolchain = None,
mnemonic = "ExcludeDefaultResources",
)
return merged_resources
def _link_resources(ctx, fixed_manifest, compiled_resources):
aapt = get_android_toolchain(ctx).aapt2.files_to_run
apk = ctx.actions.declare_file(
paths.join(ctx.label.name + "_files", "library.apk"),
)
r_txt = ctx.actions.declare_file(
paths.join(ctx.label.name + "_symbols", "R.txt"),
)
proguard_cfg = ctx.actions.declare_file(
paths.join(ctx.label.name + "_proguard", "_%s_proguard.cfg" % ctx.label.name),
)
# TODO: b/301457407 - support declare_directory in mixed builds or don't use it here
java_srcs_dir = ctx.actions.declare_directory(ctx.label.name + "_resource_jar_sources")
link_args = ctx.actions.args()
link_args.add("link")
# outputs
link_args.add("-o", apk)
link_args.add("--java", java_srcs_dir.path)
link_args.add("--proguard", proguard_cfg)
link_args.add("--output-text-symbols", r_txt)
# args from aaptflags of the framework-res module definition
link_args.add("--private-symbols", "com.android.internal")
link_args.add("--no-auto-version")
link_args.add("--auto-add-overlay")
link_args.add("--enable-sparse-encoding")
# flags from Soong's aapt2Flags function in build/soong/java/aar.go
link_args.add("--no-static-lib-packages")
link_args.add("--min-sdk-version", api.effective_version_string("current"))
link_args.add("--target-sdk-version", api.effective_version_string("current"))
link_args.add("--version-code", ctx.attr._platform_sdk_version[BuildSettingInfo].value)
# Some builds set AppsDefaultVersionName() to include the build number ("O-123456"). aapt2 copies the
# version name of framework-res into app manifests as compileSdkVersionCodename, which confuses things
# if it contains the build number. Use the PlatformVersionName instead.
# Unique to framework-res, see https://cs.android.com/android/platform/superproject/main/+/main:build/soong/java/aar.go;l=271-275;drc=ee51bd6588ceb122dbf5f6d12bc398a1ce7f37ed.
link_args.add("--version-name", ctx.attr._platform_version_name[BuildSettingInfo].value)
# extra link flags from Soong's aaptBuildActions in build/soong/java/app.go
link_args.add("--product", ctx.attr._aapt_characteristics[BuildSettingInfo].value)
for config in ctx.attr._aapt_config[BuildSettingInfo].value:
# TODO: b/301593550 - commas can't be escaped in a string-list passed in a platform mapping,
# so commas are switched for ":" in soong injection, and back-substituted into commas
# wherever the AAPTCharacteristics product config variable is used.
link_args.add("-c", config.replace(":", ","))
if ctx.attr._aapt_preferred_config[BuildSettingInfo].value:
link_args.add("--preferred-density", ctx.attr._aapt_preferred_config[BuildSettingInfo].value)
# inputs
link_args.add("--manifest", fixed_manifest)
link_args.add("-A", paths.join(paths.dirname(ctx.build_file_path), ctx.attr.assets_dir))
link_args.add(compiled_resources)
ctx.actions.run(
inputs = [compiled_resources, fixed_manifest] + ctx.files.assets,
outputs = [apk, java_srcs_dir, proguard_cfg, r_txt],
executable = aapt,
arguments = [link_args],
toolchain = None,
mnemonic = "AaptLinkFrameworkRes",
progress_message = "Linking Framework Resources with Aapt...",
)
return apk, r_txt, proguard_cfg, java_srcs_dir
def _package_resource_source_jar(ctx, java_srcs_dir):
r_java = ctx.actions.declare_file(
ctx.label.name + ".srcjar",
)
srcjar_args = ctx.actions.args()
srcjar_args.add("-write_if_changed")
srcjar_args.add("-jar")
srcjar_args.add("-o", r_java)
srcjar_args.add("-C", java_srcs_dir.path)
srcjar_args.add("-D", java_srcs_dir.path)
ctx.actions.run(
inputs = [java_srcs_dir],
outputs = [r_java],
executable = ctx.executable._soong_zip,
arguments = [srcjar_args],
toolchain = None,
mnemonic = "FrameworkResSrcJar",
)
return r_java
def _generate_binary_r(ctx, r_txt, fixed_manifest):
host_javabase = common.get_host_javabase(ctx)
busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run
out_class_jar = ctx.actions.declare_file(
ctx.label.name + "_resources.jar",
)
_busybox.generate_binary_r(
ctx,
out_class_jar = out_class_jar,
r_txt = r_txt,
manifest = fixed_manifest,
busybox = busybox,
host_javabase = host_javabase,
)
return out_class_jar
def _impl(ctx):
fixed_manifest = _fix_manifest(ctx)
compiled_resources = _compile_resources(ctx)
apk, r_txt, proguard_cfg, java_srcs_dir = _link_resources(ctx, fixed_manifest, compiled_resources)
r_java = _package_resource_source_jar(ctx, java_srcs_dir)
out_class_jar = _generate_binary_r(ctx, r_txt, fixed_manifest)
# Unused but required to satisfy the native android_binary rule consuming this rule's JavaInfo provider.
fake_proto_manifest = ctx.actions.declare_file("fake/proto_manifest.pb")
ctx.actions.run_shell(
inputs = [],
outputs = [fake_proto_manifest],
command = "touch {}".format(fake_proto_manifest.path),
tools = [],
mnemonic = "TouchFakeProtoManifest",
)
return [
AndroidApplicationResourceInfo(
resource_apk = apk,
resource_java_src_jar = r_java,
resource_java_class_jar = out_class_jar,
manifest = fixed_manifest,
resource_proguard_config = proguard_cfg,
main_dex_proguard_config = None,
r_txt = r_txt,
resources_zip = None,
databinding_info = None,
should_compile_java_srcs = False,
),
JavaInfo(
output_jar = out_class_jar,
compile_jar = out_class_jar,
source_jar = r_java,
manifest_proto = fake_proto_manifest,
),
DataBindingV2Info(
databinding_v2_providers_in_deps = [],
databinding_v2_providers_in_exports = [],
),
DefaultInfo(files = depset([apk])),
OutputGroupInfo(
srcjar = depset([r_java]),
classjar = depset([out_class_jar]),
resource_apk = depset([apk]),
),
AndroidDexInfo(
# Though there is no dexing happening in this rule, this class jar is
# forwarded to the native android_binary rule because it outputs a pre-dex
# deploy jar in a provider.
deploy_jar = out_class_jar,
final_classes_dex_zip = None,
java_resource_jar = None,
),
]
_framework_resources_internal = rule(
attrs = {
"assets": _BASE_ATTRS["assets"],
"assets_dir": _BASE_ATTRS["assets_dir"],
"manifest": _BASE_ATTRS["manifest"],
"resource_files": _BASE_ATTRS["resource_files"],
"resource_zips": attr.label_list(
allow_files = True,
doc = "list of zip files containing Android resources.",
),
"_host_javabase": _BASE_ATTRS["_host_javabase"],
"_soong_zip": attr.label(allow_single_file = True, cfg = "exec", executable = True, default = "//build/soong/zip/cmd:soong_zip"),
"_zip2zip": attr.label(allow_single_file = True, cfg = "exec", executable = True, default = "//build/soong/cmd/zip2zip:zip2zip"),
"_manifest_fixer": attr.label(cfg = "exec", executable = True, default = "//build/soong/scripts:manifest_fixer"),
"_platform_sdk_version": attr.label(
default = Label("//build/bazel/product_config:platform_sdk_version"),
),
"_platform_version_name": attr.label(
default = Label("//build/bazel/product_config:platform_version_name"),
),
"_aapt_characteristics": attr.label(
default = Label("//build/bazel/product_config:aapt_characteristics"),
),
"_aapt_config": attr.label(
default = Label("//build/bazel/product_config:aapt_config"),
),
"_aapt_preferred_config": attr.label(
default = Label("//build/bazel/product_config:aapt_preferred_config"),
),
"_platform_sdk_final": attr.label(
default = "//build/bazel/product_config:platform_sdk_final",
doc = "PlatformSdkFinal product variable",
),
"_unbundled_build_apps": attr.label(
default = "//build/bazel/product_config:unbundled_build_apps",
doc = "UnbundledBuildApps product variable",
),
},
implementation = _impl,
provides = [AndroidApplicationResourceInfo, OutputGroupInfo],
toolchains = [
"@rules_android//toolchains/android:toolchain_type",
],
fragments = ["android"],
)
def framework_resources(
name,
certificate = None,
certificate_name = None,
tags = [],
target_compatible_with = [],
visibility = None,
manifest = None,
**kwargs):
framework_resources_internal_name = ":" + name + common.PACKAGED_RESOURCES_SUFFIX
_framework_resources_internal(
name = framework_resources_internal_name[1:],
tags = tags + ["manual"],
target_compatible_with = target_compatible_with,
visibility = ["//visibility:private"],
manifest = manifest,
**kwargs
)
# Rely on native android_binary until apk packaging and signing is starlarkified
# TODO: b/301986521 - use starlark version of this logic once implemented.
native.android_binary(
name = name,
application_resources = framework_resources_internal_name,
debug_signing_keys = debug_signing_key(name, certificate, certificate_name),
target_compatible_with = target_compatible_with,
visibility = visibility,
tags = tags,
manifest = manifest,
)
native.filegroup(
name = name + ".aapt.srcjar",
srcs = [name],
output_group = "srcjar",
visibility = visibility,
tags = tags,
)
native.filegroup(
name = name + ".aapt.jar",
srcs = [name],
output_group = "classjar",
visibility = visibility,
tags = tags,
)
native.filegroup(
name = name + ".export-package.apk",
srcs = [name],
output_group = "resource_apk",
visibility = visibility,
tags = tags,
)