// Copyright 2015 Google Inc. 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.config.BuildConfiguration; 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.Rule; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.packages.RuleClass.Builder; 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; /** * Tests for the constraint enforcement system. */ public class ConstraintsTest extends AbstractConstraintsTest { @Override public void setUp() throws Exception { super.setUp(); // 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'])"); } /** * 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(Builder builder, RuleDefinitionEnvironment env) { return builder .setUndocumented() .compatibleWith(env.getLabel("//buildenv/rule_class_compat:b")) .restrictedTo(env.getLabel("//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 class BadRuleClassDefaultRule implements RuleDefinition { @Override public RuleClass build(Builder builder, RuleDefinitionEnvironment env) { return builder .setUndocumented() // These defaults are invalid since compatibleWith and restrictedTo can't mix // environments from the same group. .compatibleWith(env.getLabel("//buildenv/rule_class_compat:a")) .restrictedTo(env.getLabel("//buildenv/rule_class_compat:b")) .build(); } @Override public Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name("bad_rule_class_default") .ancestors(BaseRuleClasses.RuleBase.class) .factoryClass(UnknownRuleConfiguredTarget.class) .build(); } } private static final class RuleClassWithImplicitAndLateBoundDefaults implements RuleDefinition { @Override public RuleClass build(Builder builder, RuleDefinitionEnvironment env) { return builder .setUndocumented() .add(Attribute.attr("$implicit", BuildType.LABEL) .value(Label.parseAbsoluteUnchecked("//helpers:implicit"))) .add(Attribute.attr(":latebound", BuildType.LABEL) .value( new Attribute.LateBoundLabel() { @Override public Label getDefault(Rule rule, BuildConfiguration configuration) { return Label.parseAbsoluteUnchecked("//helpers:latebound"); } })) .add(Attribute.attr("normal", BuildType.LABEL) .allowedFileTypes(FileTypeSet.NO_FILE) .value(Label.parseAbsoluteUnchecked("//helpers:default"))) .build(); } @Override public Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name("rule_with_implicit_and_latebound_deps") .ancestors(BaseRuleClasses.RuleBase.class) .factoryClass(UnknownRuleConfiguredTarget.class) .build(); } } private static final class ConstraintExemptRuleClass implements RuleDefinition { @Override public RuleClass build(Builder builder, RuleDefinitionEnvironment env) { return builder .setUndocumented() .exemptFromConstraintChecking("for testing removal of restricted_to / compatible_with") .build(); } @Override public Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name("totally_free_rule") .ancestors(BaseRuleClasses.RuleBase.class) .factoryClass(UnknownRuleConfiguredTarget.class) .build(); } } /** * 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(new BadRuleClassDefaultRule()); builder.addRuleDefinition(new RuleClassWithImplicitAndLateBoundDefaults()); builder.addRuleDefinition(new ConstraintExemptRuleClass()); 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(); } /** * By default, a rule *implicitly* supports all defaults, meaning the explicitly known * environment set is empty. */ public void testDefaultSupportedEnvironments() 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. */ public void testConstrainedSupportedEnvironments() 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. */ public void testCompatibleSupportedEnvironments() 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. */ public void testSupportedEnvironmentsConstrainedtoNothing() throws Exception { new EnvironmentGroupMaker("buildenv/foo").setEnvironments("a", "b").setDefaults("a").make(); reporter.removeHandler(failFastHandler); String ruleDef = getDependencyRule(constrainedTo()); assertNull(scratchConfiguredTarget("hello", "dep", ruleDef)); assertContainsEvent("attribute cannot be empty"); } /** * Restrict the environments within one group, declare compatibility for another. */ public void testSupportedEnvironmentsInMultipleGroups() 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. */ public void testSameEnvironmentCompatibleAndRestricted() 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")); assertNull(scratchConfiguredTarget("hello", "dep", ruleDef)); assertContainsEvent("//buildenv/foo:b cannot appear both here and in restricted_to"); } /** * Two labels from the same group can't appear in different attributes. */ public void testSameGroupCompatibleAndRestricted() 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")); assertNull(scratchConfiguredTarget("hello", "dep", ruleDef)); 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. */ public void testSupportedEnvironmentsRuleClassDefaults() throws Exception { writeRuleClassDefaultEnvironments(); String ruleDef = "rule_class_default(name = 'a')"; Set