blob: 2378a00650ea42929e1da3a5f0d7b07bb135f28d [file] [log] [blame]
// 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.actions;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.util.Fingerprint;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Environment variables for build or test actions.
*
* <p>The action environment consists of two parts.
*
* <ol>
* <li>All the environment variables with a fixed value, stored in a map.
* <li>All the environment variables inherited from the client environment, stored in a set.
* </ol>
*
* <p>Inherited environment variables must be declared in the Action interface (see {@link
* Action#getClientEnvironmentVariables}), so that the dependency on the client environment is known
* to the execution framework for correct incremental builds.
*
* <p>By splitting the environment, we can handle environment variable changes more efficiently -
* the dependency of the action on the environment variable are tracked in Skyframe (and in the
* action cache), such that Bazel knows exactly which actions it needs to rerun, and does not have
* to reanalyze the entire dependency graph.
*/
@AutoCodec
public final class ActionEnvironment {
/** A map of environment variables. */
public interface EnvironmentVariables {
/**
* Returns the environment variables as a map.
*
* <p>WARNING: this allocations additional objects if the underlying implementation is a {@link
* CompoundEnvironmentVariables}; use sparingly.
*/
ImmutableMap<String, String> toMap();
default boolean isEmpty() {
return toMap().isEmpty();
}
default int size() {
return toMap().size();
}
}
/**
* An {@link EnvironmentVariables} that combines variables from two different environments without
* allocation a new map.
*/
static class CompoundEnvironmentVariables implements EnvironmentVariables {
private final EnvironmentVariables current;
private final EnvironmentVariables base;
CompoundEnvironmentVariables(Map<String, String> vars, EnvironmentVariables base) {
this.current = new SimpleEnvironmentVariables(vars);
this.base = base;
}
@Override
public boolean isEmpty() {
return current.isEmpty() && base.isEmpty();
}
@Override
public ImmutableMap<String, String> toMap() {
Map<String, String> result = new LinkedHashMap<>();
result.putAll(base.toMap());
result.putAll(current.toMap());
return ImmutableMap.copyOf(result);
}
}
/** A simple {@link EnvironmentVariables}. */
static class SimpleEnvironmentVariables implements EnvironmentVariables {
static EnvironmentVariables create(Map<String, String> vars) {
if (vars.isEmpty()) {
return EMPTY_ENVIRONMENT_VARIABLES;
}
return new SimpleEnvironmentVariables(vars);
}
private final ImmutableMap<String, String> vars;
private SimpleEnvironmentVariables(Map<String, String> vars) {
this.vars = ImmutableMap.copyOf(vars);
}
@Override
public ImmutableMap<String, String> toMap() {
return vars;
}
}
/** An empty {@link EnvironmentVariables}. */
public static final EnvironmentVariables EMPTY_ENVIRONMENT_VARIABLES =
new SimpleEnvironmentVariables(ImmutableMap.of());
/**
* An empty environment, mainly for testing. Production code should never use this, but instead
* get the proper environment from the current configuration.
*/
// TODO(ulfjack): Migrate all production code to use the proper action environment, and then make
// this @VisibleForTesting or rename it to clarify.
public static final ActionEnvironment EMPTY =
new ActionEnvironment(EMPTY_ENVIRONMENT_VARIABLES, ImmutableSet.of());
/**
* Splits the given map into a map of variables with a fixed value, and a set of variables that
* should be inherited, the latter of which are identified by having a {@code null} value in the
* given map. Returns these two parts as a new {@link ActionEnvironment} instance.
*/
public static ActionEnvironment split(Map<String, String> env) {
// Care needs to be taken that the two sets don't overlap - the order in which the two parts are
// combined later is undefined.
Map<String, String> fixedEnv = new TreeMap<>();
Set<String> inheritedEnv = new TreeSet<>();
for (Map.Entry<String, String> entry : env.entrySet()) {
if (entry.getValue() != null) {
fixedEnv.put(entry.getKey(), entry.getValue());
} else {
String key = entry.getKey();
inheritedEnv.add(key);
}
}
return create(new SimpleEnvironmentVariables(fixedEnv), ImmutableSet.copyOf(inheritedEnv));
}
private final EnvironmentVariables fixedEnv;
private final Iterable<String> inheritedEnv;
private ActionEnvironment(EnvironmentVariables fixedEnv, Iterable<String> inheritedEnv) {
CollectionUtils.checkImmutable(inheritedEnv);
this.fixedEnv = fixedEnv;
this.inheritedEnv = inheritedEnv;
}
/**
* Creates a new action environment. The order in which the environments are combined is
* undefined, so callers need to take care that the key set of the {@code fixedEnv} map and the
* set of {@code inheritedEnv} elements are disjoint.
*/
@AutoCodec.Instantiator
public static ActionEnvironment create(
EnvironmentVariables fixedEnv, Iterable<String> inheritedEnv) {
if (fixedEnv.isEmpty() && Iterables.isEmpty(inheritedEnv)) {
return EMPTY;
}
return new ActionEnvironment(fixedEnv, inheritedEnv);
}
public static ActionEnvironment create(
Map<String, String> fixedEnv, Iterable<String> inheritedEnv) {
return new ActionEnvironment(SimpleEnvironmentVariables.create(fixedEnv), inheritedEnv);
}
public static ActionEnvironment create(Map<String, String> fixedEnv) {
return new ActionEnvironment(new SimpleEnvironmentVariables(fixedEnv), ImmutableSet.of());
}
/**
* Returns a copy of the environment with the given fixed variables added to it, <em>overwriting
* any existing occurrences of those variables</em>.
*/
public ActionEnvironment addFixedVariables(Map<String, String> vars) {
return new ActionEnvironment(new CompoundEnvironmentVariables(vars, fixedEnv), inheritedEnv);
}
/** Returns the combined size of the fixed and inherited environments. */
public int size() {
return fixedEnv.size() + Iterables.size(inheritedEnv);
}
/**
* Returns the 'fixed' part of the environment, i.e., those environment variables that are set to
* fixed values and their values. This should only be used for testing and to compute the cache
* keys of actions. Use {@link #resolve} instead to get the complete environment.
*/
public EnvironmentVariables getFixedEnv() {
return fixedEnv;
}
/**
* Returns the 'inherited' part of the environment, i.e., those environment variables that are
* inherited from the client environment and therefore have no fixed value here. This should only
* be used for testing and to compute the cache keys of actions. Use {@link #resolve} instead to
* get the complete environment.
*/
public Iterable<String> getInheritedEnv() {
return inheritedEnv;
}
/**
* Resolves the action environment and adds the resulting entries to the given {@code result} map,
* by looking up any inherited env variables in the given {@code clientEnv}.
*
* <p>We pass in a map to mutate to avoid creating and merging intermediate maps.
*/
public void resolve(Map<String, String> result, Map<String, String> clientEnv) {
Preconditions.checkNotNull(clientEnv);
result.putAll(fixedEnv.toMap());
for (String var : inheritedEnv) {
String value = clientEnv.get(var);
if (value != null) {
result.put(var, value);
}
}
}
public void addTo(Fingerprint f) {
f.addStringMap(fixedEnv.toMap());
f.addStrings(inheritedEnv);
}
}