Introduce a Skyframe function: ActionEnvironmentFunction 

ActionEnvironmentFunction returns the list of environment
variable with the one overwritten by --action_env being
replaced. This let other Skyframe function declares
dependency to any value of the environment and being
influenced by the --action_env flag.

This will be used to declare dependency of remote repositories
on environment variables (step 3 of
https://bazel.build/designs/2016/10/18/repository-invalidation.html)

--
Change-Id: I1ed3fb6f48e8e17d4d64c903fccecb6ed7596350
Reviewed-on: https://cr.bazel.build/7974
PiperOrigin-RevId: 146918603
MOS_MIGRATED_REVID=146918603
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
index 29bf5a5..3590138 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CommandEnvironment.java
@@ -477,9 +477,7 @@
         getWorkingDirectory(),
         defaultsPackageContents,
         getCommandId(),
-        // TODO(bazel-team): this optimization disallows rule-specified additional dependencies
-        // on the client environment!
-        getWhitelistedClientEnv(),
+        clientEnv,
         timestampGranularityMonitor,
         options);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionEnvironmentFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionEnvironmentFunction.java
new file mode 100644
index 0000000..ea4cde0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionEnvironmentFunction.java
@@ -0,0 +1,78 @@
+// 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.
+
+package com.google.devtools.build.lib.skyframe;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * Skyframe function that provides the effective value for a client environment variable. This will
+ * either be the value coming from the default client environment, or the value coming from the
+ * --action_env flag, if the variable's value is explicitly set.
+ */
+public final class ActionEnvironmentFunction implements SkyFunction {
+
+  @Nullable
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException {
+    Map<String, String> actionEnv = PrecomputedValue.ACTION_ENV.get(env);
+    String key = (String) skyKey.argument();
+    if (actionEnv.containsKey(key) && actionEnv.get(key) != null) {
+      return new ClientEnvironmentValue(actionEnv.get(key));
+    }
+    return env.getValue(SkyKey.create(SkyFunctions.CLIENT_ENVIRONMENT_VARIABLE, key));
+  }
+
+  /** @return the SkyKey to invoke this function for the environment variable {@code variable}. */
+  public static SkyKey key(String variable) {
+    return SkyKey.create(SkyFunctions.ACTION_ENVIRONMENT_VARIABLE, variable);
+  }
+
+  /**
+   * Returns a map of environment variable key => values, getting them from Skyframe. Returns null
+   * if and only if some dependencies from Skyframe still need to be resolved.
+   */
+  public static Map<String, String> getEnvironmentView(Environment env, Iterable<String> keys)
+      throws InterruptedException {
+    ImmutableList.Builder<SkyKey> skyframeKeysBuilder = ImmutableList.builder();
+    for (String key : keys) {
+      skyframeKeysBuilder.add(key(key));
+    }
+    ImmutableList<SkyKey> skyframeKeys = skyframeKeysBuilder.build();
+    Map<SkyKey, SkyValue> values = env.getValues(skyframeKeys);
+    if (env.valuesMissing()) {
+      return null;
+    }
+    // To return the initial order and support null values, we use a LinkedHashMap.
+    LinkedHashMap<String, String> result = new LinkedHashMap<>();
+    for (SkyKey key : skyframeKeys) {
+      ClientEnvironmentValue value = (ClientEnvironmentValue) values.get(key);
+      result.put(key.argument().toString(), value.getValue());
+    }
+    return Collections.unmodifiableMap(result);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValue.java
index 05b9277..a53475a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrecomputedValue.java
@@ -83,6 +83,9 @@
   static final Precomputed<UUID> BUILD_ID =
       new Precomputed<>(SkyKey.create(SkyFunctions.PRECOMPUTED, "build_id"));
 
+  static final Precomputed<Map<String, String>> ACTION_ENV =
+      new Precomputed<>(SkyKey.create(SkyFunctions.PRECOMPUTED, "action_env"));
+
   static final Precomputed<WorkspaceStatusAction> WORKSPACE_STATUS_KEY =
       new Precomputed<>(SkyKey.create(SkyFunctions.PRECOMPUTED, "workspace_status_action"));
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
index 3e138ad..476e313 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyFunctions.java
@@ -24,6 +24,8 @@
   public static final SkyFunctionName PRECOMPUTED = SkyFunctionName.create("PRECOMPUTED");
   public static final SkyFunctionName CLIENT_ENVIRONMENT_VARIABLE =
       SkyFunctionName.create("CLIENT_ENVIRONMENT_VARIABLE");
+  public static final SkyFunctionName ACTION_ENVIRONMENT_VARIABLE =
+      SkyFunctionName.create("ACTION_ENVIRONMENT_VARIABLE");
   public static final SkyFunctionName FILE_STATE = SkyFunctionName.create("FILE_STATE");
   public static final SkyFunctionName DIRECTORY_LISTING_STATE =
       SkyFunctionName.create("DIRECTORY_LISTING_STATE");
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index 06017be..25c087d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -146,10 +146,13 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.Callable;
@@ -350,6 +353,7 @@
     ImmutableMap.Builder<SkyFunctionName, SkyFunction> map = ImmutableMap.builder();
     map.put(SkyFunctions.PRECOMPUTED, new PrecomputedFunction());
     map.put(SkyFunctions.CLIENT_ENVIRONMENT_VARIABLE, new ClientEnvironmentFunction(clientEnv));
+    map.put(SkyFunctions.ACTION_ENVIRONMENT_VARIABLE, new ActionEnvironmentFunction());
     map.put(SkyFunctions.FILE_STATE, new FileStateFunction(tsgm, externalFilesHelper));
     map.put(SkyFunctions.DIRECTORY_LISTING_STATE,
         new DirectoryListingStateFunction(externalFilesHelper));
@@ -940,6 +944,7 @@
       String defaultsPackageContents,
       UUID commandId,
       Map<String, String> clientEnv,
+      Map<String, String> actionEnv,
       TimestampGranularityMonitor tsgm) {
     Preconditions.checkNotNull(pkgLocator);
     Preconditions.checkNotNull(tsgm);
@@ -948,6 +953,7 @@
     this.tsgm.set(tsgm);
     maybeInjectPrecomputedValuesForAnalysis();
     setCommandId(commandId);
+    PrecomputedValue.ACTION_ENV.set(injectable(), actionEnv);
     this.clientEnv.set(clientEnv);
     setBlacklistedPackagePrefixesFile(getBlacklistedPackagePrefixesFile());
     setShowLoadingProgress(packageCacheOptions.showLoadingProgress);
@@ -1699,6 +1705,14 @@
       TimestampGranularityMonitor tsgm,
       OptionsClassProvider options)
       throws InterruptedException, AbruptExitException {
+    // ImmutableMap does not support null values, so use a LinkedHashMap instead.
+    LinkedHashMap<String, String> actionEnvironment = new LinkedHashMap<>();
+    BuildConfiguration.Options opt = options.getOptions(BuildConfiguration.Options.class);
+    if (opt != null) {
+      for (Entry<String, String> v : opt.actionEnvironment) {
+        actionEnvironment.put(v.getKey(), v.getValue());
+      }
+    }
     preparePackageLoading(
         createPackageLocator(
             eventHandler,
@@ -1710,6 +1724,7 @@
         defaultsPackageContents,
         commandId,
         clientEnv,
+        Collections.unmodifiableMap(actionEnvironment),
         tsgm);
     setDeletedPackages(packageCacheOptions.getDeletedPackages());