xref: /aosp_15_r20/build/bazel/rules/abi/abi_dump_test.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
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