1# Copyright (C) 2023 The Android Open Source Project 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 15# framework-res is a highly customized android_app module in Soong. 16# Direct translation to an android_binary rule (as is done for other 17# android_app modules) is made difficult due to Soong code name checking 18# for this specific module, e.g. to: 19# - Skip java compilation and dexing of R.java generated from resources 20# - Provide custom aapt linking flags that are exclusive to this module, 21# some of which depend on product configuration. 22# - Provide custom output groups exclusively used by reverse dependencies 23# of this module. 24# A separate rule, implemented below is preferred over implementing a similar 25# customization within android_binary. 26 27load(":debug_signing_key.bzl", "debug_signing_key") 28load("@bazel_skylib//lib:paths.bzl", "paths") 29load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") 30load("@rules_android//rules/android_binary_internal:rule.bzl", "sanitize_attrs") 31load("@rules_android//rules/android_binary_internal:attrs.bzl", _BASE_ATTRS = "ATTRS") 32load("@rules_android//rules:busybox.bzl", _busybox = "busybox") 33load("@rules_android//rules:common.bzl", "common") 34load("@rules_android//rules:utils.bzl", "get_android_toolchain") 35load("//build/bazel/rules/android:manifest_fixer.bzl", "manifest_fixer") 36load("//build/bazel/rules/common:api.bzl", "api") 37load("//build/bazel/rules/common:config.bzl", "has_unbundled_build_apps") 38 39def _fix_manifest(ctx): 40 fixed_manifest = ctx.actions.declare_file( 41 paths.join(ctx.label.name, "AndroidManifest.xml"), 42 ) 43 target_sdk_version = manifest_fixer.target_sdk_version_for_manifest_fixer( 44 target_sdk_version = "current", 45 platform_sdk_final = ctx.attr._platform_sdk_final[BuildSettingInfo].value, 46 has_unbundled_build_apps = has_unbundled_build_apps(ctx.attr._unbundled_build_apps), 47 ) 48 49 manifest_fixer.fix( 50 ctx, 51 manifest_fixer = ctx.executable._manifest_fixer, 52 in_manifest = ctx.file.manifest, 53 out_manifest = fixed_manifest, 54 min_sdk_version = api.effective_version_string("current"), 55 target_sdk_version = target_sdk_version, 56 ) 57 return fixed_manifest 58 59def _compile_resources(ctx): 60 host_javabase = common.get_host_javabase(ctx) 61 aapt = get_android_toolchain(ctx).aapt2.files_to_run 62 busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run 63 64 # Unzip resource zips so they can be compiled by aapt and packaged with the 65 # proper directory structure at linking. 66 unzip = get_android_toolchain(ctx).unzip_tool 67 68 # TODO: b/301457407 - support declare_directory in mixed builds or don't use it here 69 resource_unzip_dir = ctx.actions.declare_directory(ctx.label.name + "_resource_zips") 70 zip_args = ctx.actions.args() 71 zip_args.add("-qq") 72 zip_args.add_all(ctx.files.resource_zips) 73 zip_args.add("-d", resource_unzip_dir.path) 74 ctx.actions.run( 75 inputs = ctx.files.resource_zips, 76 outputs = [resource_unzip_dir], 77 executable = unzip.files_to_run, 78 arguments = [zip_args], 79 toolchain = None, 80 mnemonic = "UnzipResourceZips", 81 ) 82 compiled_resources = ctx.actions.declare_file( 83 paths.join(ctx.label.name + "_symbols", "symbols.zip"), 84 ) 85 _busybox.compile( 86 ctx, 87 out_file = compiled_resources, 88 resource_files = ctx.files.resource_files + [resource_unzip_dir], 89 aapt = aapt, 90 busybox = busybox, 91 host_javabase = host_javabase, 92 ) 93 94 # The resource processor busybox runs the same aapt2 compile command with 95 # and without --pseudo-localize, and places the output in the "default" and 96 # "generated" top-level folders of symbol.zip, respectively. This results in 97 # duplicated resources under "default" and "generated", which would normally 98 # be resolved by resource merging (when using the android rules). Resource 99 # merging, however, does not properly handle product tags, and should not be 100 # needed to build framework resources as they have no dependencies. As Soong 101 # always calls aapt2 with --pseudo-localize, this is resolved by deleting 102 # the "default" top-level directory from the symbols.zip output of the 103 # compile step. 104 merged_resources = ctx.actions.declare_file( 105 paths.join(ctx.label.name + "_symbols", "symbols_merged.zip"), 106 ) 107 merge_args = ctx.actions.args() 108 merge_args.add("-i", compiled_resources) 109 merge_args.add("-o", merged_resources) 110 merge_args.add("-x", "default/**/*") 111 ctx.actions.run( 112 inputs = [compiled_resources], 113 outputs = [merged_resources], 114 executable = ctx.executable._zip2zip, 115 arguments = [merge_args], 116 toolchain = None, 117 mnemonic = "ExcludeDefaultResources", 118 ) 119 return merged_resources 120 121def _link_resources(ctx, fixed_manifest, compiled_resources): 122 aapt = get_android_toolchain(ctx).aapt2.files_to_run 123 apk = ctx.actions.declare_file( 124 paths.join(ctx.label.name + "_files", "library.apk"), 125 ) 126 r_txt = ctx.actions.declare_file( 127 paths.join(ctx.label.name + "_symbols", "R.txt"), 128 ) 129 proguard_cfg = ctx.actions.declare_file( 130 paths.join(ctx.label.name + "_proguard", "_%s_proguard.cfg" % ctx.label.name), 131 ) 132 133 # TODO: b/301457407 - support declare_directory in mixed builds or don't use it here 134 java_srcs_dir = ctx.actions.declare_directory(ctx.label.name + "_resource_jar_sources") 135 link_args = ctx.actions.args() 136 link_args.add("link") 137 138 # outputs 139 link_args.add("-o", apk) 140 link_args.add("--java", java_srcs_dir.path) 141 link_args.add("--proguard", proguard_cfg) 142 link_args.add("--output-text-symbols", r_txt) 143 144 # args from aaptflags of the framework-res module definition 145 link_args.add("--private-symbols", "com.android.internal") 146 link_args.add("--no-auto-version") 147 link_args.add("--auto-add-overlay") 148 link_args.add("--enable-sparse-encoding") 149 150 # flags from Soong's aapt2Flags function in build/soong/java/aar.go 151 link_args.add("--no-static-lib-packages") 152 link_args.add("--min-sdk-version", api.effective_version_string("current")) 153 link_args.add("--target-sdk-version", api.effective_version_string("current")) 154 link_args.add("--version-code", ctx.attr._platform_sdk_version[BuildSettingInfo].value) 155 156 # Some builds set AppsDefaultVersionName() to include the build number ("O-123456"). aapt2 copies the 157 # version name of framework-res into app manifests as compileSdkVersionCodename, which confuses things 158 # if it contains the build number. Use the PlatformVersionName instead. 159 # Unique to framework-res, see https://cs.android.com/android/platform/superproject/main/+/main:build/soong/java/aar.go;l=271-275;drc=ee51bd6588ceb122dbf5f6d12bc398a1ce7f37ed. 160 link_args.add("--version-name", ctx.attr._platform_version_name[BuildSettingInfo].value) 161 162 # extra link flags from Soong's aaptBuildActions in build/soong/java/app.go 163 link_args.add("--product", ctx.attr._aapt_characteristics[BuildSettingInfo].value) 164 for config in ctx.attr._aapt_config[BuildSettingInfo].value: 165 # TODO: b/301593550 - commas can't be escaped in a string-list passed in a platform mapping, 166 # so commas are switched for ":" in soong injection, and back-substituted into commas 167 # wherever the AAPTCharacteristics product config variable is used. 168 link_args.add("-c", config.replace(":", ",")) 169 if ctx.attr._aapt_preferred_config[BuildSettingInfo].value: 170 link_args.add("--preferred-density", ctx.attr._aapt_preferred_config[BuildSettingInfo].value) 171 172 # inputs 173 link_args.add("--manifest", fixed_manifest) 174 link_args.add("-A", paths.join(paths.dirname(ctx.build_file_path), ctx.attr.assets_dir)) 175 link_args.add(compiled_resources) 176 177 ctx.actions.run( 178 inputs = [compiled_resources, fixed_manifest] + ctx.files.assets, 179 outputs = [apk, java_srcs_dir, proguard_cfg, r_txt], 180 executable = aapt, 181 arguments = [link_args], 182 toolchain = None, 183 mnemonic = "AaptLinkFrameworkRes", 184 progress_message = "Linking Framework Resources with Aapt...", 185 ) 186 return apk, r_txt, proguard_cfg, java_srcs_dir 187 188def _package_resource_source_jar(ctx, java_srcs_dir): 189 r_java = ctx.actions.declare_file( 190 ctx.label.name + ".srcjar", 191 ) 192 srcjar_args = ctx.actions.args() 193 srcjar_args.add("-write_if_changed") 194 srcjar_args.add("-jar") 195 srcjar_args.add("-o", r_java) 196 srcjar_args.add("-C", java_srcs_dir.path) 197 srcjar_args.add("-D", java_srcs_dir.path) 198 ctx.actions.run( 199 inputs = [java_srcs_dir], 200 outputs = [r_java], 201 executable = ctx.executable._soong_zip, 202 arguments = [srcjar_args], 203 toolchain = None, 204 mnemonic = "FrameworkResSrcJar", 205 ) 206 return r_java 207 208def _generate_binary_r(ctx, r_txt, fixed_manifest): 209 host_javabase = common.get_host_javabase(ctx) 210 busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run 211 out_class_jar = ctx.actions.declare_file( 212 ctx.label.name + "_resources.jar", 213 ) 214 215 _busybox.generate_binary_r( 216 ctx, 217 out_class_jar = out_class_jar, 218 r_txt = r_txt, 219 manifest = fixed_manifest, 220 busybox = busybox, 221 host_javabase = host_javabase, 222 ) 223 return out_class_jar 224 225def _impl(ctx): 226 fixed_manifest = _fix_manifest(ctx) 227 228 compiled_resources = _compile_resources(ctx) 229 230 apk, r_txt, proguard_cfg, java_srcs_dir = _link_resources(ctx, fixed_manifest, compiled_resources) 231 232 r_java = _package_resource_source_jar(ctx, java_srcs_dir) 233 234 out_class_jar = _generate_binary_r(ctx, r_txt, fixed_manifest) 235 236 # Unused but required to satisfy the native android_binary rule consuming this rule's JavaInfo provider. 237 fake_proto_manifest = ctx.actions.declare_file("fake/proto_manifest.pb") 238 ctx.actions.run_shell( 239 inputs = [], 240 outputs = [fake_proto_manifest], 241 command = "touch {}".format(fake_proto_manifest.path), 242 tools = [], 243 mnemonic = "TouchFakeProtoManifest", 244 ) 245 246 return [ 247 AndroidApplicationResourceInfo( 248 resource_apk = apk, 249 resource_java_src_jar = r_java, 250 resource_java_class_jar = out_class_jar, 251 manifest = fixed_manifest, 252 resource_proguard_config = proguard_cfg, 253 main_dex_proguard_config = None, 254 r_txt = r_txt, 255 resources_zip = None, 256 databinding_info = None, 257 should_compile_java_srcs = False, 258 ), 259 JavaInfo( 260 output_jar = out_class_jar, 261 compile_jar = out_class_jar, 262 source_jar = r_java, 263 manifest_proto = fake_proto_manifest, 264 ), 265 DataBindingV2Info( 266 databinding_v2_providers_in_deps = [], 267 databinding_v2_providers_in_exports = [], 268 ), 269 DefaultInfo(files = depset([apk])), 270 OutputGroupInfo( 271 srcjar = depset([r_java]), 272 classjar = depset([out_class_jar]), 273 resource_apk = depset([apk]), 274 ), 275 AndroidDexInfo( 276 # Though there is no dexing happening in this rule, this class jar is 277 # forwarded to the native android_binary rule because it outputs a pre-dex 278 # deploy jar in a provider. 279 deploy_jar = out_class_jar, 280 final_classes_dex_zip = None, 281 java_resource_jar = None, 282 ), 283 ] 284 285_framework_resources_internal = rule( 286 attrs = { 287 "assets": _BASE_ATTRS["assets"], 288 "assets_dir": _BASE_ATTRS["assets_dir"], 289 "manifest": _BASE_ATTRS["manifest"], 290 "resource_files": _BASE_ATTRS["resource_files"], 291 "resource_zips": attr.label_list( 292 allow_files = True, 293 doc = "list of zip files containing Android resources.", 294 ), 295 "_host_javabase": _BASE_ATTRS["_host_javabase"], 296 "_soong_zip": attr.label(allow_single_file = True, cfg = "exec", executable = True, default = "//build/soong/zip/cmd:soong_zip"), 297 "_zip2zip": attr.label(allow_single_file = True, cfg = "exec", executable = True, default = "//build/soong/cmd/zip2zip:zip2zip"), 298 "_manifest_fixer": attr.label(cfg = "exec", executable = True, default = "//build/soong/scripts:manifest_fixer"), 299 "_platform_sdk_version": attr.label( 300 default = Label("//build/bazel/product_config:platform_sdk_version"), 301 ), 302 "_platform_version_name": attr.label( 303 default = Label("//build/bazel/product_config:platform_version_name"), 304 ), 305 "_aapt_characteristics": attr.label( 306 default = Label("//build/bazel/product_config:aapt_characteristics"), 307 ), 308 "_aapt_config": attr.label( 309 default = Label("//build/bazel/product_config:aapt_config"), 310 ), 311 "_aapt_preferred_config": attr.label( 312 default = Label("//build/bazel/product_config:aapt_preferred_config"), 313 ), 314 "_platform_sdk_final": attr.label( 315 default = "//build/bazel/product_config:platform_sdk_final", 316 doc = "PlatformSdkFinal product variable", 317 ), 318 "_unbundled_build_apps": attr.label( 319 default = "//build/bazel/product_config:unbundled_build_apps", 320 doc = "UnbundledBuildApps product variable", 321 ), 322 }, 323 implementation = _impl, 324 provides = [AndroidApplicationResourceInfo, OutputGroupInfo], 325 toolchains = [ 326 "@rules_android//toolchains/android:toolchain_type", 327 ], 328 fragments = ["android"], 329) 330 331def framework_resources( 332 name, 333 certificate = None, 334 certificate_name = None, 335 tags = [], 336 target_compatible_with = [], 337 visibility = None, 338 manifest = None, 339 **kwargs): 340 framework_resources_internal_name = ":" + name + common.PACKAGED_RESOURCES_SUFFIX 341 _framework_resources_internal( 342 name = framework_resources_internal_name[1:], 343 tags = tags + ["manual"], 344 target_compatible_with = target_compatible_with, 345 visibility = ["//visibility:private"], 346 manifest = manifest, 347 **kwargs 348 ) 349 350 # Rely on native android_binary until apk packaging and signing is starlarkified 351 # TODO: b/301986521 - use starlark version of this logic once implemented. 352 native.android_binary( 353 name = name, 354 application_resources = framework_resources_internal_name, 355 debug_signing_keys = debug_signing_key(name, certificate, certificate_name), 356 target_compatible_with = target_compatible_with, 357 visibility = visibility, 358 tags = tags, 359 manifest = manifest, 360 ) 361 362 native.filegroup( 363 name = name + ".aapt.srcjar", 364 srcs = [name], 365 output_group = "srcjar", 366 visibility = visibility, 367 tags = tags, 368 ) 369 370 native.filegroup( 371 name = name + ".aapt.jar", 372 srcs = [name], 373 output_group = "classjar", 374 visibility = visibility, 375 tags = tags, 376 ) 377 378 native.filegroup( 379 name = name + ".export-package.apk", 380 srcs = [name], 381 output_group = "resource_apk", 382 visibility = visibility, 383 tags = tags, 384 ) 385