SOURCE_DATE_EPOCH environment variable override the timestamp

This is applying the version 1.1 of the specification (https://reproducible-builds.org/specs/source-date-epoch/)
where the only timestamp that Bazel puts in the final targets is overridden by the value of SOURCE_DATE_EPOCH.

This change also remove the legacy SOURCE_DATE_EPOCH handling which wasn't really following
the spec.

Note that Bazel itself tries hard to remove all timestamps from intermediary binaries and
overridde SOURCE_DATE_EPOCH in most action, which is a not according to the version 1.0 of the spec
but according to the expected change for version 1.1.

RELNOTES: SOURCE_DATE_EPOCH (https://reproducible-builds.org/specs/source-date-epoch/) can
be used to override the timestamp used for stamped target (when using --stamp).

Fixes #2240.

Change-Id: I074e7905fa6745cc706f7391340aeae9188909ca
PiperOrigin-RevId: 176489717
diff --git a/scripts/bootstrap/bootstrap.sh b/scripts/bootstrap/bootstrap.sh
index 3f8ba74..57b5332 100755
--- a/scripts/bootstrap/bootstrap.sh
+++ b/scripts/bootstrap/bootstrap.sh
@@ -27,9 +27,6 @@
 EMBED_LABEL_ARG=()
 if [ -n "${EMBED_LABEL}" ]; then
     EMBED_LABEL_ARG=(--stamp --embed_label "${EMBED_LABEL}")
-    if [ -n "${SOURCE_DATE_EPOCH}" ]; then
-        EMBED_LABEL_ARG+=(--experimental_embed_timestamp_epoch "${SOURCE_DATE_EPOCH}")
-    fi
 fi
 
 : ${JAVA_VERSION:="1.8"}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfo.java
index 7af9837..b4ab9ee 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BuildInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildInfo.java
@@ -24,9 +24,6 @@
    */
   public static final String BUILD_EMBED_LABEL = "BUILD_EMBED_LABEL";
 
-  /** Named constant for the reference timestamp to be included. */
-  public static final String SOURCE_DATE_EPOCH = "SOURCE_DATE_EPOCH";
-
   /**
    * The name of the user that performs the build.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
index e5f2499..ae70deb 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/WorkspaceStatusAction.java
@@ -69,15 +69,6 @@
     public String embedLabel;
 
     @Option(
-      name = "experimental_embed_timestamp_epoch",
-      defaultValue = "-1",
-      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
-      effectTags = {OptionEffectTag.UNKNOWN},
-      help = "Alternative timestamp to be used in stamping the binary"
-    )
-    public long embedTimestampEpoch;
-
-    @Option(
       name = "workspace_status_command",
       defaultValue = "",
       category = "misc",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelWorkspaceStatusModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelWorkspaceStatusModule.java
index 234465e..363b282 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelWorkspaceStatusModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelWorkspaceStatusModule.java
@@ -78,6 +78,7 @@
     private final String username;
     private final String hostname;
     private final com.google.devtools.build.lib.shell.Command getWorkspaceStatusCommand;
+    private final Map<String, String> clientEnv;
 
     private BazelWorkspaceStatusAction(
         WorkspaceStatusAction.Options options,
@@ -95,6 +96,7 @@
       this.volatileStatus = volatileStatus;
       this.username = USER_NAME.value();
       this.hostname = hostname;
+      this.clientEnv = clientEnv;
       this.getWorkspaceStatusCommand =
           options.workspaceStatusCommand.equals(PathFragment.EMPTY_FRAGMENT)
               ? null
@@ -198,19 +200,7 @@
         stableMap.put(BuildInfo.BUILD_EMBED_LABEL, options.embedLabel);
         stableMap.put(BuildInfo.BUILD_HOST, hostname);
         stableMap.put(BuildInfo.BUILD_USER, username);
-        // TODO(#2240): We currently take the timestamp from an option. This is very
-        // explicit and in line with the way the embedded label is passed to bazel.
-        // While this approach solves the problem of properly packaging bazel, there is the
-        // expectation that the value be taken from the SOURCE_DATE_EPOCH environment variable.
-        // However, currently there is no clear understanding on which environment to be taken;
-        // it could be the client environment or the action environment which is controlled
-        // by the --action_env options. (We almost certainly do not want the server environment.)
-        // So, to avoid surprises, we take an explicit option till a satisfying design is found;
-        // the latter should be designed and implemented eventually.
-        if (options.embedTimestampEpoch >= 0) {
-          stableMap.put(BuildInfo.SOURCE_DATE_EPOCH, Long.toString(options.embedTimestampEpoch));
-        }
-        volatileMap.put(BuildInfo.BUILD_TIMESTAMP, Long.toString(System.currentTimeMillis()));
+        volatileMap.put(BuildInfo.BUILD_TIMESTAMP, Long.toString(getCurrentTimeMillis()));
 
         Map<String, String> overallMap = new TreeMap<>();
         overallMap.putAll(volatileMap);
@@ -236,6 +226,24 @@
       return ActionResult.EMPTY;
     }
 
+    /**
+     * This method returns the current time for stamping, using SOURCE_DATE_EPOCH
+     * (https://reproducible-builds.org/specs/source-date-epoch/) if provided.
+     */
+    private long getCurrentTimeMillis() {
+      if (clientEnv.containsKey("SOURCE_DATE_EPOCH")) {
+        String value = clientEnv.get("SOURCE_DATE_EPOCH").trim();
+        if (!value.isEmpty()) {
+          try {
+            return Long.parseLong(value) * 1000;
+          } catch (NumberFormatException ex) {
+            // Fall-back to use the current time if SOURCE_DATE_EPOCH is not a long.
+          }
+        }
+      }
+      return System.currentTimeMillis();
+    }
+
     @Override
     public boolean equals(Object o) {
       if (!(o instanceof BazelWorkspaceStatusAction)) {
@@ -334,11 +342,6 @@
           BuildInfo.BUILD_EMBED_LABEL, Key.of(KeyType.STRING, options.embedLabel, "redacted"));
       builder.put(BuildInfo.BUILD_HOST, Key.of(KeyType.STRING, "hostname", "redacted"));
       builder.put(BuildInfo.BUILD_USER, Key.of(KeyType.STRING, "username", "redacted"));
-      if (options.embedTimestampEpoch >= 0) {
-        builder.put(
-            BuildInfo.SOURCE_DATE_EPOCH,
-            Key.of(KeyType.STRING, Long.toString(options.embedTimestampEpoch), "0"));
-      }
       return builder.build();
     }
 
@@ -391,4 +394,5 @@
   public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorBuilder builder) {
     builder.addActionContext(new BazelWorkspaceStatusActionContext(options));
   }
+
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/WriteBuildInfoPropertiesAction.java b/src/main/java/com/google/devtools/build/lib/rules/java/WriteBuildInfoPropertiesAction.java
index 7b8195e..26be2ab 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/WriteBuildInfoPropertiesAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/WriteBuildInfoPropertiesAction.java
@@ -158,9 +158,8 @@
         if (includeVolatile) {
           addValues(keys, values, context.getVolatileKeys());
           long timeMillis = timestamp;
-          Key sourceDateEpoch = context.getStableKeys().get(BuildInfo.SOURCE_DATE_EPOCH);
-          if (sourceDateEpoch != null) {
-            timeMillis = Long.valueOf(sourceDateEpoch.getDefaultValue()) * 1000L;
+          if (values.containsKey(BuildInfo.BUILD_TIMESTAMP)) {
+            timeMillis = Long.valueOf(values.get(BuildInfo.BUILD_TIMESTAMP)) * 1000L;
           }
           keys.put("BUILD_TIMESTAMP", Long.toString(timeMillis / 1000));
           keys.put("BUILD_TIME", timestampFormatter.format(timeMillis));
diff --git a/src/test/shell/integration/BUILD b/src/test/shell/integration/BUILD
index b2d9439..7547103 100644
--- a/src/test/shell/integration/BUILD
+++ b/src/test/shell/integration/BUILD
@@ -204,6 +204,13 @@
 )
 
 sh_test(
+    name = "stamping_test",
+    size = "medium",
+    srcs = ["stamping_test.sh"],
+    data = [":test-deps"],
+)
+
+sh_test(
     name = "discard_graph_edges_test",
     size = "medium",
     srcs = ["discard_graph_edges_test.sh"],
diff --git a/src/test/shell/integration/stamping_test.sh b/src/test/shell/integration/stamping_test.sh
new file mode 100755
index 0000000..829cbe0
--- /dev/null
+++ b/src/test/shell/integration/stamping_test.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+#
+# Copyright 2017 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# An end-to-end test that Bazel's experimental UI produces reasonable output.
+
+# Load the test setup defined in the parent directory
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source "${CURRENT_DIR}/../integration_test_setup.sh" \
+  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+set -e
+
+function set_up() {
+  mkdir -p pkg
+  cat > pkg/BUILD <<EOF
+genrule(
+  name = "stamped",
+  outs = ["stamped.txt"],
+  cmd = "grep BUILD_TIMESTAMP volatile-status.txt | cut -d 2 -f ' ' >$@",
+  stamp = True,
+)
+
+genrule(
+  name = "unstamped",
+  outs = ["unstamped.txt"],
+  cmd = "grep BUILD_TIMESTAMP volatile-status.txt | cut -d 2 -f ' ' >$@",
+  stamp = False,
+)
+EOF
+}
+
+function test_source_date_epoch() {
+  bazel clean --expunge &> $TEST_log
+  bazel build --nostamp //pkg:* &> $TEST_log || fail "failed to build //pkg:*"
+  expect_equals 0 $(cat bazel-genfiles/pkg/stamped.txt)
+  expect_equals 0 $(cat bazel-genfiles/pkg/unstamped.txt)
+
+  bazel clean --expunge &> $TEST_log
+  SOURCE_DATE_EPOCH=0 bazel build --stamp //pkg:* &> $TEST_log || fail "failed to build //pkg:*"
+  expect_equals 0 $(cat bazel-genfiles/pkg/stamped.txt)
+  expect_equals 0 $(cat bazel-genfiles/pkg/unstamped.txt)
+
+  bazel clean --expunge &> $TEST_log
+  SOURCE_DATE_EPOCH=10 bazel build --stamp //pkg:* &> $TEST_log || fail "failed to build //pkg:*"
+  expect_equals 10 $(cat bazel-genfiles/pkg/stamped.txt)
+  expect_equals 0 $(cat bazel-genfiles/pkg/unstamped.txt)
+}