blob: 7eb1d1b6c2403ad471a177e96fdf6d2f84be481b [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 com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.util.BuildViewTestBase.AnalysisFailureRecorder;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.analysis.util.DummyTestFragment;
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.packages.RuleClass.ToolchainResolutionMode;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
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.Before;
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 = {'foo': 'a'})
config_setting(
name = 'b',
values = {'foo': '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(Label.parseCanonicalUnchecked("//foo:default"))
.allowedFileTypes(FileTypeSet.ANY_FILE)));
private static final MockRule RULE_WITH_NO_PLATFORM =
() ->
MockRule.define(
"rule_with_no_platform",
(builder, env) ->
builder
.add(attr("deps", LABEL_LIST).allowedFileTypes())
.useToolchainResolution(ToolchainResolutionMode.DISABLED));
@Override
protected ConfiguredRuleClassProvider createRuleClassProvider() {
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)
.addRuleDefinition(RULE_WITH_NO_PLATFORM);
TestRuleClassProvider.addStandardRules(builder);
// Allow use of --foo as a dummy flag
builder.addConfigurationFragment(DummyTestFragment.class);
return builder.build();
}
@Before
public void setupStarlarkJavaBinary() throws Exception {
setBuildLanguageOptions("--experimental_google_legacy_api");
}
@Test
public void basicConfigurability() throws Exception {
writeHelloRules(/*includeDefaultCondition=*/true);
writeConfigRules();
checkRule(
"//java/hello:hello",
"--foo=a",
/*expected:*/ ImmutableList.of(ADEP_INPUT),
/*not expected:*/ ImmutableList.of(BDEP_INPUT, DEFAULTDEP_INPUT));
checkRule(
"//java/hello:hello",
"--foo=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",
"--foo=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",
"--foo=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("--foo=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:a': ['a.java'],
'//conditions:b': ['c.java'],
}))
""");
reporter.removeHandler(failFastHandler); // Expect errors.
useConfiguration("--foo=a");
getConfiguredTarget("//java/hello:hello");
assertContainsEvent(
"in srcs attribute of java_binary rule //java/hello:hello: Label '//java/hello:a.java' is"
+ " duplicated");
}
/**
* 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("--foo=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("--foo=a");
getConfiguredTarget("//java/hello:hello");
assertContainsEvent(
"Label '//java/hello:a.java' is duplicated in the 'srcs' attribute of rule 'hello'");
}
/**
* 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("--foo=a");
ConfiguredTargetAndData binary = getConfiguredTargetAndData("//test:the_rule");
AttributeMap attributes = getMapperFromConfiguredTargetAndTarget(binary);
assertThat(attributes.get("$computed_attr", Type.STRING)).isEqualTo("a2");
// configuration b:
useConfiguration("--foo=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",
"""
java_library(
name = 'int_key',
srcs = select({123: ['a.java']})
)
""");
assertTargetError(
"//java/foo:int_key", "select: got int for dict key, want a Label or label string");
}
@Test
public void configKeyTypeChecking_Bool() throws Exception {
reporter.removeHandler(failFastHandler); // Expect errors.
scratch.file(
"java/foo/BUILD",
"""
java_library(
name = 'bool_key',
srcs = select({True: ['a.java']})
)
""");
assertTargetError(
"//java/foo:bool_key", "select: got bool for dict key, want a Label or label string");
}
@Test
public void configKeyTypeChecking_None() throws Exception {
reporter.removeHandler(failFastHandler); // Expect errors.
scratch.file(
"java/foo/BUILD",
"""
java_library(
name = 'none_key',
srcs = select({None: ['a.java']})
)
""");
assertTargetError(
"//java/foo:none_key", "select: got NoneType for dict key, want a Label or label string");
}
@Test
public void selectWithoutConditionsMakesNoSense() throws Exception {
reporter.removeHandler(failFastHandler); // Expect errors.
scratch.file(
"foo/BUILD",
"""
genrule(
name = 'nothing',
srcs = [],
outs = ['notmuch'],
cmd = select({})
)
""");
assertTargetError(
"//foo:nothing",
"select({}) with an empty dictionary can never resolve because it includes no conditions "
+ "to match");
}
/**
* 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 = {'foo': 'a'})
""");
writeHelloRules(/*includeDefaultCondition=*/true);
getConfiguredTarget("//java/hello:hello");
assertContainsEvent("no such target '//conditions:b'");
}
/**
* 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 = {'foo': '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 select() condition for //java/hello:hello");
assertDoesNotContainEvent("//conditions:a"); // This one is legitimate..
}
@Test
public void configKeyNonexistentTarget() throws Exception {
reporter.removeHandler(failFastHandler); // Expect errors.
scratch.file(
"foo/BUILD",
"""
genrule(
name = 'g',
outs = ['g.out'],
cmd = select({':fake': ''})
)
""");
assertThat(getConfiguredTarget("//foo:g")).isNull();
assertContainsEvent("//foo:fake is not a valid select() condition for //foo:g");
}
@Test
public void configKeyNonexistentTarget_otherPackage() throws Exception {
reporter.removeHandler(failFastHandler); // Expect errors.
scratch.file(
"conditions/BUILD",
"""
config_setting(
name = 'a',
values = {'foo': 'a'})
""");
scratch.file("bar/BUILD");
scratch.file(
"foo/BUILD",
"""
genrule(
name = 'g',
outs = ['g.out'],
# With an invalid target and a real target, validate skyframe error handling.
# See http://b/162021059 for details.
cmd = select({'//bar:fake': '', '//conditions:a': ''})
)
""");
assertThat(getConfiguredTarget("//foo:g")).isNull();
assertContainsEvent("bar/BUILD: no such target '//bar:fake'");
assertContainsEvent("foo/BUILD:1:8: errors encountered resolving select() keys for //foo:g");
}
/**
* Tests config keys with multiple requirements.
*/
@Test
public void multiConditionConfigKeys() throws Exception {
writeHelloRules(/*includeDefaultCondition=*/true);
scratch.file(
"conditions/BUILD",
"""
config_setting(
name = 'a',
values = {
'foo': 'a',
'compilation_mode': 'dbg'
})
config_setting(
name = 'b',
values = {'foo': 'b'})
""");
checkRule(
"//java/hello:hello",
"--foo=a",
/*expected:*/ ImmutableList.of(DEFAULTDEP_INPUT),
/*not expected:*/ ImmutableList.of(ADEP_INPUT, BDEP_INPUT));
checkRule(
"//java/hello:hello",
ImmutableList.of("--foo=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("--foo=a");
checkRule(
"//java/hello:hello",
"--foo=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 = {'foo': 'c'})
config_setting(
name = 'b',
values = {'foo': 'b'})
""");
// Iteration 2: same exact analysis should now apply the default condition.
invalidatePackages();
checkRule(
"//java/hello:hello",
"--foo=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 "
+ "or they resolve to the same value.");
}
/**
* 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', 'foo': '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", "--foo", "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 multiple matches are allowed for conditions where the value is the same. */
@Test
public void multipleMatchesSameValue() 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': ['a.in'],",
" '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':default.in'],",
" }))");
checkRule(
"//a:gen",
"srcs",
ImmutableList.of("-c", "opt", "--define", "foo=bar"),
/*expected:*/ ImmutableList.of("src a/a.in"),
/*not expected:*/ ImmutableList.of("src a/default.in"));
}
/**
* 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 = {'foo': 'baz'})
config_setting(
name = 'b_a_c', # Named to come alphabetically after a and b but before c.
values = {'define': 'a=1', 'foo': '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", "--foo", "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",
"--foo=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\" in //java/hello:hello 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);
AnalysisFailureRecorder analysisFailureRecorder = new AnalysisFailureRecorder();
eventBus.register(analysisFailureRecorder);
useConfiguration("");
assertThat(getConfiguredTarget("//java/hello:hello_default_no_match_error")).isNull();
assertContainsEvent(
"configurable attribute \"srcs\" in //java/hello:hello_default_no_match_error doesn't"
+ " match this configuration. Would a default condition help?\n"
+ "\n"
+ "Conditions checked:");
// Verify a Root Cause is reported when a target cannot be configured due to no matching config.
assertThat(analysisFailureRecorder.causes).hasSize(1);
AnalysisRootCauseEvent rootCause = analysisFailureRecorder.causes.get(0);
assertThat(rootCause.getLabel())
.isEqualTo(Label.parseCanonical("//java/hello:hello_default_no_match_error"));
eventBus.unregister(analysisFailureRecorder);
analysisFailureRecorder = new AnalysisFailureRecorder();
eventBus.register(analysisFailureRecorder);
eventCollector.clear();
assertThat(getConfiguredTarget("//java/hello:hello_custom_no_match_error")).isNull();
assertContainsEvent(
"configurable attribute \"srcs\" in //java/hello:hello_custom_no_match_error doesn't match "
+ "this configuration: You always have to choose condition a!");
// Verify a Root Cause is reported when a target cannot be configured due to no matching config.
assertThat(analysisFailureRecorder.causes).hasSize(1);
rootCause = analysisFailureRecorder.causes.get(0);
assertThat(rootCause.getLabel())
.isEqualTo(Label.parseCanonical("//java/hello:hello_custom_no_match_error"));
}
@Test
public void nativeTypeConcatenatedWithSelect() throws Exception {
writeConfigRules();
scratch.file(
"java/foo/rule.bzl",
"""
def _rule_impl(ctx):
return []
myrule = rule(
implementation = _rule_impl,
attrs = {
'deps': attr.label_keyed_string_dict()
},
)
""");
scratch.file(
"java/foo/BUILD",
"""
load(':rule.bzl', 'myrule')
myrule(
name = 'mytarget',
deps = {':always': 'always'} | select({
'//conditions:a': {':a': 'a'},
'//conditions:b': {':b': 'b'},
})
)
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",
"--foo=b",
/*expected:*/ ImmutableList.of("bin java/foo/libalways.jar", "bin java/foo/libb.jar"),
/*not expected:*/ ImmutableList.of("bin java/foo/liba.jar"));
checkRule(
"//java/foo:mytarget",
"--foo=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/rule.bzl",
"""
def _rule_impl(ctx):
return []
myrule = rule(
implementation = _rule_impl,
attrs = {
'deps': attr.label_keyed_string_dict()
},
)
""");
scratch.file(
"java/foo/BUILD",
"""
load(':rule.bzl', 'myrule')
myrule(
name = 'mytarget',
deps = select({
'//conditions:a': {':a': 'a'},
'//conditions:b': {':b': 'b'},
}) | {':always': 'always'}
)
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",
"--foo=b",
/*expected:*/ ImmutableList.of("bin java/foo/libalways.jar", "bin java/foo/libb.jar"),
/*not expected:*/ ImmutableList.of("bin java/foo/liba.jar"));
checkRule(
"//java/foo:mytarget",
"--foo=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/rule.bzl",
"""
def _rule_impl(ctx):
return []
myrule = rule(
implementation = _rule_impl,
attrs = {
'deps': attr.label_keyed_string_dict()
},
)
""");
scratch.file(
"java/foo/BUILD",
"""
load(':rule.bzl', 'myrule')
myrule(
name = 'mytarget',
deps = select({
'//conditions:a': {':a': 'a'},
'//conditions:b': {':b': 'b'},
}) | select({
'//conditions:a': {':a2': 'a2'},
'//conditions:b': {':b2': 'b2'},
})
)
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",
"--foo=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"));
checkRule(
"//java/foo:mytarget",
"--foo=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 dictsWithSameKey() throws Exception {
writeConfigRules();
scratch.file(
"java/foo/rule.bzl",
"""
def _rule_impl(ctx):
outputs = []
for target, value in ctx.attr.deps.items():
output = ctx.actions.declare_file(target.label.name + value)
ctx.actions.write(content = value, output = output)
outputs.append(output)
return [DefaultInfo(files=depset(outputs))]
myrule = rule(
implementation = _rule_impl,
attrs = {
'deps': attr.label_keyed_string_dict()
},
)
""");
scratch.file(
"java/foo/BUILD",
"""
load(':rule.bzl', 'myrule')
myrule(
name = 'mytarget',
deps = select({
'//conditions:a': {':a': 'a'},
}) | select({
'//conditions:a': {':a': 'a2'},
})
)
java_library(
name = 'a',
srcs = ['a.java']
)
filegroup(
name = 'group',
srcs = [':mytarget'],
)
""");
checkRule(
"//java/foo:group",
"srcs",
ImmutableList.of("--foo=a"),
/*expected:*/ ImmutableList.of("bin java/foo/aa2"),
/*not expected:*/ ImmutableList.of("bin java/foo/aa"));
}
@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);
assertThrows(NoSuchTargetException.class, () -> getTarget("//java/foo:binary"));
assertContainsEvent("Cannot combine 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("--foo=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'], allow_empty = True))
""");
reporter.removeHandler(failFastHandler);
assertThrows(NoSuchTargetException.class, () -> getTarget("//foo:binary"));
assertContainsEvent("Cannot combine 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("--foo=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 []
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("--foo=a");
ConfiguredTargetAndData ctad = getConfiguredTargetAndData("//srctest:gen");
AttributeMap attributes = getMapperFromConfiguredTargetAndTarget(ctad);
assertThat(attributes.get("srcs", 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("--foo=a");
ConfiguredTargetAndData ctad = getConfiguredTargetAndData("//foo:rule");
AttributeMap attributes = getMapperFromConfiguredTargetAndTarget(ctad);
assertThat(attributes.get("dep", BuildType.LABEL))
.isEqualTo(Label.parseCanonical("//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("Cannot combine 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(
"select({}) with an empty dictionary can never resolve because it includes no conditions "
+ "to match");
}
@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(
"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"));
}
@Test
public void selectDirectlyOnConstraints() throws Exception {
// Tests select()ing directly on a constraint_value (with no intermediate config_setting).
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'],
)
""");
scratch.file(
"check/defs.bzl",
"""
def _impl(ctx):
pass
simple_rule = rule(
implementation = _impl,
attrs = {'srcs': attr.label_list(allow_files = True)}
)
""");
scratch.file(
"check/BUILD",
"""
load('//check:defs.bzl', 'simple_rule')
filegroup(name = 'adep', srcs = ['afile'])
filegroup(name = 'bdep', srcs = ['bfile'])
simple_rule(name = 'hello',
srcs = select({
'//conditions:apple': [':adep'],
'//conditions:banana': [':bdep'],
}))
""");
checkRule(
"//check:hello",
"srcs",
ImmutableList.of("--platforms=//conditions:apple_platform"),
/*expected:*/ ImmutableList.of("src check/afile"),
/*not expected:*/ ImmutableList.of("src check/bfile", "src check/defaultfile"));
checkRule(
"//check:hello",
"srcs",
ImmutableList.of("--platforms=//conditions:banana_platform"),
/*expected:*/ ImmutableList.of("src check/bfile"),
/*not expected:*/ ImmutableList.of("src check/afile", "src check/defaultfile"));
}
@Test
public void nonToolchainResolvingTargetsCantSelectDirectlyOnConstraints() throws Exception {
// Tests select()ing directly on a constraint_value (with no intermediate config_setting).
scratch.file(
"conditions/BUILD",
"""
constraint_setting(name = 'fruit')
constraint_value(name = 'apple', constraint_setting = 'fruit')
platform(
name = 'apple_platform',
constraint_values = [':apple'],
)
""");
scratch.file(
"check/BUILD",
"""
filegroup(name = 'adep', srcs = ['afile'])
rule_with_no_platform(name = 'hello',
deps = select({
'//conditions:apple': [':adep'],
})
)
""");
reporter.removeHandler(failFastHandler);
useConfiguration("--platforms=//conditions:apple_platform");
assertThat(getConfiguredTarget("//check:hello")).isNull();
assertContainsEvent("//conditions:apple is not a valid select() condition for //check:hello");
}
@Test
public void selectOnlyToolchainResolvingTargetsCanSelectDirectlyOnConstraints() throws Exception {
// Tests select()ing directly on a constraint_value when the rule uses toolchain resolution
// *only if it has a select()*. As of this test, alias() is the only rule that supports that
// (see Alias#useToolchainResolution(ToolchainResolutionMode.ENABLED_ONLY_FOR_COMMON_LOGIC).
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'],
)
""");
scratch.file(
"check/defs.bzl",
"""
def _impl(ctx):
pass
simple_rule = rule(
implementation = _impl,
attrs = {}
)
""");
scratch.file(
"check/BUILD",
"""
load('//check:defs.bzl', 'simple_rule')
filegroup(name = 'bdep', srcs = ['bfile'])
simple_rule(name = 'hello')
simple_rule(name = 'tere')
alias(
name = 'selectable_alias',
actual = select({
'//conditions:apple': ':hello',
'//conditions:banana': ':tere',
}))
""");
useConfiguration("--platforms=//conditions:apple_platform");
assertThat(
getConfiguredTarget("//check:selectable_alias")
.getActual()
.getLabel()
.getCanonicalForm())
.isEqualTo("//check:hello");
}
@Test
public void multipleMatchErrorWhenAliasResolvesToSameSetting() throws Exception {
scratch.file(
"a/BUILD",
"""
config_setting(
name = 'foo',
define_values = { 'foo': '1' })
alias(
name = 'alias_to_foo',
actual = ':foo')
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
':foo': 0,
'alias_to_foo': 1,
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNull();
assertContainsEvent(
"configurable attribute \"boolean_attr\" in //a:binary doesn't match this configuration. "
+ "Would a default condition help?\n\n"
+ "Conditions checked:\n"
+ " //a:foo\n"
+ " //a:alias_to_foo");
}
@Test
public void defaultVisibilityConfigSetting_noVisibilityEnforcement() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=false");
scratch.file("c/BUILD", "config_setting(name = 'foo', define_values = { 'foo': '1' })");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNotNull();
assertNoEvents();
}
@Test
public void privateVisibilityConfigSetting_noVisibilityEnforcement() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=false");
scratch.file(
"c/BUILD",
"""
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
visibility = ['//visibility:private']
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNotNull();
assertNoEvents();
}
@Test
public void publicVisibilityConfigSetting_noVisibilityEnforcement() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=false");
scratch.file(
"c/BUILD",
"""
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
visibility = ['//visibility:public']
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNotNull();
assertNoEvents();
}
@Test
public void defaultVisibilityConfigSetting_defaultIsPublic() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=false");
scratch.file("c/BUILD", "config_setting(name = 'foo', define_values = { 'foo': '1' })");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNotNull();
assertNoEvents();
}
@Test
public void privateVisibilityConfigSetting_defaultIsPublic() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=false");
scratch.file(
"c/BUILD",
"""
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
visibility = ['//visibility:private']
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNull();
assertContainsEvent("'//c:foo' is not visible from\ntarget '//a:binary'");
}
@Test
public void publicVisibilityConfigSetting_defaultIsPublic() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=false");
scratch.file(
"c/BUILD",
"""
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
visibility = ['//visibility:public']
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNotNull();
assertNoEvents();
}
@Test
public void defaultPublicVisibility_aliasVisibilityIgnored_aliasVisibilityIsDefault()
throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions(
"--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=false");
scratch.file(
"c/BUILD",
"""
alias(
name = 'foo_alias',
actual = ':foo')
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo_alias': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNotNull();
assertNoEvents();
}
@Test
public void defaultPublicVisibility_aliasVisibilityIgnored_aliasVisibilityIsExplicit()
throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions(
"--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=false");
scratch.file(
"c/BUILD",
"""
alias(
name = 'foo_alias',
actual = ':foo',
# Current flag combo skips this and directly checks the config_setting's visibility.
visibility = ['//visibility:private']
)
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo_alias': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNotNull();
assertNoEvents();
}
@Test
public void defaultPublicVisibility_aliasVisibilityIgnored_configSettingVisibilityIsExplicit()
throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions(
"--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=false");
scratch.file(
"c/BUILD",
"""
alias(
name = 'foo_alias',
actual = ':foo',
# Current flag combo skips this and directly checks the config_setting's visibility.
visibility = ['//visibility:public']
)
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
visibility = ['//visibility:private']
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo_alias': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNull();
assertContainsEvent("'//c:foo' is not visible from\ntarget '//a:binary'");
}
@Test
public void defaultPublicVisibility_trimmedConfigsDontCrash() throws Exception {
// When enforcing config_setting visibility with
// --incompatible_config_setting_private_default_visibility=false, the alias
// ConfiguredTargetAndData clones itself with the ConfiguredTargetAndData of the config_setting
// it refers to. ConfiguredTargetAndData.fromConfiguredTarget has a safety check that both
// configs are the same. When the target with a select() is a test and --trim_test_configuration
// is on, the alias takes the parent's config (with TestOptions) but the config_setting has it
// stripped. This is a regression test that Blaze doesn't crash expecting those configs to be
// equal.
setPackageOptions(
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=false");
useConfiguration("--trim_test_configuration=true");
scratch.file(
"c/defs.bzl",
"""
def _impl(ctx):
output = ctx.outputs.out
ctx.actions.write(output = output, content = 'hi', is_executable = True)
return [DefaultInfo(executable = output)]
fake_test = rule(
attrs = {
'msg': attr.string(),
},
test = True,
outputs = {'out': 'foo.out'},
implementation = _impl,
)
""");
scratch.file(
"c/BUILD",
"""
load(':defs.bzl', 'fake_test')
alias(
name = 'foo_alias',
actual = ':foo',
)
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
)
fake_test(
name = 'foo_test',
msg = select({
':foo_alias': 'hi',
'//conditions:default': 'there'
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//c:foo_test")).isNotNull();
assertNoEvents();
}
@Test
public void defaultVisibilityConfigSetting_defaultIsPrivate() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=true");
scratch.file("c/BUILD", "config_setting(name = 'foo', define_values = { 'foo': '1' })");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNull();
assertContainsEvent("'//c:foo' is not visible from\ntarget '//a:binary'");
}
@Test
public void privateVisibilityConfigSetting_defaultIsPrivate() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=true");
scratch.file(
"c/BUILD",
"""
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
visibility = ['//visibility:private']
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNull();
assertContainsEvent("'//c:foo' is not visible from\ntarget '//a:binary'");
}
@Test
public void publicVisibilityConfigSetting_defaultIsPrivate() throws Exception {
// Production builds default to private visibility, but BuildViewTestCase defaults to public.
setPackageOptions("--default_visibility=private",
"--incompatible_enforce_config_setting_visibility=true",
"--incompatible_config_setting_private_default_visibility=true");
scratch.file(
"c/BUILD",
"""
config_setting(
name = 'foo',
define_values = { 'foo': '1' },
visibility = ['//visibility:public']
)
""");
scratch.file(
"a/BUILD",
"""
rule_with_boolean_attr(
name = 'binary',
boolean_attr = select({
'//c:foo': 0,
'//conditions:default': 1
}))
""");
reporter.removeHandler(failFastHandler);
assertThat(getConfiguredTarget("//a:binary")).isNotNull();
assertNoEvents();
}
@Test
public void selectWithLabelKeysInMacro() throws Exception {
writeConfigRules();
scratch.file("java/BUILD");
scratch.file(
"java/macros.bzl",
"""
def my_java_binary(name, deps = [], **kwargs):
native.java_binary(
name = name,
deps = select({
Label('//conditions:a'): [Label('//java/foo:a')],
'//conditions:b': [Label('//java/foo:b')],
}) + select({
'//conditions:a': [Label('//java/foo:a2')],
Label('//conditions:b'): [Label('//java/foo:b2')],
}),
**kwargs,
)
""");
scratch.file(
"java/foo/BUILD",
"""
load('//java:macros.bzl', 'my_java_binary')
my_java_binary(
name = 'binary',
srcs = ['binary.java'],
)
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",
"--foo=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"));
}
}