Allows libraries to be linked more than once.

Libraries are tagged with LINKABLE_MORE_THAN_ONCE can be linked into more than
one shared library and not give an error.

RELNOTES:none
PiperOrigin-RevId: 297352891
Change-Id: Id32b5c341bfd9d5906d67216773e82b0d8b63faf
diff --git a/examples/experimental_cc_shared_library.bzl b/examples/experimental_cc_shared_library.bzl
index 07ea515..fedb4ca 100644
--- a/examples/experimental_cc_shared_library.bzl
+++ b/examples/experimental_cc_shared_library.bzl
@@ -9,22 +9,32 @@
 
 # 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(
     fields = {
         "children": "Other GraphNodeInfo from dependencies of this target",
         "exported_by": "Labels of targets that can export the library of this node",
         "label": "Label of the target visited",
+        "linkable_more_than_once": "Linkable into more than a single cc_shared_library",
     },
 )
 CcSharedLibraryInfo = provider(
     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",
-        "static_libs": "All libraries linked statically into this library",
     },
 )
 
@@ -47,7 +57,7 @@
         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] = True
+            link_statically_labels[node_label] = node.linkable_more_than_once
             all_children.extend(node.children)
     return (link_statically_labels, link_dynamically_labels)
 
@@ -67,7 +77,7 @@
         dynamic_dep_entry = (
             dep[CcSharedLibraryInfo].exports,
             dep[CcSharedLibraryInfo].linker_input,
-            dep[CcSharedLibraryInfo].static_libs,
+            dep[CcSharedLibraryInfo].link_once_static_libs,
         )
         dynamic_deps.append(dynamic_dep_entry)
         transitive_dynamic_deps.append(dep[CcSharedLibraryInfo].dynamic_deps)
@@ -88,19 +98,19 @@
             exports_map[export] = linker_input
     return exports_map
 
-def _build_static_libs_map(merged_shared_library_infos):
-    static_libs_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():
-        static_libs = entry[2]
+        link_once_static_libs = entry[2]
         linker_input = entry[1]
-        for static_lib in static_libs:
-            if static_lib in static_libs_map:
+        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 " + static_libs_map[static_lib] +
+                     " library statically. Both " + link_once_static_libs_map[static_lib] +
                      " and " + str(linker_input.owner) +
                      " link statically" + static_lib)
-            static_libs_map[static_lib] = str(linker_input.owner)
-    return static_libs_map
+            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 = []
@@ -147,9 +157,9 @@
         cc_toolchain,
         transitive_exports,
         preloaded_deps_direct_labels,
-        static_libs_map):
+        link_once_static_libs_map):
     linker_inputs = []
-    static_libs = []
+    link_once_static_libs = []
 
     graph_structure_aspect_nodes = []
     dependency_linker_inputs = []
@@ -181,9 +191,9 @@
             dynamic_linker_input = transitive_exports[owner]
             linker_inputs.append(dynamic_linker_input)
         elif owner in link_statically_labels:
-            if owner in static_libs_map:
+            if owner in link_once_static_libs_map:
                 fail(owner + " is already linked statically in " +
-                     static_libs_map[owner] + " but not exported")
+                     link_once_static_libs_map[owner] + " but not exported")
 
             if owner in direct_exports:
                 wrapped_library = _wrap_static_library_with_alwayslink(
@@ -193,7 +203,8 @@
                     linker_input,
                 )
 
-                static_libs.append(owner)
+                if not link_statically_labels[owner]:
+                    link_once_static_libs.append(owner)
                 linker_inputs.append(wrapped_library)
             else:
                 can_be_linked_statically = False
@@ -206,13 +217,14 @@
                         can_be_linked_statically = True
                         break
                 if can_be_linked_statically:
-                    static_libs.append(owner)
+                    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 (linker_inputs, static_libs)
+    return (linker_inputs, link_once_static_libs)
 
 def _same_package_or_above(label_a, label_b):
     if label_a.workspace_name != label_b.workspace_name:
@@ -267,15 +279,15 @@
 
         preloaded_dep_merged_cc_info = cc_common.merge_cc_infos(cc_infos = preloaded_deps_cc_infos)
 
-    static_libs_map = _build_static_libs_map(merged_cc_shared_library_info)
+    link_once_static_libs_map = _build_link_once_static_libs_map(merged_cc_shared_library_info)
 
