| """Tests for C++ linking action.""" | |
| load("@bazel_features//:features.bzl", "bazel_features") | |
| load("@rules_testing//lib:analysis_test.bzl", "test_suite") | |
| load("@rules_testing//lib:truth.bzl", "matching", "subjects") | |
| load("@rules_testing//lib:util.bzl", "util") | |
| load("//cc:cc_binary.bzl", "cc_binary") | |
| load("//cc:cc_library.bzl", "cc_library") | |
| load("//cc:cc_test.bzl", "cc_test") | |
| load("//tests/cc/testutil:cc_analysis_test.bzl", "cc_analysis_test") | |
| load("//tests/cc/testutil:link_action_subject.bzl", "link_action_subject") | |
| def _has_constraint(env, platform_target): | |
| return env.ctx.target_platform_has_constraint( | |
| platform_target[platform_common.ConstraintValueInfo], | |
| ) | |
| def _test_linkopts_and_lib_srcs_order(name, **kwargs): | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo", | |
| srcs = [ | |
| "somedir/libbar.so", | |
| "someotherdir/qux.so", | |
| ], | |
| linkopts = [ | |
| "-ldl", | |
| "-lutil", | |
| ], | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| attrs = { | |
| "_is_macos": attr.label(default = "@platforms//os:macos"), | |
| }, | |
| impl = _test_linkopts_and_lib_srcs_order_impl, | |
| target = name + "/foo", | |
| **kwargs | |
| ) | |
| def _extract_x_linker_args(args): | |
| x_linker_args = [] | |
| for i in range(len(args)): | |
| if args[i] == "-Xlinker": | |
| arg = " ".join((args[i], args[i + 1])) | |
| x_linker_args.append(arg) | |
| return x_linker_args | |
| def _test_linkopts_and_lib_srcs_order_impl(env, target): | |
| assert_link_action = link_action_subject.from_target(env, target) | |
| is_macos = _has_constraint(env, env.ctx.attr._is_macos) | |
| assert_link_action.argv().contains_at_least_predicates([ | |
| matching.str_matches("-L*somedir"), | |
| matching.str_matches("-L*someotherdir"), | |
| matching.equals_wrapper("-lbar"), | |
| matching.str_endswith("qux.so") if is_macos else matching.equals_wrapper("-l:qux.so"), | |
| matching.equals_wrapper("-ldl"), | |
| matching.equals_wrapper("-lutil"), | |
| ]).in_order() | |
| x_linker_args = _extract_x_linker_args(assert_link_action.argv().actual) | |
| assert_x_linker_args = subjects.collection( | |
| x_linker_args, | |
| sortable = False, | |
| meta = assert_link_action.meta.derive("argv"), | |
| ) | |
| assert_x_linker_args.contains_at_least_predicates([ | |
| matching.equals_wrapper("-Xlinker -rpath"), | |
| matching.str_matches("-Xlinker *somedir"), | |
| matching.equals_wrapper("-Xlinker -rpath"), | |
| matching.str_matches("-Xlinker *someotherdir"), | |
| ]).in_order() | |
| def _test_dynamic_mode_libraries_unaffected_by_legacy_whole_archive(name, **kwargs): | |
| util.helper_target( | |
| cc_library, | |
| name = name + "/bar", | |
| srcs = ["bar.cc"], | |
| alwayslink = True, | |
| ) | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo", | |
| deps = [name + "/bar"], | |
| srcs = ["foo.cc"], | |
| ) | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/libfoo.so", | |
| srcs = ["foo.cc"], | |
| deps = [name + "/bar"], | |
| linkshared = True, | |
| linkstatic = False, | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_dynamic_mode_libraries_unaffected_by_legacy_whole_archive_impl, | |
| targets = { | |
| "default": name + "/foo", | |
| "dynamic_lib": name + "/libfoo.so", | |
| }, | |
| config_settings = { | |
| "//command_line_option:legacy_whole_archive": True, | |
| }, | |
| test_features = [ | |
| "supports_dynamic_linker", | |
| ], | |
| attrs = { | |
| "_is_macos": attr.label(default = "@platforms//os:macos"), | |
| }, | |
| **kwargs | |
| ) | |
| def _test_dynamic_mode_libraries_unaffected_by_legacy_whole_archive_impl(env, targets): | |
| is_macos = _has_constraint(env, env.ctx.attr._is_macos) | |
| whole_archive_arg_predicate = matching.equals_wrapper("-Wl,-whole-archive") | |
| if is_macos: | |
| whole_archive_arg_predicate = matching.str_matches("-Wl,-force_load") | |
| # verify a regular binary is affected to ensure we're testing what we think we're testing | |
| assert_link_action_default = link_action_subject.from_target(env, targets.default) | |
| assert_link_action_default.argv().contains_predicate(whole_archive_arg_predicate) | |
| assert_link_action_dynamic = link_action_subject.from_target(env, targets.dynamic_lib) | |
| assert_link_action_dynamic.argv().not_contains_predicate(whole_archive_arg_predicate) | |
| def _test_dynamic_mode_srcs_with_feature(name, **kwargs): | |
| util.helper_target( | |
| cc_test, | |
| name = name + "/foo_test", | |
| srcs = ["foo.cc"], | |
| features = ["dynamic_link_test_srcs"], | |
| ) | |
| util.helper_target( | |
| cc_test, | |
| name = name + "/foo_test_static", | |
| srcs = ["foo.cc"], | |
| features = ["dynamic_link_test_srcs"], | |
| linkstatic = True, | |
| ) | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo_bin", | |
| srcs = ["foo.cc"], | |
| ) | |
| cc_analysis_test( | |
| name, | |
| impl = _test_dynamic_mode_srcs_with_feature_impl, | |
| attrs = { | |
| "_is_windows": attr.label(default = "@platforms//os:windows"), | |
| }, | |
| targets = { | |
| "foo_test": name + "/foo_test", | |
| "foo_test_static": name + "/foo_test_static", | |
| "foo_bin": name + "/foo_bin", | |
| }, | |
| test_features = [ | |
| "supports_pic", | |
| "supports_dynamic_linker", | |
| "supports_interface_shared_libraries", | |
| ], | |
| config_settings = { | |
| "//command_line_option:force_pic": "True", | |
| }, | |
| **kwargs | |
| ) | |
| def _test_dynamic_mode_srcs_with_feature_impl(env, targets): | |
| if _has_constraint(env, env.ctx.attr._is_windows): | |
| # TODO: Fix this test on Windows. | |
| return | |
| assert_test_link_action = link_action_subject.from_target(env, targets.foo_test) | |
| assert_test_link_action.inputs().contains_predicate( | |
| matching.file_path_matches("_solib_*libfoo_Utest.ifso"), | |
| ) | |
| assert_test_link_action.argv().contains_predicate( | |
| matching.str_matches("_solib_*libfoo_Utest.ifso"), | |
| ) | |
| assert_test_runfiles = env.expect.that_target(targets.foo_test).runfiles() | |
| assert_test_runfiles.contains_predicate( | |
| matching.str_matches("_solib_*libfoo_Utest.so"), | |
| ) | |
| assert_test_static_link_action = link_action_subject.from_target(env, targets.foo_test_static) | |
| assert_test_static_link_action.inputs().contains( | |
| "{package}/_objs/{name}/foo.pic.o", | |
| ) | |
| assert_test_static_runfiles = env.expect.that_target(targets.foo_test_static).runfiles() | |
| assert_test_static_runfiles.contains_exactly([ | |
| "{workspace}/{package}/{name}", | |
| ]) | |
| assert_bin_link_action = link_action_subject.from_target(env, targets.foo_bin) | |
| assert_bin_link_action.inputs().contains( | |
| "{package}/_objs/{name}/foo.pic.o", | |
| ) | |
| assert_bin_runfiles = env.expect.that_target(targets.foo_bin).runfiles() | |
| assert_bin_runfiles.contains_exactly([ | |
| "{workspace}/{package}/{name}", | |
| ]) | |
| def _test_dynamic_mode_swrcs_without_feature(name, **kwargs): | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo", | |
| srcs = ["foo.cc"], | |
| features = [ | |
| "-static_link_test_srcs", | |
| ], | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_dynamic_mode_swrcs_without_feature_impl, | |
| target = name + "/foo", | |
| test_features = [ | |
| "supports_pic", | |
| "supports_dynamic_linker", | |
| "supports_interface_shared_libraries", | |
| ], | |
| config_settings = { | |
| "//command_line_option:force_pic": "True", | |
| "//command_line_option:dynamic_mode": "default", | |
| }, | |
| **kwargs | |
| ) | |
| def _test_dynamic_mode_swrcs_without_feature_impl(env, target): | |
| assert_link_action = link_action_subject.from_target(env, target) | |
| assert_link_action.inputs().contains("{package}/_objs/{name}/foo.pic.o") | |
| assert_link_action.inputs().not_contains_predicate( | |
| matching.file_path_matches("libfoo.ifso"), | |
| ) | |
| env.expect.that_target(target).runfiles().contains_exactly([ | |
| "{workspace}/{package}/{name}", | |
| ]) | |
| def _test_interface_output_for_dynamic_library(name, **kwargs): | |
| util.helper_target( | |
| cc_library, | |
| name = name + "/foo", | |
| srcs = ["foo.cc"], | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_interface_output_for_dynamic_library_impl, | |
| target = name + "/foo", | |
| test_features = [ | |
| "supports_dynamic_linker", | |
| "supports_interface_shared_libraries", | |
| ], | |
| **kwargs | |
| ) | |
| def _test_interface_output_for_dynamic_library_impl(env, target): | |
| assert_link_action = env.expect.that_target(target).action_generating( | |
| "{package}/{test_name}/libfoo.so", | |
| ) | |
| assert_link_action.mnemonic().equals("CppLink") | |
| assert_link_action.inputs().contains_predicate( | |
| matching.file_path_matches("link_dynamic_library"), | |
| ) | |
| def _test_pie_disabled_for_shared_libraries(name, **kwargs): | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo", | |
| srcs = ["foo.cc"], | |
| linkopts = [ | |
| "-pie", | |
| "-other", | |
| "-pie", | |
| ], | |
| linkshared = True, | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_pie_disabled_for_shared_libraries_impl, | |
| target = name + "/foo", | |
| **kwargs | |
| ) | |
| def _test_pie_disabled_for_shared_libraries_impl(env, target): | |
| assert_link_action = env.expect.that_target(target).action_named("CppLink") | |
| assert_link_action.argv().contains("-other") | |
| assert_link_action.argv().not_contains("-pie") | |
| def _test_pie_option_kept_for_executables(name, **kwargs): | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo", | |
| srcs = ["foo.cc"], | |
| linkopts = [ | |
| "-pie", | |
| "-other", | |
| "-pie", | |
| ], | |
| linkshared = False, | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_pie_option_kept_for_executables_impl, | |
| target = name + "/foo", | |
| **kwargs | |
| ) | |
| def _test_pie_option_kept_for_executables_impl(env, target): | |
| assert_link_action = env.expect.that_target(target).action_named("CppLink") | |
| assert_link_action.argv().contains_at_least([ | |
| "-pie", | |
| "-other", | |
| "-pie", | |
| ]).in_order() | |
| def _test_linkopts_come_after_linker_inputs(name, **kwargs): | |
| util.helper_target( | |
| cc_library, | |
| name = name + "/bar", | |
| srcs = ["bar.cc"], | |
| ) | |
| util.helper_target( | |
| cc_library, | |
| name = name + "/baz", | |
| srcs = ["baz.cc"], | |
| ) | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo", | |
| srcs = ["foo.cc"], | |
| deps = [ | |
| name + "/bar", | |
| name + "/baz", | |
| ], | |
| linkopts = [ | |
| "fake_linkopt_1", | |
| "fake_linkopt_2", | |
| ], | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_linkopts_come_after_linker_inputs_impl, | |
| target = name + "/foo", | |
| **kwargs | |
| ) | |
| def _test_linkopts_come_after_linker_inputs_impl(env, target): | |
| assert_link_action = link_action_subject.from_target(env, target) | |
| link_action = assert_link_action.actual | |
| inputs = link_action.inputs.to_list() | |
| argv_map = {} | |
| for n, arg in enumerate(link_action.argv): | |
| argv_map[arg] = n | |
| max_argv_idx = 0 | |
| for input in inputs: | |
| max_argv_idx = max(max_argv_idx, argv_map.get(input.path, 0)) | |
| assert_link_action.argv().contains_at_least([ | |
| link_action.argv[max_argv_idx], | |
| "fake_linkopt_1", | |
| "fake_linkopt_2", | |
| ]).in_order() | |
| def _test_linkstamp_objects_exposed(name, **kwargs): | |
| util.helper_target( | |
| cc_library, | |
| name = name + "/bar", | |
| linkstamp = "linkstamp.cc", | |
| ) | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo", | |
| deps = [ | |
| name + "/bar", | |
| ], | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_linkstamp_objects_exposed_impl, | |
| target = name + "/foo", | |
| **kwargs | |
| ) | |
| def _test_linkstamp_objects_exposed_impl(env, target): | |
| assert_link_action = link_action_subject.from_target(env, target) | |
| assert_link_action.inputs().contains( | |
| "{package}/{test_name}/_objs/foo/{package}/linkstamp.o", | |
| ) | |
| def _test_linkopts_for_generated_library(name, **kwargs): | |
| util.helper_target( | |
| native.genrule, | |
| name = name + "/genlib", | |
| outs = [name + "/genlib.a"], | |
| cmd = "touch $@", | |
| ) | |
| util.helper_target( | |
| cc_library, | |
| name = name + "/bar", | |
| srcs = [name + "/genlib"], | |
| linkstatic = True, | |
| ) | |
| util.helper_target( | |
| cc_binary, | |
| name = name + "/foo.so", | |
| deps = [name + "/bar"], | |
| linkstatic = True, | |
| linkshared = True, | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_linkopts_for_generated_library_impl, | |
| target = name + "/foo.so", | |
| config_settings = { | |
| "//command_line_option:incompatible_remove_legacy_whole_archive": False, | |
| "//command_line_option:legacy_whole_archive": True, | |
| }, | |
| test_features = [ | |
| "supports_dynamic_linker", | |
| ], | |
| attrs = { | |
| "_is_macos": attr.label(default = "@platforms//os:macos"), | |
| }, | |
| **kwargs | |
| ) | |
| def _test_linkopts_for_generated_library_impl(env, target): | |
| is_macos = _has_constraint(env, env.ctx.attr._is_macos) | |
| assert_link_action = env.expect.that_target(target).action_generating( | |
| "{package}/{test_name}/foo.so", | |
| ) | |
| link_argv_predicates = [ | |
| matching.equals_wrapper("-shared"), | |
| matching.equals_wrapper("-o"), | |
| matching.str_matches("-out/*/foo.so"), | |
| ] | |
| if is_macos: | |
| link_argv_predicates.extend([ | |
| matching.str_matches("-Wl,-force_load*/genlib.a"), | |
| ]) | |
| else: | |
| link_argv_predicates.extend([ | |
| matching.equals_wrapper("-Wl,-whole-archive"), | |
| matching.str_matches("-out/*/genlib.a"), | |
| matching.equals_wrapper("-Wl,-no-whole-archive"), | |
| ]) | |
| assert_link_action.argv().contains_at_least_predicates(link_argv_predicates).in_order() | |
| def _test_cc_library_linkopts_in_shared_library(name, **kwargs): | |
| util.helper_target( | |
| cc_library, | |
| name = name + "/foo", | |
| srcs = ["foo.cc"], | |
| linkopts = ["-Lfoo_opt1", "-lfoo_opt2"], | |
| ) | |
| cc_analysis_test( | |
| name = name, | |
| impl = _test_cc_library_linkopts_in_shared_library_impl, | |
| target = name + "/foo", | |
| test_features = ["supports_dynamic_linker"], | |
| **kwargs | |
| ) | |
| def _test_cc_library_linkopts_in_shared_library_impl(env, target): | |
| # Verify the specified linkopts are used in the link action for the | |
| # shared library but not in the link action for the archive. | |
| linkopts = ["-Lfoo_opt1", "-lfoo_opt2"] | |
| assert_archive_link_action = env.expect.that_target(target).action_generating( | |
| "{package}/{test_name}/libfoo.a", | |
| ) | |
| assert_archive_link_action.mnemonic().equals("CppArchive") | |
| assert_archive_link_action.argv().contains_none_of(linkopts) | |
| assert_so_link_action = env.expect.that_target(target).action_generating( | |
| "{package}/{test_name}/libfoo.so", | |
| ) | |
| assert_so_link_action.mnemonic().equals("CppLink") | |
| assert_so_link_action.argv().contains_at_least(linkopts) | |
| def cc_link_action_tests(name): | |
| tests = [ | |
| _test_linkopts_and_lib_srcs_order, | |
| _test_dynamic_mode_libraries_unaffected_by_legacy_whole_archive, | |
| _test_dynamic_mode_srcs_with_feature, | |
| _test_dynamic_mode_swrcs_without_feature, | |
| _test_interface_output_for_dynamic_library, | |
| _test_pie_disabled_for_shared_libraries, | |
| _test_pie_option_kept_for_executables, | |
| _test_linkopts_come_after_linker_inputs, | |
| _test_linkstamp_objects_exposed, | |
| _test_cc_library_linkopts_in_shared_library, | |
| ] | |
| if bazel_features.rules.analysis_tests_can_transition_on_experimental_incompatible_flags: | |
| tests.append(_test_linkopts_for_generated_library) | |
| test_suite( | |
| name = name, | |
| tests = tests, | |
| ) |