| // Copyright 2015 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 com.google.devtools.build.lib.analysis.config.CompilationMode; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.packages.BuildType; | |
| import com.google.devtools.build.lib.packages.ConfiguredAttributeMapper; | |
| import com.google.devtools.build.lib.packages.Type; | |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.regex.Pattern; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * Unit tests for {@link ConfiguredAttributeMapper}. | |
| * | |
| * <p>This is distinct from {@link | |
| * com.google.devtools.build.lib.analysis.select.ConfiguredAttributeMapperCommonTest} because the | |
| * latter needs to inherit from {@link | |
| * com.google.devtools.build.lib.analysis.select.AbstractAttributeMapperTest} to run tests common to | |
| * all attribute mappers. | |
| */ | |
| @RunWith(JUnit4.class) | |
| public class ConfiguredAttributeMapperTest extends BuildViewTestCase { | |
| /** | |
| * Returns a ConfiguredAttributeMapper bound to the given rule with the target configuration. | |
| */ | |
| private ConfiguredAttributeMapper getMapper(String label) throws Exception { | |
| ConfiguredTargetAndData ctad = getConfiguredTargetAndData(label); | |
| return getMapperFromConfiguredTargetAndTarget(ctad); | |
| } | |
| private void writeConfigRules() throws Exception { | |
| scratch.file("conditions/BUILD", | |
| "config_setting(", | |
| " name = 'a',", | |
| " values = {'define': 'mode=a'})", | |
| "config_setting(", | |
| " name = 'b',", | |
| " values = {'define': 'mode=b'})"); | |
| } | |
| /** | |
| * Tests that {@link ConfiguredAttributeMapper#get} only gets the configuration-appropriate | |
| * value. | |
| */ | |
| @Test | |
| public void testGetAttribute() throws Exception { | |
| writeConfigRules(); | |
| scratch.file("a/BUILD", | |
| "genrule(", | |
| " name = 'gen',", | |
| " srcs = [],", | |
| " outs = ['out'],", | |
| " cmd = select({", | |
| " '//conditions:a': 'a command',", | |
| " '//conditions:b': 'b command',", | |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': 'default command',", | |
| " }))"); | |
| useConfiguration("--define", "mode=a"); | |
| assertThat(getMapper("//a:gen").get("cmd", Type.STRING)).isEqualTo("a command"); | |
| useConfiguration("--define", "mode=b"); | |
| assertThat(getMapper("//a:gen").get("cmd", Type.STRING)).isEqualTo("b command"); | |
| useConfiguration("--define", "mode=c"); | |
| assertThat(getMapper("//a:gen").get("cmd", Type.STRING)).isEqualTo("default command"); | |
| } | |
| /** | |
| * Tests that label visitation only travels down configuration-appropriate paths. | |
| */ | |
| @Test | |
| public void testLabelVisitation() throws Exception { | |
| writeConfigRules(); | |
| scratch.file("a/BUILD", | |
| "sh_binary(", | |
| " name = 'bin',", | |
| " srcs = ['bin.sh'],", | |
| " deps = select({", | |
| " '//conditions:a': [':adep'],", | |
| " '//conditions:b': [':bdep'],", | |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':defaultdep'],", | |
| " }))", | |
| "sh_library(", | |
| " name = 'adep',", | |
| " srcs = ['adep.sh'])", | |
| "sh_library(", | |
| " name = 'bdep',", | |
| " srcs = ['bdep.sh'])", | |
| "sh_library(", | |
| " name = 'defaultdep',", | |
| " srcs = ['defaultdep.sh'])"); | |
| List<Label> visitedLabels = new ArrayList<>(); | |
| Label binSrc = Label.parseCanonical("//a:bin.sh"); | |
| useConfiguration("--define", "mode=a"); | |
| addRelevantLabels(getMapper("//a:bin"), visitedLabels); | |
| assertThat(visitedLabels).containsExactly(binSrc, Label.parseCanonical("//a:adep")); | |
| visitedLabels.clear(); | |
| useConfiguration("--define", "mode=b"); | |
| addRelevantLabels(getMapper("//a:bin"), visitedLabels); | |
| assertThat(visitedLabels).containsExactly(binSrc, Label.parseCanonical("//a:bdep")); | |
| visitedLabels.clear(); | |
| useConfiguration("--define", "mode=c"); | |
| addRelevantLabels(getMapper("//a:bin"), visitedLabels); | |
| assertThat(visitedLabels).containsExactly(binSrc, Label.parseCanonical("//a:defaultdep")); | |
| } | |
| private static void addRelevantLabels( | |
| ConfiguredAttributeMapper mapper, List<Label> visitedLabels) { | |
| mapper.visitAllLabels( | |
| (attribute, label) -> { | |
| if (label.getPackageIdentifier().getPackageFragment().toString().equals("a")) { | |
| visitedLabels.add(label); | |
| } | |
| }); | |
| } | |
| /** | |
| * Tests that for configurable attributes where the *values* are evaluated in different | |
| * configurations, the configuration checking still uses the original configuration. | |
| */ | |
| @Test | |
| public void testConfigurationTransitions() throws Exception { | |
| writeConfigRules(); | |
| scratch.file("a/BUILD", | |
| "genrule(", | |
| " name = 'gen',", | |
| " srcs = [],", | |
| " outs = ['out'],", | |
| " cmd = 'nothing',", | |
| " tools = select({", | |
| " '//conditions:a': [':adep'],", | |
| " '//conditions:b': [':bdep'],", | |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': [':defaultdep'],", | |
| " }))", | |
| "sh_binary(", | |
| " name = 'adep',", | |
| " srcs = ['adep.sh'])", | |
| "sh_binary(", | |
| " name = 'bdep',", | |
| " srcs = ['bdep.sh'])", | |
| "sh_binary(", | |
| " name = 'defaultdep',", | |
| " srcs = ['defaultdep.sh'])"); | |
| useConfiguration("--define", "mode=b"); | |
| // Target configuration is in dbg mode, so we should match //conditions:b: | |
| assertThat(getMapper("//a:gen").get("tools", BuildType.LABEL_LIST)) | |
| .containsExactly(Label.parseCanonical("//a:bdep")); | |
| // Verify the "tools" dep uses a different configuration that's not also in "dbg": | |
| assertThat( | |
| getTarget("//a:gen") | |
| .getAssociatedRule() | |
| .getRuleClassObject() | |
| .getAttributeByName("tools") | |
| .getTransitionFactory() | |
| .isTool()) | |
| .isTrue(); | |
| assertThat(getHostConfiguration().getCompilationMode()).isEqualTo(CompilationMode.OPT); | |
| } | |
| @Test | |
| public void testConcatenatedSelects() throws Exception { | |
| scratch.file("hello/BUILD", | |
| "config_setting(name = 'a', values = {'define': 'foo=a'})", | |
| "config_setting(name = 'b', values = {'define': 'foo=b'})", | |
| "config_setting(name = 'c', values = {'define': 'bar=c'})", | |
| "config_setting(name = 'd', values = {'define': 'bar=d'})", | |
| "genrule(", | |
| " name = 'gen',", | |
| " srcs = select({':a': ['a.in'], ':b': ['b.in']})", | |
| " + select({':c': ['c.in'], ':d': ['d.in']}),", | |
| " outs = ['out'],", | |
| " cmd = 'nothing',", | |
| ")"); | |
| useConfiguration("--define", "foo=a", "--define", "bar=d"); | |
| assertThat(getMapper("//hello:gen").get("srcs", BuildType.LABEL_LIST)) | |
| .containsExactly( | |
| Label.parseCanonical("//hello:a.in"), Label.parseCanonical("//hello:d.in")); | |
| } | |
| @Test | |
| public void testNoneValuesMeansAttributeIsNotExplicitlySet() throws Exception { | |
| writeConfigRules(); | |
| scratch.file("a/BUILD", | |
| "genrule(", | |
| " name = 'gen',", | |
| " srcs = [],", | |
| " outs = ['out'],", | |
| " cmd = '',", | |
| " message = select({", | |
| " '//conditions:a': 'defined message',", | |
| " '//conditions:b': None,", | |
| " }))"); | |
| useConfiguration("--define", "mode=a"); | |
| assertThat(getMapper("//a:gen").isAttributeValueExplicitlySpecified("message")).isTrue(); | |
| useConfiguration("--define", "mode=b"); | |
| assertThat(getMapper("//a:gen").isAttributeValueExplicitlySpecified("message")).isFalse(); | |
| } | |
| @Test | |
| public void testNoneValuesWithMultipleSelectsAllNone() 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': 'defined message 2',", | |
| " '//conditions:b': None,", | |
| " }),", | |
| ")"); | |
| useConfiguration("--define", "mode=a"); | |
| assertThat(getMapper("//a:gen").isAttributeValueExplicitlySpecified("message")).isTrue(); | |
| useConfiguration("--define", "mode=b"); | |
| assertThat(getMapper("//a:gen").isAttributeValueExplicitlySpecified("message")).isFalse(); | |
| } | |
| @Test | |
| public void testNoneValueOnDefaultConditionWithNullDefault() throws Exception { | |
| writeConfigRules(); | |
| scratch.file("a/BUILD", | |
| "cc_library(", | |
| " name = 'lib',", | |
| " srcs = ['lib.cc'],", | |
| " linkstamp = select({", | |
| " '//conditions:a': 'notused_linkstamp.cc',", | |
| " '" + BuildType.Selector.DEFAULT_CONDITION_KEY + "': None,", | |
| " }),", | |
| ")"); | |
| useConfiguration(); | |
| assertThat(getMapper("//a:lib").isAttributeValueExplicitlySpecified("linkstamp")).isFalse(); | |
| assertThat(getMapper("//a:lib").get("linkstamp", BuildType.LABEL)).isNull(); | |
| } | |
| @Test | |
| public void testNoneValueOnMandatoryAttribute() throws Exception { | |
| scratch.file("a/BUILD", "alias(name='a', actual=select({'//conditions:default': None}))"); | |
| reporter.removeHandler(failFastHandler); | |
| getConfiguredTarget("//a:a"); | |
| assertContainsEvent("Mandatory attribute 'actual' resolved to 'None'"); | |
| } | |
| @Test | |
| public void testAliasedConfigSetting() throws Exception { | |
| writeConfigRules(); | |
| scratch.file( | |
| "a/BUILD", | |
| "alias(", | |
| " name = 'aliased_a',", | |
| " actual = '//conditions:a',", | |
| ")", | |
| "genrule(", | |
| " name = 'gen',", | |
| " srcs = [],", | |
| " outs = ['out'],", | |
| " cmd = '',", | |
| " message = select({", | |
| " ':aliased_a': 'defined message',", | |
| " '//conditions:default': None,", | |
| " }))"); | |
| useConfiguration("--define", "mode=a"); | |
| assertThat(getMapper("//a:gen").get("message", Type.STRING)).isEqualTo("defined message"); | |
| } | |
| @Test | |
| public void noMatchErrorFormat() throws Exception { | |
| scratch.file( | |
| "a/BUILD", | |
| "config_setting(", | |
| " name = 'a',", | |
| " values = {'define': 'mode=a'})", | |
| "config_setting(", | |
| " name = 'b',", | |
| " values = {'define': 'mode=b'})", | |
| "genrule(", | |
| " name = 'g',", | |
| " srcs = [],", | |
| " outs = ['out'],", | |
| " cmd = '',", | |
| " message = select({", | |
| " ':a': 'not chosen',", | |
| " ':b': 'not chosen',", | |
| " }))"); | |
| reporter.removeHandler(failFastHandler); | |
| getConfiguredTarget("//a:g"); | |
| // Match with a regex pattern because the error message includes the failing target's | |
| // configuration ID, which can vary among builds. | |
| assertContainsEvent( | |
| Pattern.compile( | |
| ".*configurable attribute \"message\" in //a:g doesn't match this configuration. Would" | |
| + " a default condition help\\?\n" | |
| + "\n" | |
| + "Conditions checked:\n" | |
| + " //a:a\n" | |
| + " //a:b\n" | |
| + "\n" | |
| + "To see a condition's definition, run: bazel query --output=build <condition" | |
| + " label>.\n" | |
| + "\n" | |
| + "This instance of //a:g has configuration identifier .*. To inspect its" | |
| + " configuration, run: bazel config .*.\n" | |
| + "\n" | |
| + "For more help, see" | |
| + " https://bazel.build/docs/configurable-attributes" | |
| + "#faq-select-choose-condition.*")); | |
| } | |
| } |