Skip to content

Commit

Permalink
Implement basic transition() inputs and outputs.
Browse files Browse the repository at this point in the history
inputs affect the settings parameter to the transition implementation function; only declared inputs are included in that map.
outputs constrain the settings which may be returned by the transition implementation function; the returned setting keys must exactly match the declared outputs.

Progress toward #5574 and #6237.

RELNOTES: None.
PiperOrigin-RevId: 220111780
  • Loading branch information
c-parsons authored and Copybara-Service committed Nov 5, 2018
1 parent e121420 commit 64c2114
Show file tree
Hide file tree
Showing 6 changed files with 390 additions and 26 deletions.
Expand Up @@ -16,6 +16,7 @@
import com.google.devtools.build.lib.skylarkbuildapi.config.ConfigurationTransitionApi;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.syntax.BaseFunction;
import java.util.List;

/**
* Implementation of {@link ConfigurationTransitionApi}.
Expand All @@ -26,10 +27,15 @@ public class StarlarkDefinedConfigTransition implements ConfigurationTransitionA

private final BaseFunction impl;
private final boolean forAnalysisTesting;
private final List<String> inputs;
private final List<String> outputs;

public StarlarkDefinedConfigTransition(BaseFunction impl, boolean forAnalysisTesting) {
public StarlarkDefinedConfigTransition(BaseFunction impl, boolean forAnalysisTesting,
List<String> inputs, List<String> outputs) {
this.impl = impl;
this.forAnalysisTesting = forAnalysisTesting;
this.inputs = inputs;
this.outputs = outputs;
}

/**
Expand All @@ -50,6 +56,23 @@ public Boolean isForAnalysisTesting() {
return forAnalysisTesting;
}

/**
* Returns the input option keys for this transition. Only option keys contained in this
* list will be provided in the 'settings' argument given to the transition implementation
* function.
*/
public List<String> getInputs() {
return inputs;
}

/**
* Returns the output option keys for this transition. The transition implementation function
* must return a dictionary where the option keys exactly match the elements of this list.
*/
public List<String> getOutputs() {
return outputs;
}

