Implement starlark rule class transitions.
Add the `cfg` parameter to rule definition which can take transitions created by the transition() fxn. Split out common work for attribute and rule transitions into a util class and rename attribute transition work to distinguish from rule transition work.
Prohibit build settings targets from transitioning themselves since there is no use case for this (yet).
For now, prohibit rule transitions from accessing *any* attributes that are resolved through selects. This is an overly restrictive way to deal with the potential cycles caused by attribute-parameterized rule transitions and configurable attributes. But will fine tuned in follow up CLs to prevent this CL from being too large.
PiperOrigin-RevId: 228017175
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionSplitTransitionProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionSplitTransitionProvider.java
deleted file mode 100644
index d374a75..0000000
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionSplitTransitionProvider.java
+++ /dev/null
@@ -1,373 +0,0 @@
-// 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.skylark;
-
-import static com.google.devtools.build.lib.analysis.skylark.SkylarkAttributesCollection.ERROR_MESSAGE_FOR_NO_ATTR;
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
-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;
-import com.google.devtools.build.lib.analysis.config.FragmentOptions;
-import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
-import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
-import com.google.devtools.build.lib.packages.Attribute;
-import com.google.devtools.build.lib.packages.Attribute.SplitTransitionProvider;
-import com.google.devtools.build.lib.packages.AttributeMap;
-import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper;
-import com.google.devtools.build.lib.packages.StructImpl;
-import com.google.devtools.build.lib.packages.StructProvider;
-import com.google.devtools.build.lib.syntax.Environment;
-import com.google.devtools.build.lib.syntax.EvalException;
-import com.google.devtools.build.lib.syntax.Mutability;
-import com.google.devtools.build.lib.syntax.Runtime;
-import com.google.devtools.build.lib.syntax.Runtime.NoneType;
-import com.google.devtools.build.lib.syntax.SkylarkDict;
-import com.google.devtools.build.lib.syntax.SkylarkType;
-import com.google.devtools.common.options.OptionDefinition;
-import com.google.devtools.common.options.OptionsParser;
-import com.google.devtools.common.options.OptionsParsingException;
-import java.lang.reflect.Field;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class implements a split transition provider that takes a Skylark transition function as
- * input. The transition function takes a settings argument, which is a dictionary containing the
- * current option values. It either returns a dictionary mapping option name to new option value
- * (for a patch transition), or a dictionary of such dictionaries (for a split transition).
- *
- * <p>TODO(bazel-team): Consider allowing dependency-typed attributes to actually return providers
- * instead of just labels (see {@link SkylarkAttributesCollection#addAttribute}).
- */
-public class FunctionSplitTransitionProvider implements SplitTransitionProvider {
-
- private static final String COMMAND_LINE_OPTION_PREFIX = "//command_line_option:";
-
- private final StarlarkDefinedConfigTransition starlarkDefinedConfigTransition;
-
- public FunctionSplitTransitionProvider(
- StarlarkDefinedConfigTransition starlarkDefinedConfigTransition) {
- this.starlarkDefinedConfigTransition = starlarkDefinedConfigTransition;
- }
-
- @Override
- public SplitTransition apply(AttributeMap attributeMap) {
- return new FunctionSplitTransition(starlarkDefinedConfigTransition, attributeMap);
- }
-
- private static class FunctionSplitTransition implements SplitTransition {
- private final StarlarkDefinedConfigTransition starlarkDefinedConfigTransition;
- private final StructImpl attrObject;
-
- FunctionSplitTransition(
- StarlarkDefinedConfigTransition starlarkDefinedConfigTransition,
- AttributeMap attributeMap) {
- Preconditions.checkArgument(attributeMap instanceof ConfiguredAttributeMapper);
- this.starlarkDefinedConfigTransition = starlarkDefinedConfigTransition;
-
- ConfiguredAttributeMapper configuredAttributeMapper =
- (ConfiguredAttributeMapper) attributeMap;
- LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
- for (String attribute : attributeMap.getAttributeNames()) {
- Object val =
- configuredAttributeMapper.get(attribute, attributeMap.getAttributeType(attribute));
- attributes.put(
- Attribute.getSkylarkName(attribute),
- val == null ? Runtime.NONE : SkylarkType.convertToSkylark(val, (Environment) null));
- }
- attrObject = StructProvider.STRUCT.create(attributes, ERROR_MESSAGE_FOR_NO_ATTR);
- }
-
- @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.
- try {
- Map<String, OptionInfo> optionInfoMap = buildOptionInfo(buildOptions);
- SkylarkDict<String, Object> settings =
- buildSettings(buildOptions, optionInfoMap, starlarkDefinedConfigTransition.getInputs());
-
- ImmutableList.Builder<BuildOptions> splitBuildOptions = ImmutableList.builder();
-
- ImmutableList<Map<String, Object>> transitions =
- starlarkDefinedConfigTransition.getChangedSettings(settings, attrObject);
- // TODO(juliexxia): Validate that the output values correctly match the output types.
- validateFunctionOutputs(transitions, starlarkDefinedConfigTransition.getOutputs());
-
- for (Map<String, Object> transition : transitions) {
- BuildOptions options = buildOptions.clone();
- applyTransition(options, transition, optionInfoMap);
- splitBuildOptions.add(options);
- }
- return splitBuildOptions.build();
-
- } catch (InterruptedException | EvalException e) {
- // TODO(juliexxia): Throw an exception better than RuntimeException.
- throw new RuntimeException(e);
- }
- }
-
- 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(
- starlarkDefinedConfigTransition.getLocationForErrorReporting(),
- String.format("transition function returned undeclared output '%s'", outputKey));
- }
- }
-
- if (!remainingOutputs.isEmpty()) {
- throw new EvalException(
- starlarkDefinedConfigTransition.getLocationForErrorReporting(),
- String.format(
- "transition outputs [%s] were not defined by transition function",
- Joiner.on(", ").join(remainingOutputs)));
- }
- }
- }
-
- /**
- * Given a label-like string representing a command line option, returns the command line
- * option string that it represents. This is a temporary measure to support command line
- * options with strings that look "label-like", so that migrating users using this
- * experimental syntax is easier later.
- *
- * @throws EvalException if the given string is not a valid format to represent to
- * a command line option
- */
- private String commandLineOptionLabelToOption(String label) throws EvalException {
- if (label.startsWith(COMMAND_LINE_OPTION_PREFIX)) {
- return label.substring(COMMAND_LINE_OPTION_PREFIX.length());
- } else {
- throw new EvalException(
- starlarkDefinedConfigTransition.getLocationForErrorReporting(),
- String.format(
- "Option key '%s' is of invalid form. "
- + "Expected command line option to begin with %s",
- label, COMMAND_LINE_OPTION_PREFIX));
- }
- }
-
- /**
- * For all the options in the BuildOptions, build a map from option name to its information.
- */
- private Map<String, OptionInfo> buildOptionInfo(BuildOptions buildOptions) {
- ImmutableMap.Builder<String, OptionInfo> builder = new ImmutableMap.Builder<>();
-
- ImmutableSet<Class<? extends FragmentOptions>> optionClasses =
- buildOptions
- .getNativeOptions()
- .stream()
- .map(FragmentOptions::getClass)
- .collect(ImmutableSet.toImmutableSet());
-
- for (Class<? extends FragmentOptions> optionClass : optionClasses) {
- ImmutableList<OptionDefinition> optionDefinitions =
- OptionsParser.getOptionDefinitions(optionClass);
- for (OptionDefinition def : optionDefinitions) {
- String optionName = def.getOptionName();
- builder.put(optionName, new OptionInfo(optionClass, def));
- }
- }
-
- return builder.build();
- }
-
- /**
- * Enter the options in buildOptions into a skylark dictionary, and return the dictionary.
- *
- * @throws IllegalArgumentException If the method is unable to look up the value in buildOptions
- * corresponding to an entry in optionInfoMap.
- * @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, 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 {
- Field field = optionInfo.getDefinition().getField();
- FragmentOptions options = buildOptions.get(optionInfo.getOptionClass());
- Object optionValue = field.get(options);
-
- dict.put(optionKey, optionValue, null, mutability);
- } 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(
- starlarkDefinedConfigTransition.getLocationForErrorReporting(),
- String.format(
- "transition inputs [%s] do not correspond to valid settings",
- Joiner.on(", ").join(remainingInputs)));
- }
-
- return dict;
- }
- }
-
- /**
- * Apply the transition dictionary to the build option, using optionInfoMap to look up the
- * option info.
- *
- * @throws RuntimeException If a requested option field is inaccessible.
- */
- private void applyTransition(BuildOptions buildOptions, Map<String, Object> transition,
- Map<String, OptionInfo> optionInfoMap)
- throws EvalException {
- for (Map.Entry<String, Object> entry : transition.entrySet()) {
- String optionKey = entry.getKey();
-
- // TODO(juliexxia): Handle keys which correspond to build_setting target labels instead
- // of assuming every key is for a command line option.
- String optionName = commandLineOptionLabelToOption(optionKey);
- Object optionValue = entry.getValue();
-
- // Convert NoneType to null.
- if (optionValue instanceof NoneType) {
- optionValue = null;
- }
-
- try {
- if (!optionInfoMap.containsKey(optionName)) {
- throw new EvalException(
- starlarkDefinedConfigTransition.getLocationForErrorReporting(),
- String.format(
- "transition output '%s' does not correspond to a valid setting", optionKey));
- }
-
- OptionInfo optionInfo = optionInfoMap.get(optionName);
- OptionDefinition def = optionInfo.getDefinition();
- Field field = def.getField();
- FragmentOptions options = buildOptions.get(optionInfo.getOptionClass());
- if (optionValue == null || def.getType().isInstance(optionValue)) {
- field.set(options, optionValue);
- } else if (optionValue instanceof String) {
- field.set(options, def.getConverter().convert((String) optionValue));
- } else {
- throw new EvalException(
- starlarkDefinedConfigTransition.getLocationForErrorReporting(),
- "Invalid value type for option '" + optionName + "'");
- }
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "IllegalAccess for option " + optionName + ": " + e.getMessage());
- } catch (OptionsParsingException e) {
- throw new EvalException(
- starlarkDefinedConfigTransition.getLocationForErrorReporting(),
- "OptionsParsingError for option '" + optionName + "': " + e.getMessage());
- }
- }
-
- BuildConfiguration.Options buildConfigOptions;
- buildConfigOptions = buildOptions.get(BuildConfiguration.Options.class);
-
- if (starlarkDefinedConfigTransition.isForAnalysisTesting()) {
- buildConfigOptions.evaluatingForAnalysisTest = true;
- }
- updateOutputDirectoryNameFragment(buildConfigOptions, transition);
- }
-
- /**
- * Compute the output directory name fragment corresponding to the transition, and append it to
- * the existing name fragment in buildConfigOptions.
- *
- * @throws IllegalStateException If MD5 support is not available.
- */
- private void updateOutputDirectoryNameFragment(BuildConfiguration.Options buildConfigOptions,
- Map<String, Object> transition) {
- String transitionString = new String();
- for (Map.Entry<String, Object> entry : transition.entrySet()) {
- transitionString += entry.getKey() + ":";
- if (entry.getValue() != null) {
- transitionString += entry.getValue().toString() + "@";
- }
- }
-
- // TODO(waltl): for transitions that don't read settings, it is possible to precompute and
- // reuse the MD5 digest and even the transition itself.
- try {
- byte[] bytes = transitionString.getBytes(US_ASCII);
- MessageDigest md = MessageDigest.getInstance("MD5");
- byte[] digest = md.digest(bytes);
- String hexDigest = BaseEncoding.base16().lowerCase().encode(digest);
-
- if (buildConfigOptions.transitionDirectoryNameFragment == null) {
- buildConfigOptions.transitionDirectoryNameFragment = hexDigest;
- } else {
- buildConfigOptions.transitionDirectoryNameFragment += "-" + hexDigest;
- }
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("MD5 not available", e);
- }
- }
-
- /**
- * Stores option info useful to a FunctionSplitTransition.
- */
- private static class OptionInfo {
- private final Class<? extends FragmentOptions> optionClass;
- private final OptionDefinition definition;
-
- public OptionInfo(Class<? extends FragmentOptions> optionClass,
- OptionDefinition definition) {
- this.optionClass = optionClass;
- this.definition = definition;
- }
-
- Class<? extends FragmentOptions> getOptionClass() {
- return optionClass;
- }
-
- OptionDefinition getDefinition() {
- return definition;
- }
- }
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionTransitionUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionTransitionUtil.java
new file mode 100644
index 0000000..d0ee650
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/FunctionTransitionUtil.java
@@ -0,0 +1,343 @@
+// 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.skylark;
+
+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;
+import com.google.devtools.build.lib.analysis.config.FragmentOptions;
+import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
+import com.google.devtools.build.lib.packages.StructImpl;
+import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.Mutability;
+import com.google.devtools.build.lib.syntax.Runtime.NoneType;
+import com.google.devtools.build.lib.syntax.SkylarkDict;
+import com.google.devtools.common.options.OptionDefinition;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.OptionsParsingException;
+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;
+
+/**
+ * Utility class for common work done across {@link StarlarkAttributeTransitionProvider} and {@link
+ * StarlarkRuleTransitionProvider}.
+ */
+public class FunctionTransitionUtil {
+
+ private static final String COMMAND_LINE_OPTION_PREFIX = "//command_line_option:";
+
+ /**
+ * Figure out what build settings the given transition changes and apply those changes to the
+ * incoming {@link BuildOptions}. For native options, this involves a preprocess step of
+ * converting options to their "command line form".
+ *
+ * Also Validate that transitions output sensical results.
+ *
+ * @param buildOptions the pre-transition build options
+ * @param starlarkTransition the transition to apply
+ * @param attrObject the attributes of the rule to which this transition is attached
+ * @return the post-transition build options
+ */
+ static List<BuildOptions> applyAndValidate(
+ BuildOptions buildOptions,
+ StarlarkDefinedConfigTransition starlarkTransition,
+ StructImpl attrObject) {
+ // TODO(waltl): consider building this once and use it across different split
+ // transitions.
+ try {
+ Map<String, OptionInfo> optionInfoMap = buildOptionInfo(buildOptions);
+ SkylarkDict<String, Object> settings =
+ buildSettings(buildOptions, optionInfoMap, starlarkTransition);
+
+ ImmutableList.Builder<BuildOptions> splitBuildOptions = ImmutableList.builder();
+
+ ImmutableList<Map<String, Object>> transitions =
+ starlarkTransition.getChangedSettings(settings, attrObject);
+ // TODO(juliexxia): Validate that the output values correctly match the output types.
+ validateFunctionOutputs(transitions, starlarkTransition);
+
+ for (Map<String, Object> transition : transitions) {
+ BuildOptions options = buildOptions.clone();
+ applyTransition(options, transition, optionInfoMap, starlarkTransition);
+ splitBuildOptions.add(options);
+ }
+ return splitBuildOptions.build();
+
+ } catch (InterruptedException | EvalException e) {
+ // TODO(juliexxia): Throw an exception better than RuntimeException.
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void validateFunctionOutputs(
+ ImmutableList<Map<String, Object>> transitions,
+ StarlarkDefinedConfigTransition starlarkTransition)
+ throws EvalException {
+ for (Map<String, Object> transition : transitions) {
+ LinkedHashSet<String> remainingOutputs =
+ Sets.newLinkedHashSet(starlarkTransition.getOutputs());
+ for (String outputKey : transition.keySet()) {
+ if (!remainingOutputs.remove(outputKey)) {
+ throw new EvalException(
+ starlarkTransition.getLocationForErrorReporting(),
+ String.format("transition function returned undeclared output '%s'", outputKey));
+ }
+ }
+
+ if (!remainingOutputs.isEmpty()) {
+ throw new EvalException(
+ starlarkTransition.getLocationForErrorReporting(),
+ String.format(
+ "transition outputs [%s] were not defined by transition function",
+ Joiner.on(", ").join(remainingOutputs)));
+ }
+ }
+ }
+
+ /**
+ * Given a label-like string representing a command line option, returns the command line option
+ * string that it represents. This is a temporary measure to support command line options with
+ * strings that look "label-like", so that migrating users using this experimental syntax is
+ * easier later.
+ *
+ * @throws EvalException if the given string is not a valid format to represent to a command line
+ * option
+ */
+ private static String commandLineOptionLabelToOption(
+ String label, StarlarkDefinedConfigTransition starlarkTransition) throws EvalException {
+ if (label.startsWith(COMMAND_LINE_OPTION_PREFIX)) {
+ return label.substring(COMMAND_LINE_OPTION_PREFIX.length());
+ } else {
+ throw new EvalException(
+ starlarkTransition.getLocationForErrorReporting(),
+ String.format(
+ "Option key '%s' is of invalid form. "
+ + "Expected command line option to begin with %s",
+ label, COMMAND_LINE_OPTION_PREFIX));
+ }
+ }
+
+ /** For all the options in the BuildOptions, build a map from option name to its information. */
+ private static Map<String, OptionInfo> buildOptionInfo(BuildOptions buildOptions) {
+ ImmutableMap.Builder<String, OptionInfo> builder = new ImmutableMap.Builder<>();
+
+ ImmutableSet<Class<? extends FragmentOptions>> optionClasses =
+ buildOptions.getNativeOptions().stream()
+ .map(FragmentOptions::getClass)
+ .collect(ImmutableSet.toImmutableSet());
+
+ for (Class<? extends FragmentOptions> optionClass : optionClasses) {
+ ImmutableList<OptionDefinition> optionDefinitions =
+ OptionsParser.getOptionDefinitions(optionClass);
+ for (OptionDefinition def : optionDefinitions) {
+ String optionName = def.getOptionName();
+ builder.put(optionName, new OptionInfo(optionClass, def));
+ }
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Enter the options in buildOptions into a skylark dictionary, and return the dictionary.
+ *
+ * @throws IllegalArgumentException If the method is unable to look up the value in buildOptions
+ * corresponding to an entry in optionInfoMap
+ * @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
+ */
+ static SkylarkDict<String, Object> buildSettings(
+ BuildOptions buildOptions,
+ Map<String, OptionInfo> optionInfoMap,
+ StarlarkDefinedConfigTransition starlarkTransition)
+ throws EvalException {
+ LinkedHashSet<String> remainingInputs = Sets.newLinkedHashSet(starlarkTransition.getInputs());
+
+ 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 {
+ Field field = optionInfo.getDefinition().getField();
+ FragmentOptions options = buildOptions.get(optionInfo.getOptionClass());
+ Object optionValue = field.get(options);
+
+ dict.put(optionKey, optionValue, null, mutability);
+ } 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(
+ starlarkTransition.getLocationForErrorReporting(),
+ String.format(
+ "transition inputs [%s] do not correspond to valid settings",
+ Joiner.on(", ").join(remainingInputs)));
+ }
+
+ return dict;
+ }
+ }
+
+ /**
+ * Apply the transition dictionary to the build option, using optionInfoMap to look up the option
+ * info.
+ *
+ * @param buildOptions the pre-transition build options
+ * @param newValues a map of option name: option value entries to override current option
+ * values in the buildOptions param
+ * @param optionInfoMap a map of option name: option info for all native options that may
+ * be accessed in this transition
+ * @param starlarkTransition transition object that is being applied. Used for error reporting
+ * and checking for analysis testing
+ * @throws EvalException If a requested option field is inaccessible
+ */
+ static void applyTransition(
+ BuildOptions buildOptions,
+ Map<String, Object> newValues,
+ Map<String, OptionInfo> optionInfoMap,
+ StarlarkDefinedConfigTransition starlarkTransition)
+ throws EvalException {
+ for (Map.Entry<String, Object> entry : newValues.entrySet()) {
+ String optionKey = entry.getKey();
+
+ // TODO(juliexxia): Handle keys which correspond to build_setting target labels instead
+ // of assuming every key is for a command line option.
+ String optionName = commandLineOptionLabelToOption(optionKey, starlarkTransition);
+ Object optionValue = entry.getValue();
+
+ // Convert NoneType to null.
+ if (optionValue instanceof NoneType) {
+ optionValue = null;
+ }
+
+ try {
+ if (!optionInfoMap.containsKey(optionName)) {
+ throw new EvalException(
+ starlarkTransition.getLocationForErrorReporting(),
+ String.format(
+ "transition output '%s' does not correspond to a valid setting", optionKey));
+ }
+
+ OptionInfo optionInfo = optionInfoMap.get(optionName);
+ OptionDefinition def = optionInfo.getDefinition();
+ Field field = def.getField();
+ FragmentOptions options = buildOptions.get(optionInfo.getOptionClass());
+ if (optionValue == null || def.getType().isInstance(optionValue)) {
+ field.set(options, optionValue);
+ } else if (optionValue instanceof String) {
+ field.set(options, def.getConverter().convert((String) optionValue));
+ } else {
+ throw new EvalException(
+ starlarkTransition.getLocationForErrorReporting(),
+ "Invalid value type for option '" + optionName + "'");
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "IllegalAccess for option " + optionName + ": " + e.getMessage());
+ } catch (OptionsParsingException e) {
+ throw new EvalException(
+ starlarkTransition.getLocationForErrorReporting(),
+ "OptionsParsingError for option '" + optionName + "': " + e.getMessage());
+ }
+ }
+
+ BuildConfiguration.Options buildConfigOptions;
+ buildConfigOptions = buildOptions.get(BuildConfiguration.Options.class);
+
+ if (starlarkTransition.isForAnalysisTesting()) {
+ buildConfigOptions.evaluatingForAnalysisTest = true;
+ }
+ updateOutputDirectoryNameFragment(buildConfigOptions, newValues);
+ }
+
+ /**
+ * Compute the output directory name fragment corresponding to the transition, and append it to
+ * the existing name fragment in buildConfigOptions.
+ *
+ * @throws IllegalStateException If MD5 support is not available
+ */
+ private static void updateOutputDirectoryNameFragment(
+ BuildConfiguration.Options buildConfigOptions, Map<String, Object> transition) {
+ String transitionString = "";
+ for (Map.Entry<String, Object> entry : transition.entrySet()) {
+ transitionString += entry.getKey() + ":";
+ if (entry.getValue() != null) {
+ transitionString += entry.getValue() + "@";
+ }
+ }
+
+ // TODO(waltl): for transitions that don't read settings, it is possible to precompute and
+ // reuse the MD5 digest and even the transition itself.
+ try {
+ byte[] bytes = transitionString.getBytes(US_ASCII);
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] digest = md.digest(bytes);
+ String hexDigest = BaseEncoding.base16().lowerCase().encode(digest);
+
+ if (buildConfigOptions.transitionDirectoryNameFragment == null) {
+ buildConfigOptions.transitionDirectoryNameFragment = hexDigest;
+ } else {
+ buildConfigOptions.transitionDirectoryNameFragment += "-" + hexDigest;
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("MD5 not available", e);
+ }
+ }
+
+ /** Stores option info useful to a FunctionSplitTransition. */
+ static class OptionInfo {
+ private final Class<? extends FragmentOptions> optionClass;
+ private final OptionDefinition definition;
+
+ public OptionInfo(Class<? extends FragmentOptions> optionClass, OptionDefinition definition) {
+ this.optionClass = optionClass;
+ this.definition = definition;
+ }
+
+ Class<? extends FragmentOptions> getOptionClass() {
+ return optionClass;
+ }
+
+ OptionDefinition getDefinition() {
+ return definition;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
index 225b803..68c43bd 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
@@ -273,10 +273,11 @@
} else {
builder.hasStarlarkDefinedTransition();
}
- builder.cfg(new FunctionSplitTransitionProvider(starlarkDefinedTransition));
+ builder.cfg(new StarlarkAttributeTransitionProvider(starlarkDefinedTransition));
} else if (!trans.equals("target")) {
- throw new EvalException(ast.getLocation(),
- "cfg must be either 'data', 'host', or 'target'.");
+ // TODO(b/121134880): update error message when starlark build configurations is ready.
+ throw new EvalException(
+ ast.getLocation(), "cfg must be either 'data', 'host', or 'target'.");
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
index c9931dc8..d1bd717 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
@@ -37,6 +37,7 @@
import com.google.devtools.build.lib.analysis.TemplateVariableInfo;
import com.google.devtools.build.lib.analysis.config.ConfigAwareRuleClassBuilder;
import com.google.devtools.build.lib.analysis.config.HostTransition;
+import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
import com.google.devtools.build.lib.analysis.skylark.SkylarkAttr.Descriptor;
import com.google.devtools.build.lib.analysis.test.TestConfiguration;
import com.google.devtools.build.lib.cmdline.Label;
@@ -276,6 +277,7 @@
SkylarkList<?> execCompatibleWith,
Object analysisTest,
Object buildSetting,
+ Object cfg,
FuncallExpression ast,
Environment funcallEnv,
StarlarkContext context)
@@ -349,6 +351,13 @@
builder.addRequiredToolchains(
collectToolchainLabels(
toolchains.getContents(String.class, "toolchains"), ast.getLocation()));
+
+ if (!buildSetting.equals(Runtime.NONE) && !cfg.equals(Runtime.NONE)) {
+ throw new EvalException(
+ ast.getLocation(),
+ "Build setting rules cannot use the `cfg` param to apply transitions to themselves.");
+ }
+ // TODO(juliexxia): use @Param.enableOnlyWithFlag for this.
if (!buildSetting.equals(Runtime.NONE)) {
if (funcallEnv.getSemantics().experimentalBuildSettingApi()) {
builder.setBuildSetting((BuildSetting) buildSetting);
@@ -360,6 +369,16 @@
+ "specifying --experimental_build_setting_api");
}
}
+ if (!cfg.equals(Runtime.NONE)) {
+ if (!(cfg instanceof StarlarkDefinedConfigTransition)) {
+ throw new EvalException(
+ ast.getLocation(),
+ "`cfg` must be set to a transition object initialized by the transition() function.");
+ }
+ StarlarkDefinedConfigTransition starlarkDefinedConfigTransition =
+ (StarlarkDefinedConfigTransition) cfg;
+ builder.cfg(new StarlarkRuleTransitionProvider(starlarkDefinedConfigTransition));
+ }
for (Object o : providesArg) {
if (!SkylarkAttr.isProvider(o)) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkAttributeTransitionProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkAttributeTransitionProvider.java
new file mode 100644
index 0000000..ae86e86
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkAttributeTransitionProvider.java
@@ -0,0 +1,86 @@
+// 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.skylark;
+
+import static com.google.devtools.build.lib.analysis.skylark.FunctionTransitionUtil.applyAndValidate;
+import static com.google.devtools.build.lib.analysis.skylark.SkylarkAttributesCollection.ERROR_MESSAGE_FOR_NO_ATTR;
+
+import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
+import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransitionProvider;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper;
+import com.google.devtools.build.lib.packages.StructImpl;
+import com.google.devtools.build.lib.packages.StructProvider;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkType;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * This class implements a {@link SplitTransitionProvider} to provide a starlark-defined transition
+ * that rules can apply to their dependencies' configurations. This transition has access to (1) the
+ * a map of the current configuration's build settings and (2) the configured attributes of the
+ * given rule (not its dependencies').
+ *
+ * <p>For starlark defined rule class transitions, see {@link StarlarkRuleTransitionProvider}.
+ *
+ * <p>TODO(bazel-team): Consider allowing dependency-typed attributes to actually return providers
+ * instead of just labels (see {@link SkylarkAttributesCollection#addAttribute}).
+ */
+public class StarlarkAttributeTransitionProvider implements SplitTransitionProvider {
+ private final StarlarkDefinedConfigTransition starlarkDefinedConfigTransition;
+
+ StarlarkAttributeTransitionProvider(
+ StarlarkDefinedConfigTransition starlarkDefinedConfigTransition) {
+ this.starlarkDefinedConfigTransition = starlarkDefinedConfigTransition;
+ }
+
+ @Override
+ public SplitTransition apply(AttributeMap attributeMap) {
+ Preconditions.checkArgument(attributeMap instanceof ConfiguredAttributeMapper);
+ return new FunctionSplitTransition(
+ starlarkDefinedConfigTransition, (ConfiguredAttributeMapper) attributeMap);
+ }
+
+ private static class FunctionSplitTransition implements SplitTransition {
+ private final StarlarkDefinedConfigTransition starlarkDefinedConfigTransition;
+ private final StructImpl attrObject;
+
+ FunctionSplitTransition(
+ StarlarkDefinedConfigTransition starlarkDefinedConfigTransition,
+ ConfiguredAttributeMapper attributeMap) {
+ this.starlarkDefinedConfigTransition = starlarkDefinedConfigTransition;
+
+ LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
+ for (String attribute : attributeMap.getAttributeNames()) {
+ Object val = attributeMap.get(attribute, attributeMap.getAttributeType(attribute));
+ attributes.put(
+ Attribute.getSkylarkName(attribute),
+ val == null ? Runtime.NONE : SkylarkType.convertToSkylark(val, (Environment) null));
+ }
+ attrObject = StructProvider.STRUCT.create(attributes, ERROR_MESSAGE_FOR_NO_ATTR);
+ }
+
+ @Override
+ public final List<BuildOptions> split(BuildOptions buildOptions) {
+ return applyAndValidate(buildOptions, starlarkDefinedConfigTransition, attrObject);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkRuleTransitionProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkRuleTransitionProvider.java
new file mode 100644
index 0000000..cc86ba3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/StarlarkRuleTransitionProvider.java
@@ -0,0 +1,105 @@
+// 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.skylark;
+
+import static com.google.devtools.build.lib.analysis.skylark.FunctionTransitionUtil.applyAndValidate;
+
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
+import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.RawAttributeMapper;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.RuleTransitionFactory;
+import com.google.devtools.build.lib.packages.StructImpl;
+import com.google.devtools.build.lib.packages.StructProvider;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.SkylarkType;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * This class implements {@link RuleTransitionFactory} to provide a starlark-defined transition that
+ * rules can apply to their own configuration. This transition has access to (1) the a map of the
+ * current configuration's build settings and (2) the configured* attributes of the given rule (not
+ * its dependencies').
+ *
+ * <p>*In some corner cases, we can't access the configured attributes the configuration of the
+ * child may be different than the configuration of the parent. For now, forbid all access to
+ * attributes that read selects.
+ *
+ * <p>For starlark-defined attribute transitions, see {@link StarlarkAttributeTransitionProvider}.
+ */
+public class StarlarkRuleTransitionProvider implements RuleTransitionFactory {
+
+ private final StarlarkDefinedConfigTransition starlarkDefinedConfigTransition;
+
+ StarlarkRuleTransitionProvider(StarlarkDefinedConfigTransition starlarkDefinedConfigTransition) {
+ this.starlarkDefinedConfigTransition = starlarkDefinedConfigTransition;
+ }
+
+ @Override
+ public PatchTransition buildTransitionFor(Rule rule) {
+ return new FunctionPatchTransition(starlarkDefinedConfigTransition, rule);
+ }
+
+ private static class FunctionPatchTransition implements PatchTransition {
+
+ private final StarlarkDefinedConfigTransition starlarkDefinedConfigTransition;
+ private final StructImpl attrObject;
+
+ FunctionPatchTransition(
+ StarlarkDefinedConfigTransition starlarkDefinedConfigTransition, Rule rule) {
+ this.starlarkDefinedConfigTransition = starlarkDefinedConfigTransition;
+
+ LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
+ RawAttributeMapper attributeMapper = RawAttributeMapper.of(rule);
+ for (Attribute attribute : rule.getAttributes()) {
+ Object val = attributeMapper.getRawAttributeValue(rule, attribute);
+ if (val instanceof BuildType.SelectorList) {
+ // For now, don't allow access to attributes that read selects.
+ // TODO(b/121134880): make this restriction more fine grained.
+ continue;
+ }
+ attributes.put(
+ Attribute.getSkylarkName(attribute.getPublicName()),
+ val == null ? Runtime.NONE : SkylarkType.convertToSkylark(val, (Environment) null));
+ }
+ attrObject =
+ StructProvider.STRUCT.create(
+ attributes,
+ "No attribute '%s'. Either this attribute does "
+ + "not exist for this rule or is set by a select. Starlark rule transitions "
+ + "currently cannot read attributes behind selects.");
+ }
+
+ // TODO(b/121134880): validate that the targets these transitions are applied on don't read any
+ // attributes that are then configured by the outputs of these transitions.
+ @Override
+ public BuildOptions patch(BuildOptions buildOptions) {
+ List<BuildOptions> result =
+ applyAndValidate(buildOptions, starlarkDefinedConfigTransition, attrObject);
+ if (result.size() != 1) {
+ // TODO(b/121134880): handle this with a better (checked) exception)
+ throw new RuntimeException(
+ "Rule transition only allowed to return a single transitioned configuration.");
+ }
+ return Iterables.getOnlyElement(result);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleTransitionFactory.java b/src/main/java/com/google/devtools/build/lib/packages/RuleTransitionFactory.java
index 2445ddb..f6019133 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/RuleTransitionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/RuleTransitionFactory.java
@@ -23,15 +23,16 @@
/**
* Generates a transition to be used when entering the given rule.
*
- * <p>This cannot be a
- * {@link com.google.devtools.build.lib.analysis.config.transitions.SplitTransition} because
- * splits are conceptually a property of the <i>parent<i> rule. In other words, it makes sense for
- * a parent to say "build my deps in configurations A and B". But it doesn't make sense for a dep
- * to say "build myself in configurations A and B" if its parent doesn't know how to intelligently
- * handle the results.
+ * <p>This cannot be a {@link
+ * com.google.devtools.build.lib.analysis.config.transitions.SplitTransition} because splits are
+ * conceptually a property of the <i>parent<i> rule. In other words, it makes sense for a parent
+ * to say "build my deps in configurations A and B". But it doesn't make sense for a dep to say
+ * "build myself in configurations A and B" if its parent doesn't know how to intelligently handle
+ * the results.
*
- * <p>If this class determines that no transition should be performed, it should return
- * {@code NoTransition.INSTANCE}.
+ * <p>If this class determines that no transition should be performed, it should return {@code
+ * NoTransition.INSTANCE}.
*/
+ // TODO(bazel-team): refactor to only take an AttributeMap since that's how it's used anyway.
PatchTransition buildTransitionFor(Rule rule);
}
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
index 18543da..c5f3f7b 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/SkylarkRuleFunctionsApi.java
@@ -31,6 +31,7 @@
import com.google.devtools.build.lib.syntax.Runtime.UnboundMarker;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.lib.syntax.SkylarkSemantics.FlagIdentifier;
/**
* Interface for a global Skylark library containing rule-related helper and registration functions.
@@ -328,6 +329,18 @@
+ "See the <a href='config.html'><code>config</code></a> module. If this is "
+ "set, a mandatory attribute named \"build_setting_default\" is automatically"
+ "added to this rule, with a type corresponding to the value passed in here."),
+ @Param(
+ name = "cfg",
+ type = Object.class,
+ noneable = true,
+ defaultValue = "None",
+ named = true,
+ positional = false,
+ enableOnlyWithFlag = FlagIdentifier.EXPERIMENTAL_STARLARK_CONFIG_TRANSITION,
+ valueWhenDisabled = "None",
+ doc =
+ "If set, points to the configuration transition the rule will "
+ + "apply to its own configuration before analysis.")
},
useAst = true,
useEnvironment = true,
@@ -349,6 +362,7 @@
SkylarkList<?> execCompatibleWith,
Object analysisTest,
Object buildSetting,
+ Object cfg,
FuncallExpression ast,
Environment funcallEnv,
StarlarkContext context)
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
index 492ceb8..413c818 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemantics.java
@@ -43,6 +43,8 @@
SkylarkSemantics::experimentalEnableAndroidMigrationApis),
EXPERIMENTAL_BUILD_SETTING_API(SkylarkSemantics::experimentalBuildSettingApi),
EXPERIMENTAL_PLATFORM_API(SkylarkSemantics::experimentalPlatformsApi),
+ EXPERIMENTAL_STARLARK_CONFIG_TRANSITION(
+ SkylarkSemantics::experimentalStarlarkConfigTransitions),
INCOMPATIBLE_DISABLE_OBJC_PROVIDER_RESOURCES(
SkylarkSemantics::incompatibleDisableObjcProviderResources),
INCOMPATIBLE_NO_OUTPUT_ATTR_DEFAULT(SkylarkSemantics::incompatibleNoOutputAttrDefault),
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
index 7e76957..90260c1 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
@@ -119,6 +119,7 @@
SkylarkList<?> execCompatibleWith,
Object analysisTest,
Object buildSetting,
+ Object cfg,
FuncallExpression ast,
Environment funcallEnv,
StarlarkContext context)
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/FunctionSplitTransitionProviderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
similarity index 91%
rename from src/test/java/com/google/devtools/build/lib/analysis/FunctionSplitTransitionProviderTest.java
rename to src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
index 6aab61a..803d595 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/FunctionSplitTransitionProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
@@ -18,9 +18,7 @@
import static org.junit.Assert.fail;
import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
-import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.packages.util.BazelMockAndroidSupport;
import java.util.List;
@@ -29,11 +27,9 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-/**
- * Tests for FunctionSplitTransitionProvider.
- */
+/** Tests for StarlarkAttributeTransitionProvider. */
@RunWith(JUnit4.class)
-public class FunctionSplitTransitionProviderTest extends BuildViewTestCase {
+public class StarlarkAttrTransitionProviderTest extends BuildViewTestCase {
private void writeWhitelistFile() throws Exception {
scratch.file(
@@ -85,63 +81,6 @@
}
@Test
- @SuppressWarnings("unchecked")
- public void testParameterizedTransition() throws Exception {
- setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=true");
-
- writeWhitelistFile();
- scratch.file(
- "test/skylark/my_rule.bzl",
- "def _transition_impl(settings, attr):",
- " if (attr.big_foot_is_real):",
- " return {'t0': {'//command_line_option:cpu': 'x86_64'}}",
- " else:",
- " return {'t0': {'//command_line_option:cpu': 'armeabi-v7a'}}",
- "",
- "my_transition = transition(implementation = _transition_impl, inputs = [],",
- " outputs = ['//command_line_option:cpu'])",
- "def _my_rule_impl(ctx): ",
- " return struct(",
- " split_attr_dep = ctx.split_attr.dep,",
- " attr_dep = ctx.attr.dep)",
- "my_rule = rule(",
- " implementation = _my_rule_impl,",
- " attrs = {",
- " 'big_foot_is_real': attr.bool(default = False),",
- " 'dep': attr.label(cfg = my_transition),",
- " '_whitelist_function_transition': attr.label(",
- " default = '//tools/whitelists/function_transition_whitelist',",
- " ),",
- " })",
- "def _simple_impl(ctx):",
- " return []",
- "simple_rule = rule(implementation = _simple_impl)");
-
- scratch.file(
- "test/skylark/BUILD",
- "load('//test/skylark:my_rule.bzl', 'my_rule', 'simple_rule')",
- "my_rule(name = 'lies', dep = ':dep')",
- "my_rule(name = 'the-truth', dep = ':dep', big_foot_is_real = True)",
- "simple_rule(name = 'dep')");
-
- useConfiguration("--cpu=k8");
-
- BuildConfiguration liesDepConfiguration =
- getConfiguration(
- Iterables.getOnlyElement(
- (List<ConfiguredTarget>)
- getConfiguredTarget("//test/skylark:lies").get("attr_dep")));
- assertThat(liesDepConfiguration.getCpu()).isEqualTo("armeabi-v7a");
-
- BuildConfiguration theTruthDepConfiguration =
- getConfiguration(
- Iterables.getOnlyElement(
- (List<ConfiguredTarget>)
- getConfiguredTarget("//test/skylark:the-truth").get("attr_dep")));
- assertThat(theTruthDepConfiguration.getCpu()).isEqualTo("x86_64");
- }
-
- @Test
public void testFunctionSplitTransitionCheckSplitAttrDeps() throws Exception {
writeBasicTestFiles();
testSplitTransitionCheckSplitAttrDeps(getConfiguredTarget("//test/skylark:test"));
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
new file mode 100644
index 0000000..b77875e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java
@@ -0,0 +1,273 @@
+// 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;
+
+import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.TestCase.fail;
+
+import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
+import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for StarlarkRuleTransitionProvider. */
+@RunWith(JUnit4.class)
+public class StarlarkRuleTransitionProviderTest extends BuildViewTestCase {
+
+ @Test
+ public void testOutputOnlyTransition() throws Exception {
+ setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=true");
+ scratch.file(
+ "test/transitions.bzl",
+ "def _impl(settings, attr):",
+ " return {'//command_line_option:test_arg': ['post-transition']}",
+ "my_transition = transition(implementation = _impl, inputs = [],",
+ " outputs = ['//command_line_option:test_arg'])");
+ scratch.file(
+ "test/rules.bzl",
+ "load('//test:transitions.bzl', 'my_transition')",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(implementation = _impl, cfg = my_transition)");
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'test')");
+
+ useConfiguration("--test_arg=pre-transition");
+
+ BuildConfiguration configuration = getConfiguration(getConfiguredTarget("//test"));
+ assertThat(configuration.getOptions().get(TestOptions.class).testArguments)
+ .containsExactly("post-transition");
+ }
+
+ @Test
+ public void testInputAndOutputTransition() throws Exception {
+ setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=true");
+ scratch.file(
+ "test/transitions.bzl",
+ "def _impl(settings, attr):",
+ " return {'//command_line_option:test_arg': ",
+ " [settings['//command_line_option:test_arg'][0]+'->post-transition']}",
+ "my_transition = transition(",
+ " implementation = _impl,",
+ " inputs = ['//command_line_option:test_arg'],",
+ " outputs = ['//command_line_option:test_arg'],",
+ ")");
+
+ scratch.file(
+ "test/rules.bzl",
+ "load('//test:transitions.bzl', 'my_transition')",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(implementation = _impl, cfg = my_transition)");
+
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'test')");
+
+ useConfiguration("--test_arg=pre-transition");
+
+ BuildConfiguration configuration = getConfiguration(getConfiguredTarget("//test"));
+ assertThat(configuration.getOptions().get(TestOptions.class).testArguments)
+ .containsExactly("pre-transition->post-transition");
+ }
+
+ @Test
+ public void testBuildSettingCannotTransition() throws Exception {
+ setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=true");
+ scratch.file(
+ "test/transitions.bzl",
+ "def _impl(settings, attr):",
+ " return {'//command_line_option:test_arg': ['post-transition']}",
+ "my_transition = transition(implementation = _impl, inputs = [],",
+ " outputs = ['//command_line_option:test_arg'])");
+ scratch.file(
+ "test/rules.bzl",
+ "load('//test:transitions.bzl', 'my_transition')",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " cfg = my_transition,",
+ " build_setting = config.string()",
+ ")");
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'test')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test");
+ assertContainsEvent(
+ "Build setting rules cannot use the `cfg` param to apply transitions to themselves");
+ }
+
+ @Test
+ public void testBadCfgInput() throws Exception {
+ setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=true");
+ scratch.file(
+ "test/rules.bzl",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " cfg = 'my_transition',",
+ ")");
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'test')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test");
+ assertContainsEvent(
+ "`cfg` must be set to a transition object initialized by the transition() function.");
+ }
+
+ @Test
+ public void testMultipleReturnConfigs() throws Exception {
+ setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=true");
+ scratch.file(
+ "test/transitions.bzl",
+ "def _impl(settings, attr):",
+ " return {",
+ " 't0': {'//command_line_option:test_arg': ['split_one']},",
+ " 't1': {'//command_line_option:test_arg': ['split_two']},",
+ " }",
+ "my_transition = transition(implementation = _impl, inputs = [],",
+ " outputs = ['//command_line_option:test_arg'])");
+ scratch.file(
+ "test/rules.bzl",
+ "load('//test:transitions.bzl', 'my_transition')",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(implementation = _impl, cfg = my_transition)");
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'test')");
+
+ try {
+ getConfiguredTarget("//test");
+ fail("Should have failed");
+ } catch (RuntimeException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Rule transition only allowed to return a single transitioned configuration.");
+ }
+ }
+
+ @Test
+ public void testCanDoBadStuffWithParameterizedTransitionsAndSelects() throws Exception {
+ setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=true");
+ scratch.file(
+ "test/transitions.bzl",
+ "def _impl(settings, attr):",
+ " if (attr.my_configurable_attr):",
+ " return {'//command_line_option:test_arg': ['true']}",
+ " else:",
+ " return {'//command_line_option:test_arg': ['false']}",
+ "my_transition = transition(implementation = _impl, inputs = [],",
+ " outputs = ['//command_line_option:test_arg'])");
+ scratch.file(
+ "test/rules.bzl",
+ "load('//test:transitions.bzl', 'my_transition')",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " cfg = my_transition,",
+ " attrs = {'my_configurable_attr': attr.bool(default = False)},",
+ ")");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule')",
+ "my_rule(",
+ " name = 'test',",
+ " my_configurable_attr = select({",
+ " '//conditions:default': False,",
+ " ':true-config': True,",
+ " })",
+ ")",
+ "config_setting(",
+ " name = 'true-config',",
+ " values = {'test_arg': 'true'},",
+ ")");
+
+ try {
+ getConfiguredTarget("//test");
+ fail("Should have failed");
+ } catch (RuntimeException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "No attribute 'my_configurable_attr'. "
+ + "Either this attribute does not exist for this rule or is set by a select. "
+ + "Starlark rule transitions currently cannot read attributes behind selects.");
+ }
+ }
+
+ @Test
+ public void testLabelTypedAttrReturnsLabelNotDep() throws Exception {
+ setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=true");
+ scratch.file(
+ "test/transitions.bzl",
+ "def _impl(settings, attr):",
+ " if attr.dict_attr[Label('//test:key')] == 'value':",
+ " return {'//command_line_option:test_arg': ['post-transition']}",
+ " else:",
+ " return {'//command_line_option:test_arg': ['uh-oh']}",
+ "my_transition = transition(implementation = _impl, inputs = [],",
+ " outputs = ['//command_line_option:test_arg'])");
+ scratch.file(
+ "test/rules.bzl",
+ "load('//test:transitions.bzl', 'my_transition')",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(",
+ " implementation = _impl,",
+ " cfg = my_transition,",
+ " attrs = {'dict_attr': attr.label_keyed_string_dict()},",
+ ")",
+ "simple_rule = rule(_impl)");
+ scratch.file(
+ "test/BUILD",
+ "load('//test:rules.bzl', 'my_rule', 'simple_rule')",
+ "my_rule(",
+ " name = 'test',",
+ " dict_attr = {':key': 'value'},",
+ ")",
+ "simple_rule(name = 'key')");
+
+ useConfiguration("--test_arg=pre-transition");
+
+ BuildConfiguration configuration = getConfiguration(getConfiguredTarget("//test"));
+ assertThat(configuration.getOptions().get(TestOptions.class).testArguments)
+ .containsExactly("post-transition");
+ }
+
+ @Test
+ public void testCannotTransitionWithoutFlag() throws Exception {
+ setSkylarkSemanticsOptions("--experimental_starlark_config_transitions=false");
+ scratch.file(
+ "test/transitions.bzl",
+ "def _impl(settings, attr):",
+ " return {'//command_line_option:test_arg': ['post-transition']}",
+ "my_transition = transition(implementation = _impl, inputs = [],",
+ " outputs = ['//command_line_option:test_arg'])");
+ scratch.file(
+ "test/rules.bzl",
+ "load('//test:transitions.bzl', 'my_transition')",
+ "def _impl(ctx):",
+ " return []",
+ "my_rule = rule(implementation = _impl, cfg = my_transition)");
+ scratch.file("test/BUILD", "load('//test:rules.bzl', 'my_rule')", "my_rule(name = 'test')");
+
+ reporter.removeHandler(failFastHandler);
+ getConfiguredTarget("//test");
+ assertContainsEvent(
+ "transition() is experimental and disabled by default. This API is in "
+ + "development and subject to change at any time. Use "
+ + "--experimental_starlark_config_transitions to use this experimental API.");
+ }
+}