# Copyright (C) 2022 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( "@bazel_tools//tools/build_defs/cc:action_names.bzl", "CPP_COMPILE_ACTION_NAME", "C_COMPILE_ACTION_NAME", ) load("@soong_injection//api_levels:platform_versions.bzl", "platform_versions") load("//build/bazel/platforms:platform_utils.bzl", "platforms") load( "//build/bazel/rules/cc:cc_library_common.bzl", "build_compilation_flags", "get_non_header_srcs", "is_bionic_lib", "is_bootstrap_lib", "parse_apex_sdk_version", ) load("//build/bazel/rules/cc:cc_library_static.bzl", "CcStaticLibraryInfo") AbiDumpInfo = provider(fields = ["dump_files"]) AbiDiffInfo = provider(fields = ["diff_files"]) _ABI_CLASS_PLATFORM = "platform" def _abi_dump_aspect_impl(target, ctx): if not _abi_diff_enabled(ctx, ctx.label.name, True): return [ AbiDumpInfo( dump_files = depset(), ), ] transitive_dumps = [] direct_dumps = [] if CcStaticLibraryInfo in target: direct_dumps.extend(_create_abi_dumps( ctx, target, ctx.rule.files.srcs_cpp, ctx.rule.attr.copts_cpp, CPP_COMPILE_ACTION_NAME, )) direct_dumps.extend(_create_abi_dumps( ctx, target, ctx.rule.files.srcs_c, ctx.rule.attr.copts_c, C_COMPILE_ACTION_NAME, )) for dep in ctx.rule.attr.static_deps: if AbiDumpInfo in dep: transitive_dumps.append(dep[AbiDumpInfo].dump_files) return [ AbiDumpInfo( dump_files = depset( direct_dumps, transitive = transitive_dumps, ), ), ] abi_dump_aspect = aspect( implementation = _abi_dump_aspect_impl, attr_aspects = ["static_deps", "whole_archive_deps"], attrs = { "_skip_abi_checks": attr.label( default = "//build/bazel/flags/cc/abi:skip_abi_checks", ), # Need this in order to call _abi_diff_enabled in the aspects code. "_within_apex": attr.label( default = "//build/bazel/rules/apex:within_apex", ), "_abi_dumper": attr.label( allow_files = True, executable = True, cfg = "exec", default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-dumper"), ), "_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")), }, toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], fragments = ["cpp"], provides = [AbiDumpInfo], ) def _create_abi_dumps(ctx, target, srcs, user_flags, action_name): dumps = [] if len(srcs) == 0: return dumps compilation_context, compilation_flags = build_compilation_flags( ctx, ctx.rule.attr.roots + ctx.rule.attr.deps + ctx.rule.attr.includes, user_flags, action_name, ) sources, headers = get_non_header_srcs(srcs) header_inputs = ( headers + compilation_context.headers.to_list() + compilation_context.direct_headers + compilation_context.direct_private_headers + compilation_context.direct_public_headers + compilation_context.direct_textual_headers ) objects = [] linker_inputs = target[CcInfo].linking_context.linker_inputs.to_list() # These are created in cc_library_static and there should be only one # linker_inputs and one libraries if CcInfo in target and len(linker_inputs) == 1 and len(linker_inputs[0].libraries) == 1: objects = linker_inputs[0].libraries[0].objects for file in sources: output = _create_abi_dump(ctx, target, file, objects, header_inputs, compilation_flags) dumps.append(output) return dumps def _include_flag(flag): return ["-I", flag] def _create_abi_dump(ctx, target, src, objects, header_inputs, compilation_flags): """ Utility function to generate abi dump file.""" file = paths.join(src.dirname, target.label.name + "." + src.basename + ".sdump") output = ctx.actions.declare_file(file) args = ctx.actions.args() args.add("--root-dir", ".") args.add("-o", output) args.add(src) args.add_all(ctx.rule.attr.exports[0][CcInfo].compilation_context.includes.to_list(), map_each = _include_flag) args.add("--") args.add_all(compilation_flags) # The following two args come from here: # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/builder.go;l=247;drc=ba17c7243d0e297efbc6fb5385d6d5aa81db9152 args.add("-w") # TODO(b/254625084): support darwin as well. args.add("-isystem", "prebuilts/clang-tools/linux-x86/clang-headers") ctx.actions.run( inputs = [src] + header_inputs + objects, executable = ctx.executable._abi_dumper, outputs = [output], arguments = [args], # TODO(b/186116353): enable sandbox once the bug is fixed. execution_requirements = { "no-sandbox": "1", }, mnemonic = "AbiDump", ) return output def create_linked_abi_dump(ctx, dump_files): """ Utility function to generate abi dump files.""" shared_files = ctx.attr.shared[DefaultInfo].files.to_list() if len(shared_files) != 1: fail("Expected only one shared library file") file = ctx.attr.soname + ".lsdump" output = ctx.actions.declare_file(file) args = ctx.actions.args() args.add("--root-dir", ".") args.add("-o", output) args.add("-so", shared_files[0]) inputs = dump_files + [shared_files[0]] if ctx.file.symbol_file: args.add("-v", ctx.file.symbol_file.path) inputs.append(ctx.file.symbol_file) for v in ctx.attr.exclude_symbol_versions: args.add("--exclude-symbol-version", v) for t in ctx.attr.exclude_symbol_tags: args.add("--exclude-symbol-tag", t) args.add("-arch", platforms.get_target_arch(ctx.attr._platform_utils)) args.add_all(ctx.attr.root[CcInfo].compilation_context.includes.to_list(), map_each = _include_flag) args.add_all([d.path for d in dump_files]) ctx.actions.run( inputs = inputs, executable = ctx.executable._abi_linker, outputs = [output], arguments = [args], # TODO(b/186116353): enable sandbox once the bug is fixed. execution_requirements = { "no-sandbox": "1", }, mnemonic = "AbiLink", ) return output def find_abi_config(_ctx): sdk_version = str(platform_versions.platform_sdk_version) prev_version = int(parse_apex_sdk_version(sdk_version)) version = "current" if platform_versions.platform_sdk_final: prev_version -= 1 version = sdk_version return prev_version, version def create_abi_diff(ctx, dump_file): prev_version, version = find_abi_config(ctx) arch = platforms.get_target_arch(ctx.attr._platform_utils) bitness = platforms.get_target_bitness(ctx.attr._platform_utils) abi_class = _ABI_CLASS_PLATFORM # The logic below comes from: # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/library.go;l=1891;drc=c645853ab73ac8c5889b42f4ce7dc9353ee8fd35 abi_reference_file = None if not platform_versions.platform_sdk_final: abi_reference_file = _find_abi_ref_file(ctx, prev_version, arch, bitness, abi_class, dump_file.basename) if not abi_reference_file: prev_version -= 1 diff_files = [] # We need to do the abi check for the previous version and current version if the reference # abi dump files are available. If the current previous version doesn't have the reference # abi dump file we will check against one version earlier. if not abi_reference_file: abi_reference_file = _find_abi_ref_file(ctx, prev_version, arch, bitness, abi_class, dump_file.basename) if abi_reference_file: diff_files.append(_run_abi_diff(ctx, arch, prev_version, dump_file, abi_reference_file, True)) abi_reference_file = _find_abi_ref_file(ctx, version, arch, bitness, abi_class, dump_file.basename) if abi_reference_file: diff_files.append(_run_abi_diff(ctx, arch, version, dump_file, abi_reference_file, False)) return diff_files def _run_abi_diff(ctx, arch, version, dump_file, abi_reference_file, prev_version_diff): lib_name = ctx.attr.soname.removesuffix(".so") args = ctx.actions.args() if ctx.attr.check_all_apis: args.add("-check-all-apis") else: args.add_all(["-allow-unreferenced-changes", "-allow-unreferenced-elf-symbol-changes"]) if prev_version_diff: args.add("-target-version", version + 1) diff_file_name = ctx.attr.soname + "." + str(version) + ".abidiff" else: args.add("-target-version", "current") diff_file_name = ctx.attr.soname + ".abidiff" args.add("-allow-extensions") if len(ctx.attr.diff_flags) > 0: args.add_all(ctx.attr.diff_flags) args.add("-lib", lib_name) args.add("-arch", arch) diff_file = ctx.actions.declare_file(diff_file_name) args.add("-o", diff_file) args.add("-new", dump_file) args.add("-old", abi_reference_file) ctx.actions.run( inputs = [dump_file, abi_reference_file], executable = ctx.executable._abi_diff, outputs = [diff_file], arguments = [args], execution_requirements = { "no-sandbox": "1", }, mnemonic = "AbiDiff", ) return diff_file def _find_abi_ref_file(ctx, version, arch, bitness, abi_class, lsdump_name): # Currently we only support platform. if abi_class == _ABI_CLASS_PLATFORM: abi_ref_dumps = ctx.attr.abi_ref_dumps_platform else: fail("Unsupported ABI class: %s" % abi_class) # The expected reference abi dump file ref_dump_file = paths.join( ctx.attr.ref_dumps_home, abi_class, str(version), str(bitness), arch, "source-based", lsdump_name, ) ref_file = None for file in abi_ref_dumps.files.to_list(): if ref_dump_file == file.path: ref_file = file break return ref_file def _abi_diff_enabled(ctx, lib_name, is_aspect): # The logic here is based on: # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/sabi.go;l=103;drc=cb0ac95bde896fa2aa59193a37ceb580758c322c if ctx.attr._skip_abi_checks[BuildSettingInfo].value: return False if not platforms.is_target_android(ctx.attr._platform_utils): return False if ctx.coverage_instrumented(): return False if ctx.attr._within_apex[BuildSettingInfo].value: if not is_aspect and not ctx.attr.has_stubs: return False # Logic comes from here: # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/sabi.go;l=158;drc=cb0ac95bde896fa2aa59193a37ceb580758c322c elif is_bionic_lib(lib_name) or is_bootstrap_lib(lib_name): return False # TODO(b/260611960): handle all the other checks in sabi.go return True def _abi_dump_impl(ctx): diff_files = depset() if _abi_diff_enabled(ctx, ctx.attr.soname.removesuffix(".so"), False) and ctx.attr.root != None: dump_files = ctx.attr.root[AbiDumpInfo].dump_files.to_list() linked_dump_file = create_linked_abi_dump(ctx, dump_files) diff_files = depset(create_abi_diff(ctx, linked_dump_file)) return ([ DefaultInfo(files = diff_files), AbiDiffInfo(diff_files = diff_files), ]) abi_dump = rule( implementation = _abi_dump_impl, attrs = { "shared": attr.label(mandatory = True, providers = [CcSharedLibraryInfo]), "root": attr.label(providers = [CcInfo], aspects = [abi_dump_aspect]), "soname": attr.string(mandatory = True), "has_stubs": attr.bool(default = False), "enabled": attr.bool(default = False), "explicitly_disabled": attr.bool(default = False), "symbol_file": attr.label(allow_single_file = True), "exclude_symbol_versions": attr.string_list(default = []), "exclude_symbol_tags": attr.string_list(default = []), "check_all_apis": attr.bool(default = False), "diff_flags": attr.string_list(default = []), "abi_ref_dumps_platform": attr.label(default = "//prebuilts/abi-dumps/platform:bp2build_all_srcs"), "ref_dumps_home": attr.string(default = "prebuilts/abi-dumps"), "_skip_abi_checks": attr.label( default = "//build/bazel/flags/cc/abi:skip_abi_checks", ), "_within_apex": attr.label( default = "//build/bazel/rules/apex:within_apex", ), # TODO(b/254625084): For the following tools we need to support darwin as well. "_abi_dumper": attr.label( allow_files = True, executable = True, cfg = "exec", default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-dumper"), ), "_abi_linker": attr.label( allow_files = True, executable = True, cfg = "exec", default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-linker"), ), "_abi_diff": attr.label( allow_files = True, executable = True, cfg = "exec", default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-diff"), ), "_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")), }, fragments = ["cpp"], toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], )