Don't propagate non-shared transitive linker inputs from `rust_static|shared_library` (#1299)

A Rust `staticlib` or `cdylib` doesn't need to propagate linker inputs of its dependencies, except for shared libraries.

- The tests are currently disabled on osx because the CI [Toolchain does not support dynamic linking]( https://buildkite.com/bazel/rules-rust-rustlang/builds/6126#a83dbb56-50b0-4a95-bb39-09e3a78ed0d0).
- I also had to link to `Bcrypt.lib` => https://github.com/rust-lang/rust/issues/91974
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index fd43cb7..c423296 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -7,7 +7,10 @@
   # TODO: Switch manual tag to platform constraint after bazel 4.0.
   - "//test/versioned_dylib:versioned_dylib_test"
 default_macos_targets: &default_macos_targets
+  - "--"
   - "//..."
+  # TODO: CI toolchain does not support dynamic linking.
+  - "-//test/linker_inputs_propagation/..."
 default_windows_targets: &default_windows_targets
   - "--" # Allows negative patterns; hack for https://github.com/bazelbuild/continuous-integration/pull/245
   - "//..."
diff --git a/rust/platform/triple_mappings.bzl b/rust/platform/triple_mappings.bzl
index 50a9449..ad4dedb 100644
--- a/rust/platform/triple_mappings.bzl
+++ b/rust/platform/triple_mappings.bzl
@@ -163,7 +163,7 @@
     "unknown": [],
     "uwp": ["ws2_32.lib"],
     "wasi": [],
-    "windows": ["advapi32.lib", "ws2_32.lib", "userenv.lib"],
+    "windows": ["advapi32.lib", "ws2_32.lib", "userenv.lib", "Bcrypt.lib"],
 }
 
 def cpu_arch_to_constraints(cpu_arch):
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index b8428f2..47fc576 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -1015,6 +1015,21 @@
 def _is_dylib(dep):
     return not bool(dep.static_library or dep.pic_static_library)
 
