| """ |
| Copyright (C) 2021 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. |
| """ |
| |
| load(":apex_key.bzl", "ApexKeyInfo") |
| load(":prebuilt_etc.bzl", "PrebuiltEtcInfo") |
| load(":sh_binary.bzl", "ShBinaryInfo") |
| load("//build/bazel/rules/cc:stripped_cc_common.bzl", "StrippedCcBinaryInfo") |
| load("//build/bazel/rules/android:android_app_certificate.bzl", "AndroidAppCertificateInfo") |
| load("//build/bazel/rules/apex:transition.bzl", "apex_transition", "shared_lib_transition_32", "shared_lib_transition_64") |
| load("//build/bazel/rules/apex:cc.bzl", "ApexCcInfo", "apex_cc_aspect") |
| |
| DIR_LIB = "lib" |
| DIR_LIB64 = "lib64" |
| |
| ApexInfo = provider( |
| "ApexInfo has no field currently and is used by apex rule dependents to ensure an attribute is a target of apex rule.", |
| fields = {}, |
| ) |
| |
| # Prepare the input files info for bazel_apexer_wrapper to generate APEX filesystem image. |
| def _prepare_apexer_wrapper_inputs(ctx): |
| # dictionary to return in the format: |
| # apex_manifest[(image_file_dirname, image_file_basename)] = bazel_output_file |
| apex_manifest = {} |
| |
| x86_constraint = ctx.attr._x86_constraint[platform_common.ConstraintValueInfo] |
| x86_64_constraint = ctx.attr._x86_64_constraint[platform_common.ConstraintValueInfo] |
| arm_constraint = ctx.attr._arm_constraint[platform_common.ConstraintValueInfo] |
| arm64_constraint = ctx.attr._arm64_constraint[platform_common.ConstraintValueInfo] |
| |
| if ctx.target_platform_has_constraint(x86_constraint): |
| _add_libs_32_target(ctx, "x86", apex_manifest) |
| elif ctx.target_platform_has_constraint(x86_64_constraint): |
| _add_libs_64_target(ctx, "x86", "x86_64", apex_manifest) |
| elif ctx.target_platform_has_constraint(arm_constraint): |
| _add_libs_32_target(ctx, "arm", apex_manifest) |
| elif ctx.target_platform_has_constraint(arm64_constraint): |
| _add_libs_64_target(ctx, "arm", "arm64", apex_manifest) |
| |
| # Handle prebuilts |
| for dep in ctx.attr.prebuilts: |
| # TODO: Support more prebuilts than just PrebuiltEtc |
| prebuilt_etc_info = dep[PrebuiltEtcInfo] |
| |
| directory = "etc" |
| if prebuilt_etc_info.sub_dir != None and prebuilt_etc_info.sub_dir != "": |
| directory = "/".join([directory, prebuilt_etc_info.sub_dir]) |
| |
| if prebuilt_etc_info.filename != None and prebuilt_etc_info.filename != "": |
| filename = prebuilt_etc_info.filename |
| else: |
| filename = dep.label.name |
| |
| apex_manifest[(directory, filename)] = prebuilt_etc_info.src |
| |
| # Handle binaries |
| for dep in ctx.attr.binaries: |
| if ShBinaryInfo in dep: |
| # sh_binary requires special handling on directory/filename construction. |
| sh_binary_info = dep[ShBinaryInfo] |
| default_info = dep[DefaultInfo] |
| if sh_binary_info != None: |
| directory = "bin" |
| if sh_binary_info.sub_dir != None and sh_binary_info.sub_dir != "": |
| directory = "/".join([directory, sh_binary_info.sub_dir]) |
| |
| if sh_binary_info.filename != None and sh_binary_info.filename != "": |
| filename = sh_binary_info.filename |
| else: |
| filename = dep.label.name |
| |
| apex_manifest[(directory, filename)] = default_info.files_to_run.executable |
| elif CcInfo in dep: |
| # cc_binary just takes the final executable from the runfiles. |
| apex_manifest[("bin", dep.label.name)] = dep[DefaultInfo].files_to_run.executable |
| |
| apex_content_inputs = [] |
| |
| bazel_apexer_wrapper_manifest = ctx.actions.declare_file("%s_bazel_apexer_wrapper_manifest" % ctx.attr.name) |
| file_lines = [] |
| |
| # Store the apex file target directory, file name and the path in the source tree in a file. |
| # This file will be read by the bazel_apexer_wrapper to create the apex input directory. |
| # Here is an example: |
| # {etc/tz,tz_version,system/timezone/output_data/version/tz_version} |
| for (apex_dirname, apex_basename), bazel_input_file in apex_manifest.items(): |
| apex_content_inputs.append(bazel_input_file) |
| file_lines += [",".join([apex_dirname, apex_basename, bazel_input_file.path])] |
| |
| ctx.actions.write(bazel_apexer_wrapper_manifest, "\n".join(file_lines)) |
| |
| return apex_content_inputs, bazel_apexer_wrapper_manifest |
| |
| def _add_libs_32_target(ctx, key, apex_manifest): |
| if len(ctx.split_attr.native_shared_libs_32.keys()) > 0: |
| _add_lib_file(DIR_LIB, ctx.split_attr.native_shared_libs_32[key], apex_manifest) |
| |
| def _add_libs_64_target(ctx, key_32, key_64, apex_manifest): |
| _add_libs_32_target(ctx, key_32, apex_manifest) |
| if len(ctx.split_attr.native_shared_libs_64.keys()) > 0: |
| _add_lib_file(DIR_LIB64, ctx.split_attr.native_shared_libs_64[key_64], apex_manifest) |
| |
| def _add_lib_file(dir, libs, apex_manifest): |
| for dep in libs: |
| apex_cc_info = dep[ApexCcInfo] |
| for lib_file in apex_cc_info.transitive_shared_libs.to_list(): |
| apex_manifest[(dir, lib_file.basename)] = lib_file |
| |
| # conv_apex_manifest - Convert the JSON APEX manifest to protobuf, which is needed by apexer. |
| def _convert_apex_manifest_json_to_pb(ctx, apex_toolchain): |
| apex_manifest_json = ctx.file.manifest |
| apex_manifest_pb = ctx.actions.declare_file("apex_manifest.pb") |
| |
| ctx.actions.run( |
| outputs = [apex_manifest_pb], |
| inputs = [ctx.file.manifest], |
| executable = apex_toolchain.conv_apex_manifest, |
| arguments = [ |
| "proto", |
| apex_manifest_json.path, |
| "-o", |
| apex_manifest_pb.path, |
| ], |
| mnemonic = "ConvApexManifest", |
| ) |
| |
| return apex_manifest_pb |
| |
| # apexer - generate the APEX file. |
| def _run_apexer(ctx, apex_toolchain, apex_content_inputs, bazel_apexer_wrapper_manifest, apex_manifest_pb): |
| # Inputs |
| file_contexts = ctx.file.file_contexts |
| apex_key_info = ctx.attr.key[ApexKeyInfo] |
| privkey = apex_key_info.private_key |
| pubkey = apex_key_info.public_key |
| android_jar = apex_toolchain.android_jar |
| android_manifest = ctx.file.android_manifest |
| |
| # Outputs |
| apex_output_file = ctx.actions.declare_file(ctx.attr.name + ".apex.unsigned") |
| |
| # Arguments |
| args = ctx.actions.args() |
| args.add_all(["--manifest", apex_manifest_pb.path]) |
| args.add_all(["--file_contexts", file_contexts.path]) |
| args.add_all(["--key", privkey.path]) |
| args.add_all(["--pubkey", pubkey.path]) |
| min_sdk_version = ctx.attr.min_sdk_version |
| |
| # TODO(b/215339575): This is a super rudimentary way to convert "current" to a numerical number. |
| # Generalize this to API level handling logic in a separate Starlark utility, preferably using |
| # API level maps dumped from api_levels.go |
| if min_sdk_version == "current": |
| min_sdk_version = "10000" |
| args.add_all(["--min_sdk_version", min_sdk_version]) |
| args.add_all(["--bazel_apexer_wrapper_manifest", bazel_apexer_wrapper_manifest]) |
| args.add_all(["--apexer_path", apex_toolchain.apexer]) |
| |
| # apexer needs the list of directories containing all auxilliary tools invoked during |
| # the creation of an apex |
| avbtool_files = apex_toolchain.avbtool[DefaultInfo].files_to_run |
| e2fsdroid_files = apex_toolchain.e2fsdroid[DefaultInfo].files_to_run |
| apexer_tool_paths = [ |
| # These are built by make_injection |
| apex_toolchain.apexer.dirname, |
| |
| # These are real Bazel targets |
| avbtool_files.executable.dirname, |
| e2fsdroid_files.executable.dirname, |
| ] |
| |
| args.add_all(["--apexer_tool_path", ":".join(apexer_tool_paths)]) |
| args.add_all(["--apex_output_file", apex_output_file]) |
| |
| if android_manifest != None: |
| args.add_all(["--android_manifest", android_manifest.path]) |
| |
| inputs = apex_content_inputs + [ |
| bazel_apexer_wrapper_manifest, |
| apex_manifest_pb, |
| file_contexts, |
| privkey, |
| pubkey, |
| android_jar, |
| ] |
| |
| tools = [ |
| avbtool_files, |
| e2fsdroid_files, |
| |
| apex_toolchain.apexer, |
| apex_toolchain.mke2fs, |
| apex_toolchain.sefcontext_compile, |
| apex_toolchain.resize2fs, |
| apex_toolchain.aapt2, |
| ] |
| |
| if android_manifest != None: |
| inputs.append(android_manifest) |
| |
| ctx.actions.run( |
| inputs = inputs, |
| tools = tools, |
| outputs = [apex_output_file], |
| executable = ctx.executable._bazel_apexer_wrapper, |
| arguments = [args], |
| mnemonic = "BazelApexerWrapper", |
| ) |
| |
| return apex_output_file |
| |
| # Sign a file with signapk. |
| def _run_signapk(ctx, unsigned_file, signed_file, private_key, public_key, mnemonic): |
| # Inputs |
| inputs = [ |
| unsigned_file, |
| private_key, |
| public_key, |
| ctx.executable._signapk, |
| ] |
| |
| # Outputs |
| outputs = [signed_file] |
| |
| # Arguments |
| args = ctx.actions.args() |
| args.add_all(["-a", 4096]) |
| args.add_all(["--align-file-size"]) |
| args.add_all([public_key, private_key]) |
| args.add_all([unsigned_file, signed_file]) |
| |
| ctx.actions.run( |
| inputs = inputs, |
| outputs = outputs, |
| executable = ctx.executable._signapk, |
| arguments = [args], |
| mnemonic = mnemonic, |
| ) |
| |
| return signed_file |
| |
| # Compress a file with apex_compression_tool. |
| def _run_apex_compression_tool(ctx, apex_toolchain, input_file, output_file_name): |
| # Inputs |
| inputs = [ |
| input_file, |
| ] |
| |
| avbtool_files = apex_toolchain.avbtool[DefaultInfo].files_to_run |
| tools = [ |
| avbtool_files, |
| apex_toolchain.apex_compression_tool, |
| apex_toolchain.soong_zip, |
| ] |
| |
| # Outputs |
| compressed_file = ctx.actions.declare_file(output_file_name) |
| outputs = [compressed_file] |
| |
| # Arguments |
| args = ctx.actions.args() |
| args.add_all(["compress"]) |
| tool_dirs = [apex_toolchain.soong_zip.dirname, avbtool_files.executable.dirname] |
| args.add_all(["--apex_compression_tool", ":".join(tool_dirs)]) |
| args.add_all(["--input", input_file]) |
| args.add_all(["--output", compressed_file]) |
| |
| ctx.actions.run( |
| inputs = inputs, |
| tools = tools, |
| outputs = outputs, |
| executable = apex_toolchain.apex_compression_tool, |
| arguments = [args], |
| mnemonic = "BazelApexCompressing", |
| ) |
| return compressed_file |
| |
| # See the APEX section in the README on how to use this rule. |
| def _apex_rule_impl(ctx): |
| apex_toolchain = ctx.toolchains["//build/bazel/rules/apex:apex_toolchain_type"].toolchain_info |
| |
| apex_content_inputs, bazel_apexer_wrapper_manifest = _prepare_apexer_wrapper_inputs(ctx) |
| apex_manifest_pb = _convert_apex_manifest_json_to_pb(ctx, apex_toolchain) |
| |
| unsigned_apex_output_file = _run_apexer(ctx, apex_toolchain, apex_content_inputs, bazel_apexer_wrapper_manifest, apex_manifest_pb) |
| |
| apex_cert_info = ctx.attr.certificate[AndroidAppCertificateInfo] |
| private_key = apex_cert_info.pk8 |
| public_key = apex_cert_info.pem |
| |
| signed_apex = ctx.outputs.apex_output |
| _run_signapk(ctx, unsigned_apex_output_file, signed_apex, private_key, public_key, "BazelApexSigning") |
| output_file = signed_apex |
| |
| if ctx.attr.compressible: |
| compressed_apex_output_file = _run_apex_compression_tool(ctx, apex_toolchain, signed_apex, ctx.attr.name + ".capex.unsigned") |
| signed_capex = ctx.outputs.capex_output |
| _run_signapk(ctx, compressed_apex_output_file, signed_capex, private_key, public_key, "BazelCompressedApexSigning") |
| |
| files_to_build = depset([output_file]) |
| return [DefaultInfo(files = files_to_build), ApexInfo()] |
| |
| _apex = rule( |
| implementation = _apex_rule_impl, |
| attrs = { |
| "manifest": attr.label(allow_single_file = [".json"]), |
| "android_manifest": attr.label(allow_single_file = [".xml"]), |
| "file_contexts": attr.label(allow_single_file = True, mandatory = True), |
| "key": attr.label(providers = [ApexKeyInfo]), |
| "certificate": attr.label(providers = [AndroidAppCertificateInfo]), |
| "min_sdk_version": attr.string(default = "current"), |
| "updatable": attr.bool(default = True), |
| "installable": attr.bool(default = True), |
| "compressible": attr.bool(default = False), |
| "native_shared_libs_32": attr.label_list( |
| providers = [ApexCcInfo], |
| aspects = [apex_cc_aspect], |
| cfg = shared_lib_transition_32, |
| doc = "The libs compiled for 32-bit", |
| ), |
| "native_shared_libs_64": attr.label_list( |
| providers = [ApexCcInfo], |
| aspects = [apex_cc_aspect], |
| cfg = shared_lib_transition_64, |
| doc = "The libs compiled for 64-bit", |
| ), |
| "binaries": attr.label_list( |
| providers = [ |
| # The dependency must produce _all_ of the providers in _one_ of these lists. |
| [ShBinaryInfo], # sh_binary |
| [StrippedCcBinaryInfo, CcInfo], # cc_binary (stripped) |
| ], |
| cfg = apex_transition, |
| ), |
| "prebuilts": attr.label_list(providers = [PrebuiltEtcInfo], cfg = apex_transition), |
| "apex_output": attr.output(doc = "signed .apex output"), |
| "capex_output": attr.output(doc = "signed .capex output"), |
| |
| # Required to use apex_transition. This is an acknowledgement to the risks of memory bloat when using transitions. |
| "_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"), |
| "_bazel_apexer_wrapper": attr.label( |
| cfg = "host", |
| doc = "The apexer wrapper to avoid the problem where symlinks are created inside apex image.", |
| executable = True, |
| default = "//build/bazel/rules/apex:bazel_apexer_wrapper", |
| ), |
| "_signapk": attr.label( |
| cfg = "host", |
| doc = "The signapk tool.", |
| executable = True, |
| default = "//build/make/tools/signapk", |
| ), |
| "_x86_constraint": attr.label( |
| default = Label("//build/bazel/platforms/arch:x86"), |
| ), |
| "_x86_64_constraint": attr.label( |
| default = Label("//build/bazel/platforms/arch:x86_64"), |
| ), |
| "_arm_constraint": attr.label( |
| default = Label("//build/bazel/platforms/arch:arm"), |
| ), |
| "_arm64_constraint": attr.label( |
| default = Label("//build/bazel/platforms/arch:arm64"), |
| ), |
| }, |
| toolchains = ["//build/bazel/rules/apex:apex_toolchain_type"], |
| fragments = ["platform"], |
| ) |
| |
| def apex( |
| name, |
| manifest = "apex_manifest.json", |
| android_manifest = None, |
| file_contexts = None, |
| key = None, |
| certificate = None, |
| min_sdk_version = None, |
| updatable = True, |
| installable = True, |
| compressible = False, |
| native_shared_libs_32 = [], |
| native_shared_libs_64 = [], |
| binaries = [], |
| prebuilts = [], |
| **kwargs): |
| "Bazel macro to correspond with the APEX bundle Soong module." |
| |
| # If file_contexts is not specified, then use the default from //system/sepolicy/apex. |
| # https://cs.android.com/android/platform/superproject/+/master:build/soong/apex/builder.go;l=259-263;drc=b02043b84d86fe1007afef1ff012a2155172215c |
| if file_contexts == None: |
| file_contexts = "//system/sepolicy/apex:" + name + "-file_contexts" |
| |
| apex_output = name + ".apex" |
| capex_output = None |
| if compressible: |
| capex_output = name + ".capex" |
| |
| _apex( |
| name = name, |
| manifest = manifest, |
| android_manifest = android_manifest, |
| file_contexts = file_contexts, |
| key = key, |
| certificate = certificate, |
| min_sdk_version = min_sdk_version, |
| updatable = updatable, |
| installable = installable, |
| compressible = compressible, |
| native_shared_libs_32 = native_shared_libs_32, |
| native_shared_libs_64 = native_shared_libs_64, |
| binaries = binaries, |
| prebuilts = prebuilts, |
| |
| # Enables predeclared output builds from command line directly, e.g. |
| # |
| # $ bazel build //path/to/module:com.android.module.apex |
| # $ bazel build //path/to/module:com.android.module.capex |
| apex_output = apex_output, |
| capex_output = capex_output, |
| **kwargs |
| ) |