blob: c67e5bc41e0f085752044c031b428efbfecbae73 [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.
"""Implementation of cc_shared_library"""
load(":common/cc/cc_common.bzl", "cc_common")
load(":common/cc/cc_helper.bzl", "cc_helper")
load(":common/cc/cc_info.bzl", "CcInfo")
load(":common/cc/cc_shared_library_hint_info.bzl", "CcSharedLibraryHintInfo")
load(":common/cc/semantics.bzl", "semantics")
load(":common/paths.bzl", "paths")
load(":common/proto/proto_info.bzl", "ProtoInfo")
# 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"
GraphNodeInfo = provider(
"Nodes in the graph of shared libraries.",
fields = {
"children": "Other GraphNodeInfo from dependencies of this target",
"owners": "Owners of the linker inputs in the targets 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",
},
)
def _programmatic_error(message = ""):
fail("Your build has triggered a programmatic error in the cc_shared_library rule. " +
"Please file an issue in https://github.com/bazelbuild/bazel : {}".format(message))
def _sort_linker_inputs(topologically_sorted_labels, label_to_linker_inputs, linker_inputs_count):
# len(label_to_linker_inputs) might not match the topologically_sorted_labels
# size. The latter is looking at nodes in the graph but a node may not
# necessarily contribute any linker_inputs. For example a cc_library with
# no sources and only deps. Every linker_input.owner must be in
# topologically_sorted_labels otherwise there is an error in the rule
# implementation of the target providing that linker_input, e.g. it's
# missing a CcSharedLibraryHintInfo if it had custom owner names for linker
# inputs.
sorted_linker_inputs = []
for label in topologically_sorted_labels:
if label not in label_to_linker_inputs:
# This is ok. It can happen if no linker_inputs
# were added by a node in the graph.
continue
sorted_linker_inputs.extend(label_to_linker_inputs[label])
if len(sorted_linker_inputs) != linker_inputs_count:
owners = []
for sorted_linker_input in sorted_linker_inputs:
owners.append(str(sorted_linker_input.owner))
_programmatic_error("{} vs {}".format(",".join(owners), linker_inputs_count))
return sorted_linker_inputs
# For each target, find out whether it should be linked statically or
# dynamically. The transitive_dynamic_dep_labels parameter is only needed for
# binaries because they link all dynamic_deps (cc_binary|cc_test).
def _separate_static_and_dynamic_link_libraries(
ctx,
direct_children,
can_be_linked_dynamically):
(
transitive_dynamic_dep_labels,
all_dynamic_dep_linker_inputs,
) = _build_map_direct_dynamic_dep_to_transitive_dynamic_deps(ctx)
node = None
all_children = reversed(direct_children)
targets_to_be_linked_statically_map = {}
targets_to_be_linked_dynamically_set = {}
seen_labels = {}
# The cc_shared_library graph is parallel to the cc_library graph.
# Propagation of linker inputs between cc_libraries happens via the CcInfo
# provider. Parallel to this we have cc_shared_libraries which may decide
# different partitions of the cc_library graph.
#
# In a previous implementation of cc_shared_library we relied on the
# topological sort given by flattening
# cc_info.linking_context.linker_inputs.to_list(), however this was wrong
# because the dependencies of a shared library (i.e. a pruned node here)
# influenced the final order.
#
# In order to fix this, the pruning below was changed from breadth-first
# traversal to depth-first traversal. While doing this we also recreate a
# depset with topological order that takes into account the pruned nodes
# and which will later be used to order the libraries in the linking
# command line. This will be in topological order and will respect the
# order of the deps as listed on the BUILD file as much as possible.
#
# Here we say "first_owner" because each node (see GraphNodeInfo) may have
# more than one linker_input (each potentially with a different owner) but
# using only the first owner as a key is enough.
first_owner_to_depset = {}
# Horrible I know. Perhaps Starlark team gives me a way to prune a tree.
for i in range(2147483647):
if not len(all_children):
break
node = all_children[-1]
must_add_children = False
# The *_seen variables are used to track a programmatic error and fail
# if it happens. Every value in node.owners presumably corresponds to
# a linker_input in the same exact target. Therefore if we have seen
# any of the owners already, then we must have also seen all the other
# owners in the same node. Viceversa when we haven't seen them yet. If
# both of these values are non-zero after the loop, the most likely
# reason would be a bug in the implementation. It could potentially be
# triggered by users if they use owner labels that do not keep most of
# the ctx.label.package and ctx.label.name which then clash with other
# target's owners (unlikely). For now though if the error is
# triggered, it's reasonable to require manual revision by
# the cc_shared_library implementation owners.
has_owners_seen = False
has_owners_not_seen = False
linked_dynamically = False
linked_statically = False
for owner in node.owners:
# TODO(bazel-team): Do not convert Labels to string to save on
# garbage string allocations.
owner_str = str(owner)
if owner_str in seen_labels:
has_owners_seen = True
continue
has_owners_not_seen = True
seen_labels[owner_str] = True
if owner_str in can_be_linked_dynamically:
targets_to_be_linked_dynamically_set[owner_str] = True
linked_dynamically = True
else:
targets_to_be_linked_statically_map[owner_str] = node.linkable_more_than_once
must_add_children = True
linked_statically = True
if has_owners_seen and has_owners_not_seen:
_programmatic_error()
if linked_dynamically and linked_statically:
error_owners_list = [str(owner) for owner in node.owners]
# Our granularity is target level. Unless there is a different
# unsupported custom implementation of this rule it should be
# impossible for two linker_inputs from the same target to be
# linked differently, one statically and the other dynamically.
_programmatic_error(
message = "Nodes with linker_inputs linked statically and dynamically:" +
"\n{}".format("\n".join(error_owners_list)),
)
if must_add_children:
# The order in which we process the children matter. all_children
# is being used as a stack, we will process first the nodes at the
# top of the stack (last in the list). The children are the
# dependencies of the current node, in order to respect the order
# in which dependencies were listed in the deps attribute in the
# BUILD file we must reverse the list so that the first one listed
# in the BUILD file is processed first.
all_children.extend(reversed(node.children))
else:
if node.owners[0] not in first_owner_to_depset:
# We have 3 cases in this branch:
# 1. Node has no children
# 2. The children have been pruned because the node is linked dynamically
# 3. Node has children that have been processed
# For case 3 we add the children's depsets. For case 2 we add the dynamic
# dep labels for transitive dynamic deps.
transitive = []
if str(node.owners[0]) in targets_to_be_linked_statically_map:
for child in node.children:
transitive.append(first_owner_to_depset[child.owners[0]])
elif str(node.owners[0]) in transitive_dynamic_dep_labels:
transitive.append(transitive_dynamic_dep_labels[str(node.owners[0])])
first_owner_to_depset[node.owners[0]] = depset(direct = node.owners, transitive = transitive, order = "topological")
all_children.pop()
topologically_sorted_labels = []
if direct_children:
transitive = []
for child in direct_children:
transitive.append(first_owner_to_depset[child.owners[0]])
topologically_sorted_labels = depset(transitive = transitive, order = "topological").to_list()
return (targets_to_be_linked_statically_map, targets_to_be_linked_dynamically_set, topologically_sorted_labels, all_dynamic_dep_linker_inputs)
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:
dynamic_dep_entry = struct(
exports = dep[CcSharedLibraryInfo].exports,
linker_input = dep[CcSharedLibraryInfo].linker_input,
link_once_static_libs = 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, order = "topological")
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.exports
linker_input = entry.linker_input
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
# The map points from the target that can only be linked once to the
# cc_shared_library target that already links it.
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.link_once_static_libs
linker_input = entry.linker_input
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 _is_dynamic_only(library_to_link):
if (library_to_link.static_library == None and
library_to_link.pic_static_library == None and
(library_to_link.objects == None or len(library_to_link.objects) == 0) and
(library_to_link.pic_objects == None or len(library_to_link.pic_objects) == 0)):
return True
return False
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:
if _is_dynamic_only(old_library_to_link):
new_libraries_to_link.append(old_library_to_link)
continue
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,
objects = old_library_to_link.objects,
pic_static_library = old_library_to_link.pic_static_library,
pic_objects = old_library_to_link.pic_objects,
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 _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 _check_if_target_should_be_exported_with_filter(target, current_label, exports_filter):
for export_filter in exports_filter:
export_filter_label = current_label.relative(export_filter)
if _check_if_target_under_path(target, export_filter_label):
return True
return False
# Checks if the linker_input has code to link statically, i.e. either
# archives or object files, ignores library.dynamic_library.
def _contains_code_to_link(linker_input):
for library in linker_input.libraries:
if (library.static_library != None or
library.pic_static_library != None or
len(library.objects) or len(library.pic_objects)):
return True
return False
def _find_top_level_linker_input_labels(
nodes,
linker_inputs_to_be_linked_statically_map,
targets_to_be_linked_dynamically_set):
top_level_linker_input_labels_set = {}
nodes_to_check = list(nodes)
seen_nodes_set = {}
for i in range(2147483647):
if i == len(nodes_to_check):
break
node = nodes_to_check[i]
must_add_children = False
node_str_owners = []
for owner in node.owners:
owner_str = str(owner)
node_str_owners.append(owner_str)
if owner_str in linker_inputs_to_be_linked_statically_map:
must_add_children = True
for linker_input in linker_inputs_to_be_linked_statically_map[owner_str]:
if _contains_code_to_link(linker_input):
top_level_linker_input_labels_set[owner_str] = True
must_add_children = False
break
elif owner_str not in targets_to_be_linked_dynamically_set:
# This can happen when there was a target in the graph that exported other libraries'
# linker_inputs but didn't contribute any linker_input of its own.
must_add_children = True
node_key = "".join(node_str_owners)
if must_add_children and node_key not in seen_nodes_set:
nodes_to_check.extend(node.children)
seen_nodes_set[node_key] = True
return top_level_linker_input_labels_set
def _filter_inputs(
ctx,
feature_configuration,
cc_toolchain,
deps,
transitive_exports,
link_once_static_libs_map):
curr_link_once_static_libs_set = {}
graph_structure_aspect_nodes = []
dependency_linker_inputs_sets = []
direct_deps_set = {}
for dep in deps:
direct_deps_set[str(dep.label)] = True
dependency_linker_inputs_sets.append(dep[CcInfo].linking_context.linker_inputs)
graph_structure_aspect_nodes.append(dep[GraphNodeInfo])
if ctx.attr.experimental_disable_topo_sort_do_not_use_remove_before_7_0:
dependency_linker_inputs = depset(transitive = dependency_linker_inputs_sets).to_list()
else:
dependency_linker_inputs = depset(transitive = dependency_linker_inputs_sets, order = "topological").to_list()
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
# The targets_to_be_linked_statically_map points to whether the target to
# be linked statically can be linked more than once.
# Entries in unused_dynamic_linker_inputs will be marked None if they are
# used
(
targets_to_be_linked_statically_map,
targets_to_be_linked_dynamically_set,
topologically_sorted_labels,
unused_dynamic_linker_inputs,
) = _separate_static_and_dynamic_link_libraries(
ctx,
graph_structure_aspect_nodes,
can_be_linked_dynamically,
)
linker_inputs_to_be_linked_statically_map = {}
for linker_input in dependency_linker_inputs:
owner = str(linker_input.owner)
if owner in targets_to_be_linked_statically_map:
linker_inputs_to_be_linked_statically_map.setdefault(owner, []).append(linker_input)
top_level_linker_input_labels_set = _find_top_level_linker_input_labels(
graph_structure_aspect_nodes,
linker_inputs_to_be_linked_statically_map,
targets_to_be_linked_dynamically_set,
)
# We keep track of precompiled_only_dynamic_libraries, so that we can add
# them to runfiles.
precompiled_only_dynamic_libraries = []
exports = {}
linker_inputs_seen = {}
linker_inputs_count = 0
label_to_linker_inputs = {}
experimental_remove_before_7_0_linker_inputs = []
def _add_linker_input_to_dict(owner, linker_input):
experimental_remove_before_7_0_linker_inputs.append(linker_input)
label_to_linker_inputs.setdefault(owner, []).append(linker_input)
# We use this dictionary to give an error if a target containing only
# precompiled dynamic libraries is placed directly in roots. If such a
# precompiled dynamic library is needed it would be because a target in the
# parallel cc_library graph actually needs it. Therefore the precompiled
# dynamic library should be made a dependency of that cc_library instead.
dynamic_only_roots = {}
linked_statically_but_not_exported = {}
for linker_input in dependency_linker_inputs:
stringified_linker_input = cc_helper.stringify_linker_input(linker_input)
if stringified_linker_input in linker_inputs_seen:
continue
linker_inputs_seen[stringified_linker_input] = True
owner = str(linker_input.owner)
if owner in targets_to_be_linked_dynamically_set:
unused_dynamic_linker_inputs[transitive_exports[owner].owner] = None
# Link the library in this iteration dynamically,
# transitive_exports contains the artifacts produced by a
# cc_shared_library
_add_linker_input_to_dict(linker_input.owner, transitive_exports[owner])
linker_inputs_count += 1
elif owner in targets_to_be_linked_statically_map:
if semantics.is_bazel and not linker_input.libraries:
# TODO(bazel-team): semantics.should_create_empty_archive() should be
# cleaned up and return False in every case. cc_libraries shouldn't
# produce empty archives. For now issue #19920 is only fixed in Bazel.
continue
if owner in link_once_static_libs_map:
# We are building a dictionary that will allow us to give
# proper errors for libraries that have been linked multiple
# times elsewhere but haven't been exported. The values in the
# link_once_static_libs_map dictionary are the
# cc_shared_library targets. In this iteration we know of at
# least one target (i.e. owner) which is being linked
# statically by the cc_shared_library
# link_once_static_libs_map[owner] but is not being exported
linked_statically_but_not_exported.setdefault(link_once_static_libs_map[owner], []).append(owner)
dynamic_only_libraries = []
static_libraries = []
for library in linker_input.libraries:
if _is_dynamic_only(library):
dynamic_only_libraries.append(library)
else:
static_libraries.append(library)
if len(dynamic_only_libraries):
precompiled_only_dynamic_libraries.extend(dynamic_only_libraries)
if not len(static_libraries):
if owner in direct_deps_set:
dynamic_only_roots[owner] = True
_add_linker_input_to_dict(linker_input.owner, linker_input)
linker_inputs_count += 1
continue
if len(static_libraries) and owner in dynamic_only_roots:
dynamic_only_roots.pop(owner)
linker_input_to_be_linked_statically = linker_input
if owner in top_level_linker_input_labels_set:
linker_input_to_be_linked_statically = _wrap_static_library_with_alwayslink(
ctx,
feature_configuration,
cc_toolchain,
linker_input,
)
if _check_if_target_should_be_exported_with_filter(
linker_input.owner,
ctx.label,
ctx.attr.exports_filter,
):
exports[owner] = True
_add_linker_input_to_dict(linker_input.owner, linker_input_to_be_linked_statically)
linker_inputs_count += 1
if not targets_to_be_linked_statically_map[owner]:
curr_link_once_static_libs_set[owner] = True
if dynamic_only_roots:
message = ("Do not place libraries which only contain a " +
"precompiled dynamic library in roots. The following " +
"libraries only have precompiled dynamic libraries:\n")
for dynamic_only_root in dynamic_only_roots:
message += dynamic_only_root + "\n"
fail(message)
linker_inputs_count += _add_unused_dynamic_deps(ctx, unused_dynamic_linker_inputs, _add_linker_input_to_dict, topologically_sorted_labels, link_indirect_deps = False)
if ctx.attr.experimental_disable_topo_sort_do_not_use_remove_before_7_0:
linker_inputs = experimental_remove_before_7_0_linker_inputs
else:
linker_inputs = _sort_linker_inputs(
topologically_sorted_labels,
label_to_linker_inputs,
linker_inputs_count,
)
_throw_linked_but_not_exported_errors(linked_statically_but_not_exported)
return (exports, linker_inputs, curr_link_once_static_libs_set.keys(), precompiled_only_dynamic_libraries)
def _throw_linked_but_not_exported_errors(error_libs_dict):
if not error_libs_dict:
return
error_builder = ["The following libraries were linked statically by different cc_shared_libraries but not exported:\n"]
for cc_shared_library_target, error_libs in error_libs_dict.items():
error_builder.append("cc_shared_library %s:\n" % str(cc_shared_library_target))
for error_lib in error_libs:
error_builder.append(" \"%s\",\n" % str(error_lib))
error_builder.append("If you are sure that the previous libraries are exported by the cc_shared_libraries because:\n")
error_builder.append(" 1. You have visibility declarations in the source code\n")
error_builder.append(" 2. Or you are passing a visibility script to the linker to export symbols from them\n")
error_builder.append("then add those libraries to roots or exports_filter for each cc_shared_library.\n")
fail("".join(error_builder))
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 _get_deps(ctx):
if len(ctx.attr.deps) and len(ctx.attr.roots):
fail(
"You are using the attribute 'roots' and 'deps'. 'deps' is the " +
"new name for the attribute 'roots'. The attribute 'roots' will be" +
"removed in the future",
attr = "roots",
)
deps = ctx.attr.deps
if not len(deps):
deps = ctx.attr.roots
if len(deps) == 0:
fail(
"'cc_shared_library' must have at least one dependency in 'deps' (or 'roots')",
attr = "deps",
)
return deps
def _build_map_direct_dynamic_dep_to_transitive_dynamic_deps(ctx):
all_dynamic_dep_linker_inputs = {}
direct_dynamic_dep_to_transitive_dynamic_deps = {}
for dep in ctx.attr.dynamic_deps:
owner = dep[CcSharedLibraryInfo].linker_input.owner
all_dynamic_dep_linker_inputs[owner] = dep[CcSharedLibraryInfo].linker_input
transitive_dynamic_dep_labels = []
for dynamic_dep in dep[CcSharedLibraryInfo].dynamic_deps.to_list():
all_dynamic_dep_linker_inputs[dynamic_dep.linker_input.owner] = dynamic_dep.linker_input
transitive_dynamic_dep_labels.append(dynamic_dep.linker_input.owner)
transitive_dynamic_dep_labels_set = depset(transitive_dynamic_dep_labels, order = "topological")
for export in dep[CcSharedLibraryInfo].exports:
direct_dynamic_dep_to_transitive_dynamic_deps[export] = transitive_dynamic_dep_labels_set
return direct_dynamic_dep_to_transitive_dynamic_deps, all_dynamic_dep_linker_inputs
def _add_unused_dynamic_deps(ctx, unused_dynamic_linker_inputs, add_linker_inputs_lambda, topologically_sorted_labels, link_indirect_deps):
linker_inputs_count = 0
direct_dynamic_dep_labels = {dep[CcSharedLibraryInfo].linker_input.owner: True for dep in ctx.attr.dynamic_deps}
topologically_sorted_labels_set = {label: True for label in topologically_sorted_labels}
for dynamic_linker_input_owner, unused_linker_input in unused_dynamic_linker_inputs.items():
should_link_input = (unused_linker_input and
(link_indirect_deps or dynamic_linker_input_owner in direct_dynamic_dep_labels))
if should_link_input:
add_linker_inputs_lambda(
dynamic_linker_input_owner,
unused_dynamic_linker_inputs[dynamic_linker_input_owner],
)
linker_inputs_count += 1
if dynamic_linker_input_owner not in topologically_sorted_labels_set:
topologically_sorted_labels.append(dynamic_linker_input_owner)
return linker_inputs_count
def _cc_shared_library_impl(ctx):
if not cc_common.check_experimental_cc_shared_library():
if len(ctx.attr.static_deps):
fail(
"This attribute is a no-op and its usage" +
" is forbidden after cc_shared_library is no longer experimental. " +
"Remove it from every cc_shared_library target",
attr = "static_deps",
)
if len(ctx.attr.roots):
fail(
"This attribute has been renamed to 'deps'. Simply rename the" +
" attribute on the target.",
attr = "roots",
)
deps = _get_deps(ctx)
cc_toolchain = cc_helper.find_cpp_toolchain(ctx)
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
requested_features = ctx.features + ["force_no_whole_archive"],
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 deps:
# Do not check for overlap between targets matched by the current
# rule's exports_filter and what is in exports_map. A library in roots
# will have to be linked in statically into the current rule with 100%
# guarantee and it will also have to be exported. Therefore, we must
# check it's not already exported by a different shared library. On the
# other hand, a library in the transitive closure of the current rule
# may be matched by the exports_filter but if it's already exported by
# a dynamic_dep then it won't be linked statically (therefore not give
# an error either) in the current target. The rule will intentionally
# not throw an error in these cases.
if str(export.label) in exports_map:
fail("Trying to export a library already exported by a different shared library: " +
str(export.label))
link_once_static_libs_map = _build_link_once_static_libs_map(merged_cc_shared_library_info)
(exports, linker_inputs, curr_link_once_static_libs_set, precompiled_only_dynamic_libraries) = _filter_inputs(
ctx,
feature_configuration,
cc_toolchain,
deps,
exports_map,
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))
main_output = None
if ctx.attr.shared_lib_name:
main_output = ctx.actions.declare_file(ctx.attr.shared_lib_name)
additional_inputs = []
additional_outputs = []
link_variables = {}
additional_output_groups = {}
pdb_file = None
if cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "generate_pdb_file"):
if ctx.attr.shared_lib_name:
pdb_file = ctx.actions.declare_file(paths.replace_extension(ctx.attr.shared_lib_name, ".pdb"))
else:
pdb_file = ctx.actions.declare_file(ctx.label.name + ".pdb")
additional_outputs.append(pdb_file)
additional_output_groups["pdb_file"] = depset([pdb_file])
if cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "targets_windows"):
object_files = []
for linker_input in linking_context.linker_inputs.to_list():
for library in linker_input.libraries:
if library.pic_static_library != None:
if library.pic_objects != None:
object_files.extend(library.pic_objects)
elif library.static_library != None:
if library.objects != None:
object_files.extend(library.objects)
def_parser = ctx.file._def_parser
generated_def_file = None
if def_parser != None:
generated_def_file = cc_helper.generate_def_file(ctx, def_parser, object_files, ctx.label.name)
additional_output_groups["def_file"] = depset([generated_def_file])
custom_win_def_file = ctx.file.win_def_file
win_def_file = cc_helper.get_windows_def_file_for_linking(ctx, custom_win_def_file, generated_def_file, feature_configuration)
link_variables["def_file_path"] = win_def_file.path
additional_inputs.append(win_def_file)
additional_inputs.extend(ctx.files.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 = additional_inputs,
name = ctx.label.name,
output_type = "dynamic_library",
main_output = main_output,
variables_extension = link_variables,
additional_outputs = additional_outputs,
)
runfiles_files = []
if linking_outputs.library_to_link.resolved_symlink_dynamic_library != None:
runfiles_files.append(linking_outputs.library_to_link.resolved_symlink_dynamic_library)
# This is different to cc_binary(linkshared=1). Bazel never handles the
# linking implicitly for a cc_binary(linkshared=1) but it does so for a cc_shared_library,
# for which it will use the symlink in the solib directory. If we don't add it, a dependent
# linked against it would fail.
runfiles_files.append(linking_outputs.library_to_link.dynamic_library)
runfiles = ctx.runfiles(
files = runfiles_files,
)
for dep in ctx.attr.dynamic_deps:
runfiles = runfiles.merge(dep[DefaultInfo].data_runfiles)
precompiled_only_dynamic_libraries_runfiles = []
for precompiled_dynamic_library in precompiled_only_dynamic_libraries:
# precompiled_dynamic_library.dynamic_library could be None if the library to link just contains
# an interface library which is valid if the actual library is obtained from the system.
if precompiled_dynamic_library.dynamic_library != None:
if precompiled_dynamic_library.resolved_symlink_dynamic_library != None:
precompiled_only_dynamic_libraries_runfiles.append(precompiled_dynamic_library.resolved_symlink_dynamic_library)
else:
precompiled_only_dynamic_libraries_runfiles.append(precompiled_dynamic_library.dynamic_library)
runfiles = runfiles.merge(ctx.runfiles(files = precompiled_only_dynamic_libraries_runfiles))
for export in deps:
exports[str(export.label)] = True
if not semantics.get_experimental_link_static_libraries_once(ctx):
curr_link_once_static_libs_set = {}
library = []
if linking_outputs.library_to_link.resolved_symlink_dynamic_library != None:
library.append(linking_outputs.library_to_link.resolved_symlink_dynamic_library)
else:
library.append(linking_outputs.library_to_link.dynamic_library)
interface_library = []
if linking_outputs.library_to_link.resolved_symlink_interface_library != None:
interface_library.append(linking_outputs.library_to_link.resolved_symlink_interface_library)
elif linking_outputs.library_to_link.interface_library != None:
interface_library.append(linking_outputs.library_to_link.interface_library)
else:
interface_library = library
return [
DefaultInfo(
files = depset(library),
runfiles = runfiles,
),
OutputGroupInfo(
main_shared_library_output = depset(library),
interface_library = depset(interface_library),
**additional_output_groups
),
CcSharedLibraryInfo(
dynamic_deps = merged_cc_shared_library_info,
exports = exports.keys(),
link_once_static_libs = curr_link_once_static_libs_set,
linker_input = cc_common.create_linker_input(
owner = ctx.label,
libraries = depset([linking_outputs.library_to_link] + precompiled_only_dynamic_libraries),
),
),
]
def _graph_structure_aspect_impl(target, ctx):
children = []
attributes = dir(ctx.rule.attr)
owners = [ctx.label]
if CcSharedLibraryHintInfo in target:
attributes = getattr(target[CcSharedLibraryHintInfo], "attributes", dir(ctx.rule.attr))
owners = getattr(target[CcSharedLibraryHintInfo], "owners", [ctx.label])
# Collect graph structure info from any possible deplike attribute. The aspect
# itself applies across every deplike attribute (attr_aspects is *), so enumerate
# over all attributes and consume GraphNodeInfo if available.
for fieldname in attributes:
deps = getattr(ctx.rule.attr, fieldname, None)
if type(deps) == "list":
for dep in deps:
if type(dep) == "Target" and GraphNodeInfo in dep:
children.append(dep[GraphNodeInfo])
elif type(deps) == "Target" and GraphNodeInfo in deps:
children.append(deps[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(
owners = owners,
children = children,
linkable_more_than_once = linkable_more_than_once,
)]
graph_structure_aspect = aspect(
attr_aspects = ["*"],
required_providers = [[CcInfo], [ProtoInfo], [CcSharedLibraryHintInfo]],
required_aspect_providers = [[CcInfo], [CcSharedLibraryHintInfo]],
implementation = _graph_structure_aspect_impl,
)
cc_shared_library = rule(
implementation = _cc_shared_library_impl,
doc = """
<p>It produces a shared library.</p>
<h4 id="cc_shard_library_examples">Example</h4>
<pre class="code">
cc_shared_library(
name = "foo_shared",
deps = [
":foo",
],
dynamic_deps = [
":bar_shared",
],
additional_linker_inputs = [
":foo.lds",
],
user_link_flags = [
"-Wl,--version-script=$(location :foo.lds)",
],
)
cc_library(
name = "foo",
srcs = ["foo.cc"],
hdrs = ["foo.h"],
deps = [
":bar",
":baz",
],
)
cc_shared_library(
name = "bar_shared",
shared_lib_name = "bar.so",
deps = [":bar"],
)
cc_library(
name = "bar",
srcs = ["bar.cc"],
hdrs = ["bar.h"],
)
cc_library(
name = "baz",
srcs = ["baz.cc"],
hdrs = ["baz.h"],
)
</pre>
<p>In the example <code>foo_shared</code> statically links <code>foo</code>
and <code>baz</code>, the latter being a transitive dependency. It doesn't
link <code>bar</code> because it is already provided dynamically by the
<code>dynamic_dep</code> <code>bar_shared</code>.</p>
<p><code>foo_shared</code> uses a linker script *.lds file to control which
symbols should be exported. The <code>cc_shared_library</code> rule logic does
not control which symbols get exported, it only uses what is assumed to be
exported to give errors during analysis phase if two shared libraries export the
same targets.</p>
<p>Every direct dependency of <code>cc_shared_library</code> is assumed to be
exported. Therefore, Bazel assumes during analysis that <code>foo</code> is being
exported by <code>foo_shared</code>. <code>baz</code> is not assumed to be exported
by <code>foo_shared</code>. Every target matched by the <code>exports_filter</code>
is also assumed to be exported.</p>
<p>Every single <code>cc_library</code> in the example should appear at most in one
<code>cc_shared_library</code>. If we wanted to link <code>baz</code> also into
<code>bar_shared</code> we would need to add
<code>tags = ["LINKABLE_MORE_THAN_ONCE"]</code> to <code>baz</code>.</p>
<p>Due to the <code>shared_lib_name</code> attribute, the file produced by
<code>bar_shared</code> will have the name <code>bar.so</code> as opposed
to the name <code>libbar.so</code> that it would have by default on Linux.</p>
<h4 id="cc_shard_library_examples">Errors</h4>
<h5><code>Two shared libraries in dependencies export the same symbols.</code></h5>
<p>This will happen whenever you are creating a target with two different
<code>cc_shared_library</code> dependencies that export the same target. To fix this
you need to stop the libraries from being exported in one of the
<code>cc_shared_library</code> dependencies.</p>
<h5><code>Two shared libraries in dependencies link the same library statically</code></h5>
<p>This will happen whenever you are creating a new <code>cc_shared_library</code> with two
different <code>cc_shared_library</code> dependencies that link the same target statically.
Similar to the error with exports.</p>
<p>One way to fix this is to stop linking the library into one of the
<code>cc_shared_library</code> dependencies. At the same time, the one that still links it
needs to export the library so that the one not linking it keeps visibility to
the symbols. Another way is to pull out a third library that exports the target.
A third way is to tag the culprit <code>cc_library</code> with <code>LINKABLE_MORE_THAN_ONCE</code>
but this fix should be rare and you should absolutely make sure that the
<code>cc_library</code> is indeed safe to link more than once.</p>
<h5><code>'//foo:foo' is already linked statically in '//bar:bar' but not exported`</code></h5>
<p>This means that a library in the transitive closure of your <code>deps</code> is reachable
without going through one of the <code>cc_shared_library</code> dependencies but is already
linked into a different <code>cc_shared_library</code> in <code>dynamic_deps</code> and is not
exported.</p>
<p>The solution is to export it from the <code>cc_shared_library</code> dependency or pull out
a third <code>cc_shared_library</code> that exports it.</p>
<h5><code>Do not place libraries which only contain a precompiled dynamic library in deps.
</code></h5>
<p>If you have a precompiled dynamic library, this doesn't need to and cannot be
linked statically into the current <code>cc_shared_library</code> target that you are
currently creating. Therefore, it doesn't belong in <code>deps</code> of the
<code>cc_shared_library</code>. If this precompiled dynamic library is a dependency of one
of your <code>cc_libraries</code>, then the <code>cc_library</code> needs to depend on it
directly.</p>
<h5><code>Trying to export a library already exported by a different shared library</code></h5>
<p>You will see this error if on the current rule you are claiming to export a
target that is already being exported by one of your dynamic dependencies.</p>
<p>To fix this, remove the target from <code>deps</code> and just rely on it from the dynamic
dependency or make sure that the <code>exports_filter</code> doesn't catch this target.</p>""",
attrs = {
"additional_linker_inputs": attr.label_list(allow_files = True, doc = """
Any additional files that you may want to pass to the linker, for example, linker scripts.
You have to separately pass any linker flags that the linker needs in order to be aware
of this file. You can do so via the <code>user_link_flags</code> attribute."""),
"shared_lib_name": attr.string(doc = """
By default cc_shared_library will use a name for the shared library output file based on
the target's name and the platform. This includes an extension and sometimes a prefix.
Sometimes you may not want the default name, for example, when loading C++ shared libraries
for Python the default lib* prefix is often not desired, in which case you can use this
attribute to choose a custom name."""),
"dynamic_deps": attr.label_list(providers = [CcSharedLibraryInfo], doc = """
These are other <code>cc_shared_library</code> dependencies the current target depends on.
<p>
The <code>cc_shared_library</code> implementation will use the list of
<code>dynamic_deps</code> (transitively, i.e. also the <code>dynamic_deps</code> of the
current target's <code>dynamic_deps</code>) to decide which <code>cc_libraries</code> in
the transitive <code>deps</code> should not be linked in because they are already provided
by a different <code>cc_shared_library</code>.
</p>"""),
"experimental_disable_topo_sort_do_not_use_remove_before_7_0": attr.bool(default = False),
"exports_filter": attr.string_list(doc = """
This attribute contains a list of targets that are claimed to be exported by the current
shared library.
<p>
Any target <code>deps</code> is already understood to be exported by the shared library.
This attribute should be used to list any targets that are exported by the shared library
but are transitive dependencies of <code>deps</code>.
</p>
<p>
Note that this attribute is not actually adding a dependency edge to those targets, the
dependency edge should instead be created by <code>deps</code>.The entries in this
attribute are just strings. Keep in mind that when placing a target in this attribute,
this is considered a claim that the shared library exports the symbols from that target.
The <code>cc_shared_library</code> logic doesn't actually handle telling the linker which
symbols should be exported.
</p>
<p>The following syntax is allowed:</p>
<p><code>//foo:__package__</code> to account for any target in foo/BUILD</p>
<p><code>//foo:__subpackages__</code> to account for any target in foo/BUILD or any other
package below foo/ like foo/bar/BUILD</p>"""),
"win_def_file": attr.label(allow_single_file = [".def"], doc = """
The Windows DEF file to be passed to linker.
<p>This attribute should only be used when Windows is the target platform.
It can be used to <a href="https://msdn.microsoft.com/en-us/library/d91k01sh.aspx">
export symbols</a> during linking a shared library.</p>"""),
"roots": attr.label_list(providers = [CcInfo], aspects = [graph_structure_aspect]),
"deps": attr.label_list(providers = [CcInfo], aspects = [graph_structure_aspect], doc = """
Top level libraries that will unconditionally be statically linked into the shared library
after being whole-archived.
<p>
Any transitive library dependency of these direct deps will be linked into this shared
library as long as they have not already been linked by a <code>cc_shared_library</code>
in <code>dynamic_deps</code>.</p>
<p>
During analysis, the rule implementation will consider any target listed in
<code>deps</code> as being exported by the shared library in order to give errors when
multiple <code>cc_shared_libraries</code> export the same targets. The rule implementation
does not take care of informing the linker about which symbols should be exported by the
shared object. The user should take care of this via linker scripts or visibility
declarations in the source code.</p>
<p>
The implementation will also trigger errors whenever the same library is linked statically
into more than one <code>cc_shared_library</code>. This can be avoided by adding
<code>"LINKABLE_MORE_THAN_ONCE"</code> to the <code>cc_library.tags</code> or by listing
the `cc_library` as an export of one of the shared libraries so that one can be made a
<code>dynamic_dep</code> of the other.
</p>"""),
"static_deps": attr.string_list(),
"user_link_flags": attr.string_list(doc = """
Any additional flags that you may want to pass to the linker. For example, to make the
linker aware of a linker script passed via additional_linker_inputs you can use the
following:
<pre><code class="lang-starlark">
cc_shared_library(
name = "foo_shared",
additional_linker_inputs = select({
"//src/conditions:linux": [
":foo.lds",
":additional_script.txt",
],
"//conditions:default": []}),
user_link_flags = select({
"//src/conditions:linux": [
"-Wl,-rpath,kittens",
"-Wl,--version-script=$(location :foo.lds)",
"-Wl,--script=$(location :additional_script.txt)",
],
"//conditions:default": []}),
...
)
</code></pre>"""),
"_def_parser": semantics.get_def_parser(),
},
toolchains = cc_helper.use_cpp_toolchain(),
fragments = ["cpp"] + semantics.additional_fragments(),
)
def cc_shared_library_initializer(**kwargs):
"""Initializes dynamic_deps_attrs"""
if "dynamic_deps" in kwargs and cc_helper.is_non_empty_list_or_select(kwargs["dynamic_deps"], "dynamic_deps"):
# Propagate an aspect if dynamic_deps attribute is specified.
all_deps = []
if "deps" in kwargs:
all_deps.extend(kwargs["deps"])
if "linkshared" not in kwargs or not kwargs["linkshared"]:
if "link_extra_lib" in kwargs:
all_deps.append(kwargs["link_extra_lib"])
if "malloc" in kwargs:
all_deps.append(kwargs["malloc"])
return kwargs | {"_deps_analyzed_by_graph_structure_aspect": all_deps}
return kwargs
dynamic_deps_attrs = {
"dynamic_deps": attr.label_list(
allow_files = False,
providers = [CcSharedLibraryInfo],
doc = """
These are other <code>cc_shared_library</code> dependencies the current target depends on.
<p>
The <code>cc_shared_library</code> implementation will use the list of
<code>dynamic_deps</code> (transitively, i.e. also the <code>dynamic_deps</code> of the
current target's <code>dynamic_deps</code>) to decide which <code>cc_libraries</code> in
the transitive <code>deps</code> should not be linked in because they are already provided
by a different <code>cc_shared_library</code>.
""",
),
"_deps_analyzed_by_graph_structure_aspect": attr.label_list(
providers = [CcInfo],
aspects = [graph_structure_aspect],
),
}
for_testing_dont_use_check_if_target_under_path = _check_if_target_under_path
merge_cc_shared_library_infos = _merge_cc_shared_library_infos
build_link_once_static_libs_map = _build_link_once_static_libs_map
build_exports_map_from_only_dynamic_deps = _build_exports_map_from_only_dynamic_deps
throw_linked_but_not_exported_errors = _throw_linked_but_not_exported_errors
separate_static_and_dynamic_link_libraries = _separate_static_and_dynamic_link_libraries
sort_linker_inputs = _sort_linker_inputs
add_unused_dynamic_deps = _add_unused_dynamic_deps