blob: 449c0f13b326578207e17e70d6778fd665017c98 [file] [log] [blame]
// 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.*"));
}
}