Add stamp parameter to cc_common.link

This has the same semantics as the stamp attr of cc_binary, except it defaults to 0 to preserve the current behavior (and the desirable behavior for test rules).

RELNOTES: Add stamp parameter for cc_common.link to enable including build info
PiperOrigin-RevId: 303168329
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
index d51b49a..7f8c23d 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
@@ -125,6 +125,7 @@
       String language,
       String outputType,
       boolean linkDepsStatically,
+      int stamp,
       Sequence<?> additionalInputs, // <Artifact> expected
       Object grepIncludes,
       StarlarkThread thread)
@@ -140,6 +141,7 @@
         language,
         outputType,
         linkDepsStatically,
+        stamp,
         additionalInputs,
         /* grepIncludes= */ null,
         thread);
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 bd74c95..1f1a3055 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
@@ -23,6 +23,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.analysis.platform.ConstraintValueInfo;
 import com.google.devtools.build.lib.analysis.platform.ToolchainInfo;
@@ -1571,6 +1572,21 @@
     }
   }
 
+  private static boolean isStampingEnabled(int stamp, BuildConfiguration config)
+      throws EvalException {
+    if (stamp == 0) {
+      return false;
+    } else if (stamp == 1) {
+      return true;
+    } else if (stamp == -1) {
+      return config.stampBinaries();
+    } else {
+      throw Starlark.errorf(
+          "stamp value %d is not supported, must be 0 (disabled), 1 (enabled), or -1 (default)",
+          stamp);
+    }
+  }
+
   protected Label getCallerLabel(SkylarkActionFactory actions, String name) throws EvalException {
     try {
       return Label.create(
@@ -1707,12 +1723,15 @@
       String language,
       String outputType,
       boolean linkDepsStatically,
+      int stamp,
       Sequence<?> additionalInputs,
       Object grepIncludes,
       StarlarkThread thread)
       throws InterruptedException, EvalException {
     validateLanguage(language);
     validateOutputType(outputType);
+    boolean isStampingEnabled =
+        isStampingEnabled(stamp, actions.getRuleContext().getConfiguration());
     CcToolchainProvider ccToolchainProvider = convertFromNoneable(skylarkCcToolchainProvider, null);
     FeatureConfigurationForStarlark featureConfiguration =
         convertFromNoneable(skylarkFeatureConfiguration, null);
@@ -1759,6 +1778,7 @@
                     actions.getRuleContext().isAllowTagsPropagation()))
             .setGrepIncludes(convertFromNoneable(grepIncludes, /* defaultValue= */ null))
             .setLinkingMode(linkDepsStatically ? LinkingMode.STATIC : LinkingMode.DYNAMIC)
+            .setIsStampingEnabled(isStampingEnabled)
             .addNonCodeLinkerInputs(
                 additionalInputs.getContents(Artifact.class, "additional_inputs"))
             .setDynamicLinkType(dynamicLinkTargetType)
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/BazelCcModuleApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/BazelCcModuleApi.java
index 69ec207..d2ea0d0 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/BazelCcModuleApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp/BazelCcModuleApi.java
@@ -318,6 +318,18 @@
             defaultValue = "True",
             type = Boolean.class),
         @Param(
+            name = "stamp",
+            doc =
+                "Whether to include build information in the linked executable, if output_type is "
+                    + "'executable'. If 1, build information is always included. If 0 (the "
+                    + "default build information is always excluded. If -1, uses the default "
+                    + "behavior, which may be overridden by the --[no]stamp flag. This should be "
+                    + "unset (or set to 0) when generating the executable output for test rules.",
+            positional = false,
+            named = true,
+            defaultValue = "0",
+            type = Integer.class),
+        @Param(
             name = "additional_inputs",
             doc = "For additional inputs to the linking action, e.g.: linking scripts.",
             positional = false,
@@ -343,6 +355,7 @@
       String language,
       String outputType,
       boolean linkDepsStatically,
+      int stamp,
       Sequence<?> additionalInputs, // <FileT> expected
       Object grepIncludes,
       StarlarkThread thread)
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp/FakeCcModule.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp/FakeCcModule.java
index 6f21a43..8d9a466 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp/FakeCcModule.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp/FakeCcModule.java
@@ -279,6 +279,7 @@
       String language,
       String outputType,
       boolean linkDepsStatically,