+def _collect_nonstatic_linker_inputs(cc_info):
+    shared_linker_inputs = []
+    for linker_input in cc_info.linking_context.linker_inputs.to_list():
+        dylibs = [
+            lib
+            for lib in linker_input.libraries
+            if _is_dylib(lib)
+        ]
+        if dylibs:
+            shared_linker_inputs.append(cc_common.create_linker_input(
+                owner = linker_input.owner,
+                libraries = depset(dylibs),
+            ))
+    return shared_linker_inputs
+
 def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library):
     """If the produced crate is suitable yield a CcInfo to allow for interop with cc rules
 
@@ -1093,9 +1108,20 @@
         CcInfo(linking_context = linking_context),
         toolchain.stdlib_linkflags,
     ]
+
     for dep in getattr(attr, "deps", []):
         if CcInfo in dep:
-            cc_infos.append(dep[CcInfo])
+            # A Rust staticlib or shared library doesn't need to propagate linker inputs
+            # of its dependencies, except for shared libraries.
+            if crate_info.type in ["cdylib", "staticlib"]:
+                shared_linker_inputs = _collect_nonstatic_linker_inputs(dep[CcInfo])
+                if shared_linker_inputs:
+                    linking_context = cc_common.create_linking_context(
+                        linker_inputs = depset(shared_linker_inputs),
+                    )
+                    cc_infos.append(CcInfo(linking_context = linking_context))
+            else:
+                cc_infos.append(dep[CcInfo])
 
     if crate_info.type in ("rlib", "lib") and toolchain.libstd_and_allocator_ccinfo:
         # TODO: if we already have an rlib in our deps, we could skip this
diff --git a/test/linker_inputs_propagation/BUILD.bazel b/test/linker_inputs_propagation/BUILD.bazel
new file mode 100644
index 0000000..d305ffb
--- /dev/null
+++ b/test/linker_inputs_propagation/BUILD.bazel
@@ -0,0 +1,110 @@
+load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_import", "cc_library", "cc_test")
+load(
+    "@rules_rust//rust:defs.bzl",
+    "rust_library",
+    "rust_shared_library",
+    "rust_static_library",
+)
+
+package(default_visibility = ["//test/unit/linker_inputs_propagation:__pkg__"])
+
+rust_library(
+    name = "foo",
+    srcs = ["foo.rs"],
+    edition = "2018",
+)
+
+cc_library(
+    name = "foo_shared",
+    srcs = [
+        "foo_shared.cc",
+    ],
+    hdrs = ["foo_shared.h"],
+)
+
+cc_binary(
+    name = "foo_shared.dll",
+    srcs = [
+        "foo_shared.cc",
+        "foo_shared.h",
+    ],
+    features = ["windows_export_all_symbols"],
+    linkshared = True,
+)
+
+filegroup(
+    name = "shared_library_file",
+    srcs = [":foo_shared"],
+    output_group = "dynamic_library",
+)
+
+filegroup(
+    name = "interface_library_file",
+    srcs = [":foo_shared.dll"],
+    output_group = "interface_library",
+)
+
+cc_import(
+    name = "import_foo_shared",
+    hdrs = ["foo_shared.h"],
+    interface_library = select({
+        "@platforms//os:windows": "interface_library_file",
+        "//conditions:default": "shared_library_file",
+    }),
+    shared_library = select({
+        "@platforms//os:windows": "foo_shared.dll",
+        "//conditions:default": "shared_library_file",
+    }),
+)
+
+rust_static_library(
+    name = "staticlib_uses_foo",
+    srcs = ["bar_uses_foo.rs"],
+    edition = "2018",
+    deps = [":foo"],
+)
+
+rust_shared_library(
+    name = "sharedlib_uses_foo",
+    srcs = ["bar_uses_foo.rs"],
+    edition = "2018",
+    deps = [":foo"],
+)
+
+rust_static_library(
+    name = "staticlib_uses_shared_foo",
+    srcs = ["bar_uses_shared_foo.rs"],
+    edition = "2018",
+    deps = [":import_foo_shared"],
+)
+
+rust_static_library(
+    name = "sharedlib_uses_shared_foo",
+    srcs = ["bar_uses_shared_foo.rs"],
+    edition = "2018",
+    deps = [":import_foo_shared"],
+)
+
+cc_test(
+    name = "depends_on_foo_via_staticlib",
+    srcs = ["baz.cc"],
+    deps = [":staticlib_uses_foo"],
+)
+
+cc_test(
+    name = "depends_on_foo_via_sharedlib",
+    srcs = ["baz.cc"],
+    deps = [":sharedlib_uses_foo"],
+)
+
+cc_test(
+    name = "depends_on_shared_foo_via_sharedlib",
+    srcs = ["baz.cc"],
+    deps = [":sharedlib_uses_shared_foo"],
+)
+
+cc_test(
+    name = "depends_on_shared_foo_via_staticlib",
+    srcs = ["baz.cc"],
+    deps = [":staticlib_uses_shared_foo"],
+)
diff --git a/test/linker_inputs_propagation/bar_uses_foo.rs b/test/linker_inputs_propagation/bar_uses_foo.rs
new file mode 100644
index 0000000..3dc7995
--- /dev/null
+++ b/test/linker_inputs_propagation/bar_uses_foo.rs
@@ -0,0 +1,9 @@
+/** Safety doc.
+
+  # Safety
+
+*/
+#[no_mangle]
+pub unsafe extern "C" fn double_foo() -> i32 {
+    2 * foo::foo()
+}
diff --git a/test/linker_inputs_propagation/bar_uses_shared_foo.rs b/test/linker_inputs_propagation/bar_uses_shared_foo.rs
new file mode 100644
index 0000000..75c232d
--- /dev/null
+++ b/test/linker_inputs_propagation/bar_uses_shared_foo.rs
@@ -0,0 +1,13 @@
+extern "C" {
+    pub fn foo() -> i32;
+}
+
+/** Safety doc.
+
+  # Safety
+
+*/
+#[no_mangle]
+pub unsafe extern "C" fn double_foo() -> i32 {
+    2 * foo()
+}
diff --git a/test/linker_inputs_propagation/baz.cc b/test/linker_inputs_propagation/baz.cc
new file mode 100644
index 0000000..91805e8
--- /dev/null
+++ b/test/linker_inputs_propagation/baz.cc
@@ -0,0 +1,10 @@
+#include <assert.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+extern "C" int32_t double_foo();
+
+int main(int argc, char** argv) {
+    assert(double_foo() == 84);
+    return EXIT_SUCCESS;
+}
diff --git a/test/linker_inputs_propagation/foo.rs b/test/linker_inputs_propagation/foo.rs
new file mode 100644
index 0000000..185ce22
--- /dev/null
+++ b/test/linker_inputs_propagation/foo.rs
@@ -0,0 +1,3 @@
+pub fn foo() -> i32 {
+    42
+}
diff --git a/test/linker_inputs_propagation/foo_shared.cc b/test/linker_inputs_propagation/foo_shared.cc
new file mode 100644
index 0000000..c3fc2dd
--- /dev/null
+++ b/test/linker_inputs_propagation/foo_shared.cc
@@ -0,0 +1,7 @@
+#include <stdint.h>
+
+extern "C" {
+    int32_t foo() {
+        return 42;
+    }
+}
diff --git a/test/linker_inputs_propagation/foo_shared.h b/test/linker_inputs_propagation/foo_shared.h
new file mode 100644
index 0000000..3abc47d
--- /dev/null
+++ b/test/linker_inputs_propagation/foo_shared.h
@@ -0,0 +1,5 @@
+#include <stdint.h>
+
+extern "C" {
+    int32_t foo();
+}
diff --git a/test/unit/linker_inputs_propagation/BUILD.bazel b/test/unit/linker_inputs_propagation/BUILD.bazel
new file mode 100644
index 0000000..b3cdf15
--- /dev/null
+++ b/test/unit/linker_inputs_propagation/BUILD.bazel
@@ -0,0 +1,4 @@
+load(":linker_inputs_propagation_test.bzl", "linker_inputs_propagation_test_suite")
+
+############################ UNIT TESTS #############################
+linker_inputs_propagation_test_suite(name = "linker_inputs_propagation_test_suite")
diff --git a/test/unit/linker_inputs_propagation/linker_inputs_propagation_test.bzl b/test/unit/linker_inputs_propagation/linker_inputs_propagation_test.bzl
new file mode 100644
index 0000000..3341acd
--- /dev/null
+++ b/test/unit/linker_inputs_propagation/linker_inputs_propagation_test.bzl
@@ -0,0 +1,91 @@
+"""Unittests for propagation of linker inputs through Rust libraries"""
+
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+
+def _shared_lib_is_propagated_test_impl(ctx):
+    env = analysistest.begin(ctx)
+    tut = analysistest.target_under_test(env)
+    link_action = [action for action in tut.actions if action.mnemonic == "CppLink"][0]
+
+    lib_name = _get_lib_name(ctx, name = "foo_shared")
+    asserts.true(env, _contains_input(link_action.inputs, lib_name))
+
+    return analysistest.end(env)
+
+def _static_lib_is_not_propagated_test_impl(ctx):
+    env = analysistest.begin(ctx)
+    tut = analysistest.target_under_test(env)
+    link_action = [action for action in tut.actions if action.mnemonic == "CppLink"][0]
+
+    lib_name = _get_lib_name(ctx, name = "foo")
+    asserts.false(env, _contains_input(link_action.inputs, lib_name))
+
+    return analysistest.end(env)
+
+def _contains_input(inputs, name):
+    for input in inputs.to_list():
+        # We cannot check for name equality because rlib outputs contain
+        # a hash in their name.
+        if input.basename.startswith(name):
+            return True
+    return False
+
+def _get_lib_name(ctx, name):
+    if ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]):
+        return name
+    else:
+        return "lib{}".format(name)
+
+static_lib_is_not_propagated_test = analysistest.make(
+    _static_lib_is_not_propagated_test_impl,
+    attrs = {
+        "_windows_constraint": attr.label(default = Label("@platforms//os:windows")),
+    },
+)
+
+shared_lib_is_propagated_test = analysistest.make(
+    _shared_lib_is_propagated_test_impl,
+    attrs = {
+        "_macos_constraint": attr.label(default = Label("@platforms//os:macos")),
+        "_windows_constraint": attr.label(default = Label("@platforms//os:windows")),
+    },
+)
+
+def _linker_inputs_propagation_test():
+    static_lib_is_not_propagated_test(
+        name = "depends_on_foo_via_staticlib",
+        target_under_test = "//test/linker_inputs_propagation:depends_on_foo_via_staticlib",
+    )
+
+    shared_lib_is_propagated_test(
+        name = "depends_on_shared_foo_via_staticlib",
+        target_under_test = "//test/linker_inputs_propagation:depends_on_shared_foo_via_staticlib",
+    )
+
+    static_lib_is_not_propagated_test(
+        name = "depends_on_foo_via_sharedlib",
+        target_under_test = "//test/linker_inputs_propagation:depends_on_foo_via_sharedlib",
+    )
+
+    shared_lib_is_propagated_test(
+        name = "depends_on_shared_foo_via_sharedlib",
+        target_under_test = "//test/linker_inputs_propagation:depends_on_shared_foo_via_sharedlib",
+    )
+
+def linker_inputs_propagation_test_suite(name):
+    """Entry-point macro called from the BUILD file.
+
+    Args:
+        name: Name of the macro.
+    """
+    _linker_inputs_propagation_test()
+
+    native.test_suite(
+        name = name,
+        tests = [
+            ":depends_on_foo_via_staticlib",
+            ":depends_on_shared_foo_via_staticlib",
+            ":depends_on_foo_via_sharedlib",
+            ":depends_on_shared_foo_via_sharedlib",
+        ],
+    )