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//lib:sets.bzl", "sets") 17load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") 18load("//build/bazel/rules/cc:cc_library_shared.bzl", "cc_library_shared") 19load("//build/bazel/rules/cc:cc_library_static.bzl", "cc_library_static") 20load("//build/bazel/rules/test_common:args.bzl", "get_arg_value", "get_arg_values") 21load(":abi_dump.bzl", "abi_dump", "find_abi_config") 22 23ABI_LINKER = "prebuilts/clang-tools/linux-x86/bin/header-abi-linker" 24ABI_DIFF = "prebuilts/clang-tools/linux-x86/bin/header-abi-diff" 25 26# cxa_demangle.cpp is added as part of the stl in cc_library_shared, so it's dump 27# file is always created. 28CXA_DEMANGLE = "external/libcxxabi/external/libcxxabi/src/libc++demangle.cxa_demangle.cpp.sdump" 29REF_DUMPS_HOME = "build/bazel/rules/abi/abi-dumps" 30ARCH = "x86_64" 31BITNESS = 64 32CONFIG_SETTING_COVERAGE = { 33 "//command_line_option:collect_code_coverage": True, 34} 35CONFIG_SETTING_SKIP_ABI_CHECK = { 36 "@//build/bazel/flags/cc/abi:skip_abi_checks": True, 37} 38CONFIG_SETTING_IN_APEX = { 39 "@//build/bazel/rules/apex:within_apex": True, 40} 41 42def _abi_linker_action_test_impl(ctx): 43 env = analysistest.begin(ctx) 44 bin_home = analysistest.target_bin_dir_path(env) 45 bazel_out_base = paths.join(bin_home, ctx.label.package) 46 47 actions = analysistest.target_actions(env) 48 link_actions = [a for a in actions if a.mnemonic == "AbiLink"] 49 50 asserts.true( 51 env, 52 len(link_actions) == 1, 53 "Abi link action not found: %s" % link_actions, 54 ) 55 56 action = link_actions[0] 57 58 stripped_so = paths.join(bazel_out_base, "lib" + ctx.attr.lib_name + "_stripped.so") 59 symbol_file = paths.join(ctx.label.package, ctx.attr.symbol_file) 60 asserts.set_equals( 61 env, 62 expected = sets.make( 63 [paths.join(bazel_out_base, ctx.label.package, file + ".sdump") for file in ctx.attr.dumps] + [ 64 ABI_LINKER, 65 paths.join(bin_home, CXA_DEMANGLE), 66 stripped_so, 67 symbol_file, 68 ], 69 ), 70 actual = sets.make([ 71 file.path 72 for file in action.inputs.to_list() 73 ]), 74 ) 75 76 lsdump_file = paths.join(bazel_out_base, ctx.attr.lib_name + ".so.lsdump") 77 asserts.set_equals( 78 env, 79 expected = sets.make([lsdump_file]), 80 actual = sets.make([ 81 file.path 82 for file in action.outputs.to_list() 83 ]), 84 ) 85 86 argv = action.argv 87 _test_arg_set_correctly(env, argv, "--root-dir", ".") 88 _test_arg_set_correctly(env, argv, "-o", lsdump_file) 89 _test_arg_set_correctly(env, argv, "-so", stripped_so) 90 _test_arg_set_correctly(env, argv, "-arch", ARCH) 91 _test_arg_set_correctly(env, argv, "-v", symbol_file) 92 _test_arg_set_multi_values_correctly(env, argv, "--exclude-symbol-version", ctx.attr.exclude_symbol_versions) 93 _test_arg_set_multi_values_correctly(env, argv, "--exclude-symbol-tag", ctx.attr.exclude_symbol_tags) 94 _test_arg_set_multi_values_correctly( 95 env, 96 argv, 97 "-I", 98 [paths.join(bazel_out_base, file) for file in ctx.attr.export_includes] + 99 [paths.join(ctx.label.package, file) for file in ctx.attr.export_includes] + 100 ctx.attr.export_absolute_includes + 101 [paths.join(bin_home, file) for file in ctx.attr.export_absolute_includes], 102 ) 103 104 sdump_files = [] 105 args = " ".join(argv).split(" ") 106 args_len = len(args) 107 108 # The .sdump files are at the end of the args, the abi linker binary is always at index 0. 109 for i in reversed(range(args_len)): 110 if ".sdump" in args[i]: 111 sdump_files.append(args[i]) 112 else: 113 break 114 115 asserts.set_equals( 116 env, 117 expected = sets.make( 118 [paths.join(bazel_out_base, ctx.label.package, file + ".sdump") for file in ctx.attr.dumps] + [ 119 paths.join(bin_home, CXA_DEMANGLE), 120 ], 121 ), 122 actual = sets.make(sdump_files), 123 ) 124 125 return analysistest.end(env) 126 127__abi_linker_action_test = analysistest.make( 128 impl = _abi_linker_action_test_impl, 129 attrs = { 130 "dumps": attr.string_list(), 131 "lib_name": attr.string(), 132 "symbol_file": attr.string(), 133 "exclude_symbol_versions": attr.string_list(), 134 "exclude_symbol_tags": attr.string_list(), 135 "export_includes": attr.string_list(), 136 "export_absolute_includes": attr.string_list(), 137 "_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")), 138 }, 139) 140 141def _abi_linker_action_test(**kwargs): 142 __abi_linker_action_test( 143 target_compatible_with = [ 144 "//build/bazel/platforms/arch:x86_64", 145 "//build/bazel/platforms/os:android", 146 ], 147 **kwargs 148 ) 149 150def _test_abi_linker_action(): 151 name = "abi_linker_action" 152 static_dep_a = name + "_static_dep_a" 153 static_dep_b = name + "_static_dep_b" 154 test_name = name + "_test" 155 156 cc_library_static( 157 name = static_dep_a, 158 srcs = ["static_a.cpp"], 159 srcs_c = ["static_a.c"], 160 export_includes = ["export_includes_static_a"], 161 export_absolute_includes = ["export_absolute_includes_static_a"], 162 export_system_includes = ["export_system_includes_static_a"], 163 local_includes = ["local_includes_static_a"], 164 absolute_includes = ["absolute_includes_static_a"], 165 tags = ["manual"], 166 ) 167 168 cc_library_static( 169 name = static_dep_b, 170 srcs = ["static_b.cpp"], 171 srcs_c = ["static_b.c"], 172 deps = [":" + static_dep_a], 173 export_includes = ["export_includes_static_b"], 174 export_absolute_includes = ["export_absolute_includes_static_b"], 175 export_system_includes = ["export_system_includes_static_b"], 176 local_includes = ["local_includes_static_b"], 177 absolute_includes = ["absolute_includes_static_b"], 178 tags = ["manual"], 179 ) 180 181 symbol_file = "shared_a.map.txt" 182 exclude_symbol_versions = ["30", "31"] 183 exclude_symbol_tags = ["func_1", "func_2"] 184 185 cc_library_shared( 186 name = name, 187 srcs = ["shared.cpp"], 188 srcs_c = ["shared.c"], 189 deps = [":" + static_dep_b], 190 export_includes = ["export_includes_shared"], 191 export_absolute_includes = ["export_absolute_includes_shared"], 192 export_system_includes = ["export_system_includes_shared"], 193 local_includes = ["local_includes_shared"], 194 absolute_includes = ["absolute_includes_shared"], 195 stubs_symbol_file = name + ".map.txt", 196 abi_checker_symbol_file = symbol_file, 197 abi_checker_exclude_symbol_versions = exclude_symbol_versions, 198 abi_checker_exclude_symbol_tags = exclude_symbol_tags, 199 tags = ["manual"], 200 ) 201 202 _abi_linker_action_test( 203 name = test_name, 204 target_under_test = name + "_abi_dump", 205 dumps = [ 206 static_dep_a + ".static_a.cpp", 207 static_dep_b + ".static_b.cpp", 208 name + "__internal_root.shared.cpp", 209 static_dep_a + ".static_a.c", 210 static_dep_b + ".static_b.c", 211 name + "__internal_root.shared.c", 212 ], 213 lib_name = name, 214 symbol_file = symbol_file, 215 exclude_symbol_versions = exclude_symbol_versions, 216 exclude_symbol_tags = exclude_symbol_tags, 217 export_includes = [ 218 "export_includes_shared", 219 "export_includes_static_a", 220 "export_includes_static_b", 221 ], 222 export_absolute_includes = [ 223 "export_absolute_includes_shared", 224 "export_absolute_includes_static_a", 225 "export_absolute_includes_static_b", 226 ], 227 ) 228 229 return test_name 230 231def _abi_linker_action_run_test_impl(ctx): 232 env = analysistest.begin(ctx) 233 234 actions = analysistest.target_actions(env) 235 link_actions = [a for a in actions if a.mnemonic == "AbiLink"] 236 237 asserts.true( 238 env, 239 len(link_actions) == 1, 240 "Abi link action not found: %s" % link_actions, 241 ) 242 243 return analysistest.end(env) 244 245__abi_linker_action_run_test = analysistest.make( 246 impl = _abi_linker_action_run_test_impl, 247) 248 249def _abi_linker_action_run_test(**kwargs): 250 __abi_linker_action_run_test( 251 target_compatible_with = [ 252 "//build/bazel/platforms/arch:x86_64", 253 "//build/bazel/platforms/os:android", 254 ], 255 **kwargs 256 ) 257 258def _test_abi_linker_action_run_for_enabled(): 259 name = "abi_linker_action_run_for_enabled" 260 test_name = name + "_test" 261 262 cc_library_shared( 263 name = name, 264 abi_checker_enabled = True, 265 tags = ["manual"], 266 ) 267 268 _abi_linker_action_run_test( 269 name = test_name, 270 target_under_test = name + "_abi_dump", 271 ) 272 273 return test_name 274 275def _abi_linker_action_not_run_test_impl(ctx): 276 env = analysistest.begin(ctx) 277 278 actions = analysistest.target_actions(env) 279 link_actions = [a for a in actions if a.mnemonic == "AbiLink"] 280 281 asserts.true( 282 env, 283 len(link_actions) == 0, 284 "Abi link action found: %s" % link_actions, 285 ) 286 287 return analysistest.end(env) 288 289__abi_linker_action_not_run_test = analysistest.make( 290 impl = _abi_linker_action_not_run_test_impl, 291) 292 293def _abi_linker_action_not_run_test(**kwargs): 294 __abi_linker_action_not_run_test( 295 target_compatible_with = [ 296 "//build/bazel/platforms/arch:x86_64", 297 "//build/bazel/platforms/os:android", 298 ], 299 **kwargs 300 ) 301 302__abi_linker_action_not_run_for_no_device_test = analysistest.make( 303 impl = _abi_linker_action_not_run_test_impl, 304) 305 306def _abi_linker_action_not_run_for_no_device_test(**kwargs): 307 __abi_linker_action_not_run_for_no_device_test( 308 target_compatible_with = [ 309 "//build/bazel/platforms/arch:x86_64", 310 "//build/bazel/platforms/os:linux", 311 ], 312 **kwargs 313 ) 314 315__abi_linker_action_not_run_for_coverage_test = analysistest.make( 316 impl = _abi_linker_action_not_run_test_impl, 317 config_settings = CONFIG_SETTING_COVERAGE, 318) 319 320def _abi_linker_action_not_run_for_coverage_test(**kwargs): 321 __abi_linker_action_not_run_for_coverage_test( 322 target_compatible_with = [ 323 "//build/bazel/platforms/arch:x86_64", 324 "//build/bazel/platforms/os:android", 325 ], 326 **kwargs 327 ) 328 329__abi_linker_action_not_run_if_skipped_test = analysistest.make( 330 impl = _abi_linker_action_not_run_test_impl, 331 config_settings = CONFIG_SETTING_SKIP_ABI_CHECK, 332) 333 334def _abi_linker_action_not_run_if_skipped_test(**kwargs): 335 __abi_linker_action_not_run_if_skipped_test( 336 target_compatible_with = [ 337 "//build/bazel/platforms/arch:x86_64", 338 "//build/bazel/platforms/os:android", 339 ], 340 **kwargs 341 ) 342 343__abi_linker_action_not_run_apex_no_stubs_test = analysistest.make( 344 impl = _abi_linker_action_not_run_test_impl, 345 config_settings = CONFIG_SETTING_IN_APEX, 346) 347 348def _abi_linker_action_not_run_apex_no_stubs_test(**kwargs): 349 __abi_linker_action_not_run_apex_no_stubs_test( 350 target_compatible_with = [ 351 "//build/bazel/platforms/arch:x86_64", 352 "//build/bazel/platforms/os:android", 353 ], 354 **kwargs 355 ) 356 357def _test_abi_linker_action_not_run_for_default(): 358 name = "abi_linker_action_not_run_for_default" 359 test_name = name + "_test" 360 361 cc_library_shared( 362 name = name, 363 tags = ["manual"], 364 ) 365 366 _abi_linker_action_not_run_test( 367 name = test_name, 368 target_under_test = name + "_abi_dump", 369 ) 370 371 return test_name 372 373def _test_abi_linker_action_not_run_for_disabled(): 374 name = "abi_linker_action_not_run_for_disabled" 375 test_name = name + "_test" 376 377 cc_library_shared( 378 name = name, 379 stubs_symbol_file = name + ".map.txt", 380 abi_checker_enabled = False, 381 tags = ["manual"], 382 ) 383 384 _abi_linker_action_not_run_test( 385 name = test_name, 386 target_under_test = name + "_abi_dump", 387 ) 388 389 return test_name 390 391def _test_abi_linker_action_not_run_for_no_device(): 392 name = "abi_linker_action_not_run_for_no_device" 393 test_name = name + "_test" 394 395 cc_library_shared( 396 name = name, 397 abi_checker_enabled = True, 398 tags = ["manual"], 399 ) 400 401 _abi_linker_action_not_run_for_no_device_test( 402 name = test_name, 403 target_under_test = name + "_abi_dump", 404 ) 405 406 return test_name 407 408def _test_abi_linker_action_not_run_if_skipped(): 409 name = "abi_linker_action_not_run_if_skipped" 410 test_name = name + "_test" 411 412 cc_library_shared( 413 name = name, 414 abi_checker_enabled = True, 415 tags = ["manual"], 416 ) 417 418 _abi_linker_action_not_run_if_skipped_test( 419 name = test_name, 420 target_under_test = name + "_abi_dump", 421 ) 422 423 return test_name 424 425def _test_abi_linker_action_not_run_for_coverage_enabled(): 426 name = "abi_linker_action_not_run_for_coverage_enabled" 427 test_name = name + "_test" 428 429 cc_library_shared( 430 name = name, 431 abi_checker_enabled = True, 432 features = ["coverage"], 433 # Coverage will add an extra lib to all the shared libs, we try to avoid 434 # that by clearing the system_dynamic_deps and stl. 435 system_dynamic_deps = [], 436 stl = "none", 437 tags = ["manual"], 438 ) 439 440 _abi_linker_action_not_run_for_coverage_test( 441 name = test_name, 442 target_under_test = name + "_abi_dump", 443 ) 444 445 return test_name 446 447def _test_abi_linker_action_not_run_for_apex_no_stubs(): 448 name = "abi_linker_action_not_run_for_apex_no_stubs" 449 test_name = name + "_test" 450 451 cc_library_shared( 452 name = name, 453 abi_checker_enabled = True, 454 tags = ["manual"], 455 ) 456 457 _abi_linker_action_not_run_apex_no_stubs_test( 458 name = test_name, 459 target_under_test = name + "_abi_dump", 460 ) 461 462 return test_name 463 464def _abi_diff_action_test_impl(ctx): 465 env = analysistest.begin(ctx) 466 actions = analysistest.target_actions(env) 467 diff_actions = [a for a in actions if a.mnemonic == "AbiDiff"] 468 469 asserts.true( 470 env, 471 len(diff_actions) == 2, 472 "There should be two abi diff actions: %s" % diff_actions, 473 ) 474 475 prev_version, version = find_abi_config(ctx) 476 _verify_abi_diff_action(ctx, env, diff_actions[0], prev_version, True) 477 _verify_abi_diff_action(ctx, env, diff_actions[1], version, False) 478 479 return analysistest.end(env) 480 481def _verify_abi_diff_action(ctx, env, action, version, is_prev_version): 482 bin_home = analysistest.target_bin_dir_path(env) 483 bazel_out_base = paths.join(bin_home, ctx.label.package) 484 lsdump_file = paths.join(bazel_out_base, ctx.attr.lib_name + ".so.lsdump") 485 486 ref_dump = paths.join( 487 REF_DUMPS_HOME, 488 "platform", 489 str(version), 490 str(BITNESS), 491 ARCH, 492 "source-based", 493 ctx.attr.lib_name + ".so.lsdump", 494 ) 495 asserts.set_equals( 496 env, 497 expected = sets.make([ 498 lsdump_file, 499 ABI_DIFF, 500 ref_dump, 501 ]), 502 actual = sets.make([ 503 file.path 504 for file in action.inputs.to_list() 505 ]), 506 ) 507 508 if is_prev_version: 509 diff_file = paths.join(bazel_out_base, ".".join([ctx.attr.lib_name, "so", str(version), "abidiff"])) 510 else: 511 diff_file = paths.join(bazel_out_base, ".".join([ctx.attr.lib_name, "so", "abidiff"])) 512 513 asserts.set_equals( 514 env, 515 expected = sets.make([diff_file]), 516 actual = sets.make([ 517 file.path 518 for file in action.outputs.to_list() 519 ]), 520 ) 521 522 argv = action.argv 523 _test_arg_set_correctly(env, argv, "-o", diff_file) 524 _test_arg_set_correctly(env, argv, "-old", ref_dump) 525 _test_arg_set_correctly(env, argv, "-new", lsdump_file) 526 _test_arg_set_correctly(env, argv, "-lib", ctx.attr.lib_name) 527 _test_arg_set_correctly(env, argv, "-arch", ARCH) 528 _test_arg_exists(env, argv, "-allow-unreferenced-changes") 529 _test_arg_exists(env, argv, "-allow-unreferenced-elf-symbol-changes") 530 _test_arg_exists(env, argv, "-allow-extensions") 531 if is_prev_version: 532 _test_arg_set_correctly(env, argv, "-target-version", str(version + 1)) 533 else: 534 _test_arg_set_correctly(env, argv, "-target-version", "current") 535 536__abi_diff_action_test = analysistest.make( 537 impl = _abi_diff_action_test_impl, 538 attrs = { 539 "lib_name": attr.string(), 540 "_platform_utils": attr.label(default = Label("//build/bazel/platforms:platform_utils")), 541 }, 542) 543 544def _abi_diff_action_test(**kwargs): 545 __abi_diff_action_test( 546 target_compatible_with = [ 547 "//build/bazel/platforms/arch:x86_64", 548 "//build/bazel/platforms/os:android", 549 ], 550 **kwargs 551 ) 552 553def _test_abi_diff_action(): 554 name = "abi_diff_action" 555 test_name = name + "_test" 556 557 cc_library_shared( 558 name = name, 559 srcs = ["shared.cpp"], 560 tags = ["manual"], 561 ) 562 563 lib_name = "lib" + name 564 abi_dump_name = name + "_abi_dump_new" 565 abi_dump( 566 name = abi_dump_name, 567 shared = name + "_stripped", 568 root = name + "__internal_root", 569 soname = lib_name + ".so", 570 enabled = True, 571 abi_ref_dumps_platform = "//build/bazel/rules/abi/abi-dumps/platform:bp2build_all_srcs", 572 ref_dumps_home = "build/bazel/rules/abi/abi-dumps", 573 tags = ["manual"], 574 ) 575 576 _abi_diff_action_test( 577 name = test_name, 578 target_under_test = abi_dump_name, 579 lib_name = lib_name, 580 ) 581 582 return test_name 583 584def _abi_diff_action_not_run_test_impl(ctx): 585 env = analysistest.begin(ctx) 586 actions = analysistest.target_actions(env) 587 diff_actions = [a for a in actions if a.mnemonic == "AbiDiff"] 588 589 asserts.true( 590 env, 591 len(diff_actions) == 0, 592 "Abi diff action found: %s" % diff_actions, 593 ) 594 595 return analysistest.end(env) 596 597__abi_diff_action_not_run_test = analysistest.make( 598 impl = _abi_diff_action_not_run_test_impl, 599) 600 601def _abi_diff_action_not_run_test(**kwargs): 602 __abi_diff_action_not_run_test( 603 target_compatible_with = [ 604 "//build/bazel/platforms/arch:x86_64", 605 "//build/bazel/platforms/os:android", 606 ], 607 **kwargs 608 ) 609 610def _test_abi_diff_action_not_run_if_no_ref_dump_found(): 611 name = "abi_diff_action_not_run_if_no_ref_dump_found" 612 test_name = name + "_test" 613 614 cc_library_shared( 615 name = name, 616 srcs = ["shared.cpp"], 617 tags = ["manual"], 618 ) 619 620 lib_name = "lib" + name 621 abi_dump_name = name + "_abi_dump_new" 622 abi_dump( 623 name = abi_dump_name, 624 shared = name + "_stripped", 625 root = name + "__internal_root", 626 soname = lib_name + ".so", 627 enabled = True, 628 ref_dumps_home = "build/bazel/rules/abi/abi-dumps", 629 tags = ["manual"], 630 ) 631 632 _abi_diff_action_not_run_test( 633 name = test_name, 634 target_under_test = abi_dump_name, 635 ) 636 637 return test_name 638 639def _test_arg_set_correctly(env, argv, arg_name, expected): 640 arg = get_arg_value(argv, arg_name) 641 asserts.true( 642 env, 643 arg == expected, 644 "%s is not set correctly: expected %s, actual %s" % (arg_name, expected, arg), 645 ) 646 647def _test_arg_set_multi_values_correctly(env, argv, arg_name, expected): 648 args = get_arg_values(argv, arg_name) 649 asserts.set_equals( 650 env, 651 expected = sets.make(expected), 652 actual = sets.make(args), 653 ) 654 655def _test_arg_exists(env, argv, arg_name): 656 asserts.true( 657 env, 658 arg_name in argv, 659 "arg %s is not found" % arg_name, 660 ) 661 662def abi_dump_test_suite(name): 663 native.test_suite( 664 name = name, 665 tests = [ 666 _test_abi_linker_action(), 667 _test_abi_linker_action_not_run_for_default(), 668 _test_abi_linker_action_not_run_for_disabled(), 669 _test_abi_linker_action_run_for_enabled(), 670 _test_abi_linker_action_not_run_for_no_device(), 671 _test_abi_linker_action_not_run_for_coverage_enabled(), 672 _test_abi_linker_action_not_run_if_skipped(), 673 _test_abi_linker_action_not_run_for_apex_no_stubs(), 674 _test_abi_diff_action(), 675 _test_abi_diff_action_not_run_if_no_ref_dump_found(), 676 ], 677 ) 678