1# Copyright (C) 2022 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15load("@bazel_skylib//lib:paths.bzl", "paths") 16load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") 17load( 18 "@bazel_tools//tools/build_defs/cc:action_names.bzl", 19 "CPP_COMPILE_ACTION_NAME", 20 "C_COMPILE_ACTION_NAME", 21) 22load("@soong_injection//api_levels:platform_versions.bzl", "platform_versions") 23load("//build/bazel/platforms:platform_utils.bzl", "platforms") 24load( 25 "//build/bazel/rules/cc:cc_library_common.bzl", 26 "build_compilation_flags", 27 "get_non_header_srcs", 28 "is_bionic_lib", 29 "is_bootstrap_lib", 30 "parse_apex_sdk_version", 31) 32load("//build/bazel/rules/cc:cc_library_static.bzl", "CcStaticLibraryInfo") 33 34AbiDumpInfo = provider(fields = ["dump_files"]) 35AbiDiffInfo = provider(fields = ["diff_files"]) 36 37_ABI_CLASS_PLATFORM = "platform" 38 39def _abi_dump_aspect_impl(target, ctx): 40 if not _abi_diff_enabled(ctx, ctx.label.name, True): 41 return [ 42 AbiDumpInfo( 43 dump_files = depset(), 44 ), 45 ] 46 47 transitive_dumps = [] 48 direct_dumps = [] 49 50 if CcStaticLibraryInfo in target: 51 direct_dumps.extend(_create_abi_dumps( 52 ctx, 53 target, 54 ctx.rule.files.srcs_cpp, 55 ctx.rule.attr.copts_cpp, 56 CPP_COMPILE_ACTION_NAME, 57 )) 58 direct_dumps.extend(_create_abi_dumps( 59 ctx, 60 target, 61 ctx.rule.files.srcs_c, 62 ctx.rule.attr.copts_c, 63 C_COMPILE_ACTION_NAME, 64 )) 65 66 for dep in ctx.rule.attr.static_deps: 67 if AbiDumpInfo in dep: 68 transitive_dumps.append(dep[AbiDumpInfo].dump_files) 69 70 return [ 71 AbiDumpInfo( 72 dump_files = depset( 73 direct_dumps, 74 transitive = transitive_dumps, 75 ), 76 ), 77 ] 78 79abi_dump_aspect = aspect( 80 implementation = _abi_dump_aspect_impl, 81 attr_aspects = ["static_deps", "whole_archive_deps"], 82 attrs = { 83 "_skip_abi_checks": attr.label( 84 default = "//build/bazel/flags/cc/abi:skip_abi_checks", 85 ), 86 # Need this in order to call _abi_diff_enabled in the aspects code. 87 "_within_apex": attr.label( 88 default = "//build/bazel/rules/apex:within_apex", 89 ), 90 "_abi_dumper": attr.label( 91 allow_files = True, 92 executable = True, 93 cfg = "exec", 94 default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-dumper"), 95 ), 96 "_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")), 97 }, 98 toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], 99 fragments = ["cpp"], 100 provides = [AbiDumpInfo], 101) 102 103def _create_abi_dumps(ctx, target, srcs, user_flags, action_name): 104 dumps = [] 105 106 if len(srcs) == 0: 107 return dumps 108 109 compilation_context, compilation_flags = build_compilation_flags( 110 ctx, 111 ctx.rule.attr.roots + ctx.rule.attr.deps + ctx.rule.attr.includes, 112 user_flags, 113 action_name, 114 ) 115 sources, headers = get_non_header_srcs(srcs) 116 117 header_inputs = ( 118 headers + 119 compilation_context.headers.to_list() + 120 compilation_context.direct_headers + 121 compilation_context.direct_private_headers + 122 compilation_context.direct_public_headers + 123 compilation_context.direct_textual_headers 124 ) 125 objects = [] 126 linker_inputs = target[CcInfo].linking_context.linker_inputs.to_list() 127 128 # These are created in cc_library_static and there should be only one 129 # linker_inputs and one libraries 130 if CcInfo in target and len(linker_inputs) == 1 and len(linker_inputs[0].libraries) == 1: 131 objects = linker_inputs[0].libraries[0].objects 132 for file in sources: 133 output = _create_abi_dump(ctx, target, file, objects, header_inputs, compilation_flags) 134 dumps.append(output) 135 136 return dumps 137 138def _include_flag(flag): 139 return ["-I", flag] 140 141def _create_abi_dump(ctx, target, src, objects, header_inputs, compilation_flags): 142 """ Utility function to generate abi dump file.""" 143 144 file = paths.join(src.dirname, target.label.name + "." + src.basename + ".sdump") 145 output = ctx.actions.declare_file(file) 146 args = ctx.actions.args() 147 148 args.add("--root-dir", ".") 149 args.add("-o", output) 150 args.add(src) 151 152 args.add_all(ctx.rule.attr.exports[0][CcInfo].compilation_context.includes.to_list(), map_each = _include_flag) 153 154 args.add("--") 155 args.add_all(compilation_flags) 156 157 # The following two args come from here: 158 # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/builder.go;l=247;drc=ba17c7243d0e297efbc6fb5385d6d5aa81db9152 159 args.add("-w") 160 161 # TODO(b/254625084): support darwin as well. 162 args.add("-isystem", "prebuilts/clang-tools/linux-x86/clang-headers") 163 164 ctx.actions.run( 165 inputs = [src] + header_inputs + objects, 166 executable = ctx.executable._abi_dumper, 167 outputs = [output], 168 arguments = [args], 169 # TODO(b/186116353): enable sandbox once the bug is fixed. 170 execution_requirements = { 171 "no-sandbox": "1", 172 }, 173 mnemonic = "AbiDump", 174 ) 175 176 return output 177 178def create_linked_abi_dump(ctx, dump_files): 179 """ Utility function to generate abi dump files.""" 180 shared_files = ctx.attr.shared[DefaultInfo].files.to_list() 181 if len(shared_files) != 1: 182 fail("Expected only one shared library file") 183 184 file = ctx.attr.soname + ".lsdump" 185 output = ctx.actions.declare_file(file) 186 args = ctx.actions.args() 187 188 args.add("--root-dir", ".") 189 args.add("-o", output) 190 args.add("-so", shared_files[0]) 191 inputs = dump_files + [shared_files[0]] 192 193 if ctx.file.symbol_file: 194 args.add("-v", ctx.file.symbol_file.path) 195 inputs.append(ctx.file.symbol_file) 196 for v in ctx.attr.exclude_symbol_versions: 197 args.add("--exclude-symbol-version", v) 198 for t in ctx.attr.exclude_symbol_tags: 199 args.add("--exclude-symbol-tag", t) 200 201 args.add("-arch", platforms.get_target_arch(ctx.attr._platform_utils)) 202 203 args.add_all(ctx.attr.root[CcInfo].compilation_context.includes.to_list(), map_each = _include_flag) 204 205 args.add_all([d.path for d in dump_files]) 206 207 ctx.actions.run( 208 inputs = inputs, 209 executable = ctx.executable._abi_linker, 210 outputs = [output], 211 arguments = [args], 212 # TODO(b/186116353): enable sandbox once the bug is fixed. 213 execution_requirements = { 214 "no-sandbox": "1", 215 }, 216 mnemonic = "AbiLink", 217 ) 218 219 return output 220 221def find_abi_config(_ctx): 222 sdk_version = str(platform_versions.platform_sdk_version) 223 prev_version = int(parse_apex_sdk_version(sdk_version)) 224 version = "current" 225 if platform_versions.platform_sdk_final: 226 prev_version -= 1 227 version = sdk_version 228 229 return prev_version, version 230 231def create_abi_diff(ctx, dump_file): 232 prev_version, version = find_abi_config(ctx) 233 234 arch = platforms.get_target_arch(ctx.attr._platform_utils) 235 bitness = platforms.get_target_bitness(ctx.attr._platform_utils) 236 abi_class = _ABI_CLASS_PLATFORM 237 238 # The logic below comes from: 239 # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/library.go;l=1891;drc=c645853ab73ac8c5889b42f4ce7dc9353ee8fd35 240 abi_reference_file = None 241 if not platform_versions.platform_sdk_final: 242 abi_reference_file = _find_abi_ref_file(ctx, prev_version, arch, bitness, abi_class, dump_file.basename) 243 if not abi_reference_file: 244 prev_version -= 1 245 246 diff_files = [] 247 248 # We need to do the abi check for the previous version and current version if the reference 249 # abi dump files are available. If the current previous version doesn't have the reference 250 # abi dump file we will check against one version earlier. 251 if not abi_reference_file: 252 abi_reference_file = _find_abi_ref_file(ctx, prev_version, arch, bitness, abi_class, dump_file.basename) 253 if abi_reference_file: 254 diff_files.append(_run_abi_diff(ctx, arch, prev_version, dump_file, abi_reference_file, True)) 255 256 abi_reference_file = _find_abi_ref_file(ctx, version, arch, bitness, abi_class, dump_file.basename) 257 if abi_reference_file: 258 diff_files.append(_run_abi_diff(ctx, arch, version, dump_file, abi_reference_file, False)) 259 260 return diff_files 261 262def _run_abi_diff(ctx, arch, version, dump_file, abi_reference_file, prev_version_diff): 263 lib_name = ctx.attr.soname.removesuffix(".so") 264 265 args = ctx.actions.args() 266 267 if ctx.attr.check_all_apis: 268 args.add("-check-all-apis") 269 else: 270 args.add_all(["-allow-unreferenced-changes", "-allow-unreferenced-elf-symbol-changes"]) 271 272 if prev_version_diff: 273 args.add("-target-version", version + 1) 274 diff_file_name = ctx.attr.soname + "." + str(version) + ".abidiff" 275 else: 276 args.add("-target-version", "current") 277 diff_file_name = ctx.attr.soname + ".abidiff" 278 279 args.add("-allow-extensions") 280 281 if len(ctx.attr.diff_flags) > 0: 282 args.add_all(ctx.attr.diff_flags) 283 284 args.add("-lib", lib_name) 285 args.add("-arch", arch) 286 287 diff_file = ctx.actions.declare_file(diff_file_name) 288 args.add("-o", diff_file) 289 args.add("-new", dump_file) 290 args.add("-old", abi_reference_file) 291 292 ctx.actions.run( 293 inputs = [dump_file, abi_reference_file], 294 executable = ctx.executable._abi_diff, 295 outputs = [diff_file], 296 arguments = [args], 297 execution_requirements = { 298 "no-sandbox": "1", 299 }, 300 mnemonic = "AbiDiff", 301 ) 302 303 return diff_file 304 305def _find_abi_ref_file(ctx, version, arch, bitness, abi_class, lsdump_name): 306 # Currently we only support platform. 307 if abi_class == _ABI_CLASS_PLATFORM: 308 abi_ref_dumps = ctx.attr.abi_ref_dumps_platform 309 else: 310 fail("Unsupported ABI class: %s" % abi_class) 311 312 # The expected reference abi dump file 313 ref_dump_file = paths.join( 314 ctx.attr.ref_dumps_home, 315 abi_class, 316 str(version), 317 str(bitness), 318 arch, 319 "source-based", 320 lsdump_name, 321 ) 322 323 ref_file = None 324 325 for file in abi_ref_dumps.files.to_list(): 326 if ref_dump_file == file.path: 327 ref_file = file 328 break 329 330 return ref_file 331 332def _abi_diff_enabled(ctx, lib_name, is_aspect): 333 # The logic here is based on: 334 # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/sabi.go;l=103;drc=cb0ac95bde896fa2aa59193a37ceb580758c322c 335 336 if ctx.attr._skip_abi_checks[BuildSettingInfo].value: 337 return False 338 if not platforms.is_target_android(ctx.attr._platform_utils): 339 return False 340 if ctx.coverage_instrumented(): 341 return False 342 if ctx.attr._within_apex[BuildSettingInfo].value: 343 if not is_aspect and not ctx.attr.has_stubs: 344 return False 345 346 # Logic comes from here: 347 # https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/sabi.go;l=158;drc=cb0ac95bde896fa2aa59193a37ceb580758c322c 348 349 elif is_bionic_lib(lib_name) or is_bootstrap_lib(lib_name): 350 return False 351 352 # TODO(b/260611960): handle all the other checks in sabi.go 353 return True 354 355def _abi_dump_impl(ctx): 356 diff_files = depset() 357 if _abi_diff_enabled(ctx, ctx.attr.soname.removesuffix(".so"), False) and ctx.attr.root != None: 358 dump_files = ctx.attr.root[AbiDumpInfo].dump_files.to_list() 359 linked_dump_file = create_linked_abi_dump(ctx, dump_files) 360 diff_files = depset(create_abi_diff(ctx, linked_dump_file)) 361 362 return ([ 363 DefaultInfo(files = diff_files), 364 AbiDiffInfo(diff_files = diff_files), 365 ]) 366 367abi_dump = rule( 368 implementation = _abi_dump_impl, 369 attrs = { 370 "shared": attr.label(mandatory = True, providers = [CcSharedLibraryInfo]), 371 "root": attr.label(providers = [CcInfo], aspects = [abi_dump_aspect]), 372 "soname": attr.string(mandatory = True), 373 "has_stubs": attr.bool(default = False), 374 "enabled": attr.bool(default = False), 375 "explicitly_disabled": attr.bool(default = False), 376 "symbol_file": attr.label(allow_single_file = True), 377 "exclude_symbol_versions": attr.string_list(default = []), 378 "exclude_symbol_tags": attr.string_list(default = []), 379 "check_all_apis": attr.bool(default = False), 380 "diff_flags": attr.string_list(default = []), 381 "abi_ref_dumps_platform": attr.label(default = "//prebuilts/abi-dumps/platform:bp2build_all_srcs"), 382 "ref_dumps_home": attr.string(default = "prebuilts/abi-dumps"), 383 "_skip_abi_checks": attr.label( 384 default = "//build/bazel/flags/cc/abi:skip_abi_checks", 385 ), 386 "_within_apex": attr.label( 387 default = "//build/bazel/rules/apex:within_apex", 388 ), 389 # TODO(b/254625084): For the following tools we need to support darwin as well. 390 "_abi_dumper": attr.label( 391 allow_files = True, 392 executable = True, 393 cfg = "exec", 394 default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-dumper"), 395 ), 396 "_abi_linker": attr.label( 397 allow_files = True, 398 executable = True, 399 cfg = "exec", 400 default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-linker"), 401 ), 402 "_abi_diff": attr.label( 403 allow_files = True, 404 executable = True, 405 cfg = "exec", 406 default = Label("//prebuilts/clang-tools:linux-x86/bin/header-abi-diff"), 407 ), 408 "_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")), 409 }, 410 fragments = ["cpp"], 411 toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], 412) 413