blob: 323b5c08b73af41855143985503391d669242f14 [file] [log] [blame]
"""
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
)