aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar plf <plf@google.com>2017-07-26 15:43:59 +0200
committerGravatar Jakob Buchgraber <buchgr@google.com>2017-07-26 16:22:28 +0200
commitb344e2c5a97b0eeacb796db20bb894f21283d358 (patch)
treef9c10b7fcf10bba3ce6f5a3b2da9fd45397451f3
parent2f8b1e519ccc4ead62be57d245d559029265a9a7 (diff)
Package whitelisting: New implementation for feature whitelisting
This builds on top of mstaib's idea to use package groups to whitelist projects which can use certain features in the analysis phase. Instead of using configurations and a flag, this way of implementing whitelists requires using two helper methods, one that adds an implicit attribute to every rule that has functionality that must be whitelisted and a different method to check whether a given package is whitelisted to use that feature. RELNOTES:none PiperOrigin-RevId: 163200890
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfiguration.java72
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoader.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptions.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntry.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverter.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/whitelisting/BUILD26
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/whitelisting/Whitelist.java80
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/BazelPrerequisiteValidator.java42
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/whitelisting/BUILD34
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistCachingTest.java65
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistDummyRule.java61
-rw-r--r--src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistTest.java128
12 files changed, 490 insertions, 50 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfiguration.java
index 96d3431836..37adc1a0b1 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyConfiguration.java
@@ -33,52 +33,58 @@ import com.google.devtools.build.lib.util.Preconditions;
* should be limited to specified packages. Because this is part of the configuration, it can only
* be accessed during the analysis phase; decisions which are made during the loading phase can't
* use this information. Some examples of use cases for this fragment:
+ *
* <ul>
- * <li>A new rule class which could have a major impact on Blaze's memory usage is added. To
- * limit this impact during the experimental phase, a feature policy is added which makes it
- * an error for rules of that class to be created - or used by other rules - in any package
- * other than those defined by the policy. The policy is populated with projects who are
- * doing guided experimentation with the feature, and gradually expands as the feature rolls
- * out. Then the feature's policy can be removed, making it generally available.
- * <li>An attribute is being deprecated. To prevent rollback, a feature policy is added which
- * makes it an error for rules in packages other than those defined by the policy to specify
- * a value for that attribute. The policy is populated with existing users, and as those
- * users are migrated, they are removed from the policy until it is completely empty. Then
- * the attribute can be removed entirely.
+ * <li>A new rule class which could have a major impact on Blaze's memory usage is added. To limit
+ * this impact during the experimental phase, a feature policy is added which makes it an
+ * error for rules of that class to be created - or used by other rules - in any package other
+ * than those defined by the policy. The policy is populated with projects who are doing
+ * guided experimentation with the feature, and gradually expands as the feature rolls out.
+ * Then the feature's policy can be removed, making it generally available.
+ * <li>An attribute is being deprecated. To prevent rollback, a feature policy is added which
+ * makes it an error for rules in packages other than those defined by the policy to specify a
+ * value for that attribute. The policy is populated with existing users, and as those users
+ * are migrated, they are removed from the policy until it is completely empty. Then the
+ * attribute can be removed entirely.
* </ul>
*
* <p>To use this package:
+ *
* <ol>
- * <li>Define a feature ID in the {@link FeaturePolicyLoader}'s constructor (in the
- * RuleClassProvider). This is the string that will be used when checking for the feature in
- * rule code, as well as the string used in the flag value for {@link FeaturePolicyOptions}.
- * <li>In the RuleClass(es) which will change based on the feature's state, declare
- * {@link FeaturePolicyConfiguration} as a required configuration fragment.
- * <li>In the ConfiguredTargetFactory of those rules, get the FeaturePolicyConfiguration and
- * check {@link #isFeatureEnabledForRule(String,Label)} with the feature ID created in
- * step 1 and the label of the current rule. In the event that an error needs to be
- * displayed, use {@link #getPolicyForFeature(String)} to show the user where the policy is.
- * <li>Create a package_group containing the list of packages which should have access to this
- * feature. It can be empty (no packages can access the feature) or contain //... (all
- * packages can access the feature) to begin with.
- * <li>After the a release containing the feature ID has been pushed, update the global RC file
- * with a --feature_control_policy=(your_feature)=(your_package_group) flag. You can now
- * alter access to your feature by changing the package_group.
+ * <li>Define a feature ID in the {@link FeaturePolicyLoader}'s constructor (in the
+ * RuleClassProvider). This is the string that will be used when checking for the feature in
+ * rule code, as well as the string used in the flag value for {@link FeaturePolicyOptions}.
+ * <li>In the RuleClass(es) which will change based on the feature's state, declare {@link
+ * FeaturePolicyConfiguration} as a required configuration fragment.
+ * <li>In the ConfiguredTargetFactory of those rules, get the FeaturePolicyConfiguration and check
+ * {@link #isFeatureEnabledForRule(String,Label)} with the feature ID created in step 1 and
+ * the label of the current rule. In the event that an error needs to be displayed, use {@link
+ * #getPolicyForFeature(String)} to show the user where the policy is.
+ * <li>Create a package_group containing the list of packages which should have access to this
+ * feature. It can be empty (no packages can access the feature) or contain //... (all
+ * packages can access the feature) to begin with.
+ * <li>After the a release containing the feature ID has been pushed, update the global RC file
+ * with a --feature_control_policy=(your_feature)=(your_package_group) flag. You can now alter
+ * access to your feature by changing the package_group.
* </ol>
*
* <p>To stop using this package:
+ *
* <ol>
- * <li>Your policy should be at an end state - containing all packages (a rollout which has
- * become generally available) or no packages (a deprecated feature which has been totally
- * cleaned up).
- * <li>Make the behavior the policy controlled permanent - remove a deprecated feature, or
- * remove the check on a feature which is being rolled out.
- * <li>After this new version is released, remove the flag from the global rc file, and remove
- * the feature ID from the constructor for {@link FeaturePolicyLoader}.
+ * <li>Your policy should be at an end state - containing all packages (a rollout which has become
+ * generally available) or no packages (a deprecated feature which has been totally cleaned
+ * up).
+ * <li>Make the behavior the policy controlled permanent - remove a deprecated feature, or remove
+ * the check on a feature which is being rolled out.
+ * <li>After this new version is released, remove the flag from the global rc file, and remove the
+ * feature ID from the constructor for {@link FeaturePolicyLoader}.
* </ol>
*
* @see FeaturePolicyLoader
+ * @deprecated This is deprecated because the dependency on the package group used to hold the
+ * whitelist is not accessible through blaze query. Use {@link Whitelist}.
*/
+@Deprecated
public final class FeaturePolicyConfiguration extends BuildConfiguration.Fragment {
private final ImmutableSetMultimap<String, PackageSpecification> features;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoader.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoader.java
index 9e2c856835..7704c8c6cd 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoader.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyLoader.java
@@ -37,7 +37,13 @@ import java.util.Queue;
import java.util.Set;
import javax.annotation.Nullable;
-/** A loader for the FeaturePolicyConfiguration fragment. */
+/**
+ * A loader for the FeaturePolicyConfiguration fragment.
+ *
+ * @deprecated This is deprecated because the dependency on the package group used to hold the
+ * whitelist is not accessible through blaze query. Use {@link Whitelist}.
+ */
+@Deprecated
public final class FeaturePolicyLoader implements ConfigurationFragmentFactory {
private final ImmutableSet<String> permittedFeatures;
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptions.java
index b0036d5f23..737c1c878c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/FeaturePolicyOptions.java
@@ -21,7 +21,13 @@ import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import java.util.List;
-/** The options fragment which defines {@link FeaturePolicyConfiguration}. */
+/**
+ * The options fragment which defines {@link FeaturePolicyConfiguration}.
+ *
+ * @deprecated This is deprecated because the dependency on the package group used to hold the
+ * whitelist is not accessible through blaze query. Use {@link Whitelist}.
+ */
+@Deprecated
public final class FeaturePolicyOptions extends FragmentOptions {
/** The mapping from features to their associated package groups. */
@Option(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntry.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntry.java
index e0982a25df..b871637d17 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntry.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntry.java
@@ -17,7 +17,13 @@ package com.google.devtools.build.lib.analysis.featurecontrol;
import com.google.auto.value.AutoValue;
import com.google.devtools.build.lib.cmdline.Label;
-/** Policy value object encoding the package group which can access a given feature. */
+/**
+ * Policy value object encoding the package group which can access a given feature.
+ *
+ * @deprecated This is deprecated because the dependency on the package group used to hold the
+ * whitelist is not accessible through blaze query. Use {@link Whitelist}.
+ */
+@Deprecated
@AutoValue
public abstract class PolicyEntry {
/** Creates a new PolicyEntry for the given feature and package_group label. */
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverter.java b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverter.java
index a8f997b76b..ec245313a9 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverter.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/featurecontrol/PolicyEntryConverter.java
@@ -19,7 +19,13 @@ import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.OptionsParsingException;
-/** A converter which creates a PolicyEntry from a flag value. */
+/**
+ * A converter which creates a PolicyEntry from a flag value.
+ *
+ * @deprecated This is deprecated because the dependency on the package group used to hold the
+ * whitelist is not accessible through blaze query. Use {@link Whitelist}.
+ */
+@Deprecated
public final class PolicyEntryConverter implements Converter<PolicyEntry> {
@Override
public PolicyEntry convert(String input) throws OptionsParsingException {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/whitelisting/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/whitelisting/BUILD
new file mode 100644
index 0000000000..b31e3c224b
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/whitelisting/BUILD
@@ -0,0 +1,26 @@
+# Description:
+# Whitelisting mechanism for rolling out and deprecating pieces of Bazel functionality.
+
+package(
+ default_visibility = ["//src:__subpackages__"],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+)
+
+java_library(
+ name = "whitelisting",
+ srcs = glob(["*.java"]),
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:build-base",
+ "//src/main/java/com/google/devtools/build/lib:packages-internal",
+ "//src/main/java/com/google/devtools/build/lib:preconditions",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/common/options",
+ "//third_party:auto_value",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ ],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/whitelisting/Whitelist.java b/src/main/java/com/google/devtools/build/lib/analysis/whitelisting/Whitelist.java
new file mode 100644
index 0000000000..0da6e4d362
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/whitelisting/Whitelist.java
@@ -0,0 +1,80 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.analysis.whitelisting;
+
+import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.BuildType.LABEL;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.analysis.PackageSpecificationProvider;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.util.Preconditions;
+
+/**
+ * Class used for implementing whitelists using package groups.
+ *
+ * <p>To use add an attribute {@link getAttributeFromWhitelistName(String,Label) to the rule class
+ * which needs the whitelisting mechanism and use {@link isAvailable(RuleContext,String)} to check
+ * during analysis if a rule is present
+ */
+public final class Whitelist {
+
+ private Whitelist() {}
+
+ /**
+ * Returns an Attribute.Builder that can be used to add an implicit attribute to a rule containing
+ * a package group whitelist.
+ *
+ * @param whitelistName The name of the whitelist. This has to comply with attribute naming
+ * standards and will be used as a suffix for the attribute name.
+ * @param packageGroupWhitelist Label for the package group with the whitelist.
+ */
+ public static Attribute.Builder<Label> getAttributeFromWhitelistName(
+ String whitelistName, Label packageGroupWhitelist) {
+ String attributeName = getAttributeNameFromWhitelistName(whitelistName);
+ return attr(attributeName, LABEL)
+ .value(packageGroupWhitelist)
+ .cfg(HOST)
+ .mandatoryNativeProviders(ImmutableList.of(PackageSpecificationProvider.class));
+ }
+
+ /**
+ * Returns whether the rule in the given RuleContext is in a whitelist.
+ *
+ * @param ruleContext The context in which this check is being executed.
+ * @param whitelistName The name of the whitelist being used.
+ */
+ public static boolean isAvailable(RuleContext ruleContext, String whitelistName) {
+ String attributeName = getAttributeNameFromWhitelistName(whitelistName);
+ Preconditions.checkArgument(ruleContext.isAttrDefined(attributeName, LABEL));
+ TransitiveInfoCollection packageGroup = ruleContext.getPrerequisite(attributeName, Mode.HOST);
+ Label label = ruleContext.getLabel();
+ return packageGroup
+ .getProvider(PackageSpecificationProvider.class)
+ .getPackageSpecifications()
+ .toList()
+ .stream()
+ .anyMatch(p -> p.containsPackage(label.getPackageIdentifier()));
+ }
+
+ private static String getAttributeNameFromWhitelistName(String whitelistName) {
+ return String.format("$whitelist_%s", whitelistName);
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelPrerequisiteValidator.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelPrerequisiteValidator.java
index 81c097f19e..bb2456c08e 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelPrerequisiteValidator.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelPrerequisiteValidator.java
@@ -14,12 +14,16 @@
package com.google.devtools.build.lib.bazel.rules;
+import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.PackageSpecificationProvider;
import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.packages.Attribute;
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
import com.google.devtools.build.lib.packages.PackageGroup;
+import com.google.devtools.build.lib.packages.RawAttributeMapper;
import com.google.devtools.build.lib.packages.Rule;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.rules.AliasProvider;
@@ -72,19 +76,31 @@ public class BazelPrerequisiteValidator
new VisibilityErrorEvent(context.getConfiguration(), rule.getLabel(), errorMessage));
}
- if (prerequisiteTarget instanceof PackageGroup && !attrName.equals("visibility")) {
- context.reportError(
- rule.getAttributeLocation(attrName),
- "in "
- + attrName
- + " attribute of "
- + rule.getRuleClass()
- + " rule "
- + rule.getLabel()
- + ": package group "
- + AliasProvider.printLabelWithAliasChain(prerequisite)
- + " is misplaced here "
- + "(they are only allowed in the visibility attribute)");
+ if (prerequisiteTarget instanceof PackageGroup) {
+ ImmutableList<ImmutableList<Class<? extends TransitiveInfoProvider>>>
+ mandatoryNativeProviders =
+ RawAttributeMapper.of(rule)
+ .getAttributeDefinition(attrName)
+ .getMandatoryNativeProvidersList();
+ boolean containsPackageSpecificationProvider =
+ mandatoryNativeProviders
+ .stream()
+ .anyMatch(list -> list.contains(PackageSpecificationProvider.class));
+ // TODO(plf): Add the PackageSpecificationProvider to the 'visibility' attribute.
+ if (!attrName.equals("visibility") && !containsPackageSpecificationProvider) {
+ context.reportError(
+ rule.getAttributeLocation(attrName),
+ "in "
+ + attrName
+ + " attribute of "
+ + rule.getRuleClass()
+ + " rule "
+ + rule.getLabel()
+ + ": package group "
+ + AliasProvider.printLabelWithAliasChain(prerequisite)
+ + " is misplaced here "
+ + "(they are only allowed in the visibility attribute)");
+ }
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/BUILD b/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/BUILD
new file mode 100644
index 0000000000..cd09c6531f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/BUILD
@@ -0,0 +1,34 @@
+# Description:
+# Tests for whitelisting mechanism for rolling out and deprecating pieces of Bazel functionality.
+
+licenses(["notice"]) # Apache 2.0
+
+filegroup(
+ name = "srcs",
+ srcs = glob(
+ ["**"],
+ ),
+ visibility = ["//src/test/java/com/google/devtools/build/lib:__pkg__"],
+)
+
+java_test(
+ name = "WhitelistingTests",
+ srcs = glob(["*.java"]),
+ test_class = "com.google.devtools.build.lib.AllTests",
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:build-base",
+ "//src/main/java/com/google/devtools/build/lib:collect",
+ "//src/main/java/com/google/devtools/build/lib:packages-internal",
+ "//src/main/java/com/google/devtools/build/lib:util",
+ "//src/main/java/com/google/devtools/build/lib/analysis/whitelisting",
+ "//src/main/java/com/google/devtools/build/lib/cmdline",
+ "//src/main/java/com/google/devtools/common/options",
+ "//src/test/java/com/google/devtools/build/lib:analysis_testutil",
+ "//src/test/java/com/google/devtools/build/lib:packages_testutil",
+ "//src/test/java/com/google/devtools/build/lib:test_runner",
+ "//third_party:guava",
+ "//third_party:guava-testlib",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistCachingTest.java b/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistCachingTest.java
new file mode 100644
index 0000000000..164e6f60d3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistCachingTest.java
@@ -0,0 +1,65 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.analysis.whitelisting;
+
+import static org.junit.Assert.fail;
+
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
+import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
+import com.google.devtools.build.lib.analysis.util.AnalysisCachingTestBase;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests that whitelists are invalidated after change. */
+@RunWith(JUnit4.class)
+public final class WhitelistCachingTest extends AnalysisCachingTestBase {
+
+ @Before
+ public void addDummyRule() throws Exception {
+ ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
+ TestRuleClassProvider.addStandardRules(builder);
+ builder.addRuleDefinition(new WhitelistDummyRule());
+ useRuleClassProvider(builder.build());
+ }
+
+ @Test
+ public void testStillCorrectAfterChangesToWhitelist() throws Exception {
+ scratch.file("whitelist/BUILD", "package_group(name='whitelist', packages=[])");
+ scratch.file("x/BUILD", "rule_with_whitelist(name='x')");
+
+ reporter.removeHandler(failFastHandler);
+ try {
+ update("//x:x");
+ fail();
+ } catch (ViewCreationFailedException e) {
+ // expected
+ }
+ assertContainsEvent("Dummy is not available.");
+ eventCollector.clear();
+ reporter.addHandler(failFastHandler);
+ scratch.overwriteFile(
+ "whitelist/BUILD",
+ "package_group(",
+ " name='whitelist',",
+ " packages=[",
+ " '//...'",
+ " ])");
+ update("//x:x");
+ assertNoEvents();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistDummyRule.java b/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistDummyRule.java
new file mode 100644
index 0000000000..e91cf9a739
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistDummyRule.java
@@ -0,0 +1,61 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.analysis.whitelisting;
+
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.analysis.RunfilesProvider;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.collect.nestedset.Order;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+
+/** Definition of a test rule that uses whitelists. */
+public final class WhitelistDummyRule implements RuleDefinition, RuleConfiguredTargetFactory {
+
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .add(
+ Whitelist.getAttributeFromWhitelistName("dummy", env.getLabel("//whitelist:whitelist")))
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("rule_with_whitelist")
+ .ancestors(BaseRuleClasses.RuleBase.class)
+ .factoryClass(WhitelistDummyRule.class)
+ .build();
+ }
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext)
+ throws InterruptedException, RuleErrorException {
+ if (!Whitelist.isAvailable(ruleContext, "dummy")) {
+ ruleContext.ruleError("Dummy is not available.");
+ }
+ return new RuleConfiguredTargetBuilder(ruleContext)
+ .setFilesToBuild(NestedSetBuilder.emptySet(Order.STABLE_ORDER))
+ .addProvider(RunfilesProvider.EMPTY)
+ .build();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistTest.java b/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistTest.java
new file mode 100644
index 0000000000..cc874077ac
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/whitelisting/WhitelistTest.java
@@ -0,0 +1,128 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.analysis.whitelisting;
+
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for the Whitelist methods. */
+@RunWith(JUnit4.class)
+public final class WhitelistTest extends BuildViewTestCase {
+
+ @Override
+ protected ConfiguredRuleClassProvider getRuleClassProvider() {
+ ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
+ TestRuleClassProvider.addStandardRules(builder);
+ return builder.addRuleDefinition(new WhitelistDummyRule()).build();
+ }
+
+ @Test
+ public void testDirectPackage() throws Exception {
+ scratch.file(
+ "whitelist/BUILD",
+ "package_group(",
+ " name='whitelist',",
+ " packages=[",
+ " '//direct',",
+ " ])");
+ scratch.file("direct/BUILD", "rule_with_whitelist(name='x')");
+ getConfiguredTarget("//direct:x");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testRecursivePackage() throws Exception {
+ scratch.file(
+ "whitelist/BUILD",
+ "package_group(",
+ " name='whitelist',",
+ " packages=[",
+ " '//recursive/...',",
+ " ])");
+ scratch.file("recursive/x/BUILD", "rule_with_whitelist(name='y')");
+ getConfiguredTarget("//recursive/x:y");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testAbsentPackage() throws Exception {
+ scratch.file(
+ "whitelist/BUILD",
+ "package_group(",
+ " name='whitelist',",
+ " packages=[",
+ " '//somethingelse/...',",
+ " ])");
+ checkError("absent", "x", "Dummy is not available.", "rule_with_whitelist(name='x')");
+ }
+
+ @Test
+ public void testCatchAll() throws Exception {
+ scratch.file(
+ "whitelist/BUILD",
+ "package_group(",
+ " name='whitelist',",
+ " packages=[",
+ " '//...',",
+ " ])");
+ scratch.file("notingroup/BUILD", "rule_with_whitelist(name='x')");
+ getConfiguredTarget("//notingroup:x");
+ assertNoEvents();
+ }
+
+ @Test
+ public void testEmptyPackageGroup() throws Exception {
+ scratch.file("whitelist/BUILD", "package_group(name='whitelist', packages=[])");
+ checkError("x", "x", "Dummy is not available.", "rule_with_whitelist(name='x')");
+ }
+
+ @Test
+ public void testNonExistentPackageGroup() throws Exception {
+ checkError(
+ "x",
+ "x",
+ "every rule of type rule_with_whitelist implicitly depends upon the target"
+ + " '//whitelist:whitelist', but this target could not be found because of: no such"
+ + " package 'whitelist': BUILD file not found on package path",
+ "rule_with_whitelist(name='x')");
+ }
+
+ @Test
+ public void testIncludes() throws Exception {
+ scratch.file(
+ "subwhitelist/BUILD",
+ "package_group(",
+ " name='whitelist',",
+ " packages=[",
+ " '//x',",
+ " ])");
+ scratch.file(
+ "whitelist/BUILD",
+ "package_group(",
+ " name='whitelist',",
+ " includes=[",
+ " '//subwhitelist:whitelist',",
+ " ],",
+ " packages=[",
+ " ])");
+ scratch.file("x/BUILD", "rule_with_whitelist(", "name='x'", ")");
+ getConfiguredTarget("//x:x");
+ assertNoEvents();
+ }
+}