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