aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar gregce <gregce@google.com>2017-09-25 20:46:34 +0200
committerGravatar Vladimir Moskva <vladmos@google.com>2017-09-26 12:30:26 +0200
commit8e967419ef8405236c36d4c5a93e9b52177a63dc (patch)
tree579996873ffe7c35a781f605f4869da13ccf3022
parentb5c0202f2843d7b49a4cd234712b2c6f7f015c63 (diff)
Publish tests for configurable attributes (select).
PiperOrigin-RevId: 169938521
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/ConfigurableAttributesTest.java1175
1 files changed, 1175 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/ConfigurableAttributesTest.java b/src/test/java/com/google/devtools/build/lib/analysis/ConfigurableAttributesTest.java
new file mode 100644
index 0000000000..3334bd64c4
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/ConfigurableAttributesTest.java
@@ -0,0 +1,1175 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.analysis;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment;
+import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment.TargetProviderEnvironment;
+import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
+import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.analysis.util.MockRule;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.Attribute.ComputedDefault;
+import com.google.devtools.build.lib.packages.AttributeMap;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import com.google.devtools.build.lib.util.FileTypeSet;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for configurable attributes.
+ */
+@RunWith(JUnit4.class)
+public class ConfigurableAttributesTest extends BuildViewTestCase {
+ private void writeConfigRules() throws Exception {
+ scratch.file("conditions/BUILD",
+ "config_setting(",
+ " name = 'a',",
+ " values = {'test_arg': 'a'})",
+ "config_setting(",
+ " name = 'b',",
+ " values = {'test_arg': 'b'})");
+ }
+
+ private void writeHelloRules(boolean includeDefaultCondition) throws IOException {
+ scratch.file("java/hello/BUILD",
+ "java_binary(",
+ " name = 'hello',",
+ " srcs = ['hello.java'],",
+ " deps = select({",
+ " '//conditions:a': [':adep'],",
+ " '//conditions:b': [':bdep'],",
+ includeDefaultCondition
+ ? " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':defaultdep'],"
+ : "",
+ " }))",
+ "",
+ "java_library(",
+ " name = 'adep',",
+ " srcs = ['adep.java'])",
+ "java_library(",
+ " name = 'bdep',",
+ " srcs = ['bdep.java'])",
+ "java_library(",
+ " name = 'defaultdep',",
+ " srcs = ['defaultdep.java'])");
+ }
+
+ private static final String ADEP_INPUT = "bin java/hello/libadep.jar";
+ private static final String BDEP_INPUT = "bin java/hello/libbdep.jar";
+ private static final String CDEP_INPUT = "bin java/hello/libcdep.jar";
+ private static final String DEFAULTDEP_INPUT = "bin java/hello/libdefaultdep.jar";
+
+ /**
+ * Checks that, given the specified configuration parameters, the input rule *has* the
+ * expected dependencies and *doesn't have* the unexpected dependencies.
+ */
+ private void checkRule(String ruleLabel, Collection<String> options,
+ Iterable<String> expected, Iterable<String> notExpected) throws Exception {
+ useConfiguration(options.toArray(new String[options.size()]));
+ ConfiguredTarget binary = getConfiguredTarget(ruleLabel);
+ assertThat(binary).isNotNull();
+ Set<String> actualDeps = artifactsToStrings(getPrerequisiteArtifacts(binary, "deps"));
+ expected.forEach(expectedInput -> assertThat(actualDeps).contains(expectedInput));
+ notExpected.forEach(unexpectedInput -> assertThat(actualDeps).doesNotContain(unexpectedInput));
+ }
+
+ private void checkRule(String ruleLabel, String option,
+ Iterable<String> expected, Iterable<String> notExpected) throws Exception {
+ checkRule(ruleLabel, ImmutableList.of(option), expected, notExpected);
+ }
+
+ private static final MockRule RULE_WITH_OUTPUT_ATTR =
+ () -> MockRule.define("rule_with_output_attr", attr("out", BuildType.OUTPUT));
+
+ private static final MockRule RULE_WITH_COMPUTED_DEFAULT =
+ () -> MockRule.define(
+ "rule_with_computed_default",
+ attr("string_attr", Type.STRING),
+ attr("$computed_attr", Type.STRING).value(
+ new ComputedDefault("string_attr") {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ return rule.get("string_attr", Type.STRING) + "2";
+ }
+ }));
+
+ private static final MockRule RULE_WITH_BOOLEAN_ATTR =
+ () -> MockRule.define("rule_with_boolean_attr", attr("boolean_attr", Type.BOOLEAN));
+
+ private static final MockRule RULE_WITH_ALLOWED_VALUES =
+ () -> MockRule.define(
+ "rule_with_allowed_values",
+ attr("one_two", Type.STRING)
+ .allowedValues(new Attribute.AllowedValueSet("one", "two")));
+
+ private static final MockRule RULE_WITH_LABEL_DEFAULT =
+ () -> MockRule.define(
+ "rule_with_label_default",
+ (builder, env) ->
+ builder.add(
+ attr("dep", BuildType.LABEL)
+ .value(env.getLabel("//foo:default"))
+ .allowedFileTypes(FileTypeSet.ANY_FILE)));
+
+ @Override
+ protected ConfiguredRuleClassProvider getRuleClassProvider() {
+ ConfiguredRuleClassProvider.Builder builder =
+ new ConfiguredRuleClassProvider.Builder()
+ .addRuleDefinition(RULE_WITH_OUTPUT_ATTR)
+ .addRuleDefinition(RULE_WITH_COMPUTED_DEFAULT)
+ .addRuleDefinition(RULE_WITH_BOOLEAN_ATTR)
+ .addRuleDefinition(RULE_WITH_ALLOWED_VALUES)
+ .addRuleDefinition(RULE_WITH_LABEL_DEFAULT);
+ TestRuleClassProvider.addStandardRules(builder);
+ return builder.build();
+ }
+
+ @Test
+ public void basicConfigurability() throws Exception {
+ writeHelloRules(/*includeDefaultCondition=*/true);
+ writeConfigRules();
+ checkRule("//java/hello:hello", "--test_arg=a",
+ /*expected:*/ ImmutableList.of(ADEP_INPUT),
+ /*not expected:*/ ImmutableList.of(BDEP_INPUT, DEFAULTDEP_INPUT));
+ checkRule("//java/hello:hello", "--test_arg=b",
+ /*expected:*/ ImmutableList.of(BDEP_INPUT),
+ /*not expected:*/ ImmutableList.of(ADEP_INPUT, DEFAULTDEP_INPUT));
+ }
+
+ @Test
+ public void configurabilityDefaults() throws Exception {
+ writeHelloRules(/*includeDefaultCondition=*/true);
+ writeConfigRules();
+ checkRule("//java/hello:hello", "--test_arg=something_random",
+ /*expected:*/ ImmutableList.of(DEFAULTDEP_INPUT),
+ /*not expected:*/ ImmutableList.of(ADEP_INPUT, BDEP_INPUT));
+ checkRule("//java/hello:hello", "",
+ /*expected:*/ ImmutableList.of(DEFAULTDEP_INPUT),
+ /*not expected:*/ ImmutableList.of(ADEP_INPUT, BDEP_INPUT));
+ }
+
+ /**
+ * Duplicate label definitions are fine as long as they're in different selection branches.
+ */
+ @Test
+ public void depsWithDuplicatesInDifferentBranches() throws Exception {
+ writeConfigRules();
+ scratch.file("java/hello/BUILD",
+ "java_binary(",
+ " name = 'hello',",
+ " srcs = ['hello.java'],",
+ " deps = select({",
+ " '//conditions:a': [':adep', ':cdep'],",
+ " '//conditions:b': [':bdep', ':cdep'],",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':defaultdep'],",
+ " }))",
+ "",
+ "java_library(",
+ " name = 'adep',",
+ " srcs = ['adep.java'])",
+ "java_library(",
+ " name = 'bdep',",
+ " srcs = ['bdep.java'])",
+ "java_library(",
+ " name = 'cdep',",
+ " srcs = ['cdep.java'])");
+ checkRule("//java/hello:hello", "--test_arg=a",
+ /*expected:*/ ImmutableList.of(ADEP_INPUT, CDEP_INPUT),
+ /*not expected:*/ ImmutableList.of(BDEP_INPUT, DEFAULTDEP_INPUT));
+ }
+
+ /**
+ * Duplicate label definitions are *not* fine within the same branch.
+ */
+ @Test
+ public void depsWithDuplicatesInSameBranch() throws Exception {
+ writeConfigRules();
+ scratch.file("java/hello/BUILD",
+ "java_binary(",
+ " name = 'hello',",
+ " srcs = ['hello.java'],",
+ " deps = select({",
+ " '//conditions:a': [':adep', ':cdep', ':adep'],",
+ " '//conditions:b': [':bdep', ':cdep'],",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':defaultdep'],",
+ " }))",
+ "",
+ "java_library(",
+ " name = 'adep',",
+ " srcs = ['adep.java'])",
+ "java_library(",
+ " name = 'bdep',",
+ " srcs = ['bdep.java'])",
+ "java_library(",
+ " name = 'cdep',",
+ " srcs = ['cdep.java'])");
+
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ useConfiguration("--test_arg=a");
+ getConfiguredTarget("//java/hello:hello");
+ assertContainsEvent(
+ "Label '//java/hello:adep' is duplicated in the 'deps' attribute of rule 'hello'");
+ }
+
+ /**
+ * When an attribute includes multiple selects, we don't allow duplicates even across
+ * selects (this saves us from having to do possibly expensive value iteration since the
+ * number of values can grow exponentially with respect to the number of selects).
+ */
+ @Test
+ public void duplicatesAcrossMultipleSelects() throws Exception {
+ writeConfigRules();
+ scratch.file("java/hello/BUILD",
+ "java_binary(",
+ " name = 'hello',",
+ " srcs = select({",
+ " '//conditions:a': ['a.java'],",
+ " '//conditions:b': ['b.java'],",
+ " })",
+ " + select({",
+ " '//conditions:c': ['c.java'],",
+ " '//conditions:d': ['a.java'],",
+ " }))");
+
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ useConfiguration("--test_arg=a");
+ getConfiguredTarget("//java/hello:hello");
+ assertContainsEvent(
+ "Label '//java/hello:a.java' is duplicated in the 'srcs' attribute of rule 'hello'");
+ }
+
+ /**
+ * Even with multiple selects, duplicates are allowed within a *single* select as long as
+ * they're in different branches (and thus mutually exclusive).
+ */
+ @Test
+ public void duplicatesInDifferentBranchesMultipleSelects() throws Exception {
+ writeConfigRules();
+ scratch.file("java/hello/BUILD",
+ "java_binary(",
+ " name = 'hello',",
+ " srcs = select({",
+ " '//conditions:a': ['a.java'],",
+ " '//conditions:b': ['a.java'],",
+ " })",
+ " + select({",
+ " '//conditions:a': ['b.java'],",
+ " '//conditions:b': ['b.java'],",
+ " }))");
+
+ useConfiguration("--test_arg=a");
+ getConfiguredTarget("//java/hello:hello");
+ assertNoEvents();
+ }
+
+ /**
+ * With multiple selects, a single select still can't duplicate labels within the same branch.
+ */
+ @Test
+ public void duplicatesInSameBranchMultipleSelects() throws Exception {
+ writeConfigRules();
+ scratch.file("java/hello/BUILD",
+ "java_binary(",
+ " name = 'hello',",
+ " srcs = select({",
+ " '//conditions:a': ['a.java', 'a.java'],",
+ " '//conditions:b': ['b.java'],",
+ " })",
+ " + select({",
+ " '//conditions:a': ['c.java'],",
+ " '//conditions:b': ['d.java'],",
+ " }))");
+
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ useConfiguration("--test_arg=a");
+ getConfiguredTarget("//java/hello:hello");
+ assertContainsEvent(
+ "Label '//java/hello:a.java' is duplicated in the 'srcs' attribute of rule 'hello'");
+ }
+
+ /**
+ * Tests that {@link RedirectChaser} doesn't support configured attribute instances, and
+ * triggers an appropriate error upon finding them.
+ */
+ @Test
+ public void redirectChaser() throws Exception {
+ writeConfigRules();
+ useConfiguration("--test_arg=a");
+ scratch.file("java/hello/BUILD",
+ "alias(",
+ "name = 'good_base',",
+ "actual = ':good_redirect')",
+ "alias(",
+ "name = 'good_redirect',",
+ "actual = ':actual_content')",
+ "filegroup(",
+ "name = 'actual_content',",
+ "srcs = ['a.txt', 'b.txt'])",
+ "alias(",
+ "name = 'bad_base',",
+ "actual = ':bad_redirect')",
+ "alias(",
+ "name = 'bad_redirect',",
+ "actual = select({",
+ " '//conditions:a': ':actual_content',",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': ':actual_content',",
+ " }))",
+ "genrule(",
+ " name = 'non_filegroup_target',",
+ " srcs = [ 'whatever' ],",
+ " outs = [ 'whateverelse' ],",
+ " cmd = 'true')",
+ "alias(",
+ " name = 'base_non_filegroup_target',",
+ " actual = ':non_filegroup_target')"
+ );
+ ConfigurationEnvironment env =
+ new TargetProviderEnvironment(getSkyframeExecutor().getPackageManager(), reporter);
+
+ // Legal case:
+ assertThat(
+ RedirectChaser
+ .followRedirects(env, Label.parseAbsolute("//java/hello:good_base"), "srcs")
+ .toString())
+ .isEqualTo("//java/hello:actual_content");
+
+ // Legal case:
+ assertThat(
+ RedirectChaser
+ .followRedirects(env, Label.parseAbsolute("//java/hello:base_non_filegroup_target"),
+ "srcs")
+ .toString())
+ .isEqualTo("//java/hello:non_filegroup_target");
+
+ // Illegal case:
+ try {
+ RedirectChaser.followRedirects(env, Label.parseAbsolute("//java/hello:bad_base"), "srcs");
+ fail("Expected RedirectChaser to fail on a sequence with configurable 'srcs' values");
+ } catch (InvalidConfigurationException e) {
+ // Expected failure..
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("The value of 'actual' cannot be configuration-dependent");
+ }
+ }
+
+ /**
+ * Attributes of type {@link BuildType#OUTPUT} are not configurable.
+ */
+ @Test
+ public void outputTypeNotConfigurable() throws Exception {
+ writeConfigRules();
+ scratch.file("foo/BUILD",
+ "rule_with_output_attr(",
+ " name = 'has_an_out',",
+ " out = select({",
+ " '//conditions:a': 'a.out',",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': 'default.out'})",
+ ")");
+
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ getConfiguredTarget("//foo:has_an_out");
+ assertContainsEvent("attribute \"out\" is not configurable");
+ }
+
+ /**
+ * Attributes of type {@link BuildType#OUTPUT_LIST} are not configurable.
+ */
+ @Test
+ public void outputListTypeNotConfigurable() throws Exception {
+ writeConfigRules();
+ scratch.file("foo/BUILD",
+ "genrule(",
+ " name = 'generator',",
+ " srcs = [],",
+ " outs = select({",
+ " '//conditions:a': ['a.out'],",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': ['default.out']})",
+ ")");
+
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ getConfiguredTarget("//foo:generator");
+ assertContainsEvent("attribute \"outs\" is not configurable");
+ }
+
+ /**
+ * Tests that computed defaults faithfully reflect the values of the attributes they depend on.
+ */
+ @Test
+ public void computedDefaults() throws Exception {
+ writeConfigRules();
+ scratch.file("test/BUILD",
+ "rule_with_computed_default(",
+ " name = 'the_rule',",
+ " string_attr = select({",
+ " '//conditions:a': 'a',",
+ " '//conditions:b': 'b',",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': 'default',",
+ " }))");
+
+ // Configuration a:
+ useConfiguration("--test_arg=a");
+ ConfiguredTarget binary = getConfiguredTarget("//test:the_rule");
+ AttributeMap attributes = ConfiguredAttributeMapper.of((RuleConfiguredTarget) binary);
+ assertThat(attributes.get("$computed_attr", Type.STRING)).isEqualTo("a2");
+
+ // configuration b:
+ useConfiguration("--test_arg=b");
+ binary = getConfiguredTarget("//test:the_rule");
+ attributes = ConfiguredAttributeMapper.of((RuleConfiguredTarget) binary);
+ assertThat(attributes.get("$computed_attr", Type.STRING)).isEqualTo("b2");
+ }
+
+ @Test
+ public void configKeyTypeChecking_Int() throws Exception {
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ scratch.file("java/foo/BUILD", "int_key",
+ "java_library(",
+ " name = 'int_key',",
+ " srcs = select({123: ['a.java']})",
+ ")");
+ assertTargetError("//java/foo:int_key",
+ "Invalid key: 123. select keys must be label references");
+ }
+
+ @Test
+ public void configKeyTypeChecking_Bool() throws Exception {
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ scratch.file("java/foo/BUILD", "bool_key",
+ "java_library(",
+ " name = 'bool_key',",
+ " srcs = select({True: ['a.java']})",
+ ")");
+ assertTargetError("//java/foo:bool_key",
+ "Invalid key: true. select keys must be label references");
+ }
+
+ @Test
+ public void configKeyTypeChecking_None() throws Exception {
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ scratch.file("java/foo/BUILD", "none_key",
+ "java_library(",
+ " name = 'none_key',",
+ " srcs = select({None: ['a.java']})",
+ ")");
+ assertTargetError("//java/foo:none_key",
+ "Invalid key: None. select keys must be label references");
+ }
+
+ @Test
+ public void configKeyTypeChecking_Dict() throws Exception {
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ // If we embed a {} literal directly into the select, it fails with a Skylark error before
+ // we even get to select's type checking (since {} isn't a valid hashable type for the
+ // dictionary Skylark passes to the select function's invoke method). We can get around that
+ // by freezing the dict from an external .bzl file.
+ scratch.file("java/foo/external_dict.bzl",
+ "m = {}",
+ "def external_dict():",
+ " return m");
+ scratch.file("java/foo/BUILD", "dict_key",
+ "load('//java/foo:external_dict.bzl', 'external_dict')",
+ "java_library(",
+ " name = 'dict_key',",
+ " srcs = select({external_dict(): ['a.java']})",
+ ")");
+ assertTargetError("//java/foo:dict_key",
+ "Invalid key: {}. select keys must be label references");
+ }
+
+ /**
+ * Tests that config keys must resolve to existent targets.
+ */
+ @Test
+ public void missingConfigKey() throws Exception {
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ // Only create one of two necessary configurability rules:
+ scratch.file("conditions/BUILD",
+ "config_setting(",
+ " name = 'a',",
+ " values = {'test_arg': 'a'})");
+ writeHelloRules(/*includeDefaultCondition=*/true);
+ getConfiguredTarget("//java/hello:hello");
+ assertContainsEvent("no such target '//conditions:b': target 'b' not declared in package");
+ }
+
+ /**
+ * Tests that config keys must resolve to config_setting targets.
+ */
+ @Test
+ public void invalidConfigKey() throws Exception {
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ scratch.file("conditions/BUILD",
+ "config_setting(",
+ " name = 'a',",
+ " values = {'test_arg': 'a'})",
+ "rule_with_output_attr(",
+ " name = 'b',",
+ " out = 'b.out')");
+ writeHelloRules(/*includeDefaultCondition=*/true);
+ assertThat(getConfiguredTarget("//java/hello:hello")).isNull();
+ assertContainsEvent("//conditions:b is not a valid configuration key for //java/hello:hello");
+ assertDoesNotContainEvent("//conditions:a"); // This one is legitimate..
+ }
+
+ /**
+ * Tests config keys with multiple requirements.
+ */
+ @Test
+ public void multiConditionConfigKeys() throws Exception {
+ writeHelloRules(/*includeDefaultCondition=*/true);
+ scratch.file("conditions/BUILD",
+ "config_setting(",
+ " name = 'a',",
+ " values = {",
+ " 'test_arg': 'a',",
+ " 'compilation_mode': 'dbg'",
+ " })",
+ "config_setting(",
+ " name = 'b',",
+ " values = {'test_arg': 'b'})");
+ checkRule("//java/hello:hello", "--test_arg=a",
+ /*expected:*/ ImmutableList.of(DEFAULTDEP_INPUT),
+ /*not expected:*/ ImmutableList.of(ADEP_INPUT, BDEP_INPUT));
+ checkRule("//java/hello:hello", ImmutableList.of("--test_arg=a", "--compilation_mode=dbg"),
+ /*expected:*/ ImmutableList.of(ADEP_INPUT),
+ /*not expected:*/ ImmutableList.of(BDEP_INPUT, DEFAULTDEP_INPUT));
+ }
+
+ /**
+ * Tests that changing a config_setting invalidates the rule that uses it.
+ */
+ @Test
+ public void configKeyInvalidation() throws Exception {
+ writeHelloRules(/*includeDefaultCondition=*/true);
+ writeConfigRules();
+
+ // Iteration 1: --test_args=a should apply //conditions:a.
+ useConfiguration("--test_arg=a");
+ checkRule("//java/hello:hello", "--test_arg=a",
+ /*expected:*/ ImmutableList.of(ADEP_INPUT),
+ /*not expected:*/ ImmutableList.of(BDEP_INPUT, DEFAULTDEP_INPUT));
+
+ // Rewrite the condition for //conditions:a.
+ scratch.overwriteFile("conditions/BUILD",
+ "config_setting(",
+ " name = 'a',",
+ " values = {'test_arg': 'c'})",
+ "config_setting(",
+ " name = 'b',",
+ " values = {'test_arg': 'b'})");
+
+ // Iteration 2: same exact analysis should now apply the default condition.
+ invalidatePackages();
+ checkRule("//java/hello:hello", "--test_arg=a",
+ /*expected:*/ ImmutableList.of(DEFAULTDEP_INPUT),
+ /*not expected:*/ ImmutableList.of(ADEP_INPUT, BDEP_INPUT));
+ }
+
+ /**
+ * Tests that multiple matches are not allowed for conditions where one is not a specialization
+ * of the other.
+ */
+ @Test
+ public void multipleMatches() throws Exception {
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ scratch.file("conditions/BUILD",
+ "config_setting(",
+ " name = 'dup1',",
+ " values = {'compilation_mode': 'opt'})",
+ "config_setting(",
+ " name = 'dup2',",
+ " values = {'define': 'foo=bar'})");
+ scratch.file("a/BUILD",
+ "genrule(",
+ " name = 'gen',",
+ " cmd = '',",
+ " outs = ['gen.out'],",
+ " srcs = select({",
+ " '//conditions:dup1': ['a.in'],",
+ " '//conditions:dup2': ['b.in'],",
+ " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':default.in'],",
+ " }))");
+ useConfiguration("-c", "opt", "--define", "foo=bar");
+ assertThat(getConfiguredTarget("//a:gen")).isNull();
+ assertContainsEvent(
+ "Illegal ambiguous match on configurable attribute \"srcs\" in //a:gen:\n"
+ + "//conditions:dup1\n"
+ + "//conditions:dup2\n"
+ + "Multiple matches are not allowed unless one is unambiguously more specialized.");
+ }
+
+ /**
+ * Tests that when multiple conditions match and for every matching pair, one is
+ * a specialization of the other, the most specialized match is chosen.
+ */
+ @Test
+ public void multipleMatchesConditionAndSubcondition() throws Exception {
+ scratch.file("conditions/BUILD",
+ "config_setting(",
+ " name = 'generic',",
+ " values = {'compilation_mode': 'opt'})",
+ "config_setting(",
+ " name = 'precise',",
+ " values = {'compilation_mode': 'opt', 'define': 'foo=bar'})",
+ "config_setting(",
+ " name = 'most_precise',",
+ " values = {'compilation_mode': 'opt', 'define': 'foo=bar', 'test_arg': 'baz'})");
+ scratch.file("java/a/BUILD",
+ "java_binary(",
+ " name = 'binary',",
+ " srcs = ['binary.java'],",
+ " deps = select({",
+ " '//conditions:generic': [':generic'],",
+ " '//conditions:precise': [':precise'],",
+ " '//conditions:most_precise': [':most_precise'],",
+ " }))",
+ "java_library(",
+ " name = 'generic',",
+ " srcs = ['generic.java'])",
+ "java_library(",
+ " name = 'precise',",
+ " srcs = ['precise.java'])",
+ "java_library(",
+ " name = 'most_precise',",
+ " srcs = ['most_precise.java'])");
+ checkRule("//java/a:binary",
+ ImmutableList.of("-c", "opt", "--define", "foo=bar", "--test_arg", "baz"),
+ /*expected:*/ ImmutableList.of("bin java/a/libmost_precise.jar"),
+ /*not expected:*/ ImmutableList.of(
+ "bin java/a/libgeneric.jar",
+ "bin java/a/libprecise.jar"));
+ }
+
+ /**
+ * Tests that when multiple conditions match but one condition is more specialized than the
+ * others, it is chosen and there is no error.
+ */
+ @Test
+ public void multipleMatchesUnambiguous() throws Exception {
+ scratch.file(
+ "conditions/BUILD",
+ "config_setting(",
+ " name = 'a',",
+ " values = {'define': 'a=1'})",
+ "config_setting(",
+ " name = 'b',",
+ " values = {'compilation_mode': 'opt'})",
+ "config_setting(",
+ " name = 'c',",
+ " values = {'test_arg': 'baz'})",
+ "config_setting(",
+ " name = 'b_a_c',", // Named to come alphabetically after a and b but before c.
+ " values = {'define': 'a=1', 'test_arg': 'baz', 'compilation_mode': 'opt'})");
+ scratch.file("java/a/BUILD",
+ "java_binary(",
+ " name = 'binary',",
+ " srcs = ['binary.java'],",
+ " deps = select({",
+ " '//conditions:a': [':a'],",
+ " '//conditions:b': [':b'],",
+ " '//conditions:c': [':c'],",
+ " '//conditions:b_a_c': [':b_a_c'],",
+ " }))",
+ "java_library(",
+ " name = 'a',",
+ " srcs = ['a.java'])",
+ "java_library(",
+ " name = 'b',",
+ " srcs = ['b.java'])",
+ "java_library(",
+ " name = 'c',",
+ " srcs = ['c.java'])",
+ "java_library(",
+ " name = 'b_a_c',",
+ " srcs = ['b_a_c.java'])");
+ checkRule(
+ "//java/a:binary",
+ ImmutableList.of("--define", "a=1", "--compilation_mode", "opt", "--test_arg", "baz"),
+ /*expected:*/ ImmutableList.of("bin java/a/libb_a_c.jar"),
+ /*not expected:*/ ImmutableList.of(
+ "bin java/a/liba.jar", "bin java/a/libb.jar", "bin java/a/libc.jar"));
+ }
+
+ /** Tests that default conditions are only required when no main condition matches. */
+ @Test
+ public void noDefaultCondition() throws Exception {
+ writeHelloRules(/*includeDefaultCondition=*/false);
+ writeConfigRules();
+
+ // An explicit configuration matches: all is well.
+ checkRule("//java/hello:hello", "--test_arg=a",
+ /*expected:*/ ImmutableList.of(ADEP_INPUT),
+ /*not expected:*/ ImmutableList.of(BDEP_INPUT, DEFAULTDEP_INPUT));
+
+ // Nothing matches: expect an error.
+ reporter.removeHandler(failFastHandler);
+ useConfiguration("");
+ assertThat(getConfiguredTarget("//java/hello:hello")).isNull();
+ assertContainsEvent("Configurable attribute \"deps\" doesn't match this configuration");
+ }
+
+ @Test
+ public void noMatchCustomErrorMessage() throws Exception {
+ writeConfigRules();
+ scratch.file("java/hello/BUILD",
+ "java_binary(",
+ " name = 'hello_default_no_match_error',",
+ " srcs = select({",
+ " '//conditions:a': ['not_chosen.java'],",
+ " }))",
+ "java_binary(",
+ " name = 'hello_custom_no_match_error',",
+ " srcs = select({",
+ " '//conditions:a': ['not_chosen.java'],",
+ " },",
+ " no_match_error = 'You always have to choose condition a!'",
+ "))");
+
+ reporter.removeHandler(failFastHandler);
+ useConfiguration("");
+
+ assertThat(getConfiguredTarget("//java/hello:hello_default_no_match_error")).isNull();
+ String commonPrefix = "Configurable attribute \"srcs\" doesn't match this configuration";
+ assertContainsEvent(commonPrefix + " (would a default condition help?).\nConditions checked:");
+
+ eventCollector.clear();
+ assertThat(getConfiguredTarget("//java/hello:hello_custom_no_match_error")).isNull();
+ assertContainsEvent(commonPrefix + ": You always have to choose condition a!");
+ }
+
+ @Test
+ public void nativeTypeConcatenatedWithSelect() throws Exception {
+ writeConfigRules();
+ scratch.file("java/foo/BUILD",
+ "java_binary(",
+ " name = 'binary',",
+ " srcs = ['binary.java'],",
+ " deps = [':always'] + select({",
+ " '//conditions:a': [':a'],",
+ " '//conditions:b': [':b'],",
+ " })",
+ ")",
+ "java_library(",
+ " name = 'always',",
+ " srcs = ['always.java'])",
+ "java_library(",
+ " name = 'a',",
+ " srcs = ['a.java'])",
+ "java_library(",
+ " name = 'b',",
+ " srcs = ['b.java'])");
+
+ checkRule("//java/foo:binary", "--test_arg=b",
+ /*expected:*/ ImmutableList.of(
+ "bin java/foo/libalways.jar",
+ "bin java/foo/libb.jar"),
+ /*not expected:*/ ImmutableList.of(
+ "bin java/foo/liba.jar"));
+ }
+
+ @Test
+ public void selectConcatenatedWithNativeType() throws Exception {
+ writeConfigRules();
+ scratch.file("java/foo/BUILD",
+ "java_binary(",
+ " name = 'binary',",
+ " srcs = ['binary.java'],",
+ " deps = select({",
+ " '//conditions:a': [':a'],",
+ " '//conditions:b': [':b'],",
+ " }) + [':always'])",
+ "java_library(",
+ " name = 'always',",
+ " srcs = ['always.java'])",
+ "java_library(",
+ " name = 'a',",
+ " srcs = ['a.java'])",
+ "java_library(",
+ " name = 'b',",
+ " srcs = ['b.java'])");
+
+ checkRule("//java/foo:binary", "--test_arg=b",
+ /*expected:*/ ImmutableList.of(
+ "bin java/foo/libalways.jar",
+ "bin java/foo/libb.jar"),
+ /*not expected:*/ ImmutableList.of(
+ "bin java/foo/liba.jar"));
+ }
+
+ @Test
+ public void selectConcatenatedWithSelect() throws Exception {
+ writeConfigRules();
+ scratch.file("java/foo/BUILD",
+ "java_binary(",
+ " name = 'binary',",
+ " srcs = ['binary.java'],",
+ " deps = select({",
+ " '//conditions:a': [':a'],",
+ " '//conditions:b': [':b'],",
+ " }) + select({",
+ " '//conditions:a': [':a2'],",
+ " '//conditions:b': [':b2'],",
+ " })",
+ ")",
+ "java_library(",
+ " name = 'a',",
+ " srcs = ['a.java'])",
+ "java_library(",
+ " name = 'b',",
+ " srcs = ['b.java'])",
+ "java_library(",
+ " name = 'a2',",
+ " srcs = ['a2.java'])",
+ "java_library(",
+ " name = 'b2',",
+ " srcs = ['b2.java'])");
+
+ checkRule("//java/foo:binary", "--test_arg=b",
+ /*expected:*/ ImmutableList.of(
+ "bin java/foo/libb.jar",
+ "bin java/foo/libb2.jar"),
+ /*not expected:*/ ImmutableList.of(
+ "bin java/foo/liba.jar",
+ "bin java/foo/liba2.jar"));
+ }
+
+ @Test
+ public void selectConcatenatedWithNonSupportingType() throws Exception {
+ writeConfigRules();
+ scratch.file("foo/BUILD",
+ "rule_with_boolean_attr(",
+ " name = 'binary',",
+ " boolean_attr= 0 + select({",
+ " '//conditions:a': 0,",
+ " '//conditions:b': 1,",
+ " }))");
+
+ reporter.removeHandler(failFastHandler);
+ assertThat(getConfiguredTarget("//foo:binary")).isNull();
+ assertContainsEvent("type 'boolean' doesn't support select concatenation");
+ }
+
+ @Test
+ public void concatenationWithDifferentTypes() throws Exception {
+ writeConfigRules();
+ scratch.file("java/foo/BUILD",
+ "java_binary(",
+ " name = 'binary',",
+ " srcs = select({",
+ " '//conditions:a': ['a.java'],",
+ " '//conditions:b': ['b.java'],",
+ " }) + 'always.java'",
+ ")");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ getTarget("//java/foo:binary");
+ fail();
+ } catch (NoSuchTargetException e) {
+ assertContainsEvent("'+' operator applied to incompatible types");
+ }
+ }
+
+ @Test
+ public void selectsWithGlobs() throws Exception {
+ writeConfigRules();
+ scratch.file("java/foo/globbed/ceecee.java");
+ scratch.file("java/foo/BUILD",
+ "java_binary(",
+ " name = 'binary',",
+ " srcs = glob(['globbed/*.java']) + select({",
+ " '//conditions:a': ['a.java'],",
+ " '//conditions:b': ['b.java'],",
+ " }))");
+
+ useConfiguration("--test_arg=b");
+ ConfiguredTarget binary = getConfiguredTarget("//java/foo:binary");
+ assertThat(binary).isNotNull();
+ Set<String> sources = artifactsToStrings(getPrerequisiteArtifacts(binary, "srcs"));
+ assertThat(sources).contains("src java/foo/b.java");
+ assertThat(sources).contains("src java/foo/globbed/ceecee.java");
+ assertThat(sources).doesNotContain("src java/foo/a.java");
+ }
+
+ @Test
+ public void selectsWithGlobsWrongType() throws Exception {
+ writeConfigRules();
+ scratch.file("foo/BUILD",
+ "genrule(",
+ " name = 'gen',",
+ " srcs = [],",
+ " outs = ['gen.out'],",
+ " cmd = 'echo' + select({",
+ " '//conditions:a': 'a',",
+ " '//conditions:b': 'b',",
+ " }) + glob(['globbed.java']))");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ getTarget("//foo:binary");
+ fail();
+ } catch (NoSuchTargetException e) {
+ assertContainsEvent("'+' operator applied to incompatible types");
+ }
+ }
+
+ @Test
+ public void globsInSelect() throws Exception {
+ writeConfigRules();
+ scratch.file("java/foo/globbed/ceecee.java");
+ scratch.file("java/foo/BUILD",
+ "java_binary(",
+ " name = 'binary',",
+ " srcs = ['binary.java'] + select({",
+ " '//conditions:a': glob(['globbed/*.java']),",
+ " '//conditions:b': ['b.java'],",
+ " }))");
+
+ useConfiguration("--test_arg=a");
+ ConfiguredTarget binary = getConfiguredTarget("//java/foo:binary");
+ assertThat(binary).isNotNull();
+ Set<String> sources = artifactsToStrings(getPrerequisiteArtifacts(binary, "srcs"));
+ assertThat(sources).contains("src java/foo/binary.java");
+ assertThat(sources).contains("src java/foo/globbed/ceecee.java");
+ assertThat(sources).doesNotContain("src java/foo/b.java");
+ }
+
+ @Test
+ public void selectAcceptedInAttributeWithAllowedValues() throws Exception {
+ scratch.file("foo/BUILD",
+ "rule_with_allowed_values(",
+ " name = 'rule',",
+ " one_two = select({",
+ " '//conditions:default': 'one',",
+ " }))");
+ assertThat(getConfiguredTarget("//foo:rule")).isNotNull();
+ }
+
+ @Test
+ public void selectWithNonAllowedValueCausesError() throws Exception {
+ scratch.file("foo/BUILD",
+ "rule_with_allowed_values(",
+ " name = 'rule',",
+ " one_two = select({",
+ " '//conditions:default': 'TOTALLY_ILLEGAL_VALUE',",
+ " }))");
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ getConfiguredTarget("//foo:rule");
+ assertContainsEvent(
+ "invalid value in 'one_two' attribute: "
+ + "has to be one of 'one' or 'two' instead of 'TOTALLY_ILLEGAL_VALUE'");
+ }
+
+ @Test
+ public void selectWithMultipleNonAllowedValuesCausesMultipleErrors() throws Exception {
+ scratch.file("foo/BUILD",
+ "rule_with_allowed_values(",
+ " name = 'rule',",
+ " one_two = select({",
+ " '//conditions:a': 'TOTALLY_ILLEGAL_VALUE',",
+ " '//conditions:default': 'DIFFERENT_BUT_STILL_ILLEGAL',",
+ " }))");
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ getConfiguredTarget("//foo:rule");
+ assertContainsEvent(
+ "invalid value in 'one_two' attribute: "
+ + "has to be one of 'one' or 'two' instead of 'TOTALLY_ILLEGAL_VALUE'");
+ assertContainsEvent(
+ "invalid value in 'one_two' attribute: "
+ + "has to be one of 'one' or 'two' instead of 'DIFFERENT_BUT_STILL_ILLEGAL'");
+ }
+
+ @Test
+ public void selectConcatenationWithAllowedValues() throws Exception {
+ scratch.file("foo/BUILD",
+ "rule_with_allowed_values(",
+ " name = 'rule',",
+ " one_two = 'on' + select({",
+ " '//conditions:default': 'e',",
+ " }))");
+ assertThat(getConfiguredTarget("//foo:rule")).isNotNull();
+ }
+
+ @Test
+ public void selectConcatenationWithNonAllowedValues() throws Exception {
+ scratch.file("foo/BUILD",
+ "rule_with_allowed_values(",
+ " name = 'rule',",
+ " one_two = 'on' + select({",
+ " '//conditions:default': 'o',",
+ " }))");
+ reporter.removeHandler(failFastHandler); // Expect errors.
+ getConfiguredTarget("//foo:binary");
+ assertContainsEvent(
+ "invalid value in 'one_two' attribute: "
+ + "has to be one of 'one' or 'two' instead of 'ono'");
+ }
+
+ @Test
+ public void computedDefaultAttributesCanReferenceConfigurableAttributes() throws Exception {
+ scratch.file(
+ "test/selector_rules.bzl",
+ "def _impl(ctx):",
+ " ctx.actions.write(",
+ " output=ctx.outputs.out_file,",
+ " content=ctx.attr.string_value,",
+ " )",
+ " return struct()",
+ "",
+ "def _derived_value(string_value):",
+ " return Label(\"//test:%s\" % string_value)",
+ "",
+ "selector_rule = rule(",
+ " attrs = {",
+ " \"string_value\": attr.string(default = \"\"),",
+ " \"out_file\": attr.output(),",
+ " \"_derived\": attr.label(default = _derived_value),",
+ " },",
+ "implementation = _impl,",
+ ")");
+ scratch.file("test/BUILD",
+ "genrule(name = \"foo\", srcs = [], outs = [\"foo.out\"], cmd = \"\")");
+ scratch.file("foo/BUILD",
+ "load(\"/test/selector_rules\", \"selector_rule\")",
+ "selector_rule(",
+ " name = \"rule\",",
+ " out_file = \"rule.out\",",
+ " string_value = select({\"//conditions:default\": \"foo\"}),",
+ ")");
+ getConfiguredTarget("//foo:rule");
+ assertNoEvents();
+ }
+
+ @Test
+ public void selectableDefaultValueWithTypeDefault() throws Exception {
+ writeConfigRules();
+ scratch.file("srctest/BUILD",
+ "genrule(",
+ " name = 'gen',",
+ " cmd = '',",
+ " outs = ['gen.out'],",
+ " srcs = select({",
+ " '//conditions:a': None,",
+ " }))");
+
+ useConfiguration("--test_arg=a");
+ ConfiguredTarget binary = getConfiguredTarget("//srctest:gen");
+ AttributeMap attributes = ConfiguredAttributeMapper.of((RuleConfiguredTarget) binary);
+ assertThat(attributes.get("srcs", BuildType.LABEL_LIST)).isEmpty();
+ }
+
+ @Test
+ public void selectableDefaultValueWithRuleDefault() throws Exception {
+ writeConfigRules();
+ scratch.file("foo/BUILD",
+ "rule_with_label_default(",
+ " name = 'rule',",
+ " dep = select({",
+ " '//conditions:a': None,",
+ " }))",
+ "rule_with_boolean_attr(",
+ " name = 'default',",
+ " boolean_attr = 1)");
+
+ useConfiguration("--test_arg=a");
+ ConfiguredTarget binary = getConfiguredTarget("//foo:rule");
+ AttributeMap attributes = ConfiguredAttributeMapper.of((RuleConfiguredTarget) binary);
+ assertThat(attributes.get("dep", BuildType.LABEL)).isEqualTo(
+ Label.parseAbsolute("//foo:default"));
+ }
+
+ @Test
+ public void noneValuesWithMultipleSelectsMixedValues() throws Exception {
+ writeConfigRules();
+ scratch.file("a/BUILD",
+ "genrule(",
+ " name = 'gen',",
+ " srcs = [],",
+ " outs = ['out'],",
+ " cmd = '',",
+ " message = select({",
+ " '//conditions:a': 'defined message 1',",
+ " '//conditions:b': None,",
+ " }) + select({",
+ " '//conditions:a': None,",
+ " '//conditions:b': 'defined message 2',",
+ " }),",
+ ")");
+
+ reporter.removeHandler(failFastHandler);
+ useConfiguration("--define", "mode=a");
+ assertThat(getConfiguredTarget("//a:gen")).isNull();
+ assertContainsEvent(
+ "'+' operator applied to incompatible types (select of string, select of NoneType)");
+ }
+
+ @Test
+ public void emptySelectCannotBeConcatenated() throws Exception {
+ scratch.file("a/BUILD",
+ "genrule(",
+ " name = 'gen',",
+ " srcs = [],",
+ " outs = ['out'],",
+ " cmd = select({}) + ' always include'",
+ ")");
+
+ reporter.removeHandler(failFastHandler);
+ assertThat(getConfiguredTarget("//a:gen")).isNull();
+ assertContainsEvent(
+ "'+' operator applied to incompatible types (select of unknown, string)");
+ }
+
+ @Test
+ public void selectOnConstraints() throws Exception {
+ writeHelloRules(/*includeDefaultCondition=*/true);
+ scratch.file("conditions/BUILD",
+ "constraint_setting(name = 'fruit')",
+ "constraint_value(name = 'apple', constraint_setting = 'fruit')",
+ "constraint_value(name = 'banana', constraint_setting = 'fruit')",
+ "platform(",
+ " name = 'apple_platform',",
+ " constraint_values = [':apple'],",
+ ")",
+ "platform(",
+ " name = 'banana_platform',",
+ " constraint_values = [':banana'],",
+ ")",
+ "config_setting(",
+ " name = 'a',",
+ " constraint_values = [':apple']",
+ ")",
+ "config_setting(",
+ " name = 'b',",
+ " constraint_values = [':banana']",
+ ")");
+ checkRule("//java/hello:hello", "--experimental_platforms=//conditions:apple_platform",
+ /*expected:*/ ImmutableList.of(ADEP_INPUT),
+ /*not expected:*/ ImmutableList.of(BDEP_INPUT, DEFAULTDEP_INPUT));
+ }
+}