Add -install_name when linking shared libraries on macOS

This change exposes the name of the solib symlink available at runtime
to link actions and uses that name to set the `-install_name` of shared
libraries on macOS. Exposing the runtime solib name to link actions
allows us to remove the need for the cc_wrapper script that patched the
binary on macOS (which relied on the fact that the runtime solibs are
symlinks locally, which is not true in all cases).

Updates #7415

Closes #12304.

PiperOrigin-RevId: 342033474
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
index 3ee5e21..8e8dd9c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
@@ -302,6 +302,7 @@
         isUsingLinkerNotArchiver,
         /* binDirectoryPath= */ null,
         convertFromNoneable(outputFile, /* defaultValue= */ null),
+        /* runtimeSolibName= */ null,
         isCreatingSharedLibrary,
         convertFromNoneable(paramFile, /* defaultValue= */ null),
         /* thinltoParamFile= */ null,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
index f42bfeb..91bd2ee 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
@@ -753,4 +753,9 @@
   public boolean generateLlvmLCov() {
     return cppOptions.generateLlvmLcov;
   }
+
+  @Override
+  public boolean macosSetInstallName() {
+    return cppOptions.macosSetInstallName;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
index 289d7f7..4c76509 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionBuilder.java
@@ -872,6 +872,8 @@
               getLinkType().linkerOrArchiver().equals(LinkerOrArchiver.LINKER),
               configuration.getBinDirectory(repositoryName).getExecPath(),
               output.getExecPathString(),
+              SolibSymlinkAction.getDynamicLibrarySoname(
+                  output.getRootRelativePath(), /* preserveName= */ false),
               linkType.equals(LinkTargetType.DYNAMIC_LIBRARY),
               paramFile != null ? paramFile.getExecPathString() : null,
               thinltoParamFile != null ? thinltoParamFile.getExecPathString() : null,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
index 5233e77..c0e9073 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java
@@ -1036,6 +1036,20 @@
       help = "If enabled, give distinguishing mnemonic to header processing actions")
   public boolean useCppCompileHeaderMnemonic;
 
+  @Option(
+      name = "incompatible_macos_set_install_name",
+      defaultValue = "false",
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS},
+      metadataTags = {
+        OptionMetadataTag.INCOMPATIBLE_CHANGE,
+        OptionMetadataTag.TRIGGERED_BY_ALL_INCOMPATIBLE_CHANGES
+      },
+      help =
+          "Whether to explicitly set `-install_name` when creating dynamic libraries. "
+              + "See https://github.com/bazelbuild/bazel/issues/12370")
+  public boolean macosSetInstallName;
+
   /** See {@link #targetLibcTopLabel} documentation. * */
   @Override
   public FragmentOptions getNormalized() {
@@ -1124,6 +1138,8 @@
 
     host.experimentalStarlarkCcImport = experimentalStarlarkCcImport;
 
+    host.macosSetInstallName = macosSetInstallName;
+
     return host;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariables.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariables.java
index 8180a32..8477ece 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariables.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LinkBuildVariables.java
@@ -82,7 +82,9 @@
   /** Path to the context sensitive fdo instrument. */
   CS_FDO_INSTRUMENT_PATH("cs_fdo_instrument_path"),
   /** Path to the Propeller Optimize linker profile artifact */
-  PROPELLER_OPTIMIZE_LD_PATH("propeller_optimize_ld_path");
+  PROPELLER_OPTIMIZE_LD_PATH("propeller_optimize_ld_path"),
+  /** The name of the runtime solib symlink of the shared library. */
+  RUNTIME_SOLIB_NAME("runtime_solib_name");
 
   private final String variableName;
 
@@ -98,6 +100,7 @@
       boolean isUsingLinkerNotArchiver,
       PathFragment binDirectoryPath,
       String outputFile,
+      String runtimeSolibName,
       boolean isCreatingSharedLibrary,
       String paramFile,
       String thinltoParamFile,
@@ -166,6 +169,10 @@
       buildVariables.addStringVariable(OUTPUT_EXECPATH.getVariableName(), outputFile);
     }
 
