xref: /aosp_15_r20/build/bazel/rules/android/framework_resources.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
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