433 lines
16 KiB
Python
433 lines
16 KiB
Python
"""
|
|
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_file.bzl", "PrebuiltFileInfo")
|
|
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:
|
|
prebuilt_file_info = dep[PrebuiltFileInfo]
|
|
if prebuilt_file_info.filename:
|
|
filename = prebuilt_file_info.filename
|
|
else:
|
|
filename = dep.label.name
|
|
apex_manifest[(prebuilt_file_info.dir, filename)] = prebuilt_file_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
|
|
mke2fs_files = apex_toolchain.mke2fs[DefaultInfo].files_to_run
|
|
resize2fs_files = apex_toolchain.resize2fs[DefaultInfo].files_to_run
|
|
apexer_tool_paths = [
|
|
# These are built by make_injection
|
|
apex_toolchain.apexer.dirname,
|
|
|
|
# These are real Bazel targets
|
|
apex_toolchain.aapt2.dirname,
|
|
avbtool_files.executable.dirname,
|
|
e2fsdroid_files.executable.dirname,
|
|
mke2fs_files.executable.dirname,
|
|
resize2fs_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,
|
|
mke2fs_files,
|
|
resize2fs_files,
|
|
apex_toolchain.aapt2,
|
|
|
|
apex_toolchain.apexer,
|
|
apex_toolchain.sefcontext_compile,
|
|
]
|
|
|
|
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 = [PrebuiltFileInfo], 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
|
|
)
|