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