# 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, )