blob: 8fdc690ccc81273b6c0b3b6cd198326289158df6 [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.rules.config;
import static com.google.common.truth.Truth.assertThat;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigMatchingProvider;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.License.LicenseType;
import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import com.google.devtools.common.options.OptionMetadataTag;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import com.google.testing.junit.testparameterinjector.TestParameters;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link ConfigSetting}. */
@RunWith(TestParameterInjector.class)
public class ConfigSettingTest extends BuildViewTestCase {
/** Extra options for this test. */
public static class DummyTestOptions extends FragmentOptions {
public DummyTestOptions() {}
@Option(
name = "internal_option",
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
effectTags = {OptionEffectTag.NO_OP},
defaultValue = "super secret",
metadataTags = {OptionMetadataTag.INTERNAL})
public String internalOption;
@Option(
name = "allow_multiple_option",
defaultValue = "null",
converter = CommaSeparatedOptionListConverter.class,
allowMultiple = true,
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
effectTags = {OptionEffectTag.NO_OP})
public List<String> allowMultipleOption;
}
/** Test fragment. */
@RequiresOptions(options = {DummyTestOptions.class})
public static final class DummyTestOptionsFragment extends Fragment {
private final BuildOptions buildOptions;
public DummyTestOptionsFragment(BuildOptions buildOptions) {
this.buildOptions = buildOptions;
}
// Getter required to satisfy AutoCodec.
public BuildOptions getBuildOptions() {
return buildOptions;
}
}
@Override
protected ConfiguredRuleClassProvider createRuleClassProvider() {
ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
TestRuleClassProvider.addStandardRules(builder);
builder.addRuleDefinition(new FeatureFlagSetterRule());
builder.addConfigurationFragment(DummyTestOptionsFragment.class);
return builder.build();
}
private void writeSimpleExample() throws Exception {
scratch.file(
"pkg/BUILD",
"""
config_setting(
name = "foo",
values = {
"compilation_mode": "dbg",
"stamp": "1",
},
)
""");
}
private ConfigMatchingProvider getConfigMatchingProvider(String label) throws Exception {
return getConfiguredTarget(label).getProvider(ConfigMatchingProvider.class);
}
private boolean forceConvertMatchResult(ConfigMatchingProvider.MatchResult result)
throws Exception {
if (result.equals(ConfigMatchingProvider.MatchResult.MATCH)) {
return true;
} else if (result.equals(ConfigMatchingProvider.MatchResult.NOMATCH)) {
return false;
}
throw new IllegalStateException("Unexpected MatchResult: " + result);
}
private boolean getConfigMatchingProviderResultAsBoolean(String label) throws Exception {
return forceConvertMatchResult(getConfigMatchingProvider(label).result());
}
/** Checks the behavior of {@link ConfigSetting#isUnderToolsPackage}. */
@Test
public void isUnderToolsPackage() throws Exception {
RepositoryName toolsRepo = RepositoryName.create("tools");
// Subpackage of the tools package.
assertThat(
ConfigSetting.isUnderToolsPackage(
Label.parseCanonicalUnchecked("@tools//tools/subpkg:foo"), toolsRepo))
.isTrue();
// The tools package itself.
assertThat(
ConfigSetting.isUnderToolsPackage(
Label.parseCanonicalUnchecked("@tools//tools:foo"), toolsRepo))
.isTrue();
// The tools repo, but wrong package.
assertThat(
ConfigSetting.isUnderToolsPackage(
Label.parseCanonicalUnchecked("@tools//nottools:foo"), toolsRepo))
.isFalse();
// Not even the tools repo.
assertThat(
ConfigSetting.isUnderToolsPackage(
Label.parseCanonicalUnchecked("@nottools//nottools:foo"), toolsRepo))
.isFalse();
// A tools package but in the wrong repo.
assertThat(
ConfigSetting.isUnderToolsPackage(
Label.parseCanonicalUnchecked("@nottools//tools:foo"), toolsRepo))
.isFalse();
}
/**
* Tests that a config_setting only matches build configurations where *all* of
* its flag specifications match.
*/
@Test
public void matchingCriteria() throws Exception {
writeSimpleExample();
// First flag mismatches:
useConfiguration("-c", "opt", "--stamp");
assertThat(getConfigMatchingProviderResultAsBoolean("//pkg:foo")).isFalse();
// Second flag mismatches:
useConfiguration("-c", "dbg", "--nostamp");
assertThat(getConfigMatchingProviderResultAsBoolean("//pkg:foo")).isFalse();
// Both flags mismatch:
useConfiguration("-c", "opt", "--nostamp");
assertThat(getConfigMatchingProviderResultAsBoolean("//pkg:foo")).isFalse();
// Both flags match:
useConfiguration("-c", "dbg", "--stamp");
assertThat(getConfigMatchingProviderResultAsBoolean("//pkg:foo")).isTrue();
}
/**
* Tests that {@link ConfigMatchingProvider#label} is correct.
*/
@Test
public void labelGetter() throws Exception {
writeSimpleExample();
assertThat(getConfigMatchingProvider("//pkg:foo").label())
.isEqualTo(Label.parseCanonical("//pkg:foo"));
}
/**
* Tests that rule analysis fails on unknown options.
*/
@Test
public void unknownOption() throws Exception {
checkError("foo", "badoption",
"unknown option: 'not_an_option'",
"config_setting(",
" name = 'badoption',",
" values = {'not_an_option': 'bar'})");
}
/**
* Tests that rule analysis fails on internal options.
*/
@Test
public void internalOption() throws Exception {
checkError("foo", "badoption",
"unknown option: 'internal_option'",
"config_setting(",
" name = 'badoption',",
" values = {'internal_option': 'bar'})");
}
/**
* Tests that rule analysis fails on invalid option values.
*/
@Test
public void invalidOptionValue() throws Exception {
checkError("foo", "badvalue",
"Not a valid compilation mode: 'baz'",
"config_setting(",
" name = 'badvalue',",
" values = {'compilation_mode': 'baz'})");
}
/**
* Tests that when the first option is valid but the config_setting doesn't match,
* remaining options are still validity-checked.
*/
@Test
public void invalidOptionFartherDown() throws Exception {
checkError("foo", "badoption",
"unknown option: 'not_an_option'",
"config_setting(",
" name = 'badoption',",
" values = {",
" 'compilation_mode': 'opt',",
" 'not_an_option': 'bar',",
" })");
}
/** Tests that None is not specifiable for a key's value. */
@Test
public void noneValueInSetting() throws Exception {
checkError(
"foo",
"none",
"ERROR /workspace/foo/BUILD:1:15: //foo:none: "
+ "expected value of type 'string' for dict value element, but got None (NoneType)",
"config_setting(",
" name = 'none',",
" values = {\"none_value\": None})");
}
/**
* Tests that *some* settings (values or flag_values) must be specified.
*/
@Test
public void emptySettings() throws Exception {
checkError(
"foo",
"empty",
"in config_setting rule //foo:empty: "
+ "Either values, flag_values or constraint_values must be specified and non-empty",
"config_setting(",
" name = 'empty',",
" values = {})");
}
/**
* Tests matching on multi-value attributes with key=value entries (e.g. --define).
*/
@Test
public void multiValueDict() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {
"define": "foo=bar",
},
)
""");
useConfiguration("");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo=bar");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--define", "foo=baz");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo=bar", "--define", "bar=baz");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--define", "foo=bar", "--define", "bar=baz", "--define", "foo=nope");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo=nope", "--define", "bar=baz", "--define", "foo=bar");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void invalidDefineProducesError() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {
"define": "foo", # Value should be "foo=<something>".
},
)
""");
checkError(
"//test:match", "Variable definitions must be in the form of a 'name=value' assignment");
}
@Test
public void multipleDefines() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
define_values = {
"foo1": "bar",
"foo2": "baz",
},
)
""");
useConfiguration("");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo1=bar");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo2=baz");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo1=bar", "--define", "foo2=baz");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
/**
* Tests that for a multi-value dictionary, <code>values = { 'key': 'value' }</code> always refers
* to a single map entry. Fancy syntax like <code>values = { 'key': 'value=1,key2=value2' }</code>
* doesn't get around that.
*
* <p>This just verifies existing behavior, not explicitly desired behavior. We could enhance
* options parsing to support multi-value settings if anyone ever wanted that.
*/
@Test
public void multiValueDictSettingAlwaysSingleEntry() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {
"define": "foo=bar,baz=bat",
},
)
""");
useConfiguration("");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo=bar");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo=bar", "--define", "baz=bat");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "foo=bar,baz=bat");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--define", "makethis=a_superset", "--define", "foo=bar,baz=bat");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void definesCrossAttributes() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
define_values = {
"b": "d",
},
values = {
"define": "a=c",
},
)
""");
useConfiguration("");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "a=c");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "b=d");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "a=c", "--define", "b=d");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
/**
* Tests matching on multi-value attributes against single expected values: the actual list must
* contain the expected value.
*/
@Test
public void multiValueListSingleExpectedValue() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {
"copt": "-Dfoo",
},
)
""");
useConfiguration("");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--copt", "-Dfoo");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--copt", "-Dbar");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--copt", "-Dfoo", "--copt", "-Dbar");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--copt", "-Dbar", "--copt", "-Dfoo");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
/**
* Tests matching on multi-value flags against multiple expected values: the actual list must
* contain all expected values (and possibly more).
*
* <p>This only works for flags that can parse multiple values in the same entry. Not all flags do
* this: this varies according to each flag's definition. For example "--copt=a,b" produces a
* single entry ["a,b"], while "--extra_platforms=a,b" produces ["a", "b"].
*/
@Test
@TestParameters({
"{flags: [''], matchExpected: false}", // No flag set
"{flags: ['--allow_multiple_option', 'one'], matchExpected: false}",
"{flags: ['--allow_multiple_option', 'two'], matchExpected: false}",
"{flags: ['--allow_multiple_option', 'one', '--allow_multiple_option', 'two'], "
+ "matchExpected: true}",
"{flags: ['--allow_multiple_option', 'two', '--allow_multiple_option', 'one'], "
+ "matchExpected: true}",
"{flags: ['--allow_multiple_option', 'one,two'], matchExpected: true}",
"{flags: ['--allow_multiple_option', 'two,one'], matchExpected: true}",
"{flags: ['--allow_multiple_option', 'ten', '--allow_multiple_option', 'two', "
+ "'--allow_multiple_option', 'three', '--allow_multiple_option', 'one'], "
+ "matchExpected: true}"
})
public void multiValueListMultipleExpectedValues(List<String> flags, boolean matchExpected)
throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {
"allow_multiple_option": "one,two", # This produces ["one", "two"]
},
)
""");
useConfiguration(flags.toArray(new String[flags.size()]));
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isEqualTo(matchExpected);
}
/**
* Tests multi-value flags that don't support multiple values <b></b>in the same instance<b>. See
* comments on {@link #multiValueListMultipleExpectedValues()} for details.
*/
@Test
public void multiValueListSingleValueThatLooksLikeMultiple() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {
"copt": "one,two", # This produces ["one,two"]
},
)
""");
useConfiguration("");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--copt", "one");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--copt", "two");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--copt", "one", "--copt", "two");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--copt", "one,two", "--copt", "one");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--copt", "two,one", "--copt", "one");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void selectForDefaultGrteTop() throws Exception {
scratchConfiguredTarget("a", "a",
"config_setting(name='cs', values={'grte_top': 'default'})",
"sh_library(name='a', srcs=['a.sh'], deps=select({':cs': []}))");
}
@Test
public void requiredConfigFragmentMatcher() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {
"copt": "-Dfoo",
"javacopt": "-Dbar",
},
)
""");
Rule target = (Rule) getTarget("//test:match");
assertThat(target.getRuleClassObject().getOptionReferenceFunction().apply(target))
.containsExactly("copt", "javacopt");
}
@Test
public void matchesIfFlagValuesAndValuesBothMatch() throws Exception {
useConfiguration("--copt=-Dright", "--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dright",
},
)
config_feature_flag(
name = "flag",
allowed_values = ["right"],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void matchesIfFlagValuesMatchAndValuesAreEmpty() throws Exception {
useConfiguration("--copt=-Dright", "--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {},
)
config_feature_flag(
name = "flag",
allowed_values = ["right"],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void matchesIfValuesMatchAndFlagValuesAreEmpty() throws Exception {
useConfiguration("--copt=-Dright");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {},
values = {
"copt": "-Dright",
},
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void doesNotMatchIfNeitherFlagValuesNorValuesMatches() throws Exception {
useConfiguration("--copt=-Dright", "--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {
":flag": "wrong",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dwrong",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void doesNotMatchIfFlagValuesDoNotMatchAndValuesAreEmpty() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {
":flag": "wrong",
},
transitive_configs = [":flag"],
values = {},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void doesNotMatchIfFlagValuesDoNotMatchButValuesDo() throws Exception {
useConfiguration("--copt=-Dright", "--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {
":flag": "wrong",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dright",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void doesNotMatchIfValuesDoNotMatchAndFlagValuesAreEmpty() throws Exception {
useConfiguration("--copt=-Dright");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {},
values = {
"copt": "-Dwrong",
},
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void doesNotMatchIfValuesDoNotMatchButFlagValuesDo() throws Exception {
useConfiguration("--copt=-Dright", "--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dwrong",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void doesNotMatchIfEvenOneFlagValueDoesNotMatch() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
flag_values = {
":flag": "right",
":flag2": "bad",
},
transitive_configs = [
":flag",
":flag2",
],
values = {},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void matchesIfNonDefaultIsSpecifiedAndFlagValueIsThatValue() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
feature_flag_setter(
name = "setter",
exports_setting = ":match",
flag_values = {":flag": "actual"},
transitive_configs = [":flag"],
)
config_setting(
name = "match",
flag_values = {
":flag": "actual",
},
transitive_configs = [":flag"],
values = {},
)
config_feature_flag(
name = "flag",
allowed_values = [
"default",
"actual",
],
default_value = "default",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:setter")).isTrue();
}
@Test
public void doesNotMatchIfDefaultIsSpecifiedAndFlagValueIsNotDefault() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
feature_flag_setter(
name = "setter",
exports_setting = ":match",
flag_values = {":flag": "actual"},
transitive_configs = [":flag"],
)
config_setting(
name = "match",
flag_values = {
":flag": "default",
},
transitive_configs = [":flag"],
values = {},
)
config_feature_flag(
name = "flag",
allowed_values = [
"default",
"actual",
],
default_value = "default",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:setter")).isFalse();
}
@Test
public void doesNotRefineSettingWithSameValuesAndSameFlagValuesAndSameConstraintValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_setting(
name = "other",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isFalse();
}
@Test
public void doesNotRefineSettingWithDifferentValuesAndSameFlagValuesAndSameConstraintValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"javacopt": "-Dgood",
},
)
config_setting(
name = "other",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isFalse();
}
@Test
public void doesNotRefineSettingWithSameValuesAndSameConstraintValuesAndDifferentFlagValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_setting(
name = "other",
constraint_values = [
":value_a",
],
flag_values = {
":flag2": "good",
},
transitive_configs = [":flag2"],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isFalse();
}
@Test
public void
doesNotRefineSettingWithDifferentValuesAndDifferentFlagValuesAndDifferentConstraintValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
constraint_setting(name = "setting_b")
constraint_value(
name = "value_b",
constraint_setting = "setting_b",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dright",
},
)
config_setting(
name = "other",
constraint_values = [
":value_b",
],
flag_values = {
":flag2": "good",
},
transitive_configs = [":flag2"],
values = {
"javacopt": "-Dgood",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isFalse();
}
@Test
public void doesNotRefineSettingWithDifferentValuesAndSubsetFlagValuesAndSubsetConstraintValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
constraint_setting(name = "setting_b")
constraint_value(
name = "value_b",
constraint_setting = "setting_b",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
":value_b",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
},
)
config_setting(
name = "other",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {
"javacopt": "-Dgood",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isFalse();
}
@Test
public void doesNotRefineSettingWithSubsetValuesAndSubsetFlagValuesAndDifferentConstraintValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
constraint_setting(name = "setting_b")
constraint_value(
name = "value_b",
constraint_setting = "setting_b",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_setting(
name = "other",
constraint_values = [
":value_b",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isFalse();
}
@Test
public void doesNotRefineSettingWithSubsetValuesAndSubsetConstraintValuesAndDifferentFlagValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
constraint_setting(name = "setting_b")
constraint_value(
name = "value_b",
constraint_setting = "setting_b",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
":value_b",
],
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_setting(
name = "other",
constraint_values = [
":value_a",
],
flag_values = {
":flag2": "good",
},
transitive_configs = [":flag2"],
values = {
"copt": "-Dright",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isFalse();
}
@Test
public void doesNotRefineSettingIfThereIsNoOverlap() throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
config_setting(
name = "configA",
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
)
config_setting(
name = "configB",
constraint_values = [
":value_a",
],
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
""");
assertThat(
getConfigMatchingProvider("//test:configA")
.refines(getConfigMatchingProvider("//test:configB")))
.isFalse();
assertThat(
getConfigMatchingProvider("//test:configB")
.refines(getConfigMatchingProvider("//test:configA")))
.isFalse();
}
@Test
public void refinesSettingWithSubsetValuesAndSubsetConstraintValuesAndSameFlagValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
constraint_setting(name = "setting_b")
constraint_value(
name = "value_b",
constraint_setting = "setting_b",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
":value_b",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_setting(
name = "other",
constraint_values = [
":value_b",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isTrue();
}
@Test
public void refinesSettingWithSameValuesAndSubsetFlagValuesAndSubsetConstraintValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
constraint_setting(name = "setting_b")
constraint_value(
name = "value_b",
constraint_setting = "setting_b",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
":value_b",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_setting(
name = "other",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isTrue();
}
@Test
public void refinesSettingWithSubsetValuesAndSubsetFlagValuesAndConstraintValues()
throws Exception {
useConfiguration(
"--copt=-Dright",
"--javacopt=-Dgood",
"--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
constraint_setting(name = "setting_b")
constraint_value(
name = "value_b",
constraint_setting = "setting_b",
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
":value_b",
],
flag_values = {
":flag": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
values = {
"copt": "-Dright",
"javacopt": "-Dgood",
},
)
config_setting(
name = "other",
constraint_values = [
":value_a",
],
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
values = {
"copt": "-Dright",
},
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isTrue();
}
@Test
public void refinesSettingWithSubsetConstraintValues() throws Exception {
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "setting_a")
constraint_value(
name = "value_a",
constraint_setting = "setting_a",
)
constraint_setting(name = "setting_b")
constraint_value(
name = "value_b",
constraint_setting = "setting_b",
)
constraint_setting(name = "setting_c")
constraint_value(
name = "value_c",
constraint_setting = "setting_c",
)
platform(
name = "refined_platform",
constraint_values = [
":value_a",
":value_b",
":value_c",
],
)
platform(
name = "other_platform",
constraint_values = [
":value_a",
":value_b",
],
)
config_setting(
name = "refined",
constraint_values = [
":value_a",
":value_b",
":value_c",
],
)
config_setting(
name = "other",
constraint_values = [
":value_a",
":value_b",
],
)
""");
useConfiguration("--platforms=//test:refined_platform");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isTrue();
}
@Test
public void matchesAliasedFlagsInFlagValues() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "alias_matcher",
flag_values = {
":alias": "right",
},
transitive_configs = [":flag"],
)
alias(
name = "alias",
actual = "flag",
transitive_configs = [":flag"],
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:alias_matcher")).isTrue();
}
@Test
public void aliasedFlagsAreCountedInRefining() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/BUILD",
"""
config_setting(
name = "refined",
flag_values = {
":alias": "right",
":flag2": "good",
},
transitive_configs = [
":flag",
":flag2",
],
)
config_setting(
name = "other",
flag_values = {
":flag": "right",
},
transitive_configs = [":flag"],
)
alias(
name = "alias",
actual = "flag",
transitive_configs = [":flag"],
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
config_feature_flag(
name = "flag2",
allowed_values = [
"good",
"bad",
],
default_value = "good",
)
""");
assertThat(
getConfigMatchingProvider("//test:refined")
.refines(getConfigMatchingProvider("//test:other")))
.isTrue();
}
@Test
public void referencingSameFlagViaMultipleAliasesFails() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
checkError(
"test",
"multialias",
"in flag_values attribute of config_setting rule //test:multialias: "
+ "flag '//test:direct' referenced multiple times as ['//test:alias', '//test:direct']",
"config_setting(",
" name = 'multialias',",
" flag_values = {",
" ':alias': 'right',",
" ':direct': 'right',",
" },",
" transitive_configs = [':direct'],",
")",
"alias(",
" name = 'alias',",
" actual = 'direct',",
" transitive_configs = [':direct'],",
")",
"config_feature_flag(",
" name = 'direct',",
" allowed_values = ['right', 'wrong'],",
" default_value = 'right',",
")");
}
@Test
public void requiresValidValueForFlagValues() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
checkError(
"test",
"invalid_flag",
"in flag_values attribute of config_setting rule //test:invalid_flag: "
+ "error while parsing user-defined configuration values: "
+ "'invalid' is not a valid value for '//test:flag'",
"config_setting(",
" name = 'invalid_flag',",
" flag_values = {",
" ':flag': 'invalid',",
" },",
" transitive_configs = [':flag'])",
"config_feature_flag(",
" name = 'flag',",
" allowed_values = ['right', 'valid'],",
" default_value = 'valid',",
")");
}
@Test
public void usesAliasLabelWhenReportingErrorInFlagValues() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
checkError(
"test",
"invalid_flag",
"in flag_values attribute of config_setting rule //test:invalid_flag: "
+ "error while parsing user-defined configuration values: "
+ "'invalid' is not a valid value for '//test:alias'",
"config_setting(",
" name = 'invalid_flag',",
" flag_values = {",
" ':alias': 'invalid',",
" },",
" transitive_configs = [':flag'])",
"alias(",
" name = 'alias',",
" actual = ':flag',",
" transitive_configs = [':flag'],",
")",
"config_feature_flag(",
" name = 'flag',",
" allowed_values = ['right', 'valid'],",
" default_value = 'valid',",
")");
}
@Test
public void buildsettings_matchesFromDefault() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_flag = rule(implementation = _impl, build_setting = config.string(flag = True))
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "parmesan",
},
)
string_flag(
name = "cheese",
build_setting_default = "parmesan",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void buildsettings_matchesFromCommandLine() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_flag = rule(implementation = _impl, build_setting = config.string(flag = True))
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "gouda",
},
)
string_flag(
name = "cheese",
build_setting_default = "parmesan",
)
""");
useConfiguration("--//test:cheese=gouda");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
/**
* Regression test to ensure that non-String typed build setting values are being properly
* converted from Strings to their real type.
*/
@Test
public void buildsettings_convertedType() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
bool_flag = rule(implementation = _impl, build_setting = config.bool(flag = True))
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "bool_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "True",
},
)
bool_flag(
name = "cheese",
build_setting_default = True,
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void buildsettings_doesntMatch() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_flag = rule(implementation = _impl, build_setting = config.string(flag = True))
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "parmesan",
},
)
string_flag(
name = "cheese",
build_setting_default = "parmesan",
)
""");
useConfiguration("--//test:cheese=gouda");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void buildsettings_badType() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
int_flag = rule(implementation = _impl, build_setting = config.int(flag = True))
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "int_flag")
config_setting(
name = "match",
flag_values = {
":wishes": "gouda",
},
)
int_flag(
name = "wishes",
build_setting_default = 3,
)
""");
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:match");
assertContainsEvent("'gouda' cannot be converted to //test:wishes type int");
}
@Test
public void buildsettings_allowMultipleWorks() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True, allow_multiple = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "pepperjack",
},
)
string_flag(
name = "cheese",
build_setting_default = "gouda",
)
""");
useConfiguration("--//test:cheese=pepperjack", "--//test:cheese=brie");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void buildsettings_repeatableWorks() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_list_flag = rule(
implementation = _impl,
build_setting = config.string_list(flag = True, repeatable = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_list_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "pepperjack",
},
)
string_list_flag(
name = "cheese",
build_setting_default = ["gouda"],
)
""");
useConfiguration("--//test:cheese=pepperjack", "--//test:cheese=pepperjack=brie");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void buildsettings_repeatableWithoutFlagErrors() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_list_setting = rule(
implementation = _impl,
build_setting = config.string_list(repeatable = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_list_setting")
string_list_setting(
name = "cheese",
build_setting_default = ["gouda"],
)
""");
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:cheese");
assertContainsEvent("'repeatable' can only be set for a setting with 'flag = True'");
}
@Test
public void notBuildSettingOrFeatureFlag() throws Exception {
scratch.file(
"test/rules.bzl",
"""
def _impl(ctx):
return DefaultInfo()
default_info_rule = rule(implementation = _impl)
""");
scratch.file(
"test/BUILD",
"""
load("//test:rules.bzl", "default_info_rule")
config_setting(
name = "match",
flag_values = {
":cheese": "gouda",
},
)
default_info_rule(name = "cheese")
""");
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:match");
assertContainsEvent(
"flag_values keys must be build settings or feature flags and //test:cheese is not");
}
@Test
public void buildsettingsMatch_featureFlagsMatch() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_flag = rule(implementation = _impl, build_setting = config.string(flag = True))
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "parmesan",
":flag": "right",
},
transitive_configs = [":flag"],
)
string_flag(
name = "cheese",
build_setting_default = "parmesan",
)
config_feature_flag(
name = "flag",
allowed_values = ["right"],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void buildsettingsMatch_featureFlagsDontMatch() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_flag = rule(implementation = _impl, build_setting = config.string(flag = True))
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "parmesan",
":flag": "wrong",
},
transitive_configs = [":flag"],
)
string_flag(
name = "cheese",
build_setting_default = "parmesan",
)
config_feature_flag(
name = "flag",
allowed_values = [
"right",
"wrong",
],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void buildsettingsDontMatch_featureFlagsMatch() throws Exception {
useConfiguration("--enforce_transitive_configs_for_config_feature_flag");
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_flag = rule(implementation = _impl, build_setting = config.string(flag = True))
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_flag")
config_setting(
name = "match",
flag_values = {
":cheese": "gouda",
":flag": "right",
},
transitive_configs = [":flag"],
)
string_flag(
name = "cheese",
build_setting_default = "parmesan",
)
config_feature_flag(
name = "flag",
allowed_values = ["right"],
default_value = "right",
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void constraintValue() throws Exception {
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "notable_building")
constraint_value(
name = "empire_state",
constraint_setting = "notable_building",
)
constraint_value(
name = "space_needle",
constraint_setting = "notable_building",
)
platform(
name = "new_york_platform",
constraint_values = [":empire_state"],
)
platform(
name = "seattle_platform",
constraint_values = [":space_needle"],
)
config_setting(
name = "match",
constraint_values = [":empire_state"],
)
""");
useConfiguration("--experimental_platforms=//test:new_york_platform");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--experimental_platforms=//test:seattle_platform");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
@Test
public void multipleConstraintValues() throws Exception {
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "notable_building")
constraint_value(
name = "empire_state",
constraint_setting = "notable_building",
)
constraint_setting(name = "museum")
constraint_value(
name = "cloisters",
constraint_setting = "museum",
)
constraint_setting(name = "theme_park")
constraint_value(
name = "coney_island",
constraint_setting = "theme_park",
)
platform(
name = "manhattan_platform",
constraint_values = [
":empire_state",
":cloisters",
],
)
platform(
name = "museum_platform",
constraint_values = [":cloisters"],
)
platform(
name = "new_york_platform",
constraint_values = [
":empire_state",
":cloisters",
":coney_island",
],
)
config_setting(
name = "match",
constraint_values = [
":empire_state",
":cloisters",
],
)
""");
useConfiguration("--experimental_platforms=//test:manhattan_platform");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--experimental_platforms=//test:museum_platform");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--experimental_platforms=//test:new_york_platform");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
}
@Test
public void definesAndConstraints() throws Exception {
scratch.file(
"test/BUILD",
"""
constraint_setting(name = "notable_building")
constraint_value(
name = "empire_state",
constraint_setting = "notable_building",
)
constraint_value(
name = "space_needle",
constraint_setting = "notable_building",
)
platform(
name = "new_york_platform",
constraint_values = [":empire_state"],
)
platform(
name = "seattle_platform",
constraint_values = [":space_needle"],
)
config_setting(
name = "match",
constraint_values = [":empire_state"],
define_values = {
"b": "d",
},
values = {
"define": "a=c",
},
)
""");
useConfiguration(
"--experimental_platforms=//test:new_york_platform", "--define", "a=c", "--define", "b=d");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isTrue();
useConfiguration("--experimental_platforms=//test:new_york_platform");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "a=c");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
useConfiguration("--define", "a=c", "--experimental_platforms=//test:new_york_platform");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:match")).isFalse();
}
/**
* Tests that a config_setting doesn't allow a constraint_values list with more than one
* constraint value per constraint setting.
*/
@Test
public void multipleValuesPerSetting() throws Exception {
checkError(
"foo",
"bad",
"in config_setting rule //foo:bad: "
+ "Duplicate constraint values detected: "
+ "constraint_setting //foo:notable_building has "
+ "[//foo:empire_state, //foo:space_needle], "
+ "constraint_setting //foo:museum has "
+ "[//foo:moma, //foo:sam]",
"constraint_setting(name = 'notable_building')",
"constraint_value(name = 'empire_state', constraint_setting = 'notable_building')",
"constraint_value(name = 'space_needle', constraint_setting = 'notable_building')",
"constraint_value(name = 'peace_arch', constraint_setting = 'notable_building')",
"constraint_setting(name = 'museum')",
"constraint_value(name = 'moma', constraint_setting = 'museum')",
"constraint_value(name = 'sam', constraint_setting = 'museum')",
"config_setting(",
" name = 'bad',",
" constraint_values = [",
" ':empire_state',",
" ':space_needle',",
" ':moma',",
" ':sam',",
" ],",
");");
}
@Test
public void notAConstraintValue() throws Exception {
checkError(
"test",
"match",
"//test:what_am_i is not a constraint_value",
"genrule(",
" name = 'what_am_i',",
" srcs = [],",
" outs = ['the_answer'],",
" cmd = 'echo an eternal enigma > $@')",
"config_setting(",
" name = 'match',",
" constraint_values = [':what_am_i'],",
")");
}
private Set<LicenseType> getLicenses(String label) throws Exception {
Rule rule = (Rule) getTarget(label);
// There are two interfaces for retrieving a rule's license: from the Rule object and by
// directly reading the "licenses" attribute. For config_setting both of these should always
// be NONE. This method checks consistency between them.
Set<LicenseType> fromRule = rule.getLicense().getLicenseTypes();
Set<LicenseType> fromAttribute =
RawAttributeMapper.of(rule).get("licenses", BuildType.LICENSE).getLicenseTypes();
assertThat(fromRule).containsExactlyElementsIn(fromAttribute);
return fromRule;
}
/** Tests that default license behavior is unaffected. */
@Test
public void licensesDefault() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {
"copt": "-Dfoo",
},
)
""");
useConfiguration("--copt", "-Dfoo");
assertThat(getLicenses("//test:match")).containsExactly(LicenseType.NONE);
}
/** Tests that third-party doesn't require a license from config_setting. */
@Test
public void thirdPartyLicenseRequirement() throws Exception {
scratch.file(
"third_party/test/BUILD",
"""
config_setting(
name = "match",
values = {
"copt": "-Dfoo",
},
)
""");
useConfiguration("--copt", "-Dfoo");
assertThat(getLicenses("//third_party/test:match")).containsExactly(LicenseType.NONE);
}
/** Tests that package-wide licenses are ignored by config_setting. */
@Test
public void packageLicensesIgnored() throws Exception {
scratch.file(
"test/BUILD",
"""
licenses(["restricted"])
config_setting(
name = "match",
values = {
"copt": "-Dfoo",
},
)
""");
useConfiguration("--copt", "-Dfoo");
assertThat(getLicenses("//test:match")).containsExactly(LicenseType.NONE);
}
/** Tests that rule-specific licenses are ignored by config_setting. */
@Test
public void ruleLicensesUsed() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
licenses = ["restricted"],
values = {
"copt": "-Dfoo",
},
)
""");
useConfiguration("--copt", "-Dfoo");
assertThat(getLicenses("//test:match")).containsExactly(LicenseType.NONE);
}
@Test
public void aliasedStarlarkFlag() throws Exception {
scratch.file(
"test/flagdef.bzl",
"""
def _impl(ctx):
return []
my_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:flagdef.bzl", "my_flag")
my_flag(
name = "flag",
build_setting_default = "default",
)
alias(
name = "alias",
actual = ":flag",
)
config_setting(
name = "alias_setting",
flag_values = {":alias": "specified"},
)
""");
useConfiguration("--//test:flag=specified");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:alias_setting")).isTrue();
}
@Test
public void simpleStarlarkFlag() throws Exception {
scratch.file(
"test/flagdef.bzl",
"""
def _impl(ctx):
return []
my_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:flagdef.bzl", "my_flag")
my_flag(
name = "flag",
build_setting_default = "actual_flag_value",
)
config_setting(
name = "matches",
flag_values = {
":flag": "actual_flag_value",
},
)
config_setting(
name = "doesntmatch",
flag_values = {
":flag": "other_flag_value",
},
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:matches")).isTrue();
assertThat(getConfigMatchingProviderResultAsBoolean("//test:doesntmatch")).isFalse();
}
@Test
public void starlarkListFlagSingleValue() throws Exception {
// When a list-typed Starlark flag has value ["foo"], the config_setting's expected value "foo"
// must match exactly.
scratch.file(
"test/flagdef.bzl",
"""
def _impl(ctx):
return []
my_flag = rule(
implementation = _impl,
build_setting = config.string_list(flag = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:flagdef.bzl", "my_flag")
my_flag(
name = "one_value_flag",
build_setting_default = ["one"],
)
config_setting(
name = "matches",
flag_values = {
":one_value_flag": "one",
},
)
config_setting(
name = "doesntmatch",
flag_values = {
":one_value_flag": "other",
},
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:matches")).isTrue();
assertThat(getConfigMatchingProviderResultAsBoolean("//test:doesntmatch")).isFalse();
}
@Test
public void starlarkListFlagMultiValue() throws Exception {
// When a list-typed Starlark flag has value ["foo", "bar"], the config_setting's expected
// value "foo" must match *any* entry in the list.
scratch.file(
"test/flagdef.bzl",
"""
def _impl(ctx):
return []
my_flag = rule(
implementation = _impl,
build_setting = config.string_list(flag = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:flagdef.bzl", "my_flag")
my_flag(
name = "two_value_flag",
build_setting_default = [
"one",
"two",
],
)
config_setting(
name = "matches_one",
flag_values = {
":two_value_flag": "one",
},
)
config_setting(
name = "matches_two",
flag_values = {
":two_value_flag": "two",
},
)
config_setting(
name = "doesntmatch",
flag_values = {
":two_value_flag": "other",
},
)
""");
assertThat(getConfigMatchingProviderResultAsBoolean("//test:matches_one")).isTrue();
assertThat(getConfigMatchingProviderResultAsBoolean("//test:matches_two")).isTrue();
assertThat(getConfigMatchingProviderResultAsBoolean("//test:doesntmatch")).isFalse();
}
@Test
public void canOnlyMatchSingleValueInMultiValueFlags() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_list_flag = rule(
implementation = _impl,
build_setting = config.string_list(flag = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_list_flag")
string_list_flag(
name = "gouda",
build_setting_default = ["smoked"],
)
config_setting(
name = "match",
flag_values = {
":gouda": "smoked,fresh",
},
)
filegroup(
name = "fg",
srcs = select({
":match": [],
}),
)
""");
reporter.removeHandler(failFastHandler); // expect errors
assertThat(getConfiguredTarget("//test:fg")).isNull();
assertContainsEvent(
"\"smoked,fresh\" not a valid value for flag //test:gouda. "
+ "Only single, exact values are allowed");
}
@Test
public void singleValueThatLooksLikeMultiValueIsOkay() throws Exception {
scratch.file(
"test/build_settings.bzl",
"""
def _impl(ctx):
return []
string_flag = rule(
implementation = _impl,
build_setting = config.string(flag = True),
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:build_settings.bzl", "string_flag")
string_flag(
name = "gouda",
build_setting_default = "smoked,fresh",
)
config_setting(
name = "match",
flag_values = {
":gouda": "smoked,fresh",
},
)
filegroup(
name = "fg",
srcs = select({
":match": [],
}),
)
""");
assertThat(getConfiguredTarget("//test:fg")).isNotNull();
assertNoEvents();
}
@Test
public void labelInValuesError() throws Exception {
scratch.file(
"test/BUILD",
"""
config_setting(
name = "match",
values = {"//foo:bar": "value"},
)
""");
reporter.removeHandler(failFastHandler); // expect errors
assertThat(getConfiguredTarget("//test:match")).isNull();
assertContainsEvent(
"in values attribute of config_setting rule //test:match: '//foo:bar' is"
+ " not a valid setting name, but appears to be a label. Did you mean to place it in"
+ " flag_values instead?");
}
}