| // 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.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| 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.ConfigurationFragmentFactory; |
| import com.google.devtools.build.lib.analysis.config.FragmentOptions; |
| 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.skyframe.serialization.autocodec.AutoCodec; |
| import com.google.devtools.build.lib.testutil.TestConstants; |
| import com.google.devtools.build.lib.testutil.TestRuleClassProvider; |
| import com.google.devtools.common.options.Option; |
| import com.google.devtools.common.options.OptionDefinition; |
| import com.google.devtools.common.options.OptionDocumentationCategory; |
| import com.google.devtools.common.options.OptionEffectTag; |
| import com.google.devtools.common.options.OptionMetadataTag; |
| import com.google.devtools.common.options.OptionsParser; |
| import java.util.Map; |
| import java.util.Set; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Tests for {@link ConfigSetting}. |
| */ |
| @RunWith(JUnit4.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 = "nonselectable_option", |
| documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, |
| effectTags = {OptionEffectTag.NO_OP}, |
| defaultValue = "true") |
| public boolean nonselectableOption; |
| |
| private static final OptionDefinition NONSELECTABLE_OPTION_DEFINITION = |
| OptionsParser.getOptionDefinitionByName(DummyTestOptions.class, "nonselectable_option"); |
| |
| @Option( |
| name = "nonselectable_whitelisted_option", |
| documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, |
| effectTags = {OptionEffectTag.NO_OP}, |
| defaultValue = "true") |
| public boolean nonselectableWhitelistedOption; |
| |
| private static final OptionDefinition NONSELECTABLE_WHITELISTED_OPTION_DEFINITION = |
| OptionsParser.getOptionDefinitionByName( |
| DummyTestOptions.class, "nonselectable_whitelisted_option"); |
| |
| @Option( |
| name = "nonselectable_custom_message_option", |
| documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, |
| effectTags = {OptionEffectTag.NO_OP}, |
| defaultValue = "true") |
| public boolean nonselectableCustomMessageOption; |
| |
| private static final OptionDefinition NONSELECTABLE_CUSTOM_MESSAGE_OPTION_DEFINITION = |
| OptionsParser.getOptionDefinitionByName( |
| DummyTestOptions.class, "nonselectable_custom_message_option"); |
| |
| @Override |
| public Map<OptionDefinition, SelectRestriction> getSelectRestrictions() { |
| return ImmutableMap.of( |
| NONSELECTABLE_OPTION_DEFINITION, |
| new SelectRestriction(/*visibleWithinToolsPackage=*/ false, /*errorMessage=*/ null), |
| NONSELECTABLE_WHITELISTED_OPTION_DEFINITION, |
| new SelectRestriction(/*visibleWithinToolsPackage=*/ true, /*errorMessage=*/ null), |
| NONSELECTABLE_CUSTOM_MESSAGE_OPTION_DEFINITION, |
| new SelectRestriction( |
| /*visibleWithinToolsPackage=*/ false, |
| /*errorMessage=*/ "For very important reasons.")); |
| } |
| } |
| |
| @AutoCodec |
| static class DummyTestOptionsFragment extends BuildConfiguration.Fragment {} |
| |
| private static class DummyTestOptionsLoader implements ConfigurationFragmentFactory { |
| @Override |
| public BuildConfiguration.Fragment create(BuildOptions buildOptions) { |
| return new DummyTestOptionsFragment(); |
| } |
| |
| @Override |
| public Class<? extends BuildConfiguration.Fragment> creates() { |
| return DummyTestOptionsFragment.class; |
| } |
| |
| @Override |
| public ImmutableSet<Class<? extends FragmentOptions>> requiredOptions() { |
| return ImmutableSet.<Class<? extends FragmentOptions>>of(DummyTestOptions.class); |
| } |
| } |
| |
| @Override |
| protected ConfiguredRuleClassProvider getRuleClassProvider() { |
| ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); |
| TestRuleClassProvider.addStandardRules(builder); |
| builder.addRuleDefinition(new FeatureFlagSetterRule()); |
| builder.addConfigurationOptions(DummyTestOptions.class); |
| builder.addConfigurationFragment(new DummyTestOptionsLoader()); |
| 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); |
| } |
| |
| /** 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.parseAbsoluteUnchecked("@tools//tools/subpkg:foo"), toolsRepo)) |
| .isTrue(); |
| // The tools package itself. |
| assertThat( |
| ConfigSetting.isUnderToolsPackage( |
| Label.parseAbsoluteUnchecked("@tools//tools:foo"), toolsRepo)) |
| .isTrue(); |
| // The tools repo, but wrong package. |
| assertThat( |
| ConfigSetting.isUnderToolsPackage( |
| Label.parseAbsoluteUnchecked("@tools//nottools:foo"), toolsRepo)) |
| .isFalse(); |
| // Not even the tools repo. |
| assertThat( |
| ConfigSetting.isUnderToolsPackage( |
| Label.parseAbsoluteUnchecked("@nottools//nottools:foo"), toolsRepo)) |
| .isFalse(); |
| // A tools package but in the wrong repo. |
| assertThat( |
| ConfigSetting.isUnderToolsPackage( |
| Label.parseAbsoluteUnchecked("@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(getConfigMatchingProvider("//pkg:foo").matches()).isFalse(); |
| |
| // Second flag mismatches: |
| useConfiguration("-c", "dbg", "--nostamp"); |
| assertThat(getConfigMatchingProvider("//pkg:foo").matches()).isFalse(); |
| |
| // Both flags mismatch: |
| useConfiguration("-c", "opt", "--nostamp"); |
| assertThat(getConfigMatchingProvider("//pkg:foo").matches()).isFalse(); |
| |
| // Both flags match: |
| useConfiguration("-c", "dbg", "--stamp"); |
| assertThat(getConfigMatchingProvider("//pkg:foo").matches()).isTrue(); |
| } |
| |
| /** |
| * Tests that {@link ConfigMatchingProvider#label} is correct. |
| */ |
| @Test |
| public void labelGetter() throws Exception { |
| writeSimpleExample(); |
| assertThat(getConfigMatchingProvider("//pkg:foo").label()) |
| .isEqualTo(Label.parseAbsolute("//pkg:foo", ImmutableMap.of())); |
| } |
| |
| /** |
| * 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 analysis fails on non-selectable options. */ |
| @Test |
| public void nonselectableOption() throws Exception { |
| checkError( |
| "foo", |
| "badoption", |
| "option 'nonselectable_option' cannot be used in a config_setting", |
| "config_setting(", |
| " name = 'badoption',", |
| " values = {", |
| " 'nonselectable_option': 'true',", |
| " },", |
| ")"); |
| } |
| |
| /** |
| * Tests that whitelisted non-selectable options can't be accessed outside of the tools package. |
| */ |
| @Test |
| public void nonselectableWhitelistedOption_OutOfToolsPackage() throws Exception { |
| checkError( |
| "foo", |
| "badoption", |
| String.format( |
| "option 'nonselectable_whitelisted_option' cannot be used in a config_setting (it is " |
| + "whitelisted to %s//tools/... only)", |
| RepositoryName.create(TestConstants.TOOLS_REPOSITORY).getDefaultCanonicalForm()), |
| "config_setting(", |
| " name = 'badoption',", |
| " values = {", |
| " 'nonselectable_whitelisted_option': 'true',", |
| " },", |
| ")"); |
| } |
| |
| /** Tests that whitelisted non-selectable options can be accessed within the tools package. */ |
| @Test |
| public void nonselectableWhitelistedOption_InToolsPackage() throws Exception { |
| scratch.file( |
| TestConstants.TOOLS_REPOSITORY_SCRATCH + "tools/pkg/BUILD", |
| "config_setting(", |
| " name = 'foo',", |
| " values = {", |
| " 'nonselectable_whitelisted_option': 'true',", |
| " })"); |
| String fooLabel = TestConstants.TOOLS_REPOSITORY + "//tools/pkg:foo"; |
| assertThat(getConfigMatchingProvider(fooLabel).matches()).isTrue(); |
| } |
| |
| /** Tests that custom error messages are displayed for non-selectable options. */ |
| @Test |
| public void nonselectableCustomMessageOption() throws Exception { |
| checkError( |
| "foo", |
| "badoption", |
| "option 'nonselectable_custom_message_option' cannot be used in a config_setting. " |
| + "For very important reasons.", |
| "config_setting(", |
| " name = 'badoption',", |
| " values = {", |
| " 'nonselectable_custom_message_option': 'true',", |
| " },", |
| ")"); |
| } |
| |
| /** 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:1: //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(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo=bar"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--define", "foo=baz"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo=bar", "--define", "bar=baz"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--define", "foo=bar", "--define", "bar=baz", "--define", "foo=nope"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo=nope", "--define", "bar=baz", "--define", "foo=bar"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo1=bar"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo2=baz"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo1=bar", "--define", "foo2=baz"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo=bar"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo=bar", "--define", "baz=bat"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "foo=bar,baz=bat"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--define", "makethis=a_superset", "--define", "foo=bar,baz=bat"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| } |
| |
| @Test |
| public void definesCrossAttributes() throws Exception { |
| scratch.file("test/BUILD", |
| "config_setting(", |
| " name = 'match',", |
| " values = {", |
| " 'define': 'a=c'", |
| " },", |
| " define_values = {", |
| " 'b': 'd',", |
| " })"); |
| |
| useConfiguration(""); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "a=c"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "b=d"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "a=c", "--define", "b=d"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--copt", "-Dfoo"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--copt", "-Dbar"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--copt", "-Dfoo", "--copt", "-Dbar"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--copt", "-Dbar", "--copt", "-Dfoo"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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 |
| public void multiValueListMultipleExpectedValues() throws Exception { |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'match',", |
| " values = {", |
| " 'extra_toolchains': 'one,two',", // This produces ["one", "two"] |
| " })"); |
| |
| useConfiguration(""); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--extra_toolchains", "one"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--extra_toolchains", "two"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--extra_toolchains", "one", "--extra_toolchains", "two"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--extra_toolchains", "two", "--extra_toolchains", "one"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--extra_toolchains", "one,two"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--extra_toolchains", "two,one"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration( |
| "--extra_toolchains", |
| "ten", |
| "--extra_toolchains", |
| "two", |
| "--extra_toolchains", |
| "three", |
| "--extra_toolchains", |
| "one"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| } |
| |
| /** |
| * 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(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--copt", "one"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--copt", "two"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--copt", "one", "--copt", "two"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--copt", "one,two", "--copt", "one"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--copt", "two,one", "--copt", "one"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| } |
| |
| @Test |
| public void selectForDefaultCrosstoolTop() throws Exception { |
| String crosstoolTop = TestConstants.TOOLS_REPOSITORY + "//tools/cpp:toolchain"; |
| scratchConfiguredTarget("a", "a", |
| "config_setting(name='cs', values={'crosstool_top': '" + crosstoolTop + "'})", |
| "sh_library(name='a', srcs=['a.sh'], deps=select({':cs': []}))"); |
| } |
| |
| @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',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_feature_flag(", |
| " name = 'flag',", |
| " allowed_values = ['right'],", |
| " default_value = 'right',", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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',", |
| " },", |
| " values = {},", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_feature_flag(", |
| " name = 'flag',", |
| " allowed_values = ['right'],", |
| " default_value = 'right',", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| } |
| |
| @Test |
| public void matchesIfValuesMatchAndFlagValuesAreEmpty() throws Exception { |
| useConfiguration("--copt=-Dright"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'match',", |
| " flag_values = {},", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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',", |
| " },", |
| " values = {", |
| " 'copt': '-Dwrong',", |
| " },", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_feature_flag(", |
| " name = 'flag',", |
| " allowed_values = ['right', 'wrong'],", |
| " default_value = 'right',", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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',", |
| " },", |
| " values = {},", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_feature_flag(", |
| " name = 'flag',", |
| " allowed_values = ['right', 'wrong'],", |
| " default_value = 'right',", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_feature_flag(", |
| " name = 'flag',", |
| " allowed_values = ['right', 'wrong'],", |
| " default_value = 'right',", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| } |
| |
| @Test |
| public void doesNotMatchIfValuesDoNotMatchAndFlagValuesAreEmpty() throws Exception { |
| useConfiguration("--copt=-Dright"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'match',", |
| " flag_values = {},", |
| " values = {", |
| " 'copt': '-Dwrong',", |
| " },", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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',", |
| " },", |
| " values = {", |
| " 'copt': '-Dwrong',", |
| " },", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_feature_flag(", |
| " name = 'flag',", |
| " allowed_values = ['right', 'wrong'],", |
| " default_value = 'right',", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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',", |
| " },", |
| " values = {},", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "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:match").matches()).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',", |
| " },", |
| " values = {},", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_feature_flag(", |
| " name = 'flag',", |
| " allowed_values = ['default', 'actual'],", |
| " default_value = 'default',", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:setter").matches()).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',", |
| " },", |
| " values = {},", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_feature_flag(", |
| " name = 'flag',", |
| " allowed_values = ['default', 'actual'],", |
| " default_value = 'default',", |
| ")"); |
| assertThat(getConfigMatchingProvider("//test:setter").matches()).isFalse(); |
| } |
| |
| @Test |
| public void doesNotRefineSettingWithSameValuesAndSameFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "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 doesNotRefineSettingWithDifferentValuesAndSameFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "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 doesNotRefineSettingWithSameValuesAndDifferentFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag2'],", |
| ")", |
| "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 doesNotRefineSettingWithDifferentValuesAndDifferentFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag2'],", |
| ")", |
| "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 doesNotRefineSettingWithDifferentValuesAndSubsetFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " },", |
| " values = {", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " 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"))) |
| .isFalse(); |
| } |
| |
| @Test |
| public void doesNotRefineSettingWithSubsetValuesAndDifferentFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| " transitive_configs = [':flag2'],", |
| ")", |
| "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 refinesSettingWithSubsetValuesAndSameFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "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 refinesSettingWithSameValuesAndSubsetFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " 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 refinesSettingWithSubsetValuesAndSubsetFlagValues() throws Exception { |
| useConfiguration( |
| "--copt=-Dright", |
| "--javacopt=-Dgood", |
| "--enforce_transitive_configs_for_config_feature_flag"); |
| scratch.file( |
| "test/BUILD", |
| "config_setting(", |
| " name = 'refined',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " ':flag2': 'good',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " 'javacopt': '-Dgood',", |
| " },", |
| " transitive_configs = [':flag', ':flag2'],", |
| ")", |
| "config_setting(", |
| " name = 'other',", |
| " flag_values = {", |
| " ':flag': 'right',", |
| " },", |
| " values = {", |
| " 'copt': '-Dright',", |
| " },", |
| " 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 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(getConfigMatchingProvider("//test:alias_matcher").matches()).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 { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| |
| 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(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| } |
| |
| @Test |
| public void buildsettings_matchesFromCommandLine() throws Exception { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| useConfiguration(ImmutableMap.of("//test:cheese", "gouda")); |
| |
| 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')"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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 { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| |
| 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(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| } |
| |
| @Test |
| public void buildsettings_doesntMatch() throws Exception { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| useConfiguration(ImmutableMap.of("//test:cheese", "gouda")); |
| |
| 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(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| } |
| |
| @Test |
| public void buildsettings_badType() throws Exception { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| |
| 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 notBuildSettingOrFeatureFlag() throws Exception { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| |
| 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 { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| 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(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| } |
| |
| @Test |
| public void buildsettingsMatch_featureFlagsDontMatch() throws Exception { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| 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(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| } |
| |
| @Test |
| public void buildsettingsDontMatch_featureFlagsMatch() throws Exception { |
| setSkylarkSemanticsOptions("--experimental_build_setting_api=true"); |
| 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(getConfigMatchingProvider("//test:match").matches()).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(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--experimental_platforms=//test:seattle_platform"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration(""); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--experimental_platforms=//test:museum_platform"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--experimental_platforms=//test:new_york_platform"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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'],", |
| " values = {", |
| " 'define': 'a=c',", |
| " },", |
| " define_values = {", |
| " 'b': 'd',", |
| " },", |
| ");"); |
| |
| useConfiguration( |
| "--experimental_platforms=//test:new_york_platform", "--define", "a=c", "--define", "b=d"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isTrue(); |
| useConfiguration("--experimental_platforms=//test:new_york_platform"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "a=c"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).isFalse(); |
| useConfiguration("--define", "a=c", "--experimental_platforms=//test:new_york_platform"); |
| assertThat(getConfigMatchingProvider("//test:match").matches()).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 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(getConfigMatchingProvider("//test:matches").matches()).isTrue(); |
| assertThat(getConfigMatchingProvider("//test:doesntmatch").matches()).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(getConfigMatchingProvider("//test:matches").matches()).isTrue(); |
| assertThat(getConfigMatchingProvider("//test:doesntmatch").matches()).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(getConfigMatchingProvider("//test:matches_one").matches()).isTrue(); |
| assertThat(getConfigMatchingProvider("//test:matches_two").matches()).isTrue(); |
| assertThat(getConfigMatchingProvider("//test:doesntmatch").matches()).isFalse(); |
| } |
| } |