| // 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(getExecConfiguration().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.*")); |
| } |
| } |