+      int stamp,
       Sequence<?> additionalInputs,
       Object grepIncludes,
       StarlarkThread thread)
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index ccafaf5..4a7654b 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -1528,6 +1528,10 @@
     return directories.getOutputPath(ruleClassProvider.getRunfilesPrefix());
   }
 
+  protected String getRelativeOutputPath() {
+    return directories.getRelativeOutputPath();
+  }
+
   /**
    * Verifies whether the rule checks the 'srcs' attribute validity.
    *
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
index 004d998..c499dcf 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
@@ -23,6 +23,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.CommandLineExpansionException;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
@@ -30,6 +32,7 @@
 import com.google.devtools.build.lib.analysis.util.AnalysisTestUtil;
 import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
 import com.google.devtools.build.lib.packages.Provider;
 import com.google.devtools.build.lib.packages.SkylarkInfo;
 import com.google.devtools.build.lib.packages.SkylarkProvider;
@@ -90,7 +93,7 @@
   }
 
   private static StructImpl getMyInfoFromTarget(ConfiguredTarget configuredTarget)
-      throws Exception {
+      throws LabelSyntaxException {
     Provider.Key key =
         new SkylarkProvider.SkylarkKey(
             Label.parseAbsolute("//myinfo:myinfo.bzl", ImmutableMap.of()), "MyInfo");
@@ -5685,7 +5688,7 @@
 
   @Test
   public void testTransitiveLinkWithDeps() throws Exception {
-    setupTestTransitiveLink(scratch, "        linking_contexts = dep_linking_contexts");
+    setupTestTransitiveLink(scratch, "linking_contexts = dep_linking_contexts");
     ConfiguredTarget target = getConfiguredTarget("//foo:bin");
     assertThat(target).isNotNull();
     Artifact executable = (Artifact) getMyInfoFromTarget(target).getValue("executable");
@@ -5754,6 +5757,74 @@
   }
 
   @Test
+  public void testLinkStampExpliciltyEnabledOverridesNoStampFlag() throws Exception {
+    useConfiguration("--nostamp");
+    setupTestTransitiveLink(scratch, "stamp=1", "linking_contexts=dep_linking_contexts");
+    assertStampEnabled(getLinkstampCompileAction("//foo:bin"));
+  }
+
+  @Test
+  public void testLinkExplicitlyDisabledOverridesStampFlag() throws Exception {
+    useConfiguration("--nostamp");
+    setupTestTransitiveLink(scratch, "stamp=0", "linking_contexts=dep_linking_contexts");
+    assertStampDisabled(getLinkstampCompileAction("//foo:bin"));
+  }
+
+  @Test
+  public void testLinkStampUseFlagStamp() throws Exception {
+    useConfiguration("--stamp");
+    setupTestTransitiveLink(scratch, "stamp=-1", "linking_contexts=dep_linking_contexts");
+    assertStampEnabled(getLinkstampCompileAction("//foo:bin"));
+  }
+
+  @Test
+  public void testLinkStampUseFlagNoStamp() throws Exception {
+    useConfiguration("--nostamp");
+    setupTestTransitiveLink(scratch, "stamp=-1", "linking_contexts=dep_linking_contexts");
+    assertStampDisabled(getLinkstampCompileAction("//foo:bin"));
+  }
+
+  @Test
+  public void testLinkStampDisabledByDefaultDespiteStampFlag() throws Exception {
+    useConfiguration("--stamp");
+    setupTestTransitiveLink(scratch, "linking_contexts=dep_linking_contexts");
+    assertStampDisabled(getLinkstampCompileAction("//foo:bin"));
+  }
+
+  @Test
+  public void testLinkStampInvalid() throws Exception {
+    setupTestTransitiveLink(scratch, "stamp=2");
+    checkError(
+        "//foo:bin",
+        "stamp value 2 is not supported, must be 0 (disabled), 1 (enabled), or -1 (default)");
+  }
+
+  private CppCompileAction getLinkstampCompileAction(String label)
+      throws LabelSyntaxException, EvalException {
+    ConfiguredTarget target = getConfiguredTarget(label);
+    Artifact executable = (Artifact) getMyInfoFromTarget(target).getValue("executable");
+    CppLinkAction generatingAction = (CppLinkAction) getGeneratingAction(executable);
+    Artifact compiledLinkstamp =
+        ActionsTestUtil.getFirstArtifactEndingWith(generatingAction.getInputs(), "version.o");
+    CppCompileAction linkstampCompileAction =
+        (CppCompileAction) getGeneratingAction(compiledLinkstamp);
+    assertThat(linkstampCompileAction.getMnemonic()).isEqualTo("CppLinkstampCompile");
+    return linkstampCompileAction;
+  }
+
+  private void assertStampEnabled(CppCompileAction linkstampAction)
+      throws CommandLineExpansionException {
+    assertThat(linkstampAction.getArguments())
+        .contains(getRelativeOutputPath() + "/k8-fastbuild/include/build-info-volatile.h");
+  }
+
+  private void assertStampDisabled(CppCompileAction linkstampAction)
+      throws CommandLineExpansionException {
+    assertThat(linkstampAction.getArguments())
+        .contains(getRelativeOutputPath() + "/k8-fastbuild/include/build-info-redacted.h");
+  }
+
+  @Test
   public void testApiWithAspectsOnTargetsInExternalRepos() throws Exception {
     if (!AnalysisMock.get().isThisBazel()) {
       return;
@@ -6147,13 +6218,13 @@
         "        feature_configuration=feature_configuration,",
         "        name = ctx.label.name,",
         "        cc_toolchain=toolchain,",
-        "        " + Joiner.on("").join(additionalLines),
+        "        " + Joiner.on(",\n        ").join(additionalLines),
         "    )",
         "    return [",
         "      MyInfo(",
-        "        library=linking_outputs.library_to_link,",
-        "        executable=linking_outputs.executable",
-        "      )",
+        "          library=linking_outputs.library_to_link,",
+        "          executable=linking_outputs.executable",
+        "      ),",
         "    ]",
         "cc_bin = rule(",
         "    implementation = _cc_bin_impl,",
@@ -6177,6 +6248,7 @@
         "cc_library(",
         "    name = 'dep1',",
         "    srcs = ['dep1.cc'],",
+        "    linkstamp = 'version.cc',",
         "    hdrs = ['dep1.h'],",
         "    includes = ['dep1/baz'],",
         "    defines = ['DEP1'],",
diff --git a/src/test/shell/bazel/cc_api_rules.bzl b/src/test/shell/bazel/cc_api_rules.bzl
index 87d9949..4972113 100644
--- a/src/test/shell/bazel/cc_api_rules.bzl
+++ b/src/test/shell/bazel/cc_api_rules.bzl
@@ -141,6 +141,7 @@
         linking_contexts = linking_contexts,
         user_link_flags = user_link_flags,
         link_deps_statically = ctx.attr.linkstatic,
+        stamp = ctx.attr.stamp,
         additional_inputs = ctx.files.additional_linker_inputs,
         output_type = output_type,
     )
@@ -177,6 +178,7 @@
         "user_link_flags": attr.string_list(),
         "linkstatic": attr.bool(default = True),
         "linkshared": attr.bool(default = False),
+        "stamp": attr.int(default = -1),
         "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
     },
     fragments = ["cpp"],