-    (linker_inputs, static_libs) = _filter_inputs(
+    (linker_inputs, link_once_static_libs) = _filter_inputs(
         ctx,
         feature_configuration,
         cc_toolchain,
         exports_map,
         preloaded_deps_direct_labels,
-        static_libs_map,
+        link_once_static_libs_map,
     )
 
     linking_context = _create_linker_context(ctx, linker_inputs)
@@ -313,7 +325,7 @@
         CcSharedLibraryInfo(
             dynamic_deps = merged_cc_shared_library_info,
             exports = exports,
-            static_libs = static_libs,
+            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]),
@@ -331,6 +343,7 @@
                 children.append(dep[GraphNodeInfo])
 
     exported_by = []
+    linkable_more_than_once = False
     if hasattr(ctx.rule.attr, "tags"):
         for tag in ctx.rule.attr.tags:
             if tag.startswith("exported_by=") and len(tag) > 12:
@@ -343,11 +356,14 @@
 
                     Label(target)  # Checking synthax is ok.
                 exported_by.append(target)
+            elif tag == LINKABLE_MORE_THAN_ONCE:
+                linkable_more_than_once = True
 
     return [GraphNodeInfo(
         label = ctx.label,
         children = children,
         exported_by = exported_by,
+        linkable_more_than_once = linkable_more_than_once,
     )]
 
 graph_structure_aspect = aspect(
diff --git a/examples/test_cc_shared_library/BUILD b/examples/test_cc_shared_library/BUILD
index e860b5c..6241fad 100644
--- a/examples/test_cc_shared_library/BUILD
+++ b/examples/test_cc_shared_library/BUILD
@@ -1,5 +1,5 @@
 load("//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
-load("//examples:experimental_cc_shared_library.bzl", "cc_shared_library")
+load("//examples:experimental_cc_shared_library.bzl", "LINKABLE_MORE_THAN_ONCE", "cc_shared_library")
 load(":starlark_tests.bzl", "additional_inputs_test", "linking_suffix_test")
 
 cc_test(
@@ -26,6 +26,7 @@
     preloaded_deps = ["preloaded_dep"],
     static_deps = [
         "//examples/test_cc_shared_library:qux",
+        "//examples/test_cc_shared_library:qux2",
     ],
     user_link_flags = [
         "-Wl,-rpath,kittens",
@@ -68,6 +69,7 @@
         "baz",
         # Not exported.
         "qux",
+        "qux2",
     ],
 )
 
@@ -83,6 +85,13 @@
     hdrs = ["qux.h"],
 )
 
+cc_library(
+    name = "qux2",
+    srcs = ["qux2.cc"],
+    hdrs = ["qux2.h"],
+    tags = [LINKABLE_MORE_THAN_ONCE],
+)
+
 cc_shared_library(
     name = "bar_so",
     additional_linker_inputs = [
@@ -91,6 +100,7 @@
     static_deps = [
         "//examples/test_cc_shared_library:barX",
         "@test_repo//:bar",
+        "//examples/test_cc_shared_library:qux2",
     ],
     user_link_flags = [
         "-Wl,--version-script=$(location :bar.lds)",
@@ -117,6 +127,7 @@
     hdrs = ["bar.h"],
     deps = [
         "barX",
+        "qux2",
     ],
 )
 
diff --git a/examples/test_cc_shared_library/qux2.cc b/examples/test_cc_shared_library/qux2.cc
new file mode 100644
index 0000000..34a96dc
--- /dev/null
+++ b/examples/test_cc_shared_library/qux2.cc
@@ -0,0 +1,3 @@
+#include "examples/test_cc_shared_library/qux2.h"
+
+int qux2() { return 42; }
diff --git a/examples/test_cc_shared_library/qux2.h b/examples/test_cc_shared_library/qux2.h
new file mode 100644
index 0000000..1246a11
--- /dev/null
+++ b/examples/test_cc_shared_library/qux2.h
@@ -0,0 +1,6 @@
+#ifndef EXAMPLES_TEST_CC_SHARED_LIBRARY_QUX2_H_
+#define EXAMPLES_TEST_CC_SHARED_LIBRARY_QUX2_H_
+
+int qux2();
+
+#endif  // EXAMPLES_TEST_CC_SHARED_LIBRARY_QUX2_H_