mirror of https://github.com/bazelbuild/rules_cc
Make cc_shared_library use the builtin Starlark version
Eventually, when we are ready to pull out all the other rules to an external repository cc_shared_library will be moved with the others: cc_library, cc_binary,... RELNOTES:none PiperOrigin-RevId: 401307125 Change-Id: I14f48a7753598eaa4269554e87351a70f75111c2
This commit is contained in:
parent
8ab61cfb55
commit
aa7ff810cf
|
@ -5,152 +5,20 @@ rely on this. It requires bazel >1.2 and passing the flag
|
|||
--experimental_cc_shared_library
|
||||
"""
|
||||
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
|
||||
load("//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
|
||||
|
||||
# TODO(#5200): Add export_define to library_to_link and cc_library
|
||||
|
||||
# Add this as a tag to any target that can be linked by more than one
|
||||
# cc_shared_library because it doesn't have static initializers or anything
|
||||
# else that may cause issues when being linked more than once. This should be
|
||||
# used sparingly after making sure it's safe to use.
|
||||
LINKABLE_MORE_THAN_ONCE = "LINKABLE_MORE_THAN_ONCE"
|
||||
|
||||
CcSharedLibraryPermissionsInfo = provider(
|
||||
"Permissions for a cc shared library.",
|
||||
fields = {
|
||||
"targets": "Matches targets that can be exported.",
|
||||
},
|
||||
)
|
||||
GraphNodeInfo = provider(
|
||||
"Nodes in the graph of shared libraries.",
|
||||
fields = {
|
||||
"children": "Other GraphNodeInfo from dependencies of this target",
|
||||
"label": "Label of the target visited",
|
||||
"linkable_more_than_once": "Linkable into more than a single cc_shared_library",
|
||||
},
|
||||
)
|
||||
CcSharedLibraryInfo = provider(
|
||||
"Information about a cc shared library.",
|
||||
fields = {
|
||||
"dynamic_deps": "All shared libraries depended on transitively",
|
||||
"exports": "cc_libraries that are linked statically and exported",
|
||||
"link_once_static_libs": "All libraries linked statically into this library that should " +
|
||||
"only be linked once, e.g. because they have static " +
|
||||
"initializers. If we try to link them more than once, " +
|
||||
"we will throw an error",
|
||||
"linker_input": "the resulting linker input artifact for the shared library",
|
||||
"preloaded_deps": "cc_libraries needed by this cc_shared_library that should" +
|
||||
" be linked the binary. If this is set, this cc_shared_library has to " +
|
||||
" be a direct dependency of the cc_binary",
|
||||
},
|
||||
)
|
||||
def cc_shared_library_permissions(**kwargs):
|
||||
native.cc_shared_library_permissions(**kwargs)
|
||||
|
||||
def _separate_static_and_dynamic_link_libraries(
|
||||
direct_children,
|
||||
can_be_linked_dynamically,
|
||||
preloaded_deps_direct_labels):
|
||||
node = None
|
||||
all_children = list(direct_children)
|
||||
link_statically_labels = {}
|
||||
link_dynamically_labels = {}
|
||||
|
||||
seen_labels = {}
|
||||
|
||||
# Horrible I know. Perhaps Starlark team gives me a way to prune a tree.
|
||||
for i in range(2147483647):
|
||||
if i == len(all_children):
|
||||
break
|
||||
|
||||
node = all_children[i]
|
||||
node_label = str(node.label)
|
||||
|
||||
if node_label in seen_labels:
|
||||
continue
|
||||
seen_labels[node_label] = True
|
||||
|
||||
if node_label in can_be_linked_dynamically:
|
||||
link_dynamically_labels[node_label] = True
|
||||
elif node_label not in preloaded_deps_direct_labels:
|
||||
link_statically_labels[node_label] = node.linkable_more_than_once
|
||||
all_children.extend(node.children)
|
||||
|
||||
return (link_statically_labels, link_dynamically_labels)
|
||||
|
||||
def _create_linker_context(ctx, linker_inputs):
|
||||
return cc_common.create_linking_context(
|
||||
linker_inputs = depset(linker_inputs, order = "topological"),
|
||||
)
|
||||
|
||||
def _merge_cc_shared_library_infos(ctx):
|
||||
dynamic_deps = []
|
||||
transitive_dynamic_deps = []
|
||||
for dep in ctx.attr.dynamic_deps:
|
||||
if dep[CcSharedLibraryInfo].preloaded_deps != None:
|
||||
fail("{} can only be a direct dependency of a " +
|
||||
" cc_binary because it has " +
|
||||
"preloaded_deps".format(str(dep.label)))
|
||||
dynamic_dep_entry = (
|
||||
dep[CcSharedLibraryInfo].exports,
|
||||
dep[CcSharedLibraryInfo].linker_input,
|
||||
dep[CcSharedLibraryInfo].link_once_static_libs,
|
||||
)
|
||||
dynamic_deps.append(dynamic_dep_entry)
|
||||
transitive_dynamic_deps.append(dep[CcSharedLibraryInfo].dynamic_deps)
|
||||
|
||||
return depset(direct = dynamic_deps, transitive = transitive_dynamic_deps)
|
||||
|
||||
def _build_exports_map_from_only_dynamic_deps(merged_shared_library_infos):
|
||||
exports_map = {}
|
||||
for entry in merged_shared_library_infos.to_list():
|
||||
exports = entry[0]
|
||||
linker_input = entry[1]
|
||||
for export in exports:
|
||||
if export in exports_map:
|
||||
fail("Two shared libraries in dependencies export the same symbols. Both " +
|
||||
exports_map[export].libraries[0].dynamic_library.short_path +
|
||||
" and " + linker_input.libraries[0].dynamic_library.short_path +
|
||||
" export " + export)
|
||||
exports_map[export] = linker_input
|
||||
return exports_map
|
||||
|
||||
def _build_link_once_static_libs_map(merged_shared_library_infos):
|
||||
link_once_static_libs_map = {}
|
||||
for entry in merged_shared_library_infos.to_list():
|
||||
link_once_static_libs = entry[2]
|
||||
linker_input = entry[1]
|
||||
for static_lib in link_once_static_libs:
|
||||
if static_lib in link_once_static_libs_map:
|
||||
fail("Two shared libraries in dependencies link the same " +
|
||||
" library statically. Both " + link_once_static_libs_map[static_lib] +
|
||||
" and " + str(linker_input.owner) +
|
||||
" link statically" + static_lib)
|
||||
link_once_static_libs_map[static_lib] = str(linker_input.owner)
|
||||
return link_once_static_libs_map
|
||||
|
||||
def _wrap_static_library_with_alwayslink(ctx, feature_configuration, cc_toolchain, linker_input):
|
||||
new_libraries_to_link = []
|
||||
for old_library_to_link in linker_input.libraries:
|
||||
# TODO(#5200): This will lose the object files from a library to link.
|
||||
# Not too bad for the prototype but as soon as the library_to_link
|
||||
# constructor has object parameters this should be changed.
|
||||
new_library_to_link = cc_common.create_library_to_link(
|
||||
actions = ctx.actions,
|
||||
feature_configuration = feature_configuration,
|
||||
cc_toolchain = cc_toolchain,
|
||||
static_library = old_library_to_link.static_library,
|
||||
pic_static_library = old_library_to_link.pic_static_library,
|
||||
alwayslink = True,
|
||||
)
|
||||
new_libraries_to_link.append(new_library_to_link)
|
||||
|
||||
return cc_common.create_linker_input(
|
||||
owner = linker_input.owner,
|
||||
libraries = depset(direct = new_libraries_to_link),
|
||||
user_link_flags = depset(direct = linker_input.user_link_flags),
|
||||
additional_inputs = depset(direct = linker_input.additional_inputs),
|
||||
)
|
||||
def cc_shared_library(**kwargs):
|
||||
native.cc_shared_library(**kwargs)
|
||||
|
||||
# TODO(bazel-team): Everything below this line can be deleted when tests have
|
||||
# been moved under bazel repo
|
||||
def _check_if_target_under_path(value, pattern):
|
||||
if pattern.workspace_name != value.workspace_name:
|
||||
return False
|
||||
|
@ -161,134 +29,6 @@ def _check_if_target_under_path(value, pattern):
|
|||
|
||||
return pattern.package == value.package and pattern.name == value.name
|
||||
|
||||
def _check_if_target_can_be_exported(target, current_label, permissions):
|
||||
if permissions == None:
|
||||
return True
|
||||
|
||||
if (target.workspace_name != current_label.workspace_name or
|
||||
_same_package_or_above(current_label, target)):
|
||||
return True
|
||||
|
||||
matched_by_target = False
|
||||
for permission in permissions:
|
||||
for permission_target in permission[CcSharedLibraryPermissionsInfo].targets:
|
||||
if _check_if_target_under_path(target, permission_target):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _check_if_target_should_be_exported_without_filter(target, current_label, permissions):
|
||||
return _check_if_target_should_be_exported_with_filter(target, current_label, None, permissions)
|
||||
|
||||
def _check_if_target_should_be_exported_with_filter(target, current_label, exports_filter, permissions):
|
||||
should_be_exported = False
|
||||
if exports_filter == None:
|
||||
should_be_exported = True
|
||||
else:
|
||||
for export_filter in exports_filter:
|
||||
export_filter_label = current_label.relative(export_filter)
|
||||
if _check_if_target_under_path(target, export_filter_label):
|
||||
should_be_exported = True
|
||||
break
|
||||
|
||||
if should_be_exported:
|
||||
if _check_if_target_can_be_exported(target, current_label, permissions):
|
||||
return True
|
||||
else:
|
||||
matched_by_filter_text = ""
|
||||
if exports_filter:
|
||||
matched_by_filter_text = " (matched by filter) "
|
||||
fail(str(target) + matched_by_filter_text +
|
||||
" cannot be exported from " + str(current_label) +
|
||||
" because it's not in the same package/subpackage and the library " +
|
||||
"doesn't have the necessary permissions. Use cc_shared_library_permissions.")
|
||||
|
||||
return False
|
||||
|
||||
def _filter_inputs(
|
||||
ctx,
|
||||
feature_configuration,
|
||||
cc_toolchain,
|
||||
transitive_exports,
|
||||
preloaded_deps_direct_labels,
|
||||
link_once_static_libs_map):
|
||||
linker_inputs = []
|
||||
link_once_static_libs = []
|
||||
|
||||
graph_structure_aspect_nodes = []
|
||||
dependency_linker_inputs = []
|
||||
direct_exports = {}
|
||||
for export in ctx.attr.roots:
|
||||
direct_exports[str(export.label)] = True
|
||||
dependency_linker_inputs.extend(export[CcInfo].linking_context.linker_inputs.to_list())
|
||||
graph_structure_aspect_nodes.append(export[GraphNodeInfo])
|
||||
|
||||
can_be_linked_dynamically = {}
|
||||
for linker_input in dependency_linker_inputs:
|
||||
owner = str(linker_input.owner)
|
||||
if owner in transitive_exports:
|
||||
can_be_linked_dynamically[owner] = True
|
||||
|
||||
(link_statically_labels, link_dynamically_labels) = _separate_static_and_dynamic_link_libraries(
|
||||
graph_structure_aspect_nodes,
|
||||
can_be_linked_dynamically,
|
||||
preloaded_deps_direct_labels,
|
||||
)
|
||||
|
||||
exports = {}
|
||||
owners_seen = {}
|
||||
for linker_input in dependency_linker_inputs:
|
||||
owner = str(linker_input.owner)
|
||||
if owner in owners_seen:
|
||||
continue
|
||||
owners_seen[owner] = True
|
||||
if owner in link_dynamically_labels:
|
||||
dynamic_linker_input = transitive_exports[owner]
|
||||
linker_inputs.append(dynamic_linker_input)
|
||||
elif owner in link_statically_labels:
|
||||
if owner in link_once_static_libs_map:
|
||||
fail(owner + " is already linked statically in " +
|
||||
link_once_static_libs_map[owner] + " but not exported")
|
||||
|
||||
if owner in direct_exports:
|
||||
wrapped_library = _wrap_static_library_with_alwayslink(
|
||||
ctx,
|
||||
feature_configuration,
|
||||
cc_toolchain,
|
||||
linker_input,
|
||||
)
|
||||
|
||||
if not link_statically_labels[owner]:
|
||||
link_once_static_libs.append(owner)
|
||||
linker_inputs.append(wrapped_library)
|
||||
else:
|
||||
can_be_linked_statically = False
|
||||
|
||||
for static_dep_path in ctx.attr.static_deps:
|
||||
static_dep_path_label = ctx.label.relative(static_dep_path)
|
||||
if _check_if_target_under_path(linker_input.owner, static_dep_path_label):
|
||||
can_be_linked_statically = True
|
||||
break
|
||||
|
||||
if _check_if_target_should_be_exported_with_filter(
|
||||
linker_input.owner,
|
||||
ctx.label,
|
||||
ctx.attr.exports_filter,
|
||||
_get_permissions(ctx),
|
||||
):
|
||||
exports[owner] = True
|
||||
can_be_linked_statically = True
|
||||
|
||||
if can_be_linked_statically:
|
||||
if not link_statically_labels[owner]:
|
||||
link_once_static_libs.append(owner)
|
||||
linker_inputs.append(linker_input)
|
||||
else:
|
||||
fail("We can't link " +
|
||||
str(owner) + " either statically or dynamically")
|
||||
|
||||
return (exports, linker_inputs, link_once_static_libs)
|
||||
|
||||
def _same_package_or_above(label_a, label_b):
|
||||
if label_a.workspace_name != label_b.workspace_name:
|
||||
return False
|
||||
|
@ -304,174 +44,4 @@ def _same_package_or_above(label_a, label_b):
|
|||
|
||||
return True
|
||||
|
||||
def _get_permissions(ctx):
|
||||
if ctx.attr._enable_permissions_check[BuildSettingInfo].value:
|
||||
return ctx.attr.permissions
|
||||
return None
|
||||
|
||||
def _cc_shared_library_impl(ctx):
|
||||
cc_common.check_experimental_cc_shared_library()
|
||||
cc_toolchain = find_cc_toolchain(ctx)
|
||||
feature_configuration = cc_common.configure_features(
|
||||
ctx = ctx,
|
||||
cc_toolchain = cc_toolchain,
|
||||
requested_features = ctx.features,
|
||||
unsupported_features = ctx.disabled_features,
|
||||
)
|
||||
|
||||
merged_cc_shared_library_info = _merge_cc_shared_library_infos(ctx)
|
||||
exports_map = _build_exports_map_from_only_dynamic_deps(merged_cc_shared_library_info)
|
||||
for export in ctx.attr.roots:
|
||||
if str(export.label) in exports_map:
|
||||
fail("Trying to export a library already exported by a different shared library: " +
|
||||
str(export.label))
|
||||
|
||||
_check_if_target_should_be_exported_without_filter(export.label, ctx.label, _get_permissions(ctx))
|
||||
|
||||
preloaded_deps_direct_labels = {}
|
||||
preloaded_dep_merged_cc_info = None
|
||||
if len(ctx.attr.preloaded_deps) != 0:
|
||||
preloaded_deps_cc_infos = []
|
||||
for preloaded_dep in ctx.attr.preloaded_deps:
|
||||
preloaded_deps_direct_labels[str(preloaded_dep.label)] = True
|
||||
preloaded_deps_cc_infos.append(preloaded_dep[CcInfo])
|
||||
|
||||
preloaded_dep_merged_cc_info = cc_common.merge_cc_infos(cc_infos = preloaded_deps_cc_infos)
|
||||
|
||||
link_once_static_libs_map = _build_link_once_static_libs_map(merged_cc_shared_library_info)
|
||||
|
||||
(exports, linker_inputs, link_once_static_libs) = _filter_inputs(
|
||||
ctx,
|
||||
feature_configuration,
|
||||
cc_toolchain,
|
||||
exports_map,
|
||||
preloaded_deps_direct_labels,
|
||||
link_once_static_libs_map,
|
||||
)
|
||||
|
||||
linking_context = _create_linker_context(ctx, linker_inputs)
|
||||
|
||||
user_link_flags = []
|
||||
for user_link_flag in ctx.attr.user_link_flags:
|
||||
user_link_flags.append(ctx.expand_location(user_link_flag, targets = ctx.attr.additional_linker_inputs))
|
||||
|
||||
linking_outputs = cc_common.link(
|
||||
actions = ctx.actions,
|
||||
feature_configuration = feature_configuration,
|
||||
cc_toolchain = cc_toolchain,
|
||||
linking_contexts = [linking_context],
|
||||
user_link_flags = user_link_flags,
|
||||
additional_inputs = ctx.files.additional_linker_inputs,
|
||||
name = ctx.label.name,
|
||||
output_type = "dynamic_library",
|
||||
)
|
||||
|
||||
runfiles = ctx.runfiles(
|
||||
files = [linking_outputs.library_to_link.resolved_symlink_dynamic_library],
|
||||
)
|
||||
for dep in ctx.attr.dynamic_deps:
|
||||
runfiles = runfiles.merge(dep[DefaultInfo].data_runfiles)
|
||||
|
||||
for export in ctx.attr.roots:
|
||||
exports[str(export.label)] = True
|
||||
|
||||
debug_files = []
|
||||
if ctx.attr._experimental_debug[BuildSettingInfo].value:
|
||||
exports_debug_file = ctx.actions.declare_file(ctx.label.name + "_exports.txt")
|
||||
ctx.actions.write(content = "\n".join(exports.keys()), output = exports_debug_file)
|
||||
|
||||
link_once_static_libs_debug_file = ctx.actions.declare_file(ctx.label.name + "_link_once_static_libs.txt")
|
||||
ctx.actions.write(content = "\n".join(link_once_static_libs), output = link_once_static_libs_debug_file)
|
||||
|
||||
debug_files.append(exports_debug_file)
|
||||
debug_files.append(link_once_static_libs_debug_file)
|
||||
|
||||
if not ctx.attr._incompatible_link_once[BuildSettingInfo].value:
|
||||
link_once_static_libs = []
|
||||
|
||||
return [
|
||||
DefaultInfo(
|
||||
files = depset([linking_outputs.library_to_link.resolved_symlink_dynamic_library] + debug_files),
|
||||
runfiles = runfiles,
|
||||
),
|
||||
CcSharedLibraryInfo(
|
||||
dynamic_deps = merged_cc_shared_library_info,
|
||||
exports = exports.keys(),
|
||||
link_once_static_libs = link_once_static_libs,
|
||||
linker_input = cc_common.create_linker_input(
|
||||
owner = ctx.label,
|
||||
libraries = depset([linking_outputs.library_to_link]),
|
||||
),
|
||||
preloaded_deps = preloaded_dep_merged_cc_info,
|
||||
),
|
||||
]
|
||||
|
||||
def _graph_structure_aspect_impl(target, ctx):
|
||||
children = []
|
||||
|
||||
if hasattr(ctx.rule.attr, "deps"):
|
||||
for dep in ctx.rule.attr.deps:
|
||||
if GraphNodeInfo in dep:
|
||||
children.append(dep[GraphNodeInfo])
|
||||
|
||||
# TODO(bazel-team): Add flag to Bazel that can toggle the initialization of
|
||||
# linkable_more_than_once.
|
||||
linkable_more_than_once = False
|
||||
if hasattr(ctx.rule.attr, "tags"):
|
||||
for tag in ctx.rule.attr.tags:
|
||||
if tag == LINKABLE_MORE_THAN_ONCE:
|
||||
linkable_more_than_once = True
|
||||
|
||||
return [GraphNodeInfo(
|
||||
label = ctx.label,
|
||||
children = children,
|
||||
linkable_more_than_once = linkable_more_than_once,
|
||||
)]
|
||||
|
||||
def _cc_shared_library_permissions_impl(ctx):
|
||||
targets = []
|
||||
for target_filter in ctx.attr.targets:
|
||||
target_filter_label = ctx.label.relative(target_filter)
|
||||
if not _check_if_target_under_path(target_filter_label, ctx.label.relative(":__subpackages__")):
|
||||
fail("A cc_shared_library_permissions rule can only list " +
|
||||
"targets that are in the same package or a sub-package")
|
||||
targets.append(target_filter_label)
|
||||
|
||||
return [CcSharedLibraryPermissionsInfo(
|
||||
targets = targets,
|
||||
)]
|
||||
|
||||
graph_structure_aspect = aspect(
|
||||
attr_aspects = ["*"],
|
||||
implementation = _graph_structure_aspect_impl,
|
||||
)
|
||||
|
||||
cc_shared_library_permissions = rule(
|
||||
implementation = _cc_shared_library_permissions_impl,
|
||||
attrs = {
|
||||
"targets": attr.string_list(),
|
||||
},
|
||||
)
|
||||
|
||||
cc_shared_library = rule(
|
||||
implementation = _cc_shared_library_impl,
|
||||
attrs = {
|
||||
"additional_linker_inputs": attr.label_list(allow_files = True),
|
||||
"dynamic_deps": attr.label_list(providers = [CcSharedLibraryInfo]),
|
||||
"exports_filter": attr.string_list(),
|
||||
"permissions": attr.label_list(providers = [CcSharedLibraryPermissionsInfo]),
|
||||
"preloaded_deps": attr.label_list(providers = [CcInfo]),
|
||||
"roots": attr.label_list(providers = [CcInfo], aspects = [graph_structure_aspect]),
|
||||
"static_deps": attr.string_list(),
|
||||
"user_link_flags": attr.string_list(),
|
||||
"_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
|
||||
"_enable_permissions_check": attr.label(default = "//examples:enable_permissions_check"),
|
||||
"_experimental_debug": attr.label(default = "//examples:experimental_debug"),
|
||||
"_incompatible_link_once": attr.label(default = "//examples:incompatible_link_once"),
|
||||
},
|
||||
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], # copybara-use-repo-external-label
|
||||
fragments = ["cpp"],
|
||||
incompatible_use_toolchain_transition = True,
|
||||
)
|
||||
|
||||
for_testing_dont_use_check_if_target_under_path = _check_if_target_under_path
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||
load("//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
|
||||
load("//examples:experimental_cc_shared_library.bzl", "LINKABLE_MORE_THAN_ONCE", "cc_shared_library", "cc_shared_library_permissions")
|
||||
load("//examples:experimental_cc_shared_library.bzl", "LINKABLE_MORE_THAN_ONCE")
|
||||
load(":starlark_tests.bzl", "additional_inputs_test", "build_failure_test", "linking_suffix_test", "paths_test")
|
||||
|
||||
package(
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
load("//cc:defs.bzl", "cc_binary")
|
||||
load("//examples:experimental_cc_shared_library.bzl", "cc_shared_library")
|
||||
|
||||
cc_shared_library(
|
||||
name = "baz_so",
|
||||
|
|
Loading…
Reference in New Issue