+    if (runtimeSolibName != null && !isLtoIndexing) {
+      buildVariables.addStringVariable(RUNTIME_SOLIB_NAME.getVariableName(), runtimeSolibName);
+    }
+
     if (isLtoIndexing) {
       if (thinltoParamFile != null) {
         // This is a lto-indexing action and we want it to populate param file.
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CppConfigurationApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CppConfigurationApi.java
index bf65a92..32f11dd 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CppConfigurationApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/cpp/CppConfigurationApi.java
@@ -77,4 +77,12 @@
               + "    )<br/>"
               + ")</pre>")
   Label customMalloc();
+
+  @StarlarkMethod(
+      name = "do_not_use_macos_set_install_name",
+      structField = true,
+      // Only for migration purposes. Intentionally not documented.
+      documented = false,
+      doc = "Accessor for <code>--incompatible_macos_set_install_name</code>.")
+  boolean macosSetInstallName();
 }
diff --git a/src/test/shell/bazel/cpp_darwin_integration_test.sh b/src/test/shell/bazel/cpp_darwin_integration_test.sh
index c9f0f71..e26d2a9 100755
--- a/src/test/shell/bazel/cpp_darwin_integration_test.sh
+++ b/src/test/shell/bazel/cpp_darwin_integration_test.sh
@@ -123,5 +123,42 @@
   return 0
 }
 
+function test_cc_test_with_explicit_install_name() {
+  mkdir -p cpp
+  cat > cpp/BUILD <<EOF
+cc_library(
+  name = "foo",
+  srcs = ["foo.cc"],
+  hdrs = ["foo.h"],
+)
+cc_test(
+  name = "test",
+  srcs = ["test.cc"],
+  deps = [":foo"],
+)
+EOF
+  cat > cpp/foo.h <<EOF
+  int foo();
+EOF
+  cat > cpp/foo.cc <<EOF
+  int foo() { return 0; }
+EOF
+  cat > cpp/test.cc <<EOF
+  #include "cpp/foo.h"
+  int main() {
+    return foo();
+  }
+EOF
+
+  bazel test --incompatible_macos_set_install_name //cpp:test || \
+      fail "bazel test //cpp:test failed"
+  # Ensure @rpath is correctly set in the binary.
+  ./bazel-bin/cpp/test || \
+      fail "//cpp:test workspace execution failed, expected return 0, got $?"
+  cd bazel-bin
+  ./cpp/test || \
+      fail "//cpp:test execution failed, expected 0, but $?"
+}
+
 run_suite "Tests for Bazel's C++ rules on Darwin"
 
diff --git a/tools/osx/crosstool/cc_toolchain_config.bzl b/tools/osx/crosstool/cc_toolchain_config.bzl
index d222c75..3bd3c4b 100644
--- a/tools/osx/crosstool/cc_toolchain_config.bzl
+++ b/tools/osx/crosstool/cc_toolchain_config.bzl
@@ -6061,6 +6061,27 @@
         ],
     )
 
+    set_install_name = feature(
+        name = "set_install_name",
+        enabled = ctx.fragments.cpp.do_not_use_macos_set_install_name,
+        flag_sets = [
+            flag_set(
+                actions = [
+                    ACTION_NAMES.cpp_link_dynamic_library,
+                    ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+                ],
+                flag_groups = [
+                    flag_group(
+                        flags = [
+                            "-Wl,-install_name,@rpath/%{runtime_solib_name}",
+                        ],
+                        expand_if_available = "runtime_solib_name",
+                    ),
+                ],
+            ),
+        ],
+    )
+
     if (ctx.attr.cpu == "ios_arm64" or
         ctx.attr.cpu == "ios_arm64e" or
         ctx.attr.cpu == "ios_armv7" or
@@ -6145,6 +6166,7 @@
             compiler_input_flags_feature,
             compiler_output_flags_feature,
             objcopy_embed_flags_feature,
+            set_install_name,
         ]
     elif (ctx.attr.cpu == "darwin_x86_64" or
           ctx.attr.cpu == "darwin_arm64" or
@@ -6224,6 +6246,7 @@
             supports_dynamic_linker_feature,
             objcopy_embed_flags_feature,
             dynamic_linking_mode_feature,
+            set_install_name,
         ]
     elif (ctx.attr.cpu == "armeabi-v7a"):
         features = [
@@ -6300,6 +6323,7 @@
             compiler_output_flags_feature,
             supports_pic_feature,
             objcopy_embed_flags_feature,
+            set_install_name,
         ]
     else:
         fail("Unreachable")
@@ -6402,4 +6426,5 @@
     },
     provides = [CcToolchainConfigInfo],
     executable = True,
+    fragments = ["cpp"],
 )