@Override
public void repr(SkylarkPrinter printer) {
printer.append("<transition object>");
Expand Down
Expand Up @@ -16,9 +16,11 @@

import static java.nio.charset.StandardCharsets.US_ASCII;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
Expand All @@ -40,6 +42,7 @@
import java.lang.reflect.Field;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

Expand All @@ -58,56 +61,87 @@ public class FunctionSplitTransitionProvider implements SplitTransitionProvider
private final BaseFunction transitionFunction;
private final SkylarkSemantics semantics;
private final EventHandler eventHandler;
private final List<String> inputs;
private final List<String> expectedOutputs;

public FunctionSplitTransitionProvider(BaseFunction transitionFunction,
SkylarkSemantics semantics, EventHandler eventHandler) {
SkylarkSemantics semantics, EventHandler eventHandler,
List<String> inputs, List<String> expectedOutputs) {
this.transitionFunction = transitionFunction;
this.semantics = semantics;
this.eventHandler = eventHandler;
this.inputs = inputs;
this.expectedOutputs = expectedOutputs;
}

@Override
public SplitTransition apply(AttributeMap attributeMap) {
return new FunctionSplitTransition(transitionFunction, semantics, eventHandler);
return new FunctionSplitTransition(transitionFunction, semantics, eventHandler, inputs,
expectedOutputs);
}

private static class FunctionSplitTransition implements SplitTransition {
private final BaseFunction transitionFunction;
private final SkylarkSemantics semantics;
private final EventHandler eventHandler;
private final List<String> inputs;
private final List<String> expectedOutputs;

public FunctionSplitTransition(BaseFunction transitionFunction, SkylarkSemantics semantics,
EventHandler eventHandler) {
EventHandler eventHandler, List<String> inputs, List<String> expectedOutputs) {
this.transitionFunction = transitionFunction;
this.semantics = semantics;
this.eventHandler = eventHandler;
this.inputs = inputs;
this.expectedOutputs = expectedOutputs;
}

@Override
public final List<BuildOptions> split(BuildOptions buildOptions) {
// TODO(waltl): we should be able to build this once and use it across different split
// transitions.
Map<String, OptionInfo> optionInfoMap = buildOptionInfo(buildOptions);
SkylarkDict<String, Object> settings = buildSettings(buildOptions, optionInfoMap);
try {
Map<String, OptionInfo> optionInfoMap = buildOptionInfo(buildOptions);
SkylarkDict<String, Object> settings = buildSettings(buildOptions, optionInfoMap, inputs);

ImmutableList.Builder<BuildOptions> splitBuildOptions = ImmutableList.builder();
ImmutableList.Builder<BuildOptions> splitBuildOptions = ImmutableList.builder();

try {
ImmutableList<Map<String, Object>> transitions =
evalTransitionFunction(transitionFunction, settings);
// TODO(juliexxia): Validate that the output values correctly match the output types.
validateFunctionOutputs(transitions, expectedOutputs);

for (Map<String, Object> transition : transitions) {
BuildOptions options = buildOptions.clone();
applyTransition(options, transition, optionInfoMap);
splitBuildOptions.add(options);
}
} catch (InterruptedException e) {
return splitBuildOptions.build();

} catch (InterruptedException | EvalException e) {
// TODO(juliexxia): Throw an exception better than RuntimeException.
throw new RuntimeException(e);
} catch (EvalException e) {
throw new RuntimeException(e.print());
}
}

private void validateFunctionOutputs(
ImmutableList<Map<String, Object>> transitions,
List<String> expectedOutputs) throws EvalException {
for (Map<String, Object> transition : transitions) {
LinkedHashSet<String> remainingOutputs = Sets.newLinkedHashSet(expectedOutputs);
for (String outputKey : transition.keySet()) {
if (!remainingOutputs.remove(outputKey)) {
throw new EvalException(transitionFunction.getLocation(),
String.format("transition function returned undeclared output '%s'", outputKey));
}
}

return splitBuildOptions.build();
if (!remainingOutputs.isEmpty()) {
throw new EvalException(transitionFunction.getLocation(),
String.format("transition outputs [%s] were not defined by transition function",
Joiner.on(", ").join(remainingOutputs)));
}
}
}

/**
Expand Down Expand Up @@ -163,15 +197,24 @@ private Map<String, OptionInfo> buildOptionInfo(BuildOptions buildOptions) {
* @throws RuntimeException If the field corresponding to an option value in buildOptions is
* inaccessible due to Java language access control, or if an option name is an invalid key
* to the Skylark dictionary.
* @throws EvalException if any of the specified transition inputs do not correspond to a valid
* build setting
*/
private SkylarkDict<String, Object> buildSettings(BuildOptions buildOptions,
Map<String, OptionInfo> optionInfoMap) {
Map<String, OptionInfo> optionInfoMap, List<String> inputs) throws EvalException {
LinkedHashSet<String> remainingInputs = Sets.newLinkedHashSet(inputs);

try (Mutability mutability = Mutability.create("build_settings")) {
SkylarkDict<String, Object> dict = SkylarkDict.withMutability(mutability);

for (Map.Entry<String, OptionInfo> entry : optionInfoMap.entrySet()) {
String optionName = entry.getKey();
String optionKey = COMMAND_LINE_OPTION_PREFIX + optionName;

if (!remainingInputs.remove(optionKey)) {
// This option was not present in inputs. Skip it.
continue;
}
OptionInfo optionInfo = entry.getValue();

try {
Expand All @@ -180,12 +223,18 @@ private SkylarkDict<String, Object> buildSettings(BuildOptions buildOptions,
Object optionValue = field.get(options);

dict.put(optionKey, optionValue, null, mutability);
} catch (IllegalAccessException | EvalException e) {
} catch (IllegalAccessException e) {
// These exceptions should not happen, but if they do, throw a RuntimeException.
throw new RuntimeException(e);
}
}

if (!remainingInputs.isEmpty()) {
throw new EvalException(transitionFunction.getLocation(),
String.format("transition inputs [%s] do not correspond to valid settings",
Joiner.on(", ").join(remainingInputs)));
}

return dict;
}
}
Expand Down Expand Up @@ -283,7 +332,8 @@ private void applyTransition(BuildOptions buildOptions, Map<String, Object> tran
try {
if (!optionInfoMap.containsKey(optionName)) {
throw new EvalException(transitionFunction.getLocation(),
"Unknown option '" + optionName + "'");
String.format("transition output '%s' does not correspond to a valid setting",
optionKey));
}

OptionInfo optionInfo = optionInfoMap.get(optionName);
Expand Down
Expand Up @@ -266,7 +266,8 @@ && containsNonNoneKey(arguments, ALLOW_SINGLE_FILE_ARG)) {
builder.hasStarlarkDefinedTransition();
}
builder.cfg(new FunctionSplitTransitionProvider(
transImpl, env.getSemantics(), env.getEventHandler()));
transImpl, env.getSemantics(), env.getEventHandler(),
starlarkDefinedTransition.getInputs(), starlarkDefinedTransition.getOutputs()));
} else if (!trans.equals("target")) {
throw new EvalException(ast.getLocation(),
"cfg must be either 'data', 'host', or 'target'.");
Expand Down
Expand Up @@ -14,13 +14,15 @@

package com.google.devtools.build.lib.rules.config;

import com.google.common.collect.Sets;
import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.skylarkbuildapi.config.ConfigGlobalLibraryApi;
import com.google.devtools.build.lib.skylarkbuildapi.config.ConfigurationTransitionApi;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.SkylarkSemantics;
import java.util.HashSet;
import java.util.List;

/**
Expand All @@ -30,6 +32,8 @@
*/
public class ConfigGlobalLibrary implements ConfigGlobalLibraryApi {

private static final String COMMAND_LINE_OPTION_PREFIX = "//command_line_option:";

@Override
public ConfigurationTransitionApi transition(BaseFunction implementation, List<String> inputs,
List<String> outputs, Boolean forAnalysisTesting,
Expand All @@ -52,6 +56,27 @@ public ConfigurationTransitionApi transition(BaseFunction implementation, List<S
+ "--experimental_starlark_config_transitions to use this experimental API.");
}
}
return new StarlarkDefinedConfigTransition(implementation, forAnalysisTesting);
validateBuildSettingKeys(inputs, "input", location);
validateBuildSettingKeys(outputs, "output", location);
return new StarlarkDefinedConfigTransition(implementation, forAnalysisTesting, inputs, outputs);
}

private void validateBuildSettingKeys(List<String> optionKeys, String keyErrorDescriptor,
Location location) throws EvalException {
// TODO(juliexxia): Allow real labels, not just command line option placeholders.

HashSet<String> processedOptions = Sets.newHashSet();

for (String optionKey : optionKeys) {
if (!optionKey.startsWith(COMMAND_LINE_OPTION_PREFIX)) {
throw new EvalException(location,
String.format("invalid transition %s '%s'. If this is intended as a native option, "
+ "it must begin with //command_line_option:", keyErrorDescriptor, optionKey));
}
if (!processedOptions.add(optionKey)) {
throw new EvalException(location,
String.format("duplicate transition %s '%s'", keyErrorDescriptor, optionKey));
}
}
}
}

0 comments on commit 64c2114

Please sign in to comment.