blob: 817e73ea57591f65837c1d7204de17be025bf687 [file] [log] [blame]
// Copyright 2018 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.analysis.config;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.BazelStarlarkContext;
import com.google.devtools.build.lib.packages.StructImpl;
import com.google.devtools.build.lib.skylarkbuildapi.config.ConfigurationTransitionApi;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.Dict;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.Printer;
import com.google.devtools.build.lib.syntax.Sequence;
import com.google.devtools.build.lib.syntax.Starlark;
import com.google.devtools.build.lib.syntax.StarlarkSemantics;
import com.google.devtools.build.lib.syntax.StarlarkThread;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Implementation of {@link ConfigurationTransitionApi}.
*
* <p>Represents a configuration transition across a dependency edge defined in Starlark.
*/
public abstract class StarlarkDefinedConfigTransition implements ConfigurationTransitionApi {
private final List<String> inputs;
private final List<String> outputs;
private final Location location;
private final StoredEventHandler eventHandler;
private StarlarkDefinedConfigTransition(
List<String> inputs, List<String> outputs, Location location) {
this.inputs = inputs;
this.outputs = outputs;
this.location = location;
this.eventHandler = new StoredEventHandler();
}
/**
* Returns true if this transition is for analysis testing. If true, then only attributes of rules
* with {@code analysis_test=true} may use this transition object.
*/
public abstract Boolean isForAnalysisTesting();
/**
* 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;
}
/**
* Returns the location of the Starlark code responsible for determining the transition's changed
* settings for purposes of error reporting.
*/
public Location getLocationForErrorReporting() {
return location;
}
public StoredEventHandler getEventHandler() {
return eventHandler;
}
/**
* Given a map of a subset of the "previous" build settings, returns the changed build settings as
* a result of applying this transition.
*
* @param previousSettings a map representing the previous build settings
* @return a list of changed build setting maps; each element of the list represents a different
* child configuration (split transitions will have multiple elements in this list, other
* transitions should have a single element). Each build setting map is a map from build
* setting to target setting value; all other build settings will remain unchanged
* @throws EvalException if there is an error evaluating the transition
* @throws InterruptedException if evaluating the transition is interrupted
*/
public abstract ImmutableMap<String, Map<String, Object>> evaluate(
Map<String, Object> previousSettings, StructImpl attributeMap)
throws EvalException, InterruptedException;
public static StarlarkDefinedConfigTransition newRegularTransition(
BaseFunction impl,
List<String> inputs,
List<String> outputs,
StarlarkSemantics semantics,
StarlarkThread thread) {
return new RegularTransition(
impl, inputs, outputs, semantics, BazelStarlarkContext.from(thread));
}
public static StarlarkDefinedConfigTransition newAnalysisTestTransition(
Map<String, Object> changedSettings, Location location) {
return new AnalysisTestTransition(changedSettings, location);
}
private static class AnalysisTestTransition extends StarlarkDefinedConfigTransition {
private final Map<String, Object> changedSettings;
public AnalysisTestTransition(Map<String, Object> changedSettings, Location location) {
super(ImmutableList.of(), ImmutableList.copyOf(changedSettings.keySet()), location);
this.changedSettings = changedSettings;
}
@Override
public Boolean isForAnalysisTesting() {
return true;
}
@Override
public ImmutableMap<String, Map<String, Object>> evaluate(
Map<String, Object> previousSettings, StructImpl attributeMapper) {
return ImmutableMap.of("analysis_test", changedSettings);
}
@Override
public void repr(Printer printer) {
printer.append("<analysis_test_transition object>");
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof AnalysisTestTransition) {
AnalysisTestTransition otherTransition = (AnalysisTestTransition) object;
return Objects.equals(otherTransition.getInputs(), this.getInputs())
&& Objects.equals(otherTransition.getOutputs(), this.getOutputs())
&& Objects.equals(otherTransition.changedSettings, this.changedSettings);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(this.getInputs(), this.getOutputs(), this.changedSettings);
}
}
/** A transition with a user-defined implementation function. */
public static class RegularTransition extends StarlarkDefinedConfigTransition {
private final BaseFunction impl;
private final StarlarkSemantics semantics;
private final BazelStarlarkContext starlarkContext;
RegularTransition(
BaseFunction impl,
List<String> inputs,
List<String> outputs,
StarlarkSemantics semantics,
BazelStarlarkContext context) {
super(inputs, outputs, impl.getLocation());
this.impl = impl;
this.semantics = semantics;
this.starlarkContext = context;
}
@Override
public Boolean isForAnalysisTesting() {
return false;
}
/**
* This method evaluates the implementation function of the transition.
*
* <p>In the case of a {@link
* com.google.devtools.build.lib.analysis.config.transitions.PatchTransition}, the impl fxn
* returns a {@link Dict} of option name strings to option value object.
*
* <p>In the case of {@link
* com.google.devtools.build.lib.analysis.config.transitions.SplitTransition}, the impl fxn can
* return either a {@link Dict} of String keys to {@link Dict} values. Or it can return a list
* of {@link Dict}s in cases where the consumer doesn't care about differentiating between the
* splits (i.e. accessing later via {@code ctx.split_attrs}).
*
* @param previousSettings a map representing the previous build settings
* @param attributeMapper a map of attributes
*/
// TODO(bazel-team): integrate dict-of-dicts return type with ctx.split_attr
@Override
@SuppressWarnings("rawtypes")
public ImmutableMap<String, Map<String, Object>> evaluate(
Map<String, Object> previousSettings, StructImpl attributeMapper)
throws EvalException, InterruptedException {
Object result;
try {
result = evalFunction(impl, ImmutableList.of(previousSettings, attributeMapper));
} catch (EvalException e) {
throw new EvalException(impl.getLocation(), e.getMessage());
}
if (result instanceof Dict) {
// If we're receiving an empty dictionary, it's an error. Even if a
// transition function sometimes evaluates to a no-op, it needs to return the passed in
// settings. Return early for now since better error reporting will happen in
// {@link FunctionTransitionUtil#validateFunctionOutputsMatchesDeclaredOutputs}
if (((Dict) result).isEmpty()) {
return ImmutableMap.of("error", ImmutableMap.of());
}
// TODO(bazel-team): integrate keys with ctx.split_attr. Currently ctx.split_attr always
// keys on cpu value - we should be able to key on the keys returned here.
try {
@SuppressWarnings("rawtypes")
Map<String, Dict> dictOfDict =
((Dict<?, ?>) result)
.getContents(String.class, Dict.class, "dictionary of options dictionaries");
ImmutableMap.Builder<String, Map<String, Object>> builder = ImmutableMap.builder();
for (Map.Entry<String, Dict> entry : dictOfDict.entrySet()) { // rawtypes error
Map<String, Object> dict =
((Dict<?, ?>) entry.getValue())
.getContents(String.class, Object.class, "an option dictionary");
builder.put(entry.getKey(), dict);
}
return builder.build();
} catch (EvalException e) {
// fall through
}
try {
// Try if this is a non-split, i.e. 1:1, transition. In which case use the impl function
// name as the transition key though it would not be used anywhere.
return ImmutableMap.of(
impl.getName(),
((Dict<?, ?>) result)
.getContents(String.class, Object.class, "dictionary of options"));
} catch (EvalException e) {
throw new EvalException(impl.getLocation(), e.getMessage());
}
} else if (result instanceof Sequence) {
ImmutableMap.Builder<String, Map<String, Object>> builder = ImmutableMap.builder();
try {
int i = 0;
for (Dict<?, ?> toOptions :
((Sequence<?>) result)
.getContents(Dict.class, "dictionary of options dictionaries")) {
// TODO(b/146347033): Document this behavior.
builder.put(
Integer.toString(i++),
toOptions.getContents(String.class, Object.class, "dictionary of options"));
}
} catch (EvalException e) {
throw new EvalException(impl.getLocation(), e.getMessage());
}
return builder.build();
} else {
throw new EvalException(
impl.getLocation(),
"Transition function must return a dictionary or list of dictionaries.");
}
}
@Override
public void repr(Printer printer) {
printer.append("<transition object>");
}
/** Evaluate the input function with the given argument, and return the return value. */
private Object evalFunction(BaseFunction function, ImmutableList<Object> args)
throws InterruptedException, EvalException {
try (Mutability mutability = Mutability.create("eval_transition_function")) {
StarlarkThread thread =
StarlarkThread.builder(mutability)
.setSemantics(semantics)
.build();
thread.setPrintHandler(StarlarkThread.makeDebugPrintHandler(getEventHandler()));
starlarkContext.storeInThread(thread);
return Starlark.call(thread, function, args, /*kwargs=*/ ImmutableMap.of());
}
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof RegularTransition) {
RegularTransition otherTransition = (RegularTransition) object;
return Objects.equals(otherTransition.getInputs(), this.getInputs())
&& Objects.equals(otherTransition.getOutputs(), this.getOutputs())
&& Objects.equals(otherTransition.impl, this.impl);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(this.getInputs(), this.getOutputs(), this.impl);
}
}
}