unplugged-system/build/bazel/rules/apex/apex.bzl

1185 lines
49 KiB
Python
Raw Permalink Normal View History

# 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("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@soong_injection//apex_toolchain:constants.bzl", "default_manifest_version")
load("//build/bazel/platforms:platform_utils.bzl", "platforms")
load("//build/bazel/product_config:product_variables_providing_rule.bzl", "ProductVariablesInfo")
load("//build/bazel/rules:common.bzl", "get_dep_targets")
load("//build/bazel/rules:metadata.bzl", "MetadataFileInfo")
load("//build/bazel/rules:prebuilt_file.bzl", "PrebuiltFileInfo")
load("//build/bazel/rules:sh_binary.bzl", "ShBinaryInfo")
load("//build/bazel/rules:toolchain_utils.bzl", "verify_toolchain_exists")
load("//build/bazel/rules/android:android_app_certificate.bzl", "AndroidAppCertificateInfo", "android_app_certificate_with_default_cert")
load("//build/bazel/rules/apex:cc.bzl", "ApexCcInfo", "ApexCcMkInfo", "apex_cc_aspect")
load("//build/bazel/rules/apex:sdk_versions.bzl", "maybe_override_min_sdk_version")
load("//build/bazel/rules/apex:transition.bzl", "apex_transition", "shared_lib_transition_32", "shared_lib_transition_64")
load("//build/bazel/rules/cc:clang_tidy.bzl", "collect_deps_clang_tidy_info")
load("//build/bazel/rules/cc:stripped_cc_common.bzl", "CcUnstrippedInfo", "StrippedCcBinaryInfo")
load("//build/bazel/rules/common:api.bzl", "api")
load(
"//build/bazel/rules/license:license_aspect.bzl",
"RuleLicensedDependenciesInfo",
"license_aspect",
"license_map",
"license_map_notice_files",
"license_map_to_json",
)
load(":apex_available.bzl", "ApexAvailableInfo", "apex_available_aspect")
load(":apex_deps_validation.bzl", "ApexDepsInfo", "apex_deps_validation_aspect", "validate_apex_deps")
load(":apex_info.bzl", "ApexInfo", "ApexMkInfo")
load(":apex_key.bzl", "ApexKeyInfo")
load(":bundle.bzl", "apex_zip_files")
def _create_file_mapping(ctx):
"""Create a file mapping for the APEX filesystem image.
This returns a Dict[File, str] where the dictionary keys are paths in the
apex staging dir / filesystem image, and the values are the files and other
metadata that should be installed there.
It also returns other data structures, such as:
- requires: libs that this apex depend on from other apex or the platform
- provides: libs that this apex provide to other apex or the platform
- make_modules_to_install: make module names of libs that needs to be installed onto the platform in a bundled build (LOCAL_REQUIRED_MODULES)
- make_files_info: metadata about this apex's payload to be used for other packaging steps.
"""
# Dictionary mapping from paths in the apex to the files and associated metadata to be put there
file_mapping = {}
requires = {}
provides = {}
make_modules_to_install = {}
metadata_file_mapping = {}
# Generate a str -> str dictionary to define Make modules and variables for the
# packaging step in a mixed build. This is necessary as long as there are
# Make-derived actions that operate on bazel's outputs. If we move all Make
# packaging actions to Bazel, there's no need for this data flow.
make_files_info = {}
arch = platforms.get_target_arch(ctx.attr._platform_utils)
is_target_64_bit = platforms.get_target_bitness(ctx.attr._platform_utils) == 64
def add_file_mapping(install_dir, basename, bazel_file, klass, owner, arch = None, unstripped = None, metadata_file = None):
installed_path = paths.join(install_dir, basename)
if installed_path in file_mapping and file_mapping[installed_path] != bazel_file:
# TODO: we should figure this out and make it a failure
print("Warning: %s in this apex is already installed to %s, overwriting it with %s" %
(file_mapping[installed_path].path, installed_path, bazel_file.path))
file_mapping[installed_path] = bazel_file
metadata_file_mapping[installed_path] = metadata_file
files_info = {
"built_file": bazel_file.path,
"class": klass,
"install_dir": install_dir,
"basename": basename,
"package": owner.package,
"make_module_name": owner.name,
"arch": arch,
}
if unstripped:
files_info["unstripped_built_file"] = unstripped.path
make_files_info[installed_path] = files_info
def _add_lib_files(directory, libs, arch):
for dep in libs:
apex_cc_info = dep[ApexCcInfo]
for lib in apex_cc_info.requires_native_libs.to_list():
requires[lib] = True
for lib in apex_cc_info.provides_native_libs.to_list():
provides[lib] = True
for lib_file in apex_cc_info.transitive_shared_libs.to_list():
stripped = lib_file.stripped
unstripped = lib_file.unstripped
add_file_mapping(
directory,
stripped.basename,
stripped,
"nativeSharedLib",
stripped.owner,
arch = arch,
unstripped = unstripped,
metadata_file = lib_file.metadata_file,
)
# For bundled builds.
apex_cc_mk_info = dep[ApexCcMkInfo]
for mk_module in apex_cc_mk_info.make_modules_to_install.to_list():
make_modules_to_install[mk_module] = True
if is_target_64_bit:
_add_lib_files("lib64", ctx.attr.native_shared_libs_64, arch)
secondary_arch = platforms.get_target_secondary_arch(ctx.attr._platform_utils)
if secondary_arch:
_add_lib_files("lib", ctx.attr.native_shared_libs_32, secondary_arch)
else:
_add_lib_files("lib", ctx.attr.native_shared_libs_32, arch)
backing_libs = []
for lib in file_mapping.values():
if lib.basename not in backing_libs:
backing_libs.append(lib.basename)
backing_libs = sorted(backing_libs)
# 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
add_file_mapping(
prebuilt_file_info.dir,
filename,
prebuilt_file_info.src,
"etc",
dep.label,
arch = arch,
metadata_file = dep[MetadataFileInfo].metadata_file,
)
# 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]
if sh_binary_info:
directory = "bin"
if sh_binary_info.sub_dir:
directory = paths.join("bin", sh_binary_info.sub_dir)
filename = dep.label.name
if sh_binary_info.filename:
filename = sh_binary_info.filename
add_file_mapping(
directory,
filename,
dep[DefaultInfo].files_to_run.executable,
"shBinary",
dep.label,
arch = arch,
metadata_file = dep[MetadataFileInfo].metadata_file,
)
elif ApexCcInfo in dep:
# cc_binary just takes the final executable from the runfiles.
add_file_mapping(
"bin",
dep.label.name,
dep[DefaultInfo].files_to_run.executable,
"nativeExecutable",
dep.label,
arch,
unstripped = dep[CcUnstrippedInfo].unstripped[0].files.to_list()[0],
metadata_file = dep[MetadataFileInfo].metadata_file,
)
# Add transitive shared lib deps of apex binaries to the apex.
if is_target_64_bit:
_add_lib_files("lib64", [dep], arch)
else:
_add_lib_files("lib", [dep], arch)
return (
file_mapping,
sorted(requires.keys(), key = lambda x: x.name), # sort on just the name of the target, not package
sorted(provides.keys(), key = lambda x: x.name),
backing_libs,
sorted(make_modules_to_install),
sorted(make_files_info.values(), key = lambda x: ":".join([x["package"], x["make_module_name"], x["arch"]])),
metadata_file_mapping,
)
def _add_so(label):
return label.name + ".so"
def _add_apex_manifest_information(
ctx,
apex_toolchain,
requires_native_libs,
provides_native_libs):
apex_manifest_json = ctx.file.manifest
apex_manifest_full_json = ctx.actions.declare_file(ctx.attr.name + "_apex_manifest_full.json")
args = ctx.actions.args()
args.add(apex_manifest_json)
args.add_all(["-a", "requireNativeLibs"])
args.add_all(requires_native_libs, map_each = _add_so) # e.g. turn "//foo/bar:baz" to "baz.so"
args.add_all(["-a", "provideNativeLibs"])
args.add_all(provides_native_libs, map_each = _add_so)
manifest_version = ctx.attr._override_apex_manifest_default_version[BuildSettingInfo].value
if not manifest_version:
manifest_version = default_manifest_version
args.add_all(["-se", "version", "0", manifest_version])
# TODO: support other optional flags like -v name and -a jniLibs
args.add_all(["-o", apex_manifest_full_json])
ctx.actions.run(
inputs = [apex_manifest_json],
outputs = [apex_manifest_full_json],
executable = apex_toolchain.jsonmodify[DefaultInfo].files_to_run,
arguments = [args],
mnemonic = "ApexManifestModify",
)
return apex_manifest_full_json
# 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):
apex_manifest_pb = ctx.actions.declare_file(ctx.attr.name + "_apex_manifest.pb")
ctx.actions.run(
outputs = [apex_manifest_pb],
inputs = [apex_manifest_json],
executable = apex_toolchain.conv_apex_manifest[DefaultInfo].files_to_run,
arguments = [
"proto",
apex_manifest_json.path,
"-o",
apex_manifest_pb.path,
],
mnemonic = "ConvApexManifest",
)
return apex_manifest_pb
def _generate_canned_fs_config(ctx, filepaths):
"""Generate filesystem config.
This encodes the filemode, uid, and gid of each file in the APEX,
including apex_manifest.json and apex_manifest.pb.
NOTE: every file must have an entry.
"""
# Ensure all paths don't start with / and are normalized
filepaths = [paths.normalize(f).lstrip("/") for f in filepaths]
# Soong also sorts the config lines to be consistent with bazel
filepaths = sorted([f for f in filepaths if f])
# First, collect a set of all the directories in the apex
apex_subdirs_set = {}
for f in filepaths:
d = paths.dirname(f)
if d != "": # The root dir is handled manually below
# Make sure all the parent dirs of the current subdir are in the set, too
dirs = d.split("/")
for i in range(1, len(dirs) + 1):
apex_subdirs_set["/".join(dirs[:i])] = True
config_lines = []
config_lines.append("/ 1000 1000 0755")
config_lines.append("/apex_manifest.json 1000 1000 0644")
config_lines.append("/apex_manifest.pb 1000 1000 0644")
# Readonly if not executable. filepaths is already sorted.
config_lines += ["/" + f + " 1000 1000 0644" for f in filepaths if not f.startswith("bin/")]
# Mark all binaries as executable. filepaths is already sorted.
config_lines += ["/" + f + " 0 2000 0755" for f in filepaths if f.startswith("bin/")]
# All directories have the same permission.
config_lines += ["/" + d + " 0 2000 0755" for d in sorted(apex_subdirs_set.keys())]
output = ctx.actions.declare_file(ctx.attr.name + "_canned_fs_config.txt")
config_lines = "\n".join(config_lines) + "\n"
ctx.actions.write(output, config_lines)
if ctx.attr.canned_fs_config:
# Append the custom fs config content to the existing file
combined_output = ctx.actions.declare_file(ctx.attr.name + "_combined_canned_fs_config.txt")
ctx.actions.run_shell(
inputs = [ctx.file.canned_fs_config, output],
outputs = [combined_output],
mnemonic = "AppendCustomFsConfig",
command = "cat {i} {canned_fs_config} > {o}".format(
i = output.path,
o = combined_output.path,
canned_fs_config = ctx.file.canned_fs_config.path,
),
)
output = combined_output
return output
# Append an entry for apex_manifest.pb to the file_contexts file for this APEX,
# which is either from /system/sepolicy/apex/<apexname>-file_contexts (set in
# the apex macro) or custom file_contexts attribute value of this APEX. This
# ensures that the manifest file is correctly labeled as system_file.
def _generate_file_contexts(ctx):
file_contexts = ctx.actions.declare_file(ctx.attr.name + "-file_contexts")
ctx.actions.run_shell(
inputs = [ctx.file.file_contexts],
outputs = [file_contexts],
mnemonic = "GenerateApexFileContexts",
command = "cat {i} > {o} && echo >> {o} && echo /apex_manifest\\\\.pb u:object_r:system_file:s0 >> {o} && echo / u:object_r:system_file:s0 >> {o}"
.format(i = ctx.file.file_contexts.path, o = file_contexts.path),
)
return file_contexts
# TODO(b/255592586): This can be reused by Java rules later.
def _mark_manifest_as_test_only(ctx, apex_toolchain):
if ctx.file.android_manifest == None:
return None
android_manifest = ctx.file.android_manifest
dir_name = android_manifest.dirname
base_name = android_manifest.basename
android_manifest_fixed = ctx.actions.declare_file(paths.join(dir_name, "manifest_fixer", base_name))
args = ctx.actions.args()
args.add("--test-only")
args.add(android_manifest)
args.add(android_manifest_fixed)
ctx.actions.run(
inputs = [android_manifest],
outputs = [android_manifest_fixed],
executable = apex_toolchain.manifest_fixer[DefaultInfo].files_to_run,
arguments = [args],
mnemonic = "MarkAndroidManifestTestOnly",
)
return android_manifest_fixed
# Generate <APEX>_backing.txt file which lists all libraries used by the APEX.
def _generate_apex_backing_file(ctx, backing_libs):
backing_file = ctx.actions.declare_file(ctx.attr.name + "_backing.txt")
ctx.actions.write(
output = backing_file,
content = " ".join(backing_libs) + "\n",
)
return backing_file
# Generate installed-files.txt which lists all installed files by the APEX.
def _generate_installed_files_list(ctx, file_mapping):
installed_files = ctx.actions.declare_file(ctx.attr.name + "-installed-files.txt")
command = []
for device_path, bazel_file in file_mapping.items():
command.append("echo $(stat -L -c %%s %s) ./%s" % (bazel_file.path, device_path))
ctx.actions.run_shell(
inputs = file_mapping.values(),
outputs = [installed_files],
mnemonic = "GenerateApexInstalledFileList",
command = "(" + "; ".join(command) + ") | sort -nr > " + installed_files.path,
)
return installed_files
def _generate_notices(ctx, apex_toolchain):
licensees = license_map(ctx.attr.binaries + ctx.attr.prebuilts + ctx.attr.native_shared_libs_32 + ctx.attr.native_shared_libs_64)
licenses_file = ctx.actions.declare_file(ctx.attr.name + "_licenses.json")
ctx.actions.write(licenses_file, "[\n%s\n]\n" % ",\n".join(license_map_to_json(licensees)))
# Run HTML notice file generator.
notice_file = ctx.actions.declare_file(ctx.attr.name + "_notice_dir/NOTICE.html.gz")
notice_generator = apex_toolchain.notice_generator[DefaultInfo].files_to_run
args = ctx.actions.args()
args.add_all(["-o", notice_file, licenses_file])
# TODO(asmundak): should we extend it with license info for self
# (the case when APEX itself has applicable_licenses attribute)?
inputs = license_map_notice_files(licensees) + [licenses_file]
ctx.actions.run(
mnemonic = "GenerateNoticeFile",
inputs = inputs,
outputs = [notice_file],
executable = notice_generator,
tools = [notice_generator],
arguments = [args],
)
return notice_file
def _use_api_fingerprint(ctx):
product_vars = ctx.attr._product_variables[ProductVariablesInfo]
if not product_vars.Unbundled_build:
return False
if product_vars.Always_use_prebuilt_sdks:
return False
if not ctx.attr._unbundled_build_target_sdk_with_api_fingerprint[BuildSettingInfo].value:
return False
return True
# apexer - generate the APEX file.
def _run_apexer(ctx, apex_toolchain):
# Inputs
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
file_mapping, requires_native_libs, provides_native_libs, backing_libs, make_modules_to_install, make_files_info, metadata_file_mapping = _create_file_mapping(ctx)
canned_fs_config = _generate_canned_fs_config(ctx, file_mapping.keys())
file_contexts = _generate_file_contexts(ctx)
full_apex_manifest_json = _add_apex_manifest_information(ctx, apex_toolchain, requires_native_libs, provides_native_libs)
apex_manifest_pb = _convert_apex_manifest_json_to_pb(ctx, apex_toolchain, full_apex_manifest_json)
notices_file = _generate_notices(ctx, apex_toolchain)
api_fingerprint_file = None
file_mapping_file = ctx.actions.declare_file(ctx.attr.name + "_apex_file_mapping.json")
ctx.actions.write(file_mapping_file, json.encode({k: v.path for k, v in file_mapping.items()}))
# Outputs
apex_output_file = ctx.actions.declare_file(ctx.attr.name + ".apex.unsigned")
apexer_files = apex_toolchain.apexer[DefaultInfo].files_to_run
# Arguments
command = [ctx.executable._staging_dir_builder.path, file_mapping_file.path]
# NOTE: When used as inputs to another sandboxed action, this directory
# artifact's inner files will be made up of symlinks. Ensure that the
# aforementioned action handles symlinks correctly (e.g. following
# symlinks).
staging_dir = ctx.actions.declare_directory(ctx.attr.name + "_staging_dir")
command.append(staging_dir.path)
# start of apexer cmd
command.append(apexer_files.executable.path)
if ctx.attr._apexer_verbose[BuildSettingInfo].value:
command.append("--verbose")
command.append("--force")
command.append("--include_build_info")
command.extend(["--canned_fs_config", canned_fs_config.path])
command.extend(["--manifest", apex_manifest_pb.path])
command.extend(["--file_contexts", file_contexts.path])
command.extend(["--key", privkey.path])
command.extend(["--pubkey", pubkey.path])
command.extend(["--payload_type", "image"])
command.extend(["--payload_fs_type", "ext4"])
command.extend(["--assets_dir", notices_file.dirname])
# Override the package name, if it's expicitly specified
if ctx.attr.package_name:
command.extend(["--override_apk_package_name", ctx.attr.package_name])
else:
override_package_name = _override_manifest_package_name(ctx)
if override_package_name:
command.extend(["--override_apk_package_name", override_package_name])
if ctx.attr.logging_parent:
command.extend(["--logging_parent", ctx.attr.logging_parent])
use_api_fingerprint = _use_api_fingerprint(ctx)
target_sdk_version = str(api.final_or_future(api.default_app_target_sdk()))
if use_api_fingerprint:
api_fingerprint_file = ctx.file._api_fingerprint_txt
sdk_version_suffix = ".$(cat {})".format(api_fingerprint_file.path)
target_sdk_version = ctx.attr._platform_sdk_codename[BuildSettingInfo].value + sdk_version_suffix
command.extend(["--target_sdk_version", target_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
min_sdk_version = ctx.attr.min_sdk_version
if min_sdk_version == "current":
min_sdk_version = "10000"
override_min_sdk_version = ctx.attr._apex_global_min_sdk_version_override[BuildSettingInfo].value
min_sdk_version = str(maybe_override_min_sdk_version(min_sdk_version, override_min_sdk_version))
if min_sdk_version == "10000" and use_api_fingerprint:
min_sdk_version = ctx.attr._platform_sdk_codename[BuildSettingInfo].value + sdk_version_suffix
command.append(api_fingerprint_file.path)
command.extend(["--min_sdk_version", min_sdk_version])
# 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
sefcontext_compile_files = apex_toolchain.sefcontext_compile[DefaultInfo].files_to_run
staging_dir_builder_files = ctx.attr._staging_dir_builder[DefaultInfo].files_to_run
apexer_tool_paths = [
apex_toolchain.aapt2.dirname,
apexer_files.executable.dirname,
avbtool_files.executable.dirname,
e2fsdroid_files.executable.dirname,
mke2fs_files.executable.dirname,
resize2fs_files.executable.dirname,
sefcontext_compile_files.executable.dirname,
]
command.extend(["--apexer_tool_path", ":".join(apexer_tool_paths)])
android_manifest = ctx.file.android_manifest
if android_manifest != None:
if ctx.attr.testonly:
android_manifest = _mark_manifest_as_test_only(ctx, apex_toolchain)
command.extend(["--android_manifest", android_manifest.path])
elif ctx.attr.testonly:
command.append("--test_only")
command.append(staging_dir.path)
command.append(apex_output_file.path)
inputs = [
ctx.executable._staging_dir_builder,
file_mapping_file,
canned_fs_config,
apex_manifest_pb,
file_contexts,
notices_file,
privkey,
pubkey,
android_jar,
] + file_mapping.values()
if use_api_fingerprint:
inputs.append(api_fingerprint_file)
if android_manifest != None:
inputs.append(android_manifest)
tools = [
apexer_files,
avbtool_files,
e2fsdroid_files,
mke2fs_files,
resize2fs_files,
sefcontext_compile_files,
apex_toolchain.aapt2,
staging_dir_builder_files,
]
# This is run_shell instead of run because --target_sdk_version may
# use the API fingerprinting file contents using bash expansion,
# and only run_shell can support that by executing the whole command with
# /bin/bash -c. Regular run would quote the --target_sdk_version value with
# single quotes ('--target_sdk_version=ABC.$(cat version.txt)'), preventing
# bash expansion.
ctx.actions.run_shell(
inputs = inputs,
tools = tools,
outputs = [apex_output_file, staging_dir],
command = " ".join(command),
mnemonic = "Apexer",
)
return struct(
unsigned_apex = apex_output_file,
requires_native_libs = requires_native_libs,
provides_native_libs = provides_native_libs,
backing_libs = _generate_apex_backing_file(ctx, backing_libs),
symbols_used_by_apex = _generate_symbols_used_by_apex(ctx, apex_toolchain, staging_dir),
java_symbols_used_by_apex = _generate_java_symbols_used_by_apex(ctx, apex_toolchain),
installed_files = _generate_installed_files_list(ctx, file_mapping),
make_modules_to_install = make_modules_to_install,
make_files_info = make_files_info,
file_mapping = file_mapping,
metadata_file_mapping = metadata_file_mapping,
)
def _run_signapk(ctx, unsigned_file, signed_file, private_key, public_key, mnemonic):
"""Sign a file with signapk."""
# 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 = [
unsigned_file,
private_key,
public_key,
ctx.executable._signapk,
],
outputs = [signed_file],
executable = ctx.executable._signapk,
arguments = [args],
mnemonic = mnemonic,
)
return signed_file
# See also getOverrideManifestPackageName
# https://cs.android.com/android/platform/superproject/+/master:build/soong/apex/builder.go;l=1000;drc=241e738c7156d928e9a993b15993cb3297face45
def _override_manifest_package_name(ctx):
apex_name = ctx.attr.name
overrides = ctx.attr._product_variables[ProductVariablesInfo].ManifestPackageNameOverrides
if not overrides:
return None
matches = [o for o in overrides if o.split(":")[0] == apex_name]
if not matches:
return None
if len(matches) > 1:
fail("unexpected multiple manifest package overrides for %s, %s" % (apex_name, matches))
return matches[0].split(":")[1]
# https://cs.android.com/android/platform/superproject/+/master:build/soong/android/config.go;drc=5ca657189aac546af0aafaba11bbc9c5d889eab3;l=1501
# In Soong, we don't check whether the current apex is part of Unbundled_apps.
# Hence, we might simplify the logic by just checking product_vars["Unbundled_build"]
# TODO(b/271474456): Eventually we might default to unbundled mode in bazel-only mode
# so that we don't need to check Unbundled_apps.
def _compression_enabled(ctx):
product_vars = ctx.attr._product_variables[ProductVariablesInfo]
return product_vars.CompressedApex and len(product_vars.Unbundled_apps) == 0
# Compress a file with apex_compression_tool.
def _run_apex_compression_tool(ctx, apex_toolchain, input_file, output_file_name):
avbtool_files = apex_toolchain.avbtool[DefaultInfo].files_to_run
apex_compression_tool_files = apex_toolchain.apex_compression_tool[DefaultInfo].files_to_run
# Outputs
compressed_file = ctx.actions.declare_file(output_file_name)
# 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 = [input_file],
tools = [
avbtool_files,
apex_compression_tool_files,
apex_toolchain.soong_zip,
],
outputs = [compressed_file],
executable = apex_compression_tool_files,
arguments = [args],
mnemonic = "BazelApexCompressing",
)
return compressed_file
# Generate <module>_using.txt, which contains a list of versioned NDK symbols
# dynamically linked to by this APEX's contents. This is used for coverage
# checks.
def _generate_symbols_used_by_apex(ctx, apex_toolchain, staging_dir):
symbols_used_by_apex = ctx.actions.declare_file(ctx.attr.name + "_using.txt")
ctx.actions.run(
outputs = [symbols_used_by_apex],
inputs = [staging_dir],
tools = [
apex_toolchain.readelf.files_to_run,
apex_toolchain.gen_ndk_usedby_apex.files_to_run,
],
executable = apex_toolchain.gen_ndk_usedby_apex.files_to_run,
arguments = [
staging_dir.path,
apex_toolchain.readelf.files_to_run.executable.path,
symbols_used_by_apex.path,
],
progress_message = "Generating dynamic NDK symbol list used by the %s apex" % ctx.attr.name,
mnemonic = "ApexUsingNDKSymbolsForCoverage",
)
return symbols_used_by_apex
# Generate <module>_using.xml, which contains a list of java API metadata used
# by this APEX's contents. This is used for coverage checks.
#
# TODO(b/257954111): Add JARs and APKs as inputs to this action when we start
# building Java mainline modules.
def _generate_java_symbols_used_by_apex(ctx, apex_toolchain):
java_symbols_used_by_apex = ctx.actions.declare_file(ctx.attr.name + "_using.xml")
ctx.actions.run(
outputs = [java_symbols_used_by_apex],
inputs = [],
tools = [
apex_toolchain.dexdeps.files_to_run,
apex_toolchain.gen_java_usedby_apex.files_to_run,
],
executable = apex_toolchain.gen_java_usedby_apex.files_to_run,
arguments = [
apex_toolchain.dexdeps.files_to_run.executable.path,
java_symbols_used_by_apex.path,
],
progress_message = "Generating Java symbol list used by the %s apex" % ctx.attr.name,
mnemonic = "ApexUsingJavaSymbolsForCoverage",
)
return java_symbols_used_by_apex
def _validate_apex_deps(ctx):
transitive_deps = depset(
transitive = [
d[ApexDepsInfo].transitive_deps
for d in (
ctx.attr.native_shared_libs_32 +
ctx.attr.native_shared_libs_64 +
ctx.attr.binaries +
ctx.attr.prebuilts
)
],
)
validation_files = []
if not ctx.attr._unsafe_disable_apex_allowed_deps_check[BuildSettingInfo].value:
validation_files.append(validate_apex_deps(ctx, transitive_deps, ctx.file.allowed_apex_deps_manifest))
transitive_unvalidated_targets = []
transitive_invalid_targets = []
for _, attr_deps in get_dep_targets(ctx.attr, predicate = lambda target: ApexAvailableInfo in target).items():
for dep in attr_deps:
transitive_unvalidated_targets.append(dep[ApexAvailableInfo].transitive_unvalidated_targets)
transitive_invalid_targets.append(dep[ApexAvailableInfo].transitive_invalid_targets)
invalid_targets = depset(transitive = transitive_invalid_targets).to_list()
if len(invalid_targets) > 0:
invalid_targets_msg = "\n ".join([
"{label}; apex_available tags: {tags}".format(label = target.label, tags = list(apex_available_tags))
for target, apex_available_tags in invalid_targets
])
msg = ("`{apex_name}` apex has transitive dependencies that do not include the apex in " +
"their apex_available tags:\n {invalid_targets_msg}").format(
apex_name = ctx.label,
invalid_targets_msg = invalid_targets_msg,
)
fail(msg)
transitive_unvalidated_targets_output_file = ctx.actions.declare_file(ctx.attr.name + "_unvalidated_deps.txt")
ctx.actions.write(
transitive_unvalidated_targets_output_file,
"\n".join([
str(label) + ": " + str(reason)
for label, reason in depset(transitive = transitive_unvalidated_targets).to_list()
]),
)
return transitive_deps, transitive_unvalidated_targets_output_file, validation_files
def _verify_updatability(ctx):
# TODO(b/274732759): Add these checks as more APEXes are converted to Bazel.
#
# Keep this in sync with build/soong/apex/apex.go#checkUpdatable.
#
# - Cannot use platform APIs.
# - Cannot use external VNDK libs.
# - Does not set future_updatable.
if not ctx.attr.min_sdk_version:
fail("updatable APEXes must set min_sdk_version.")
def _generate_sbom(ctx, file_mapping, metadata_file_mapping, apex_file):
apex_filename = paths.basename(apex_file.path)
sbom_metadata_csv = ctx.actions.declare_file(apex_filename + "-sbom-metadata.csv")
command = []
metadata_files = []
sbom_metadata_csv_columns = [
"installed_file",
"module_path",
"soong_module_type",
"is_prebuilt_make_module",
"product_copy_files",
"kernel_module_copy_files",
"is_platform_generated",
"build_output_path",
"static_libraries",
"whole_static_libraries",
"is_static_lib",
]
command.append("echo " + ",".join(sbom_metadata_csv_columns))
command.append("echo %s,%s,,,,,,%s,,," % (apex_filename, ctx.label.package, apex_file.path))
for installed_file, bazel_output_file in file_mapping.items():
if metadata_file_mapping[installed_file]:
metadata_files.append(metadata_file_mapping[installed_file])
command.append("echo %s,%s,,,,,,%s,,," % (installed_file, paths.dirname(bazel_output_file.short_path), bazel_output_file.path))
ctx.actions.run_shell(
inputs = file_mapping.values(),
outputs = [sbom_metadata_csv],
mnemonic = "GenerateSBOMMetadata",
command = "(" + "; ".join(command) + ") > " + sbom_metadata_csv.path,
)
sbom_file = ctx.actions.declare_file(apex_filename + ".spdx.json")
sbom_fragment_file = ctx.actions.declare_file(apex_filename + "-fragment.spdx")
inputs = [
apex_file,
sbom_metadata_csv,
ctx.executable._generate_sbom,
]
inputs += file_mapping.values()
inputs += metadata_files
product_vars = ctx.attr._product_variables[ProductVariablesInfo]
build_fingerprint = "%s/%s/%s:%s/%s/%s:%s/%s" % (
product_vars.ProductBrand,
product_vars.DeviceProduct,
product_vars.DeviceName,
product_vars.Platform_version_name,
product_vars.BuildId,
"",
product_vars.TargetBuildVariant,
"_".join(product_vars.BuildVersionTags),
)
ctx.actions.run(
inputs = inputs,
outputs = [sbom_file, sbom_fragment_file],
arguments = [
"--output_file",
sbom_file.path,
"--metadata",
sbom_metadata_csv.path,
"--build_version",
build_fingerprint,
"--product_mfr",
product_vars.ProductManufacturer,
"--json",
"--unbundled_apex",
],
mnemonic = "GenerateSBOM",
executable = ctx.executable._generate_sbom,
)
return [sbom_file, sbom_fragment_file]
# See the APEX section in the README on how to use this rule.
def _apex_rule_impl(ctx):
verify_toolchain_exists(ctx, "//build/bazel/rules/apex:apex_toolchain_type")
if ctx.attr.updatable:
_verify_updatability(ctx)
apex_toolchain = ctx.toolchains["//build/bazel/rules/apex:apex_toolchain_type"].toolchain_info
apexer_outputs = _run_apexer(ctx, apex_toolchain)
unsigned_apex = apexer_outputs.unsigned_apex
apex_cert_info = ctx.attr.certificate[0][AndroidAppCertificateInfo]
private_key = apex_cert_info.pk8
public_key = apex_cert_info.pem
signed_apex = ctx.actions.declare_file(ctx.attr.name + ".apex")
signed_capex = None
_run_signapk(ctx, unsigned_apex, signed_apex, private_key, public_key, "BazelApexSigning")
if ctx.attr.compressible and _compression_enabled(ctx):
compressed_apex_output_file = _run_apex_compression_tool(ctx, apex_toolchain, signed_apex, ctx.attr.name + ".capex.unsigned")
signed_capex = ctx.actions.declare_file(ctx.attr.name + ".capex")
_run_signapk(ctx, compressed_apex_output_file, signed_capex, private_key, public_key, "BazelCompressedApexSigning")
apex_key_info = ctx.attr.key[ApexKeyInfo]
arch = platforms.get_target_arch(ctx.attr._platform_utils)
zip_files = apex_zip_files(
actions = ctx.actions,
name = ctx.label.name,
tools = struct(
aapt2 = apex_toolchain.aapt2,
zip2zip = ctx.executable._zip2zip,
merge_zips = ctx.executable._merge_zips,
soong_zip = apex_toolchain.soong_zip,
),
apex_file = signed_apex,
arch = arch,
secondary_arch = platforms.get_target_secondary_arch(ctx.attr._platform_utils),
)
transitive_apex_deps, transitive_unvalidated_targets_output_file, apex_deps_validation_files = _validate_apex_deps(ctx)
optional_output_groups = {}
if signed_capex:
optional_output_groups["signed_compressed_output"] = [signed_capex]
return [
DefaultInfo(files = depset([signed_apex])),
ApexInfo(
signed_output = signed_apex,
signed_compressed_output = signed_capex,
unsigned_output = unsigned_apex,
requires_native_libs = apexer_outputs.requires_native_libs,
provides_native_libs = apexer_outputs.provides_native_libs,
bundle_key_info = apex_key_info,
container_key_info = apex_cert_info,
package_name = ctx.attr.package_name,
backing_libs = apexer_outputs.backing_libs,
symbols_used_by_apex = apexer_outputs.symbols_used_by_apex,
installed_files = apexer_outputs.installed_files,
java_symbols_used_by_apex = apexer_outputs.java_symbols_used_by_apex,
base_file = zip_files.apex_only,
base_with_config_zip = zip_files.apex_with_config,
),
OutputGroupInfo(
coverage_files = [apexer_outputs.symbols_used_by_apex],
java_coverage_files = [apexer_outputs.java_symbols_used_by_apex],
backing_libs = depset([apexer_outputs.backing_libs]),
installed_files = depset([apexer_outputs.installed_files]),
transitive_unvalidated_targets = depset([transitive_unvalidated_targets_output_file]),
apex_sbom = depset(_generate_sbom(ctx, apexer_outputs.file_mapping, apexer_outputs.metadata_file_mapping, signed_apex)),
capex_sbom = depset(_generate_sbom(ctx, apexer_outputs.file_mapping, apexer_outputs.metadata_file_mapping, signed_capex) if signed_capex else []),
_validation = apex_deps_validation_files,
**optional_output_groups
),
ApexDepsInfo(transitive_deps = transitive_apex_deps),
ApexMkInfo(
make_modules_to_install = apexer_outputs.make_modules_to_install,
files_info = apexer_outputs.make_files_info,
),
collect_deps_clang_tidy_info(ctx),
]
# These are the standard aspects that should be applied on all edges that
# contribute to an APEX's payload.
STANDARD_PAYLOAD_ASPECTS = [
license_aspect,
apex_available_aspect,
apex_deps_validation_aspect,
]
_apex = rule(
implementation = _apex_rule_impl,
attrs = {
# Attributes that configure the APEX container.
"manifest": attr.label(allow_single_file = [".json"]),
"android_manifest": attr.label(allow_single_file = [".xml"]),
"package_name": attr.string(),
"logging_parent": attr.string(),
"file_contexts": attr.label(allow_single_file = True, mandatory = True),
"canned_fs_config": attr.label(
allow_single_file = True,
doc = """Path to the canned fs config file for customizing file's
uid/gid/mod/capabilities. The content of this file is appended to the
default config, so that the custom entries are preferred.
The format is /<path_or_glob> <uid> <gid> <mode> [capabilities=0x<cap>], where
path_or_glob is a path or glob pattern for a file or set of files, uid/gid
are numerial values of user ID and group ID, mode is octal value for the
file mode, and cap is hexadecimal value for the capability.""",
),
"key": attr.label(providers = [ApexKeyInfo], mandatory = True),
"certificate": attr.label(
providers = [AndroidAppCertificateInfo],
mandatory = True,
cfg = apex_transition,
),
"min_sdk_version": attr.string(
default = "current",
doc = """The minimum SDK version that this APEX must support at minimum. This is usually set to
the SDK version that the APEX was first introduced.
When not set, defaults to "10000" (or "current").""",
),
"updatable": attr.bool(default = True, doc = """Whether this APEX is considered updatable or not.
When set to true, this will enforce additional rules for making sure that the
APEX is truly updatable. To be updatable, min_sdk_version should be set as well."""),
"installable": attr.bool(default = True),
"compressible": attr.bool(default = False),
"base_apex_name": attr.string(
default = "",
doc = "The name of the base apex of this apex. For example, the AOSP variant of this apex.",
),
# Attributes that contribute to the payload.
"native_shared_libs_32": attr.label_list(
providers = [ApexCcInfo, ApexCcMkInfo, RuleLicensedDependenciesInfo],
aspects = STANDARD_PAYLOAD_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, ApexCcMkInfo, RuleLicensedDependenciesInfo],
aspects = STANDARD_PAYLOAD_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, RuleLicensedDependenciesInfo], # sh_binary
[StrippedCcBinaryInfo, CcInfo, ApexCcInfo, ApexCcMkInfo, RuleLicensedDependenciesInfo], # cc_binary (stripped)
],
cfg = apex_transition,
aspects = STANDARD_PAYLOAD_ASPECTS + [apex_cc_aspect],
),
"prebuilts": attr.label_list(
providers = [PrebuiltFileInfo, RuleLicensedDependenciesInfo],
cfg = apex_transition,
aspects = STANDARD_PAYLOAD_ASPECTS,
),
# 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"),
# Tools that are not part of the apex_toolchain.
"_staging_dir_builder": attr.label(
cfg = "exec",
doc = "The staging dir builder to avoid the problem where symlinks are created inside apex image.",
executable = True,
default = "//build/bazel/rules:staging_dir_builder",
),
"_signapk": attr.label(
cfg = "exec",
doc = "The signapk tool.",
executable = True,
default = "//build/make/tools/signapk",
),
"_zip2zip": attr.label(
cfg = "exec",
allow_single_file = True,
doc = "The tool zip2zip. Used to convert apex file to the expected directory structure.",
default = "//build/soong/cmd/zip2zip:zip2zip",
executable = True,
),
"_merge_zips": attr.label(
cfg = "exec",
allow_single_file = True,
doc = "The tool merge_zips. Used to combine base zip and config file into a single zip for mixed build aab creation.",
default = "//prebuilts/build-tools:linux-x86/bin/merge_zips",
executable = True,
),
"_platform_utils": attr.label(
default = Label("//build/bazel/platforms:platform_utils"),
),
"_generate_sbom": attr.label(
cfg = "exec",
doc = "SBOM generation tool",
executable = True,
default = "//build/make/tools/sbom:generate-sbom",
),
# allowed deps check
"_unsafe_disable_apex_allowed_deps_check": attr.label(
default = "//build/bazel/rules/apex:unsafe_disable_apex_allowed_deps_check",
),
"allowed_apex_deps_manifest": attr.label(
allow_single_file = True,
default = "//packages/modules/common/build:allowed_deps.txt",
),
# Build settings.
"_apexer_verbose": attr.label(
default = "//build/bazel/rules/apex:apexer_verbose",
doc = "If enabled, make apexer log verbosely.",
),
"_override_apex_manifest_default_version": attr.label(
default = "//build/bazel/rules/apex:override_apex_manifest_default_version",
doc = "If specified, override 'version: 0' in apex_manifest.json with this value instead of the branch default. Non-zero versions will not be changed.",
),
"_apex_global_min_sdk_version_override": attr.label(
default = "//build/bazel/rules/apex:apex_global_min_sdk_version_override",
doc = "If specified, override the min_sdk_version of this apex and in the transition and checks for dependencies.",
),
"_product_variables": attr.label(
default = "//build/bazel/product_config:product_vars",
),
# Api_fingerprint
"_unbundled_build_target_sdk_with_api_fingerprint": attr.label(
default = "//build/bazel/rules/apex:unbundled_build_target_sdk_with_api_fingerprint",
),
"_platform_sdk_codename": attr.label(
default = "//build/bazel/rules/apex:platform_sdk_codename",
),
"_api_fingerprint_txt": attr.label(
default = "//frameworks/base/api:api_fingerprint",
allow_single_file = True,
),
},
# The apex toolchain is not mandatory so that we don't get toolchain resolution errors even
# when the apex is not compatible with the current target (via target_compatible_with).
toolchains = [config_common.toolchain_type("//build/bazel/rules/apex:apex_toolchain_type", mandatory = False)],
fragments = ["platform"],
)
def apex(
name,
manifest = "apex_manifest.json",
android_manifest = None,
file_contexts = None,
key = None,
certificate = None,
certificate_name = None,
min_sdk_version = None,
updatable = True,
installable = True,
compressible = False,
native_shared_libs_32 = [],
native_shared_libs_64 = [],
binaries = [],
prebuilts = [],
package_name = None,
logging_parent = None,
canned_fs_config = None,
testonly = False,
# TODO(b/255400736): tests are not fully supported yet.
tests = [],
target_compatible_with = [],
**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"
if testonly:
compressible = False
elif tests:
fail("Apex with tests attribute needs to be testonly.")
if certificate and certificate_name:
fail("Cannot use both certificate_name and certificate attributes together. Use only one of them.")
app_cert_name = name + "_app_certificate"
if certificate_name:
# use the name key in the default cert dir
android_app_certificate_with_default_cert(
name = app_cert_name,
cert_name = certificate_name,
)
certificate_label = ":" + app_cert_name
elif certificate:
certificate_label = certificate
else:
# use the default testkey
android_app_certificate_with_default_cert(name = app_cert_name)
certificate_label = ":" + app_cert_name
target_compatible_with = select({
"//build/bazel/platforms/os:android": [],
"//conditions:default": ["@platforms//:incompatible"],
}) + target_compatible_with
_apex(
name = name,
manifest = manifest,
android_manifest = android_manifest,
file_contexts = file_contexts,
key = key,
certificate = certificate_label,
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,
package_name = package_name,
logging_parent = logging_parent,
canned_fs_config = canned_fs_config,
testonly = testonly,
target_compatible_with = target_compatible_with,
**kwargs
)