Introduces static_deps attribute to cc_shared_library.

This removes the need for linked_statically_by, although both attributes work for now. The attribute linked_statically_by will disappear from cc_library and instead we will add exported_by which will allow library authors to retain the power of deciding who exports their library.

When a library is linked statically by more than one shared library and this library was only supposed to be linked once, i.e. it contains static initializers, then Bazel will give an error.

PiperOrigin-RevId: 293319906
Change-Id: I3713511e09feffb9429d38ac3ae868498ed4afe6
diff --git a/examples/experimental_cc_shared_library.bzl b/examples/experimental_cc_shared_library.bzl
index 8bba272..80d4adc 100644
--- a/examples/experimental_cc_shared_library.bzl
+++ b/examples/experimental_cc_shared_library.bzl
@@ -9,10 +9,17 @@
 
 # TODO(#5200): Add export_define to library_to_link and cc_library
 
+# TODO(plf): Even though at the begining the idea was to completely forbid
+# anyone to link a library statically without permission from the library owner,
+# there have been complaints about this. linked_statically_by is hard to use
+# and too restrictive. static_deps will completely replace linked_statically_by.
+# Library authors will still have control of who exports them by using exported_by.
+
 GraphNodeInfo = provider(
     fields = {
         "children": "Other GraphNodeInfo from dependencies of this target",
         "label": "Label of the target visited",
+        # TODO(plf): Completely remove linked_statically_by.
         "linked_statically_by": "The value of this attribute if the target has it",
     },
 )
@@ -23,6 +30,7 @@
         "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",
         "exports": "cc_libraries that are linked statically and exported",
     },
 )
@@ -71,6 +79,7 @@
         dynamic_dep_entry = (
             dep[CcSharedLibraryInfo].exports,
             dep[CcSharedLibraryInfo].linker_input,
+            dep[CcSharedLibraryInfo].static_libs,
         )
         dynamic_deps.append(dynamic_dep_entry)
         transitive_dynamic_deps.append(dep[CcSharedLibraryInfo].dynamic_deps)
@@ -91,6 +100,20 @@
             exports_map[export] = linker_input
     return exports_map
 
+def _build_static_libs_map(merged_shared_library_infos):
+    static_libs_map = {}
+    for entry in merged_shared_library_infos.to_list():
+        static_libs = entry[2]
+        linker_input = entry[1]
+        for static_lib in static_libs:
+            if static_lib in static_libs_map:
+                fail("Two shared libraries in dependencies link the same " +
+                     " library statically. Both " + 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
+
 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:
@@ -119,7 +142,8 @@
         feature_configuration,
         cc_toolchain,
         transitive_exports,
-        preloaded_deps_direct_labels):
+        preloaded_deps_direct_labels,
+        static_libs_map):
     static_linker_inputs = []
     dynamic_linker_inputs = []
 
@@ -156,23 +180,38 @@
                 already_linked_dynamically[str(dynamic_linker_input.owner)] = True
                 dynamic_linker_inputs.append(dynamic_linker_input)
         elif owner in link_statically_labels:
-            can_be_linked_statically = False
-            for linked_statically_by in link_statically_labels[owner]:
-                if linked_statically_by == str(ctx.label):
-                    can_be_linked_statically = True
-                    static_linker_input = linker_input
-                    if owner in direct_exports:
-                        static_linker_input = _wrap_static_library_with_alwayslink(
-                            ctx,
-                            feature_configuration,
-                            cc_toolchain,
-                            linker_input,
-                        )
-                    static_linker_inputs.append(static_linker_input)
-                    break
-            if not can_be_linked_statically:
-                fail("We can't link " +
-                     str(owner) + " either statically or dynamically")
+            if owner in static_libs_map:
+                fail(owner + " is already linked statically in " +
+                     static_libs_map[owner] + " but not exported")
+
+            if owner in direct_exports:
+                static_linker_inputs.append(_wrap_static_library_with_alwayslink(
+                    ctx,
+                    feature_configuration,
+                    cc_toolchain,
+                    linker_input,
+                ))
+            else:
+                can_be_linked_statically = False
+
+                # TODO(plf): This loop will go away when linked_statically_by is
+                # replaced completely by static_deps.
+                for linked_statically_by in link_statically_labels[owner]:
+                    if linked_statically_by == str(ctx.label):
+                        can_be_linked_statically = True
+                        break
+
+                if not can_be_linked_statically:
+                    for static_dep_path in ctx.attr.static_deps:
+                        if owner.startswith(static_dep_path):
+                            can_be_linked_statically = True
+                            break
+
+                if can_be_linked_statically:
+                    static_linker_inputs.append(linker_input)
+                else:
+                    fail("We can't link " +
+                         str(owner) + " either statically or dynamically")
 
     # Every direct dynamic_dep of this rule will be linked dynamically even if we
     # didn't reach a cc_library exported by one of these dynamic_deps. In other
