blob: 18f4c9303d175dd022aaa7f73e6eef56d6432133 [file] [log] [blame]
// 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.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.skyframe.ConfiguredTargetAndData;
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
* attribute values and *doesn't have* the unexpected attribute values.
*/
private void checkRule(
String ruleLabel,
String attributeName,
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, attributeName));
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 void checkRule(
String ruleLabel,
Collection<String> options,
Iterable<String> expected,
Iterable<String> notExpected)
throws Exception {
checkRule(ruleLabel, "deps", options, 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");
ConfiguredTargetAndData binary = getConfiguredTargetAndData("//test:the_rule");
AttributeMap attributes = getMapperFromConfiguredTargetAndTarget(binary);
assertThat(attributes.get("$computed_attr", Type.STRING)).isEqualTo("a2");
// configuration b:
useConfiguration("--test_arg=b");
binary = getConfiguredTargetAndData("//test:the_rule");
attributes = getMapperFromConfiguredTargetAndTarget(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.bzl', \"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");
ConfiguredTargetAndData ctad = getConfiguredTargetAndData("//srctest:gen");
AttributeMap attributes = getMapperFromConfiguredTargetAndTarget(ctad);
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");
ConfiguredTargetAndData ctad = getConfiguredTargetAndData("//foo:rule");
AttributeMap attributes = getMapperFromConfiguredTargetAndTarget(ctad);
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 {
// create some useful constraints and platforms.
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']",
")");
scratch.file("afile", "acontents");
scratch.file("bfile", "bcontents");
scratch.file("defaultfile", "defaultcontents");
scratch.file(
"check/BUILD",
"filegroup(name = 'adep', srcs = ['afile'])",
"filegroup(name = 'bdep', srcs = ['bfile'])",
"filegroup(name = 'defaultdep', srcs = ['defaultfile'])",
"filegroup(name = 'hello',",
" srcs = select({",
" '//conditions:a': [':adep'],",
" '//conditions:b': [':bdep'],",
" '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':defaultdep'],",
" }))");
checkRule(
"//check:hello",
"srcs",
ImmutableList.of("--experimental_platforms=//conditions:apple_platform"),
/*expected:*/ ImmutableList.of("src check/afile"),
/*not expected:*/ ImmutableList.of("src check/bfile", "src check/defaultfile"));
}
}