aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rules/apex/BUILD2
-rw-r--r--rules/apex/apex.bzl62
-rw-r--r--rules/apex/apex_test.bzl33
-rw-r--r--rules/apex/toolchain.bzl6
-rw-r--r--rules/cc/cc_stub_library.bzl14
-rw-r--r--rules/partitions/partition.bzl7
-rw-r--r--rules/staging_dir_builder.py137
-rwxr-xr-xrules/staging_dir_builder_test.sh6
-rw-r--r--tests/apex/BUILD23
-rw-r--r--tests/apex/minimal_apex_using.txt.golden151
10 files changed, 362 insertions, 79 deletions
diff --git a/rules/apex/BUILD b/rules/apex/BUILD
index cf64c03e..2f1b4834 100644
--- a/rules/apex/BUILD
+++ b/rules/apex/BUILD
@@ -104,9 +104,11 @@ apex_toolchain(
avbtool = "//external/avb:avbtool",
conv_apex_manifest = "//system/apex/apexer:conv_apex_manifest",
e2fsdroid = "//external/e2fsprogs/contrib/android:e2fsdroid",
+ gen_ndk_usedby_apex = "//build/soong/scripts:gen_ndk_usedby_apex.sh",
jsonmodify = "//build/soong/scripts:jsonmodify",
manifest_fixer = "//build/soong/scripts:manifest_fixer",
mke2fs = "//external/e2fsprogs/misc:mke2fs",
+ readelf = "//prebuilts/clang/host/linux-x86:llvm-readelf",
resize2fs = "//external/e2fsprogs/resize:resize2fs",
sefcontext_compile = "//external/selinux/libselinux:sefcontext_compile",
soong_zip = "//build/soong/zip/cmd:soong_zip",
diff --git a/rules/apex/apex.bzl b/rules/apex/apex.bzl
index be67933e..40733adc 100644
--- a/rules/apex/apex.bzl
+++ b/rules/apex/apex.bzl
@@ -39,6 +39,7 @@ ApexInfo = provider(
"container_key_info": "Info of the container key provided as AndroidAppCertificateInfo.",
"package_name": "APEX package name.",
"backing_libs": "File containing libraries used by the APEX.",
+ "symbols_used_by_apex": "Symbol list used by this APEX.",
},
)
@@ -302,6 +303,15 @@ def _run_apexer(ctx, apex_toolchain):
# Arguments
args = ctx.actions.args()
args.add(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")
+ args.add(staging_dir.path)
+
+ # start of apexer cmd
args.add(apexer_files.executable.path)
if ctx.attr._apexer_verbose[BuildSettingInfo].value:
args.add("--verbose")
@@ -358,7 +368,7 @@ def _run_apexer(ctx, apex_toolchain):
elif ctx.attr.testonly:
args.add("--test_only")
- args.add("STAGING_DIR_PLACEHOLDER")
+ args.add(staging_dir.path)
args.add(apex_output_file)
inputs = [
@@ -387,17 +397,18 @@ def _run_apexer(ctx, apex_toolchain):
ctx.actions.run(
inputs = inputs,
tools = tools,
- outputs = [apex_output_file],
+ outputs = [apex_output_file, staging_dir],
executable = ctx.executable._staging_dir_builder,
arguments = [args],
mnemonic = "Apexer",
)
- return (
- apex_output_file,
- requires_native_libs,
- provides_native_libs,
- _generate_apex_backing_file(ctx, backing_libs),
+ 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),
)
# Sign a file with signapk.
@@ -454,11 +465,35 @@ def _run_apex_compression_tool(ctx, apex_toolchain, input_file, output_file_name
)
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
+
# 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
- unsigned_apex, requires_native_libs, provides_native_libs, backing_file = _run_apexer(ctx, apex_toolchain)
+ apexer_outputs = _run_apexer(ctx, apex_toolchain)
+ unsigned_apex = apexer_outputs.unsigned_apex
apex_cert_info = ctx.attr.certificate[AndroidAppCertificateInfo]
private_key = apex_cert_info.pk8
@@ -473,17 +508,22 @@ def _apex_rule_impl(ctx):
_run_signapk(ctx, compressed_apex_output_file, signed_capex, private_key, public_key, "BazelCompressedApexSigning")
apex_key_info = ctx.attr.key[ApexKeyInfo]
+
return [
DefaultInfo(files = depset([signed_apex])),
ApexInfo(
signed_output = signed_apex,
unsigned_output = unsigned_apex,
- requires_native_libs = requires_native_libs,
- provides_native_libs = provides_native_libs,
+ 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 = backing_file,
+ backing_libs = apexer_outputs.backing_libs,
+ symbols_used_by_apex = apexer_outputs.symbols_used_by_apex,
+ ),
+ OutputGroupInfo(
+ coverage_files = [apexer_outputs.symbols_used_by_apex],
),
]
diff --git a/rules/apex/apex_test.bzl b/rules/apex/apex_test.bzl
index 755c741f..33e25a59 100644
--- a/rules/apex/apex_test.bzl
+++ b/rules/apex/apex_test.bzl
@@ -1412,6 +1412,38 @@ def _test_apex_backing_file():
return test_name
+def _apex_symbols_used_by_apex_test(ctx):
+ env = analysistest.begin(ctx)
+ target_under_test = analysistest.target_under_test(env)
+ actual = target_under_test[ApexInfo].symbols_used_by_apex
+
+ asserts.equals(env, ctx.attr.expected_path, actual.short_path)
+
+ return analysistest.end(env)
+
+apex_symbols_used_by_apex_test = analysistest.make(
+ _apex_symbols_used_by_apex_test,
+ attrs = {
+ "expected_path": attr.string(),
+ },
+)
+
+def _test_apex_symbols_used_by_apex():
+ name = "apex_with_symbols_used_by_apex"
+ test_name = name + "_test"
+
+ test_apex(
+ name = name,
+ )
+
+ apex_symbols_used_by_apex_test(
+ name = test_name,
+ target_under_test = name,
+ expected_path = "build/bazel/rules/apex/apex_with_symbols_used_by_apex_using.txt",
+ )
+
+ return test_name
+
def apex_test_suite(name):
native.test_suite(
name = name,
@@ -1445,5 +1477,6 @@ def apex_test_suite(name):
_test_apex_testonly_with_manifest(),
_test_apex_testonly_without_manifest(),
_test_apex_backing_file(),
+ _test_apex_symbols_used_by_apex(),
],
)
diff --git a/rules/apex/toolchain.bzl b/rules/apex/toolchain.bzl
index 747089f4..756357d2 100644
--- a/rules/apex/toolchain.bzl
+++ b/rules/apex/toolchain.bzl
@@ -30,6 +30,8 @@ ApexToolchainInfo = provider(
"soong_zip",
"jsonmodify",
"manifest_fixer",
+ "gen_ndk_usedby_apex",
+ "readelf",
],
)
@@ -49,6 +51,8 @@ def _apex_toolchain_impl(ctx):
soong_zip = ctx.file.soong_zip,
jsonmodify = ctx.attr.jsonmodify,
manifest_fixer = ctx.attr.manifest_fixer,
+ gen_ndk_usedby_apex = ctx.attr.gen_ndk_usedby_apex,
+ readelf = ctx.attr.readelf,
),
)
return [toolchain_info]
@@ -72,5 +76,7 @@ apex_toolchain = rule(
# and has been added to apex toolchain previously.
"soong_zip": attr.label(allow_single_file = True, cfg = "exec", executable = True, mandatory = True),
"manifest_fixer": attr.label(cfg = "exec", executable = True, mandatory = True),
+ "gen_ndk_usedby_apex": attr.label(cfg = "exec", executable = True, mandatory = True, allow_single_file = [".sh"]),
+ "readelf": attr.label(cfg = "exec", executable = True, mandatory = True, allow_single_file = True),
},
)
diff --git a/rules/cc/cc_stub_library.bzl b/rules/cc/cc_stub_library.bzl
index ecb6dbad..2f579cbb 100644
--- a/rules/cc/cc_stub_library.bzl
+++ b/rules/cc/cc_stub_library.bzl
@@ -69,6 +69,9 @@ def _cc_stub_gen_impl(ctx):
abi_symbol_list = out_abi_symbol_list,
version = ctx.attr.version,
),
+ OutputGroupInfo(
+ stub_map = [out_stub_map],
+ ),
]
cc_stub_gen = rule(
@@ -142,9 +145,18 @@ def cc_stub_library_shared(name, stubs_symbol_file, version, export_includes, so
if len(soname) == 0:
fail("For stub libraries 'soname' is mandatory and must be same as the soname of its source library.")
soname_flag = "-Wl,-soname," + soname
+ stub_map = name + "_stub_map"
+ native.filegroup(
+ name = stub_map,
+ srcs = [name + "_files"],
+ output_group = "stub_map",
+ tags = ["manual"],
+ )
+ version_script_flag = "-Wl,--version-script,$(location %s)" % stub_map
native.cc_shared_library(
name = name + "_so",
- user_link_flags = [soname_flag],
+ additional_linker_inputs = [stub_map],
+ user_link_flags = [soname_flag, version_script_flag],
roots = [name + "_root"],
features = disable_crt_link(features),
target_compatible_with = target_compatible_with,
diff --git a/rules/partitions/partition.bzl b/rules/partitions/partition.bzl
index 6bf0acaf..0e455aa8 100644
--- a/rules/partitions/partition.bzl
+++ b/rules/partitions/partition.bzl
@@ -312,6 +312,8 @@ def _partition_impl(ctx):
# It seems build_image will prepend /system to the paths when building_system_image=true
ctx.actions.write(file_mapping_file, json.encode({k.removeprefix("/system"): v.path for k, v in files.items()}))
+ staging_dir = ctx.actions.declare_directory(ctx.attr.name + "_staging_dir")
+
ctx.actions.run(
inputs = [
image_info,
@@ -326,11 +328,12 @@ def _partition_impl(ctx):
executable = ctx.executable._staging_dir_builder,
arguments = [
file_mapping_file.path,
+ staging_dir.path,
toolchain.build_image.path,
- "STAGING_DIR_PLACEHOLDER",
+ staging_dir.path,
image_info.path,
output_image.path,
- "STAGING_DIR_PLACEHOLDER",
+ staging_dir.path,
],
mnemonic = "BuildPartition",
# TODO: the /usr/bin addition is because build_image uses the du command
diff --git a/rules/staging_dir_builder.py b/rules/staging_dir_builder.py
index 6961c760..a5bcee9e 100644
--- a/rules/staging_dir_builder.py
+++ b/rules/staging_dir_builder.py
@@ -19,32 +19,26 @@ import os
import shutil
import subprocess
import sys
-import tempfile
+import argparse
-def main(argv):
- '''Build a staging directory, and then call a custom command.
+def build_staging_dir(file_mapping_path, staging_dir_path, command_argv):
+ '''Create a staging dir with provided file mapping and apply the command in the dir.
- The first argument to this script must be the path to a file containing a json
- dictionary mapping paths in the staging directory to paths to files that should
- be copied there. The rest of the arguments will be run as a separate command.
- At least one other argument must be "STAGING_DIR_PLACEHOLDER", which will be
- replaced with the path to the staging directory.
+ At least
- Example:
- staging_dir_builder file_mapping.json path/to/apexer --various-apexer-flags STAGING_DIR_PLACEHOLDER path/to/out.apex.unsigned
+ Args:
+ file_mapping_path (str): path to the file mapping json
+ staging_dir_path (str): path to the staging directory
+ command_argv (str list): the command to be executed, with the first arg as the executable
'''
- if len(argv) < 2:
- sys.exit('usage: staging_dir_builder MAPPING_FILE COMMAND...')
- if "STAGING_DIR_PLACEHOLDER" not in argv[1:]:
- sys.exit('At least one argument must be "STAGING_DIR_PLACEHOLDER"')
try:
- with open(argv[0], 'r') as f:
+ with open(file_mapping_path, 'r') as f:
file_mapping = json.load(f)
except OSError as e:
sys.exit(str(e))
except json.JSONDecodeError as e:
- sys.exit(argv[0] + ": JSON decode error: " + str(e))
+ sys.exit(file_mapping_path + ": JSON decode error: " + str(e))
# Validate and clean the file_mapping. This consists of:
# - Making sure it's a dict[str, str]
@@ -53,10 +47,10 @@ def main(argv):
# - Making sure no paths use .. to break out of the staging dir
cleaned_file_mapping = {}
if not isinstance(file_mapping, dict):
- sys.exit(argv[0] + ": expected a JSON dict[str, str]")
+ sys.exit(file_mapping_path + ": expected a JSON dict[str, str]")
for path_in_staging_dir, path_in_bazel in file_mapping.items():
if not isinstance(path_in_staging_dir, str) or not isinstance(path_in_bazel, str):
- sys.exit(argv[0] + ": expected a JSON dict[str, str]")
+ sys.exit(file_mapping_path + ": expected a JSON dict[str, str]")
path_in_staging_dir = os.path.normpath(path_in_staging_dir).lstrip('/')
if path_in_staging_dir in cleaned_file_mapping:
sys.exit("Staging dir path repeated twice: " + path_in_staging_dir)
@@ -65,57 +59,72 @@ def main(argv):
cleaned_file_mapping[path_in_staging_dir] = path_in_bazel
file_mapping = cleaned_file_mapping
- argv = argv[1:]
-
- with tempfile.TemporaryDirectory() as staging_dir:
- for path_in_staging_dir, path_in_bazel in file_mapping.items():
- path_in_staging_dir = os.path.join(staging_dir, path_in_staging_dir)
-
- # Because Bazel execution root is a symlink forest, all the input files are symlinks, these
- # include the dependency files declared in the BUILD files as well as the files declared
- # and created in the bzl files. For sandbox runs the former are two or more level symlinks and
- # latter are one level symlinks. For non-sandbox runs, the former are one level symlinks
- # and the latter are actual files. Here are some examples:
- #
- # Two level symlinks:
- # system/timezone/output_data/version/tz_version ->
- # /usr/local/google/home/...out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
- # execroot/__main__/system/timezone/output_data/version/tz_version ->
- # /usr/local/google/home/.../system/timezone/output_data/version/tz_version
- #
- # Three level symlinks:
- # bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/libcrypto.so ->
- # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
- # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/libcrypto.so ->
- # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
- # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/
- # liblibcrypto_stripped.so ->
- # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
- # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/
- # liblibcrypto_unstripped.so
- #
- # One level symlinks:
- # bazel-out/android_target-fastbuild/bin/system/timezone/apex/apex_manifest.pb ->
- # /usr/local/google/home/.../out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
- # execroot/__main__/bazel-out/android_target-fastbuild/bin/system/timezone/apex/
- # apex_manifest.pb
- if os.path.islink(path_in_bazel):
- path_in_bazel = os.readlink(path_in_bazel)
+ for path_in_staging_dir, path_in_bazel in file_mapping.items():
+ path_in_staging_dir = os.path.join(staging_dir_path, path_in_staging_dir)
- # For sandbox run these are the 2nd level symlinks and we need to resolve
- while os.path.islink(path_in_bazel) and 'execroot/__main__' in path_in_bazel:
- path_in_bazel = os.readlink(path_in_bazel)
+ # Because Bazel execution root is a symlink forest, all the input files are symlinks, these
+ # include the dependency files declared in the BUILD files as well as the files declared
+ # and created in the bzl files. For sandbox runs the former are two or more level symlinks and
+ # latter are one level symlinks. For non-sandbox runs, the former are one level symlinks
+ # and the latter are actual files. Here are some examples:
+ #
+ # Two level symlinks:
+ # system/timezone/output_data/version/tz_version ->
+ # /usr/local/google/home/...out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
+ # execroot/__main__/system/timezone/output_data/version/tz_version ->
+ # /usr/local/google/home/.../system/timezone/output_data/version/tz_version
+ #
+ # Three level symlinks:
+ # bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/libcrypto.so ->
+ # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
+ # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/libcrypto.so ->
+ # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
+ # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/
+ # liblibcrypto_stripped.so ->
+ # /usr/local/google/home/yudiliu/android/aosp/master/out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
+ # execroot/__main__/bazel-out/android_x86_64-fastbuild-ST-4ecd5e98bfdd/bin/external/boringssl/
+ # liblibcrypto_unstripped.so
+ #
+ # One level symlinks:
+ # bazel-out/android_target-fastbuild/bin/system/timezone/apex/apex_manifest.pb ->
+ # /usr/local/google/home/.../out/bazel/output_user_root/b1ed7e1e9af3ebbd1403e9cf794e4884/
+ # execroot/__main__/bazel-out/android_target-fastbuild/bin/system/timezone/apex/
+ # apex_manifest.pb
+ if os.path.islink(path_in_bazel):
+ path_in_bazel = os.readlink(path_in_bazel)
- os.makedirs(os.path.dirname(path_in_staging_dir), exist_ok=True)
- shutil.copyfile(path_in_bazel, path_in_staging_dir, follow_symlinks=False)
+ # For sandbox run these are the 2nd level symlinks and we need to resolve
+ while os.path.islink(path_in_bazel) and 'execroot/__main__' in path_in_bazel:
+ path_in_bazel = os.readlink(path_in_bazel)
- for i in range(len(argv)):
- if argv[i] == 'STAGING_DIR_PLACEHOLDER':
- argv[i] = staging_dir
+ os.makedirs(os.path.dirname(path_in_staging_dir), exist_ok=True)
+ shutil.copyfile(path_in_bazel, path_in_staging_dir, follow_symlinks=False)
- result = subprocess.run(argv)
+ result = subprocess.run(command_argv)
sys.exit(result.returncode)
+def main():
+ '''Build a staging directory, and then call a custom command.
+
+ The first argument to this script must be the path to a file containing a json
+ dictionary mapping paths in the staging directory to paths to files that should
+ be copied there. The rest of the arguments will be run as a separate command.
+
+ Example:
+ staging_dir_builder file_mapping.json path/to/staging_dir path/to/apexer --various-apexer-flags path/to/out.apex.unsigned
+ '''
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "file_mapping_path",
+ help="Path to the <staging dir path>:<bazel input path> file mapping JSON.",
+ )
+ parser.add_argument(
+ "staging_dir_path",
+ help="Path to a directory to store the staging directory content.",
+ )
+ args, command_argv = parser.parse_known_args()
+ build_staging_dir(args.file_mapping_path, args.staging_dir_path, command_argv)
+
if __name__ == '__main__':
- main(sys.argv[1:])
+ main()
diff --git a/rules/staging_dir_builder_test.sh b/rules/staging_dir_builder_test.sh
index 148e79d3..27d3505d 100755
--- a/rules/staging_dir_builder_test.sh
+++ b/rules/staging_dir_builder_test.sh
@@ -104,11 +104,15 @@ echo "/ 0 2000 0755
apexer_tool_paths=${avb_tool_path}:${avb_tool_path}:${e2fsdroid_path}:${mke2fs_path}:${resize2fs_path}:${debugfs_path}:${soong_zip_path}:${aapt2_path}:${sefcontext_compile_path}
+staging_dir=$(mktemp -d /tmp/temporary-dir.XXXXXXXX)
+trap 'rm -rf -- "${staging_dir}"' EXIT
+
#############################################
# run staging_dir_builder
#############################################
"${RUNFILES_DIR}/__main__/build/bazel/rules/staging_dir_builder" \
${staging_dir_builder_manifest_file} \
+ ${staging_dir} \
${apexer_tool_path} \
--manifest ${manifest_file} \
--file_contexts ${file_contexts_file} \
@@ -116,7 +120,7 @@ apexer_tool_paths=${avb_tool_path}:${avb_tool_path}:${e2fsdroid_path}:${mke2fs_p
--apexer_tool_path "${apexer_tool_paths}" \
--android_jar_path ${android_jar} \
--canned_fs_config ${canned_fs_config} \
- STAGING_DIR_PLACEHOLDER \
+ ${staging_dir} \
${output_file}
#############################################
diff --git a/tests/apex/BUILD b/tests/apex/BUILD
index 3f295f34..c31445d7 100644
--- a/tests/apex/BUILD
+++ b/tests/apex/BUILD
@@ -62,3 +62,26 @@ diff_test(
file2 = "com.android.adbd.apex_manifest.json.golden",
target_compatible_with = ["//build/bazel/platforms/os:linux"],
)
+
+filegroup(
+ name = "minimal_apex_coverage_files",
+ srcs = ["//build/bazel/examples/apex/minimal:build.bazel.examples.apex.minimal"],
+ output_group = "coverage_files",
+)
+
+genrule(
+ name = "minimal_apex_using_txt",
+ srcs = [":minimal_apex_coverage_files"],
+ outs = ["minimal_apex_using.txt"],
+ cmd = "cat $(location :minimal_apex_coverage_files) | sort > $@",
+)
+
+diff_test(
+ name = "minimal_apex_symbols_used_by_apex_diff_test",
+ file1 = ":minimal_apex_using.txt",
+ file2 = ":minimal_apex_using.txt.golden",
+ target_compatible_with = [
+ "//build/bazel/platforms/os:android",
+ "//build/bazel/platforms/arch:arm64",
+ ],
+)
diff --git a/tests/apex/minimal_apex_using.txt.golden b/tests/apex/minimal_apex_using.txt.golden
new file mode 100644
index 00000000..c3a1d94d
--- /dev/null
+++ b/tests/apex/minimal_apex_using.txt.golden
@@ -0,0 +1,151 @@
+
+_Unwind_DeleteException@LIBC_R
+_Unwind_GetIP@LIBC_R
+_Unwind_GetLanguageSpecificData@LIBC_R
+_Unwind_GetRegionStart@LIBC_R
+_Unwind_RaiseException@LIBC_R
+_Unwind_Resume@LIBC_R
+_Unwind_SetGR@LIBC_R
+_Unwind_SetIP@LIBC_R
+__ctype_get_mb_cur_max@LIBC
+__cxa_atexit@LIBC
+__cxa_finalize@LIBC
+__cxa_finalize@LIBC
+__cxa_finalize@LIBC
+__cxa_finalize@LIBC
+__cxa_thread_atexit_impl@LIBC
+__errno@LIBC
+__fwrite_chk@LIBC_N
+__gnu_strerror_r@LIBC
+__libc_init@LIBC
+__open_2@LIBC
+__read_chk@LIBC
+__stack_chk_fail@LIBC
+__stack_chk_fail@LIBC
+__stack_chk_fail@LIBC
+__stack_chk_fail@LIBC
+__stack_chk_fail@LIBC
+__strlen_chk@LIBC
+__system_property_get@LIBC
+__vsnprintf_chk@LIBC
+abort@LIBC
+android_set_abort_message@LIBC
+btowc@LIBC
+calloc@LIBC
+clock_gettime@LIBC
+close@LIBC
+closelog@LIBC
+fflush@LIBC
+fprintf@LIBC
+fputc@LIBC
+free@LIBC
+freelocale@LIBC
+fwrite@LIBC
+getauxval@LIBC
+getc@LIBC
+iswalpha@LIBC
+iswblank@LIBC
+iswcntrl@LIBC
+iswdigit@LIBC
+iswlower@LIBC
+iswprint@LIBC
+iswpunct@LIBC
+iswspace@LIBC
+iswupper@LIBC
+iswxdigit@LIBC
+localeconv@LIBC
+malloc@LIBC
+mbrlen@LIBC
+mbrtowc@LIBC
+mbsnrtowcs@LIBC
+mbsrtowcs@LIBC
+mbtowc@LIBC
+memchr@LIBC
+memcmp@LIBC
+memcpy@LIBC
+memmove@LIBC
+memset@LIBC
+memset@LIBC
+memset@LIBC
+memset@LIBC
+memset@LIBC
+nanosleep@LIBC
+newlocale@LIBC
+openlog@LIBC
+posix_memalign@LIBC
+pthread_cond_broadcast@LIBC
+pthread_cond_destroy@LIBC
+pthread_cond_signal@LIBC
+pthread_cond_timedwait@LIBC
+pthread_cond_wait@LIBC
+pthread_detach@LIBC
+pthread_equal@LIBC
+pthread_getspecific@LIBC
+pthread_join@LIBC
+pthread_key_create@LIBC
+pthread_mutex_destroy@LIBC
+pthread_mutex_init@LIBC
+pthread_mutex_lock@LIBC
+pthread_mutex_trylock@LIBC
+pthread_mutex_unlock@LIBC
+pthread_mutexattr_destroy@LIBC
+pthread_mutexattr_init@LIBC
+pthread_mutexattr_settype@LIBC
+pthread_once@LIBC
+pthread_self@LIBC
+pthread_setspecific@LIBC
+realloc@LIBC
+sched_yield@LIBC
+setlocale@LIBC
+snprintf@LIBC
+sscanf@LIBC
+strcmp@LIBC
+strcoll@LIBC
+strftime@LIBC
+strlen@LIBC
+strlen@LIBC
+strlen@LIBC
+strlen@LIBC
+strncmp@LIBC
+strtod@LIBC
+strtod_l@LIBC_O
+strtof@LIBC
+strtof_l@LIBC_O
+strtol@LIBC
+strtold@LIBC
+strtold_l@LIBC
+strtoll@LIBC
+strtoll_l@LIBC
+strtoul@LIBC
+strtoull@LIBC
+strtoull_l@LIBC
+strxfrm@LIBC
+swprintf@LIBC
+sysconf@LIBC
+syslog@LIBC
+towlower@LIBC
+towupper@LIBC
+ungetc@LIBC
+uselocale@LIBC
+vasprintf@LIBC
+vfprintf@LIBC
+vsnprintf@LIBC
+vsscanf@LIBC
+wcrtomb@LIBC
+wcscoll@LIBC
+wcslen@LIBC
+wcsnrtombs@LIBC
+wcstod@LIBC
+wcstof@LIBC
+wcstol@LIBC
+wcstold@LIBC
+wcstoll@LIBC
+wcstoul@LIBC
+wcstoull@LIBC
+wcsxfrm@LIBC
+wctob@LIBC
+wmemchr@LIBC
+wmemcmp@LIBC
+wmemcpy@LIBC
+wmemmove@LIBC
+wmemset@LIBC