@@ -215,12 +254,14 @@
 
         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)
     (static_linker_inputs, dynamic_linker_inputs) = _filter_inputs(
         ctx,
         feature_configuration,
         cc_toolchain,
         exports_map,
         preloaded_deps_direct_labels,
+        static_libs_map,
     )
 
     linking_context = _create_linker_context(ctx, static_linker_inputs, dynamic_linker_inputs)
@@ -258,6 +299,10 @@
     for export in ctx.attr.exports:
         exports.append(str(export.label))
 
+    static_libs = []
+    for static_linker_input in static_linker_inputs:
+        static_libs.append(str(static_linker_input.owner))
+
     return [
         DefaultInfo(
             files = depset([linking_outputs.library_to_link.resolved_symlink_dynamic_library]),
@@ -266,6 +311,7 @@
         CcSharedLibraryInfo(
             dynamic_deps = merged_cc_shared_library_info,
             exports = exports,
+            static_libs = static_libs,
             linker_input = cc_common.create_linker_input(
                 owner = ctx.label,
                 libraries = depset([linking_outputs.library_to_link]),
@@ -302,6 +348,10 @@
     attrs = {
         "dynamic_deps": attr.label_list(providers = [CcSharedLibraryInfo]),
         "preloaded_deps": attr.label_list(providers = [CcInfo]),
+        # TODO(plf): Replaces linked_statically_by attribute. Instead of
+        # linked_statically_by attribute in each cc_library we will have the
+        # attribute exported_by which will restrict exportability.
+        "static_deps": attr.string_list(),
         "user_link_flags": attr.string_list(),
         "visibility_file": attr.label(allow_single_file = True),
         "exports": attr.label_list(providers = [CcInfo], aspects = [graph_structure_aspect]),
diff --git a/examples/test_cc_shared_library/BUILD b/examples/test_cc_shared_library/BUILD
index b45ca15..12a20a1 100644
--- a/examples/test_cc_shared_library/BUILD
+++ b/examples/test_cc_shared_library/BUILD
@@ -19,6 +19,9 @@
     name = "foo_so",
     dynamic_deps = ["bar_so"],
     preloaded_deps = ["preloaded_dep"],
+    static_deps = [
+        "//examples/test_cc_shared_library:qux",
+    ],
     user_link_flags = ["-Wl,-rpath,kittens"],
     visibility_file = "foo.lds",
     exports = [
@@ -51,7 +54,6 @@
     name = "foo",
     srcs = ["foo.cc"],
     hdrs = ["foo.h"],
-    linked_statically_by = ["//examples/test_cc_shared_library:foo_so"],
     deps = [
         "preloaded_dep",
         "bar",
@@ -72,7 +74,6 @@
     name = "qux",
     srcs = ["qux.cc"],
     hdrs = ["qux.h"],
-    linked_statically_by = ["//examples/test_cc_shared_library:foo_so"],
 )
 
 cc_shared_library(
@@ -88,39 +89,24 @@
     name = "bar",
     srcs = ["bar.cc"],
     hdrs = ["bar.h"],
-    linked_statically_by = [
-        "//examples/test_cc_shared_library:bar_so",
-    ],
 )
 
 cc_library(
     name = "bar2",
     srcs = ["bar2.cc"],
     hdrs = ["bar2.h"],
-    linked_statically_by = [
-        "//examples/test_cc_shared_library:bar_so",
-        "//examples/test_cc_shared_library:foo_so",
-    ],
 )
 
 cc_library(
     name = "bar3",
     srcs = ["bar3.cc"],
     hdrs = ["bar3.h"],
-    linked_statically_by = [
-        "//examples/test_cc_shared_library:bar_so",
-        "//examples/test_cc_shared_library:foo_so",
-    ],
 )
 
 cc_library(
     name = "bar4",
     srcs = ["bar4.cc"],
     hdrs = ["bar4.h"],
-    linked_statically_by = [
-        "//examples/test_cc_shared_library:bar_so",
-        "//examples/test_cc_shared_library:foo_so",
-    ],
 )
 
 sh_test(