-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Let Starlark tests inherit env variables #14849
Closed
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
98b064b
Let Starlark tests inherit env variables
fmeum 27fa390
Make TestEnvironmentInfo members immutable
fmeum 11532ef
Make adding inherited vars to ActionEnvironment memory efficient
fmeum f3a2402
Specify fixedEnv/inheritedEnv interaction
fmeum 6088168
Intern empty ActionEnvironment and CompoundEnvironmentVariables insta…
fmeum File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
import com.google.common.collect.ImmutableSet; | ||
import com.google.devtools.build.lib.util.Fingerprint; | ||
import java.util.LinkedHashMap; | ||
import java.util.LinkedHashSet; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.TreeMap; | ||
|
@@ -45,36 +46,57 @@ | |
*/ | ||
public final class ActionEnvironment { | ||
|
||
/** A map of environment variables. */ | ||
/** | ||
* A map of environment variables and their values together with a list of variables whose values | ||
* should be inherited from the client environment. | ||
*/ | ||
public interface EnvironmentVariables { | ||
|
||
/** | ||
* Returns the environment variables as a map. | ||
* Returns the fixed environment variables as a map. | ||
* | ||
* <p>WARNING: this allocates additional objects if the underlying implementation is a {@link | ||
* CompoundEnvironmentVariables}; use sparingly. | ||
*/ | ||
ImmutableMap<String, String> getFixedEnvironment(); | ||
|
||
/** | ||
* Returns the inherited environment variables as a set. | ||
* | ||
* <p>WARNING: this allocations additional objects if the underlying implementation is a {@link | ||
* <p>WARNING: this allocates additional objects if the underlying implementation is a {@link | ||
* CompoundEnvironmentVariables}; use sparingly. | ||
*/ | ||
ImmutableMap<String, String> toMap(); | ||
ImmutableSet<String> getInheritedEnvironment(); | ||
|
||
default boolean isEmpty() { | ||
return toMap().isEmpty(); | ||
return getFixedEnvironment().isEmpty() && getInheritedEnvironment().isEmpty(); | ||
} | ||
|
||
default int size() { | ||
return toMap().size(); | ||
return getFixedEnvironment().size() + getInheritedEnvironment().size(); | ||
} | ||
} | ||
|
||
/** | ||
* An {@link EnvironmentVariables} that combines variables from two different environments without | ||
* allocation a new map. | ||
* allocating a new map. | ||
*/ | ||
static class CompoundEnvironmentVariables implements EnvironmentVariables { | ||
|
||
static EnvironmentVariables create(Map<String, String> fixedVars, Set<String> inheritedVars, | ||
EnvironmentVariables base) { | ||
if (fixedVars.isEmpty() && inheritedVars.isEmpty()) { | ||
return base; | ||
} | ||
return new CompoundEnvironmentVariables(fixedVars, inheritedVars, base); | ||
} | ||
|
||
private final EnvironmentVariables current; | ||
private final EnvironmentVariables base; | ||
|
||
CompoundEnvironmentVariables(Map<String, String> vars, EnvironmentVariables base) { | ||
this.current = new SimpleEnvironmentVariables(vars); | ||
private CompoundEnvironmentVariables(Map<String, String> fixedVars, Set<String> inheritedVars, | ||
EnvironmentVariables base) { | ||
this.current = SimpleEnvironmentVariables.create(fixedVars, inheritedVars); | ||
this.base = base; | ||
} | ||
|
||
|
@@ -84,57 +106,73 @@ public boolean isEmpty() { | |
} | ||
|
||
@Override | ||
public ImmutableMap<String, String> toMap() { | ||
public ImmutableMap<String, String> getFixedEnvironment() { | ||
Map<String, String> result = new LinkedHashMap<>(); | ||
result.putAll(base.toMap()); | ||
result.putAll(current.toMap()); | ||
result.putAll(base.getFixedEnvironment()); | ||
result.putAll(current.getFixedEnvironment()); | ||
return ImmutableMap.copyOf(result); | ||
} | ||
|
||
@Override | ||
public ImmutableSet<String> getInheritedEnvironment() { | ||
Set<String> result = new LinkedHashSet<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer to use ImmutableSet.Builder. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
result.addAll(base.getInheritedEnvironment()); | ||
result.addAll(current.getInheritedEnvironment()); | ||
return ImmutableSet.copyOf(result); | ||
} | ||
} | ||
|
||
/** A simple {@link EnvironmentVariables}. */ | ||
/** | ||
* A simple {@link EnvironmentVariables}. | ||
*/ | ||
static class SimpleEnvironmentVariables implements EnvironmentVariables { | ||
|
||
static EnvironmentVariables create(Map<String, String> vars) { | ||
if (vars.isEmpty()) { | ||
static EnvironmentVariables create(Map<String, String> fixedVars, Set<String> inheritedVars) { | ||
if (fixedVars.isEmpty() && inheritedVars.isEmpty()) { | ||
return EMPTY_ENVIRONMENT_VARIABLES; | ||
} | ||
return new SimpleEnvironmentVariables(vars); | ||
return new SimpleEnvironmentVariables(fixedVars, inheritedVars); | ||
} | ||
|
||
private final ImmutableMap<String, String> vars; | ||
private final ImmutableMap<String, String> fixedVars; | ||
private final ImmutableSet<String> inheritedVars; | ||
|
||
private SimpleEnvironmentVariables(Map<String, String> fixedVars, Set<String> inheritedVars) { | ||
this.fixedVars = ImmutableMap.copyOf(fixedVars); | ||
this.inheritedVars = ImmutableSet.copyOf(inheritedVars); | ||
} | ||
|
||
private SimpleEnvironmentVariables(Map<String, String> vars) { | ||
this.vars = ImmutableMap.copyOf(vars); | ||
@Override | ||
public ImmutableMap<String, String> getFixedEnvironment() { | ||
return fixedVars; | ||
} | ||
|
||
@Override | ||
public ImmutableMap<String, String> toMap() { | ||
return vars; | ||
public ImmutableSet<String> getInheritedEnvironment() { | ||
return inheritedVars; | ||
} | ||
} | ||
|
||
/** An empty {@link EnvironmentVariables}. */ | ||
/** | ||
* An empty {@link EnvironmentVariables}. | ||
*/ | ||
public static final EnvironmentVariables EMPTY_ENVIRONMENT_VARIABLES = | ||
new SimpleEnvironmentVariables(ImmutableMap.of()); | ||
new SimpleEnvironmentVariables(ImmutableMap.of(), ImmutableSet.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()); | ||
public static final ActionEnvironment EMPTY = new ActionEnvironment(EMPTY_ENVIRONMENT_VARIABLES); | ||
|
||
/** | ||
* 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()) { | ||
|
@@ -145,59 +183,81 @@ public static ActionEnvironment split(Map<String, String> env) { | |
inheritedEnv.add(key); | ||
} | ||
} | ||
return create(new SimpleEnvironmentVariables(fixedEnv), ImmutableSet.copyOf(inheritedEnv)); | ||
return create(SimpleEnvironmentVariables.create(fixedEnv, inheritedEnv)); | ||
} | ||
|
||
private final EnvironmentVariables fixedEnv; | ||
private final ImmutableSet<String> inheritedEnv; | ||
private final EnvironmentVariables vars; | ||
|
||
private ActionEnvironment(EnvironmentVariables fixedEnv, ImmutableSet<String> inheritedEnv) { | ||
this.fixedEnv = fixedEnv; | ||
this.inheritedEnv = inheritedEnv; | ||
private ActionEnvironment(EnvironmentVariables vars) { | ||
this.vars = vars; | ||
} | ||
|
||
/** | ||
* 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. | ||
* Creates a new action environment. If an environment variable is contained in both {@link | ||
* EnvironmentVariables#getFixedEnvironment()} and {@link EnvironmentVariables#getInheritedEnvironment()}, | ||
* the result of {@link ActionEnvironment#resolve(Map, Map)} will contain the value inherited from | ||
* the client environment. | ||
*/ | ||
private static ActionEnvironment create( | ||
EnvironmentVariables fixedEnv, ImmutableSet<String> inheritedEnv) { | ||
if (fixedEnv.isEmpty() && inheritedEnv.isEmpty()) { | ||
private static ActionEnvironment create(EnvironmentVariables vars) { | ||
if (vars.isEmpty()) { | ||
return EMPTY; | ||
} | ||
return new ActionEnvironment(fixedEnv, inheritedEnv); | ||
return new ActionEnvironment(vars); | ||
} | ||
|
||
/** | ||
* Creates a new action environment. If an environment variable is contained both as a key in | ||
* {@code fixedEnv} and in {@code inheritedEnv}, the result of {@link | ||
* ActionEnvironment#resolve(Map, Map)} will contain the value inherited from the client | ||
* environment. | ||
*/ | ||
public static ActionEnvironment create( | ||
Map<String, String> fixedEnv, ImmutableSet<String> inheritedEnv) { | ||
return new ActionEnvironment(SimpleEnvironmentVariables.create(fixedEnv), inheritedEnv); | ||
return ActionEnvironment.create(SimpleEnvironmentVariables.create(fixedEnv, inheritedEnv)); | ||
} | ||
|
||
public static ActionEnvironment create(Map<String, String> fixedEnv) { | ||
return new ActionEnvironment(new SimpleEnvironmentVariables(fixedEnv), ImmutableSet.of()); | ||
return ActionEnvironment.create(SimpleEnvironmentVariables.create(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); | ||
public ActionEnvironment withAdditionalFixedVariables(Map<String, String> fixedVars) { | ||
return withAdditionalVariables(fixedVars, ImmutableSet.of()); | ||
} | ||
|
||
/** Returns the combined size of the fixed and inherited environments. */ | ||
/** | ||
* Returns a copy of the environment with the given fixed and inherited variables added to it, | ||
* <em>overwriting any existing occurrences of those variables</em>. | ||
*/ | ||
public ActionEnvironment withAdditionalVariables(Map<String, String> fixedVars, | ||
Set<String> inheritedVars) { | ||
EnvironmentVariables newVars = CompoundEnvironmentVariables.create(fixedVars, inheritedVars, | ||
vars); | ||
if (newVars == vars) { | ||
return this; | ||
} | ||
return ActionEnvironment.create(newVars); | ||
} | ||
|
||
/** | ||
* Returns an upper bound on the combined size of the fixed and inherited environments. A call to | ||
* {@link ActionEnvironment#resolve(Map, Map)} may add less entries than this number if | ||
* environment variables are contained in both the fixed and the inherited environment. | ||
*/ | ||
public int size() { | ||
return fixedEnv.size() + inheritedEnv.size(); | ||
return vars.size(); | ||
} | ||
|
||
/** | ||
* 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; | ||
public ImmutableMap<String, String> getFixedEnv() { | ||
return vars.getFixedEnvironment(); | ||
} | ||
|
||
/** | ||
|
@@ -207,7 +267,7 @@ public EnvironmentVariables getFixedEnv() { | |
* get the complete environment. | ||
*/ | ||
public ImmutableSet<String> getInheritedEnv() { | ||
return inheritedEnv; | ||
return vars.getInheritedEnvironment(); | ||
} | ||
|
||
/** | ||
|
@@ -218,8 +278,8 @@ public ImmutableSet<String> getInheritedEnv() { | |
*/ | ||
public void resolve(Map<String, String> result, Map<String, String> clientEnv) { | ||
checkNotNull(clientEnv); | ||
result.putAll(fixedEnv.toMap()); | ||
for (String var : inheritedEnv) { | ||
result.putAll(vars.getFixedEnvironment()); | ||
for (String var : vars.getInheritedEnvironment()) { | ||
String value = clientEnv.get(var); | ||
if (value != null) { | ||
result.put(var, value); | ||
|
@@ -228,7 +288,7 @@ public void resolve(Map<String, String> result, Map<String, String> clientEnv) { | |
} | ||
|
||
public void addTo(Fingerprint f) { | ||
f.addStringMap(fixedEnv.toMap()); | ||
f.addStrings(inheritedEnv); | ||
f.addStringMap(vars.getFixedEnvironment()); | ||
f.addStrings(vars.getInheritedEnvironment()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to use ImmutableMap.Builder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.