1# Copyright (C) 2022 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
15load("@bazel_skylib//lib:paths.bzl", "paths")
16load("//build/bazel/rules:toolchain_utils.bzl", "verify_toolchain_exists")
17load(":apex_info.bzl", "ApexInfo")
18load(":bundle.bzl", "build_bundle_config")
19
20def _arch_transition_impl(settings, _attr):
21    """Implementation of arch_transition.
22
23    Six arch products are included for mainline modules: x86, x86_64, x86_64only, arm, arm64, arm64only.
24    """
25    old_platform = str(settings["//command_line_option:platforms"][0])
26
27    # We can't use platforms alone to differentiate between x86_64 and x86_64
28    # with a secondary arch, which is significant for apex packaging that can
29    # optionally include the secondary arch's libs. That is currently determined
30    # by DeviceSecondaryArch in apex's lib inclusion logic, so we explicitly set
31    # DeviceSecondaryArch to "" for the 64bit only cases.
32
33    # TODO(b/249685973) Instead of using these __internal_x86 platforms, use
34    # the mainline_modules_<arch> android products
35    return {
36        # these key names must correspond to mainline_modules_<arch> product name suffixes.
37        "arm": {
38            "//command_line_option:platforms": old_platform + "__internal_arm",
39        },
40        "arm64": {
41            "//command_line_option:platforms": old_platform + "__internal_arm64",
42        },
43        "arm64only": {
44            "//command_line_option:platforms": old_platform + "__internal_arm64only",
45        },
46        "x86": {
47            "//command_line_option:platforms": old_platform + "__internal_x86",
48        },
49        "x86_64": {
50            "//command_line_option:platforms": old_platform + "__internal_x86_64",
51        },
52        "x86_64only": {
53            "//command_line_option:platforms": old_platform + "__internal_x86_64only",
54        },
55    }
56
57# Multi-arch transition.
58arch_transition = transition(
59    implementation = _arch_transition_impl,
60    inputs = [
61        "//command_line_option:platforms",
62    ],
63    outputs = [
64        "//command_line_option:platforms",
65    ],
66)
67
68def _merge_base_files(ctx, module_name, base_files):
69    """Run merge_zips to merge all files created for each arch by _apex_base_file."""
70    merged_base_file = ctx.actions.declare_file(module_name + "/" + module_name + ".zip")
71
72    # Arguments
73    args = ctx.actions.args()
74    args.add("--ignore-duplicates")
75    args.add(merged_base_file)
76    args.add_all(base_files)
77
78    ctx.actions.run(
79        inputs = base_files,
80        outputs = [merged_base_file],
81        executable = ctx.executable._merge_zips,
82        arguments = [args],
83        mnemonic = "ApexMergeBaseFiles",
84    )
85    return merged_base_file
86
87def _apex_bundle(ctx, module_name, merged_base_file, bundle_config_file):
88    """Run bundletool to create the aab file."""
89
90    # Outputs
91    bundle_file = ctx.actions.declare_file(module_name + "/" + module_name + ".aab")
92
93    # Arguments
94    args = ctx.actions.args()
95    args.add("build-bundle")
96    args.add_all(["--config", bundle_config_file])
97    args.add_all(["--modules", merged_base_file])
98    args.add_all(["--output", bundle_file])
99
100    ctx.actions.run(
101        inputs = [
102            bundle_config_file,
103            merged_base_file,
104        ],
105        outputs = [bundle_file],
106        executable = ctx.executable._bundletool,
107        arguments = [args],
108        mnemonic = "ApexBundleFile",
109    )
110    return bundle_file
111
112def _sign_bundle(ctx, aapt2, avbtool, module_name, bundle_file, apex_info):
113    """ Run dev_sign_bundle to sign the bundle_file."""
114
115    # Python3 interpreter for dev_sign_bundle to run other python scripts.
116    python_interpreter = ctx.toolchains["@bazel_tools//tools/python:toolchain_type"].py3_runtime.interpreter
117    if python_interpreter.basename != "python3":
118        python3 = ctx.actions.declare_file("python3")
119        ctx.actions.symlink(
120            output = python3,
121            target_file = python_interpreter,
122            is_executable = True,
123        )
124        python_interpreter = python3
125
126    # Input directory for dev_sign_bundle.
127    input_bundle_file = ctx.actions.declare_file(module_name + "/sign_bundle/input_dir/" + bundle_file.basename)
128    ctx.actions.symlink(
129        output = input_bundle_file,
130        target_file = bundle_file,
131    )
132
133    # Output directory  for dev_sign_bundle
134    output_dir = ctx.actions.declare_directory(module_name + "/sign_bundle/output_dir")
135
136    # Temporary directory for dev_sign_bundle
137    tmp_dir = ctx.actions.declare_directory(module_name + "/sign_bundle/tmp_dir")
138
139    # Jar file of prebuilts/bundletool
140    bundletool_jarfile = ctx.attr._bundletool_lib.files.to_list()[0]
141
142    # Keystore file
143    keystore_file = ctx.attr.dev_keystore.files.to_list()[0]
144
145    # ANDROID_HOST_OUT environment
146    debugfs_static = ctx.actions.declare_file(module_name + "/sign_bundle/android_host_out/bin/debugfs_static")
147    ctx.actions.symlink(
148        output = debugfs_static,
149        target_file = ctx.executable._debugfs,
150        is_executable = True,
151    )
152    fsck_erofs = ctx.actions.declare_file(module_name + "/sign_bundle/android_host_out/bin/fsck.erofs")
153    ctx.actions.symlink(
154        output = fsck_erofs,
155        target_file = ctx.executable._fsck_erofs,
156        is_executable = True,
157    )
158    signapk_jar = ctx.actions.declare_file(module_name + "/sign_bundle/android_host_out/framework/signapk.jar")
159    ctx.actions.symlink(
160        output = signapk_jar,
161        target_file = ctx.attr._signapk_jar.files.to_list()[0],
162        is_executable = False,
163    )
164    libconscrypt_openjdk_jni_so = ctx.actions.declare_file(module_name + "/sign_bundle/android_host_out/lib64/libconscrypt_openjdk_jni.so")
165    ctx.actions.symlink(
166        output = libconscrypt_openjdk_jni_so,
167        target_file = ctx.attr._libconscrypt_openjdk_jni.files.to_list()[1],
168        is_executable = False,
169    )
170
171    java_runtime = ctx.attr._java_runtime[java_common.JavaRuntimeInfo]
172
173    # Tools
174    tools = [
175        ctx.executable.dev_sign_bundle,
176        ctx.executable._deapexer,
177        ctx.executable._sign_apex,
178        ctx.executable._openssl,
179        ctx.executable._zip2zip,
180        aapt2,
181        avbtool.files_to_run.executable,
182        python_interpreter,
183        debugfs_static,
184        fsck_erofs,
185        bundletool_jarfile,
186        signapk_jar,
187        libconscrypt_openjdk_jni_so,
188        java_runtime.files,
189    ]
190
191    # Inputs
192    inputs = [
193        input_bundle_file,
194        keystore_file,
195        apex_info.bundle_key_info.private_key,
196        apex_info.container_key_info.pem,
197        apex_info.container_key_info.pk8,
198    ]
199
200    # Outputs
201    outputs = [output_dir, tmp_dir]
202
203    # Arguments
204    java_bin = paths.join(java_runtime.java_home, "bin")
205    args = ctx.actions.args()
206    args.add_all(["--input_dir", input_bundle_file.dirname])
207    args.add_all(["--output_dir", output_dir.path])
208    args.add_all(["--temp_dir", tmp_dir.path])
209    args.add_all(["--aapt2_path", aapt2.path])
210    args.add_all(["--bundletool_path", bundletool_jarfile.path])
211    args.add_all(["--deapexer_path", ctx.executable._deapexer.path])
212    args.add_all(["--debugfs_path", ctx.executable._debugfs.path])
213    args.add_all(["--java_binary_path", paths.join(java_bin, "java")])
214    args.add_all(["--apex_signer_path", ctx.executable._sign_apex])
215
216    ctx.actions.run(
217        inputs = inputs,
218        outputs = outputs,
219        executable = ctx.executable.dev_sign_bundle,
220        arguments = [args],
221        tools = tools,
222        env = {
223            # necessary for dev_sign_bundle.
224            "BAZEL_ANDROID_HOST_OUT": paths.dirname(debugfs_static.dirname),
225            "PATH": ":".join(
226                [
227                    python_interpreter.dirname,
228                    ctx.executable._deapexer.dirname,
229                    avbtool.files_to_run.executable.dirname,
230                    ctx.executable._openssl.dirname,
231                    ctx.executable._zip2zip.dirname,
232                    java_bin,
233                ],
234            ),
235        },
236        mnemonic = "ApexSignBundleFile",
237    )
238
239    apks_file = ctx.actions.declare_file(module_name + "/" + module_name + ".apks")
240    cert_info_file = ctx.actions.declare_file(module_name + "/" + module_name + ".cert_info.txt")
241    ctx.actions.run_shell(
242        inputs = [output_dir],
243        outputs = [apks_file, cert_info_file],
244        command = " ".join(["cp", output_dir.path + "/" + module_name + "/*", apks_file.dirname]),
245    )
246
247    return [apks_file, cert_info_file]
248
249def _apex_aab_impl(ctx):
250    """Implementation of apex_aab rule.
251
252    This drives the process of creating aab file from apex files created for each arch."""
253    verify_toolchain_exists(ctx, "//build/bazel/rules/apex:apex_toolchain_type")
254    apex_toolchain = ctx.toolchains["//build/bazel/rules/apex:apex_toolchain_type"].toolchain_info
255
256    prefixed_apex_files = []
257    apex_base_files = []
258    bundle_config_file = None
259    module_name = ctx.attr.mainline_module[0].label.name
260    for arch in ctx.split_attr.mainline_module:
261        apex_info = ctx.split_attr.mainline_module[arch][ApexInfo]
262        apex_base_files.append(apex_info.base_file)
263
264        arch_subdir = "mainline_modules_%s" % arch
265
266        # A mapping of files to a prefix directory they should be copied to.
267        # These files will be accessible with the apex_files output_group.
268        mapping = {
269            apex_info.base_file: arch_subdir,
270            apex_info.signed_output: arch_subdir,
271            apex_info.symbols_used_by_apex: arch_subdir + "/ndk_apis_usedby_apex",
272            apex_info.backing_libs: arch_subdir + "/ndk_apis_backedby_apex",
273            apex_info.java_symbols_used_by_apex: arch_subdir + "/java_apis_used_by_apex",
274            # TODO(b/262267680): create licensetexts
275            # TODO(b/262267551): create shareprojects
276        }
277
278        # Forward the individual files for all variants in an additional output group,
279        # so dependents can easily access the multi-arch base APEX files by building
280        # this target with --output_groups=apex_files.
281        #
282        # Copy them into an arch-specific directory, since they have the same basename.
283        for _file, _dir in mapping.items():
284            _out = ctx.actions.declare_file(_dir + "/" + _file.basename)
285            ctx.actions.run_shell(
286                inputs = [_file],
287                outputs = [_out],
288                command = " ".join(["cp", _file.path, _out.path]),
289            )
290            prefixed_apex_files.append(_out)
291
292    # Create .aab file
293    bundle_config_file = build_bundle_config(ctx.actions, ctx.label.name)
294    merged_base_file = _merge_base_files(ctx, module_name, apex_base_files)
295    bundle_file = _apex_bundle(ctx, module_name, merged_base_file, bundle_config_file)
296
297    # Create .apks file
298    apex_info = ctx.attr.mainline_module[0][ApexInfo]
299    package_name = apex_info.package_name
300
301    if ctx.attr.dev_sign_bundle and ctx.attr.dev_keystore and (package_name.startswith("com.google.android") or package_name.startswith("com.google.mainline")):
302        signed_files = _sign_bundle(ctx, apex_toolchain.aapt2, apex_toolchain.avbtool, module_name, bundle_file, apex_info)
303        return [
304            DefaultInfo(files = depset([bundle_file] + signed_files)),
305            OutputGroupInfo(apex_files = depset(prefixed_apex_files), signed_files = signed_files),
306        ]
307
308    return [
309        DefaultInfo(files = depset([bundle_file])),
310        OutputGroupInfo(apex_files = depset(prefixed_apex_files)),
311    ]
312
313# apex_aab rule creates multi-arch outputs of a Mainline module, such as the
314# Android Apk Bundle (.aab) file of the APEX specified in mainline_module.
315# There is no equivalent Soong module, and it is currently done in shell script
316# by invoking Soong multiple times.
317_apex_aab = rule(
318    implementation = _apex_aab_impl,
319    toolchains = [
320        # The apex toolchain is not mandatory so that we don't get toolchain resolution errors
321        # even when the aab is not compatible with the current target (via target_compatible_with).
322        config_common.toolchain_type("//build/bazel/rules/apex:apex_toolchain_type", mandatory = False),
323        "@bazel_tools//tools/python:toolchain_type",
324    ],
325    attrs = {
326        "dev_keystore": attr.label(
327            cfg = "exec",
328            executable = False,
329        ),
330        "dev_sign_bundle": attr.label(
331            cfg = "exec",
332            executable = True,
333        ),
334        "mainline_module": attr.label(
335            mandatory = True,
336            cfg = arch_transition,
337            providers = [ApexInfo],
338            doc = "The label of a mainline module target",
339        ),
340        "_allowlist_function_transition": attr.label(
341            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
342            doc = "Allow transition.",
343        ),
344        "_bundletool": attr.label(
345            cfg = "exec",
346            executable = True,
347            default = "//prebuilts/bundletool",
348        ),
349        "_bundletool_lib": attr.label(
350            cfg = "exec",
351            executable = False,
352            default = "//prebuilts/bundletool:bundletool-lib",
353        ),
354        "_deapexer": attr.label(
355            cfg = "exec",
356            executable = True,
357            default = "//system/apex/tools:deapexer",
358        ),
359        "_debugfs": attr.label(
360            cfg = "exec",
361            executable = True,
362            default = "//external/e2fsprogs/debugfs:debugfs_static",
363        ),
364        "_fsck_erofs": attr.label(
365            cfg = "exec",
366            executable = True,
367            default = "//external/erofs-utils:fsck.erofs",
368        ),
369        "_java_runtime": attr.label(
370            default = Label("@bazel_tools//tools/jdk:current_java_runtime"),
371            cfg = "exec",
372            providers = [java_common.JavaRuntimeInfo],
373        ),
374        "_libconscrypt_openjdk_jni": attr.label(
375            cfg = "exec",
376            executable = False,
377            default = "//external/conscrypt:libconscrypt_openjdk_jni",
378        ),
379        "_merge_zips": attr.label(
380            allow_single_file = True,
381            cfg = "exec",
382            executable = True,
383            default = "//build/soong/cmd/merge_zips",
384        ),
385        "_openssl": attr.label(
386            allow_single_file = True,
387            cfg = "exec",
388            executable = True,
389            default = "//prebuilts/build-tools:linux-x86/bin/openssl",
390        ),
391        "_sign_apex": attr.label(
392            cfg = "exec",
393            executable = True,
394            default = "//build/make/tools/releasetools:sign_apex",
395        ),
396        "_signapk_jar": attr.label(
397            cfg = "exec",
398            executable = False,
399            default = "//build/bazel/rules/apex:signapk_deploy_jar",
400        ),
401        "_zip2zip": attr.label(
402            allow_single_file = True,
403            cfg = "exec",
404            executable = True,
405            default = "//build/soong/cmd/zip2zip:zip2zip",
406        ),
407        "_zipper": attr.label(
408            cfg = "exec",
409            executable = True,
410            default = "@bazel_tools//tools/zip:zipper",
411        ),
412    },
413)
414
415def apex_aab(name, mainline_module, dev_sign_bundle = None, dev_keystore = None, target_compatible_with = [], **kwargs):
416    target_compatible_with = select({
417        "//build/bazel_common_rules/platforms/os:android": [],
418        "//conditions:default": ["@platforms//:incompatible"],
419    }) + target_compatible_with
420
421    _apex_aab(
422        name = name,
423        mainline_module = mainline_module,
424        dev_sign_bundle = dev_sign_bundle,
425        dev_keystore = dev_keystore,
426        target_compatible_with = target_compatible_with,
427        **kwargs
428    )
429