blob: ef6be77deebae659a71e7fdb3402476f6cd0a99d [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 target '//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 target '//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 target '//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 target '//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"));
}
}