blob: 514bbb797b51290dd0c492cd7dbcea93f3a2c2f8 [file] [log] [blame]
# Copyright 2021 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Starlark tests for cc_shared_library"""
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
load("@rules_testing//lib:truth.bzl", "matching")
load("@bazel_skylib//lib:paths.bzl", "paths")
def _same_package_or_above(label_a, label_b):
if label_a.workspace_name != label_b.workspace_name:
return False
package_a_tokenized = label_a.package.split("/")
package_b_tokenized = label_b.package.split("/")
if len(package_b_tokenized) < len(package_a_tokenized):
return False
if package_a_tokenized[0] != "":
for i in range(len(package_a_tokenized)):
if package_a_tokenized[i] != package_b_tokenized[i]:
return False
return True
def _check_if_target_under_path(value, pattern):
if pattern.workspace_name != value.workspace_name:
return False
if pattern.name == "__pkg__":
return pattern.package == value.package
if pattern.name == "__subpackages__":
return _same_package_or_above(pattern, value)
return pattern.package == value.package and pattern.name == value.name
def _linking_order_test_impl(env, target):
if env.ctx.target_platform_has_constraint(env.ctx.attr._is_linux[platform_common.ConstraintValueInfo]):
target_action = None
for action in target.actions:
if action.mnemonic == "FileWrite":
target_action = action
break
args = target_action.content.split("\n")
user_libs = [paths.basename(arg) for arg in args if arg.endswith(".o")]
env.expect.that_collection(user_libs).contains_at_least_predicates([
matching.contains("foo.pic.o"),
matching.contains("baz.pic.o"),
]).in_order()
env.expect.that_collection(args).contains_at_least([
"-lprivate_lib_so",
])
env.expect.where(
detail = "liba_suffix.pic.o should be the last user library linked",
).that_str(user_libs[-1]).equals("a_suffix.pic.o")
# qux2 is a LINKABLE_MORE_THAN_ONCE library which is enabled by semantics.
# It might not be present but if it is we want to test it's in the right
# place in the linking command line before libbar
if "qux2.pic.o" in user_libs:
found_bar = False
for arg in args:
if "-lbar_so" in arg:
found_bar = True
elif "qux2.pic.o" in arg:
env.expect.where(
detail = "qux2 should come before bar in command line",
).that_bool(found_bar).equals(False)
env.expect.where(
detail = "should have seen bar in command line",
).that_bool(found_bar).equals(True)
def _linking_order_test_macro(name, target):
analysis_test(
name = name,
impl = _linking_order_test_impl,
target = target,
attrs = {
"_is_linux": attr.label(default = "@platforms//os:linux"),
},
)
linking_order_test = _linking_order_test_macro
def _additional_inputs_test_impl(env, target):
if env.ctx.target_platform_has_constraint(env.ctx.attr._is_linux[platform_common.ConstraintValueInfo]):
found = False
target_action = None
for action in target.actions:
if action.mnemonic == "CppLink":
target_action = action
break
for arg in target_action.argv:
if arg.find("-Wl,--script=") != -1:
env.expect.that_str(
"src/main/starlark/tests/builtins_bzl/cc/cc_shared_library/test_cc_shared_library/additional_script.txt",
).equals(arg[13:])
found = True
break
env.expect.where(
detail = "Should have seen option --script=",
).that_bool(found).equals(True)
def _additional_inputs_test_macro(name, target):
analysis_test(
name = name,
impl = _additional_inputs_test_impl,
target = target,
attrs = {
"_is_linux": attr.label(default = "@platforms//os:linux"),
},
)
additional_inputs_test = _additional_inputs_test_macro
def _build_failure_test_impl(env, target):
if env.ctx.attr._message:
env.expect.that_target(target).failures().contains_predicate(matching.contains(env.ctx.attr._message))
if env.ctx.attr._messages:
for message in env.ctx.attr._messages:
env.expect.that_target(target).failures().contains_predicate(matching.contains(message))
def _build_failure_test_macro(name, target, message = "", messages = []):
analysis_test(
name = name,
impl = _build_failure_test_impl,
target = target,
expect_failure = True,
attrs = {
"_message": attr.string(default = message),
"_messages": attr.string_list(default = messages),
},
)
build_failure_test = _build_failure_test_macro
def _paths_test_impl(env, _):
env.expect.that_bool(_check_if_target_under_path(Label("//foo"), Label("//bar"))).equals(False)
env.expect.that_bool(_check_if_target_under_path(Label("@@foo//foo"), Label("@@bar//bar"))).equals(False)
env.expect.that_bool(_check_if_target_under_path(Label("//bar"), Label("@@foo//bar"))).equals(False)
env.expect.that_bool(_check_if_target_under_path(Label("@@foo//bar"), Label("@@foo//bar"))).equals(True)
env.expect.that_bool(_check_if_target_under_path(Label("@@foo//bar:bar"), Label("@@foo//bar"))).equals(True)
env.expect.that_bool(_check_if_target_under_path(Label("//bar:bar"), Label("//bar"))).equals(True)
env.expect.that_bool(_check_if_target_under_path(Label("@@foo//bar/baz"), Label("@@foo//bar"))).equals(False)
env.expect.that_bool(_check_if_target_under_path(Label("@@foo//bar/baz"), Label("@@foo//bar:__pkg__"))).equals(False)
env.expect.that_bool(_check_if_target_under_path(Label("@@foo//bar/baz"), Label("@@foo//bar:__subpackages__"))).equals(True)
env.expect.that_bool(_check_if_target_under_path(Label("@@foo//bar:qux"), Label("@@foo//bar:__pkg__"))).equals(True)
env.expect.that_bool(_check_if_target_under_path(Label("@@foo//bar"), Label("@@foo//bar/baz:__subpackages__"))).equals(False)
env.expect.that_bool(_check_if_target_under_path(Label("//bar"), Label("//bar/baz:__pkg__"))).equals(False)
env.expect.that_bool(_check_if_target_under_path(Label("//foo/bar:baz"), Label("//:__subpackages__"))).equals(True)
def _paths_test_macro(name):
native.cc_library(
name = "dummy",
)
analysis_test(
name = name,
impl = _paths_test_impl,
target = ":dummy",
)
paths_test = _paths_test_macro
def _runfiles_test_impl(env, target):
if not env.ctx.target_platform_has_constraint(env.ctx.attr._is_linux[platform_common.ConstraintValueInfo]):
return
expected_basenames = [
"libfoo_so.so",
"libbar_so.so",
"libdiff_pkg_so.so",
"libdirect_so_file.so",
"libprivate_lib_so.so",
"renamed_so_file_copy.so",
"Smain_Sstarlark_Stests_Sbuiltins_Ubzl_Scc_Scc_Ushared_Ulibrary_Stest_Ucc_Ushared_Ulibrary_Slibfoo_Uso.so",
"Smain_Sstarlark_Stests_Sbuiltins_Ubzl_Scc_Scc_Ushared_Ulibrary_Stest_Ucc_Ushared_Ulibrary_Slibbar_Uso.so",
"Smain_Sstarlark_Stests_Sbuiltins_Ubzl_Scc_Scc_Ushared_Ulibrary_Stest_Ucc_Ushared_Ulibrary3_Slibdiff_Upkg_Uso.so",
]
runfiles = [file.path for file in target[DefaultInfo].default_runfiles.files.to_list()]
for runfile in runfiles:
# Ignore Python runfiles
if "python" in runfile:
continue
found_basename = False
for expected_basename in expected_basenames:
if runfile.endswith(expected_basename):
found_basename = True
break
env.expect.where(
detail = runfile + " not found in expected basenames:\n" + "\n".join(expected_basenames),
).that_bool(found_basename).equals(True)
# Match e.g. bazel-out/k8-fastbuild/bin/src/main/starlark/tests/builtins_bzl/cc/cc_shared_library/test_cc_shared_library/libdirect_so_file.so
path_suffix = "/main/starlark/tests/builtins_bzl/cc/cc_shared_library/test_cc_shared_library"
expected_files = [
"libbar_so.so",
"libdirect_so_file.so",
"libfoo_so.so",
"libprivate_lib_so.so",
"python_test",
"renamed_so_file_copy.so",
]
for file in expected_files:
path = path_suffix + "/" + file
found = False
for runfile in runfiles:
if runfile.endswith(path):
found = True
break
env.expect.where(
detail = file + " not found in runfiles:\n" + "\n".join(runfiles),
).that_bool(found).equals(True)
def _runfiles_test_macro(name, target):
analysis_test(
name = name,
impl = _runfiles_test_impl,
target = target,
attrs = {
"_is_linux": attr.label(default = "@platforms//os:linux"),
},
)
runfiles_test = _runfiles_test_macro
def _interface_library_output_group_test_impl(env, target):
if not env.ctx.target_platform_has_constraint(env.ctx.attr._is_windows[platform_common.ConstraintValueInfo]):
return
actual_files = [interface_library.basename for interface_library in target[OutputGroupInfo].interface_library.to_list()]
env.expect.that_collection(actual_files).contains_exactly_predicates([
matching.contains("foo_so.if.lib"),
])
def _interface_library_output_group_test_macro(name, target):
analysis_test(
name = name,
impl = _interface_library_output_group_test_impl,
target = target,
attrs = {
"_is_windows": attr.label(default = "@platforms//os:windows"),
},
)
interface_library_output_group_test = _interface_library_output_group_test_macro
def _check_linking_action_lib_parameters_test_impl(env, target):
target_action = None
for action in target.actions:
if action.mnemonic == "FileWrite":
target_action = action
break
args = target_action.content.split("\n")
for arg in args:
for bad_lib_entry in env.ctx.attr._libs_that_shouldnt_be_present:
env.expect.where(
detail = "Should not have seen library `{}` in command line".format(arg),
).that_int(arg.find("{}.".format(bad_lib_entry))).equals(-1)
def _check_linking_action_lib_parameters_test_macro(name, target, libs_that_shouldnt_be_present):
analysis_test(
name = name,
impl = _check_linking_action_lib_parameters_test_impl,
target = target,
attrs = {
"_libs_that_shouldnt_be_present": attr.string_list(default = libs_that_shouldnt_be_present),
},
)
check_linking_action_lib_parameters_test = _check_linking_action_lib_parameters_test_macro
AspectCcInfo = provider("Takes a cc_info.", fields = {"cc_info": "cc_info"})
WrappedCcInfo = provider("Takes a cc_info.", fields = {"cc_info": "cc_info"})
def _forwarding_cc_lib_aspect_impl(target, ctx):
cc_info = target[WrappedCcInfo].cc_info
linker_inputs = []
owner = ctx.label.relative(ctx.label.name + ".custom")
for linker_input in cc_info.linking_context.linker_inputs.to_list():
if linker_input.owner == ctx.label.relative("indirect_dep"):
linker_inputs.append(cc_common.create_linker_input(
owner = owner,
libraries = depset(linker_input.libraries),
))
else:
linker_inputs.append(linker_input)
cc_info = CcInfo(
compilation_context = cc_info.compilation_context,
linking_context = cc_common.create_linking_context(
linker_inputs = depset(linker_inputs),
),
)
providers = [AspectCcInfo(cc_info = cc_info)]
if hasattr(cc_common, "CcSharedLibraryHintInfo"):
providers.append(cc_common.CcSharedLibraryHintInfo(
owners = [owner],
))
return providers
forwarding_cc_lib_aspect = aspect(
implementation = _forwarding_cc_lib_aspect_impl,
required_providers = [WrappedCcInfo],
provides = [AspectCcInfo, CcSharedLibraryHintInfo],
)
def _wrapped_cc_lib_impl(ctx):
descriptor_set = ctx.actions.declare_file("fake.descriptor_set")
ctx.actions.write(descriptor_set, "")
return [WrappedCcInfo(cc_info = ctx.attr.deps[0][CcInfo]), ProtoInfo(srcs = [], deps = [], descriptor_set = descriptor_set)]
wrapped_cc_lib = rule(
implementation = _wrapped_cc_lib_impl,
attrs = {
"deps": attr.label_list(providers = [CcInfo]),
},
provides = [WrappedCcInfo, ProtoInfo],
)
def _forwarding_cc_lib_impl(ctx):
hints = cc_common.CcSharedLibraryHintInfo(attributes = ["deps"])
if ctx.attr.deps:
return [ctx.attr.deps[0][AspectCcInfo].cc_info, hints]
else:
return [ctx.attr.do_not_follow_deps[0][AspectCcInfo].cc_info, hints]
def _get_provides_list():
provides = [CcInfo]
if hasattr(cc_common, "CcSharedLibraryHintInfo"):
provides.append(cc_common.CcSharedLibraryHintInfo)
return provides
forwarding_cc_lib = rule(
implementation = _forwarding_cc_lib_impl,
attrs = {
"deps": attr.label_list(providers = [WrappedCcInfo], aspects = [forwarding_cc_lib_aspect]),
"do_not_follow_deps": attr.label_list(providers = [WrappedCcInfo], aspects = [forwarding_cc_lib_aspect]),
},
provides = _get_provides_list(),
)
def _nocode_cc_lib_impl(ctx):
linker_input = cc_common.create_linker_input(
owner = ctx.label,
additional_inputs = depset([ctx.files.additional_inputs[0]]),
)
cc_info = CcInfo(linking_context = cc_common.create_linking_context(linker_inputs = depset([linker_input])))
return [cc_common.merge_cc_infos(cc_infos = [cc_info, ctx.attr.deps[0][CcInfo]])]
nocode_cc_lib = rule(
implementation = _nocode_cc_lib_impl,
attrs = {
"additional_inputs": attr.label_list(allow_files = True),
"deps": attr.label_list(),
},
provides = [CcInfo],
)
def _exports_test_impl(env, target):
actual = list(target[CcSharedLibraryInfo].exports)
# Remove the @@ prefix on Bazel
for i in range(len(actual)):
if actual[i].startswith("@@"):
actual[i] = actual[i][2:]
expected = env.ctx.attr._targets_that_should_be_claimed_to_be_exported
env.expect.where(
detail = "Exports lists do not match.",
).that_collection(actual).contains_exactly(expected).in_order()
def _exports_test_macro(name, target, targets_that_should_be_claimed_to_be_exported):
analysis_test(
name = name,
impl = _exports_test_impl,
target = target,
attrs = {
"_targets_that_should_be_claimed_to_be_exported": attr.string_list(default = targets_that_should_be_claimed_to_be_exported),
},
)
exports_test = _exports_test_macro