// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.analysis.constraints; import static com.google.common.truth.Truth.assertThat; import com.google.devtools.build.lib.analysis.BaseRuleClasses; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.RuleDefinition; import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment; import com.google.devtools.build.lib.analysis.util.MockRule; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.testutil.TestRuleClassProvider; import com.google.devtools.build.lib.testutil.UnknownRuleConfiguredTarget; import com.google.devtools.build.lib.util.FileTypeSet; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for the constraint enforcement system. */ @RunWith(JUnit4.class) public class ConstraintsTest extends AbstractConstraintsTest { @Before public final void createBuildFile() throws Exception { // Support files for RuleClassWithImplicitAndLateBoundDefaults: scratch.file("helpers/BUILD", "sh_library(name = 'implicit', srcs = ['implicit.sh'])", "sh_library(name = 'latebound', srcs = ['latebound.sh'])", "sh_library(name = 'default', srcs = ['default.sh'])"); scratch.file("config/BUILD", "config_setting(name = 'a', values = {'define': 'mode=a'})", "config_setting(name = 'b', values = {'define': 'mode=b'})"); } /** * Dummy rule class for testing rule class defaults. This class applies valid defaults. Note * that the specified environments must be independently created. */ private static final class RuleClassDefaultRule implements RuleDefinition { @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { return builder .setUndocumented() .compatibleWith(Label.parseAbsoluteUnchecked("//buildenv/rule_class_compat:b")) .restrictedTo(Label.parseAbsoluteUnchecked("//buildenv/rule_class_restrict:d")) .build(); } @Override public Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name("rule_class_default") .ancestors(BaseRuleClasses.RuleBase.class) .factoryClass(UnknownRuleConfiguredTarget.class) .build(); } } /** * Dummy rule class for testing rule class defaults. This class applies invalid defaults. Note * that the specified environments must be independently created. */ private static final MockRule BAD_RULE_CLASS_DEFAULT_RULE = () -> MockRule.define( "bad_rule_class_default", (builder, env) -> builder .setUndocumented() // These defaults are invalid since compatibleWith and restrictedTo can't mix // environments from the same group. .compatibleWith( Label.parseAbsoluteUnchecked("//buildenv/rule_class_compat:a")) .restrictedTo( Label.parseAbsoluteUnchecked("//buildenv/rule_class_compat:b"))); private static final MockRule RULE_WITH_IMPLICIT_AND_LATEBOUND_DEFAULTS = () -> MockRule.define( "rule_with_implicit_and_latebound_deps", (builder, env) -> builder .setUndocumented() .add( Attribute.attr("$implicit", BuildType.LABEL) .value(Label.parseAbsoluteUnchecked("//helpers:implicit"))) .add( Attribute.attr(":latebound", BuildType.LABEL) .value( Attribute.LateBoundDefault.fromConstantForTesting( Label.parseAbsoluteUnchecked("//helpers:latebound")))) .add( Attribute.attr("normal", BuildType.LABEL) .allowedFileTypes(FileTypeSet.NO_FILE) .value(Label.parseAbsoluteUnchecked("//helpers:default")))); private static final MockRule RULE_WITH_ENFORCED_IMPLICIT_ATTRIBUTE = () -> MockRule.define( "rule_with_enforced_implicit_deps", (builder, env) -> builder .setUndocumented() .add(Attribute.attr("$implicit", BuildType.LABEL) .value(Label.parseAbsoluteUnchecked("//helpers:implicit")) .checkConstraints())); private static final MockRule RULE_WITH_SKIPPED_ATTRIBUTE = () -> MockRule.define( "rule_with_skipped_attr", (builder, env) -> builder .setUndocumented() .add(Attribute.attr("some_attr", BuildType.LABEL) .allowedFileTypes(FileTypeSet.NO_FILE) .dontCheckConstraints())); private static final MockRule CONSTRAINT_EXEMPT_RULE_CLASS = () -> MockRule.define( "totally_free_rule", (builder, env) -> builder .setUndocumented() .exemptFromConstraintChecking( "for testing removal of restricted_to / compatible_with")); /** * Injects the rule class default rules into the default test rule class provider. */ @Override protected ConfiguredRuleClassProvider getRuleClassProvider() { ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); TestRuleClassProvider.addStandardRules(builder); builder.addRuleDefinition(new RuleClassDefaultRule()); builder.addRuleDefinition(BAD_RULE_CLASS_DEFAULT_RULE); builder.addRuleDefinition(RULE_WITH_IMPLICIT_AND_LATEBOUND_DEFAULTS); builder.addRuleDefinition(RULE_WITH_ENFORCED_IMPLICIT_ATTRIBUTE); builder.addRuleDefinition(RULE_WITH_SKIPPED_ATTRIBUTE); builder.addRuleDefinition(CONSTRAINT_EXEMPT_RULE_CLASS); return builder.build(); } /** * Writes the environments and environment groups referred to by the rule class defaults. */ private void writeRuleClassDefaultEnvironments() throws Exception { new EnvironmentGroupMaker("buildenv/rule_class_compat").setEnvironments("a", "b") .setDefaults("a").make(); new EnvironmentGroupMaker("buildenv/rule_class_restrict").setEnvironments("c", "d") .setDefaults("c").make(); } @Test public void packageErrorOnEnvironmentGroupWithMissingEnvironments() throws Exception { scratch.file("buildenv/envs/BUILD", "environment(name = 'env1')", "environment(name = 'env2')", "environment_group(", " name = 'envs',", " environments = [':env1', ':en2'],", " defaults = [':env1'])"); reporter.removeHandler(failFastHandler); assertThat(scratchConfiguredTarget("foo", "g", "genrule(" + " name = 'g'," + " srcs = []," + " outs = ['g.out']," + " cmd = ''," + " restricted_to = ['//buildenv/envs:env1'])")) .isNull(); assertContainsEvent("environment //buildenv/envs:en2 does not exist"); } /** * By default, a rule *implicitly* supports all defaults, meaning the explicitly known * environment set is empty. */ @Test public void defaultSupportedEnvironments() throws Exception { new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make(); String ruleDef = getDependencyRule(); assertThat(supportedEnvironments("dep", ruleDef)).isEmpty(); } /** * "Constraining" a rule's environments explicitly sets them. */ @Test public void constrainedSupportedEnvironments() throws Exception { new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a") .make(); String ruleDef = getDependencyRule(constrainedTo("//buildenv/foo:c")); assertThat(supportedEnvironments("dep", ruleDef)) .containsExactlyElementsIn(asLabelSet("//buildenv/foo:c")); } /** * Specifying compatibility adds the specified environments to the defaults. */ @Test public void compatibleSupportedEnvironments() throws Exception { new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b", "c").setDefaults("a") .make(); String ruleDef = getDependencyRule(compatibleWith("//buildenv/foo:c")); assertThat(supportedEnvironments("dep", ruleDef)) .containsExactlyElementsIn(asLabelSet("//buildenv/foo:a", "//buildenv/foo:c")); } /** * A rule can't support *no* environments. */ @Test public void supportedEnvironmentsConstrainedtoNothing() throws Exception { new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make(); reporter.removeHandler(failFastHandler); String ruleDef = getDependencyRule(constrainedTo()); assertThat(scratchConfiguredTarget("hello", "dep", ruleDef)).isNull(); assertContainsEvent("attribute cannot be empty"); } /** * Restrict the environments within one group, declare compatibility for another. */ @Test public void supportedEnvironmentsInMultipleGroups() throws Exception { new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make(); new EnvironmentGroupMaker("buildenv/bar").setEnvironments("c", "d").setDefaults("c").make(); String ruleDef = getDependencyRule( constrainedTo("//buildenv/foo:b"), compatibleWith("//buildenv/bar:d")); assertThat(supportedEnvironments("dep", ruleDef)) .containsExactlyElementsIn( asLabelSet("//buildenv/foo:b", "//buildenv/bar:c", "//buildenv/bar:d")); } /** * The same label can't appear in both a constraint and a compatibility declaration. */ @Test public void sameEnvironmentCompatibleAndRestricted() throws Exception { new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make(); reporter.removeHandler(failFastHandler); String ruleDef = getDependencyRule( constrainedTo("//buildenv/foo:b"), compatibleWith("//buildenv/foo:b")); assertThat(scratchConfiguredTarget("hello", "dep", ruleDef)).isNull(); assertContainsEvent("//buildenv/foo:b cannot appear both here and in restricted_to"); } /** * Two labels from the same group can't appear in different attributes. */ @Test public void sameGroupCompatibleAndRestricted() throws Exception { new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make(); reporter.removeHandler(failFastHandler); String ruleDef = getDependencyRule( constrainedTo("//buildenv/foo:a"), compatibleWith("//buildenv/foo:b")); assertThat(scratchConfiguredTarget("hello", "dep", ruleDef)).isNull(); assertContainsEvent( "//buildenv/foo:b and //buildenv/foo:a belong to the same environment group"); } /** * Tests that rule class defaults change a rule's default set of environments. */ @Test public void supportedEnvironmentsRuleClassDefaults() throws Exception { writeRuleClassDefaultEnvironments(); String ruleDef = "rule_class_default(name = 'a')"; Set