// 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.util; 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_LIST; import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL_LIST; import static com.google.devtools.build.lib.syntax.Type.BOOLEAN; import static com.google.devtools.build.lib.syntax.Type.STRING; import static com.google.devtools.build.lib.syntax.Type.STRING_LIST; import com.google.common.base.Preconditions; 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.packages.Attribute; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.util.FileTypeSet; import java.util.Arrays; /** * Provides a simple API for creating custom rule classes for tests. * *

Usage (for a custom rule type that just needs to exist): * *

 *   MockRule fooRule = () -> MockRule.define("foo_rule");
 * 
* *

Usage (for custom attributes): * *

 *   MockRule fooRule = () -> MockRule.define("foo_rule", attr("myattr", Type.STRING));
 * 
* *

Usage (for arbitrary customization): * *

 *   MockRule fooRule = () -> MockRule.define(
 *       "foo_rule",
 *       (builder, env) ->
 *           builder
 *               .removeAttribute("tags")
 *               .requiresConfigurationFragments(FooConfiguration.class);
 *       );
 * 
* * *

We use lambdas for custom rule classes because {@link ConfiguredRuleClassProvider} indexes * rule class definitions by their Java class names. So each definition has to have its own * unique Java class. * *

Both of the following forms are valid: * *

MockRule fooRule = () -> MockRule.define("foo_rule");
*
RuleDefinition fooRule = (MockRule) () -> MockRule.define("foo_rule");
* *

Use discretion in choosing your preferred form. The first is more compact. But the second * makes it clearer that fooRule is a proper rule class definition. */ public interface MockRule extends RuleDefinition { /** * Container for the desired name and custom settings for this rule class. */ class State { private final String name; private final MockRuleCustomBehavior customBehavior; State(String ruleClassName, MockRuleCustomBehavior customBehavior) { this.name = Preconditions.checkNotNull(ruleClassName); this.customBehavior = Preconditions.checkNotNull(customBehavior); } } /** * Returns a new {@link State} for this rule class with custom attributes. This is a convenience * method for lambda definitions: * *

   *   MockRule myRule = () -> MockRule.define("my_rule", attr("myattr", Type.STRING));
   * 
*/ static State define(String ruleClassName, Attribute.Builder... attributes) { return new State( ruleClassName, new MockRuleCustomBehavior.CustomAttributes(Arrays.asList(attributes))); } /** * Returns a new {@link State} for this rule class with arbitrary custom behavior. This is a * convenience method for lambda definitions: * *
   *   MockRule myRule = () -> MockRule.define(
   *       "my_rule",
   *       (builder, env) -> builder.requiresConfigurationFragments(FooConfiguration.class));
   * 
*/ static State define(String ruleClassName, MockRuleCustomBehavior customBehavior) { return new State(ruleClassName, customBehavior); } /** * Returns the basic state that defines this rule class. This is the only interface method * implementers must override. */ State define(); /** * Default "deps" attribute for rule classes that don't need special behavior. */ Attribute.Builder DEPS_ATTRIBUTE = attr("deps", BuildType.LABEL_LIST).allowedFileTypes(); /** * Builds out this rule with default attributes Blaze expects of all rules plus the custom * attributes defined by this implementation's {@link State}. * *

Do not override this method. For extra custom behavior, use * {@link #define(String, MockRuleCustomBehavior)} */ @Override default RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) { State state = define(); builder .add(attr("testonly", BOOLEAN).nonconfigurable("test").value(false)) .add(attr("deprecation", STRING).nonconfigurable("test").value((String) null)) .add(attr("tags", STRING_LIST)) .add(attr("visibility", NODEP_LABEL_LIST).orderIndependent().cfg(HOST) .nonconfigurable("test")) .add(attr(RuleClass.COMPATIBLE_ENVIRONMENT_ATTR, LABEL_LIST) .allowedFileTypes(FileTypeSet.NO_FILE) .dontCheckConstraints()) .add(attr(RuleClass.RESTRICTED_ENVIRONMENT_ATTR, LABEL_LIST) .allowedFileTypes(FileTypeSet.NO_FILE) .dontCheckConstraints()); state.customBehavior.customize(builder, environment); return builder.build(); } /** * Sets this rule class's metadata with the name defined by {@link State}. */ @Override default RuleDefinition.Metadata getMetadata() { return RuleDefinition.Metadata.builder() .name(define().name) .type(RuleClass.Builder.RuleClassType.NORMAL) .factoryClass(MockConfiguredTargetFactory.class) .ancestors(BaseRuleClasses.RootRule.class) .build(); } }