Fix linker inputs for cc_shared_library linking

This CL makes cc_shared_library and cc_binary account for the possibility that
a single rule (i.e. a single owner) may place more than one linker_input in a
linking context. There is no rule saying that this cannot be the case but until
now the implementation of cc_library did place just a single linker input per
owner.

The Starlark implementation of cc_library is slightly different and has made
this bug surface. Rather than change Starlark cc_library we fix
cc_shared_library to have the right behavior. We won't control every C++ rule
people will write using the Starlark API and therefore can't guarantee that
some other rule won't place more than one linker_input per owner.

The existing tests already broke with the Starlark cc_library implementation.

RELNOTES:none
PiperOrigin-RevId: 430195224
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
index 2e381ae..74a62ee 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_binary.bzl
@@ -452,12 +452,13 @@
 
     (link_statically_labels, link_dynamically_labels) = _separate_static_and_dynamic_link_libraries(graph_structure_aspect_nodes, can_be_linked_dynamically)
 
-    owners_seen = {}
+    linker_inputs_seen = {}
     for linker_input in linker_inputs:
-        owner = str(linker_input.owner)
-        if owner in owners_seen:
+        stringified_linker_input = cc_helper.stringify_linker_input(linker_input)
+        if stringified_linker_input in linker_inputs_seen:
             continue
-        owners_seen[owner] = True
+        linker_inputs_seen[stringified_linker_input] = True
+        owner = str(linker_input.owner)
         if owner not in link_dynamically_labels and (owner in link_statically_labels or _get_canonical_form(ctx.label) == owner):
             if owner in link_once_static_libs_map:
                 fail(owner + " is already linked statically in " + link_once_static_libs_map[owner] + " but not exported.")
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
index 97a2691..640c332 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
@@ -435,6 +435,27 @@
         inputs.extend(linker_input.additional_inputs)
     return depset(inputs, order = "topological")
 
+def _stringify_linker_input(linker_input):
+    parts = []
+    parts.append(str(linker_input.owner))
+    for library in linker_input.libraries:
+        if library.static_library != None:
+            parts.append(library.static_library.path)
+        if library.pic_static_library != None:
+            parts.append(library.pic_static_library.path)
+        if library.dynamic_library != None:
+            parts.append(library.dynamic_library.path)
+        if library.interface_library != None:
+            parts.append(library.interface_library.path)
+
+    for additional_input in linker_input.additional_inputs:
+        parts.append(additional_input.path)
+
+    for linkstamp in linker_input.linkstamps:
+        parts.append(linkstamp.file.path)
+
+    return "".join(parts)
+
 cc_helper = struct(
     merge_cc_debug_contexts = _merge_cc_debug_contexts,
     is_code_coverage_enabled = _is_code_coverage_enabled,
@@ -462,4 +483,5 @@
     dll_hash_suffix = _dll_hash_suffix,
     get_windows_def_file_for_linking = _get_windows_def_file_for_linking,
     generate_def_file = _generate_def_file,
+    stringify_linker_input = _stringify_linker_input,
 )
diff --git a/src/main/starlark/builtins_bzl/common/cc/experimental_cc_shared_library.bzl b/src/main/starlark/builtins_bzl/common/cc/experimental_cc_shared_library.bzl
index 7a8c59d..16baf79 100644
--- a/src/main/starlark/builtins_bzl/common/cc/experimental_cc_shared_library.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/experimental_cc_shared_library.bzl
@@ -263,12 +263,14 @@
     precompiled_only_dynamic_libraries = []
     unaccounted_for_libs = []
     exports = {}
-    owners_seen = {}
+    linker_inputs_seen = {}
+    dynamic_only_roots = {}
     for linker_input in dependency_linker_inputs:
-        owner = str(linker_input.owner)
-        if owner in owners_seen:
+        stringified_linker_input = cc_helper.stringify_linker_input(linker_input)
+        if stringified_linker_input in linker_inputs_seen:
             continue
-        owners_seen[owner] = True
+        linker_inputs_seen[stringified_linker_input] = True
+        owner = str(linker_input.owner)
         if owner in link_dynamically_labels:
             dynamic_linker_input = transitive_exports[owner]
             linker_inputs.append(dynamic_linker_input)
@@ -278,7 +280,6 @@
                      link_once_static_libs_map[owner] + " but not exported")
 
             is_direct_export = owner in direct_exports
-
             dynamic_only_libraries = []
             static_libraries = []
             for library in linker_input.libraries:
@@ -288,15 +289,14 @@
                     static_libraries.append(library)
 
             if len(dynamic_only_libraries):
+                precompiled_only_dynamic_libraries.extend(dynamic_only_libraries)
                 if not len(static_libraries):
                     if is_direct_export:
-                        fail("Do not place libraries which only contain a precompiled dynamic library in roots.")
-
-                precompiled_only_dynamic_libraries.extend(dynamic_only_libraries)
-
-                if not len(static_libraries):
+                        dynamic_only_roots[owner] = True
                     linker_inputs.append(linker_input)
                     continue
+            if len(static_libraries) and owner in dynamic_only_roots:
+                dynamic_only_roots.pop(owner)
 
             if is_direct_export:
                 wrapped_library = _wrap_static_library_with_alwayslink(
@@ -334,6 +334,14 @@
                 else:
                     unaccounted_for_libs.append(linker_input.owner)
 
+    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)
+
     _throw_error_if_unaccounted_libs(unaccounted_for_libs)
     return (exports, linker_inputs, link_once_static_libs, precompiled_only_dynamic_libraries)