// Copyright 2014 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.testutil; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.util.Preconditions; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * Utility for quickly creating BUILD file rules for use in tests. * * <p>The use case for this class is writing BUILD files where simple * readability for the sake of rules' relationship to the test framework * is more important than detailed semantics and layout. * * <p>The behavior provided by this class is not meant to be exhaustive, * but should handle a majority of simple cases. * * <p>Example: * * <pre> * String text = new BuildRuleBuilder("java_library", "MyRule") .setSources("First.java", "Second.java", "Third.java") .setDeps(":library", "//java/com/google/common/collect") .setResources("schema/myschema.xsd") .build(); * </pre> * */ public class BuildRuleBuilder { protected final RuleClass ruleClass; protected final String ruleName; private Multimap<String, String> multiValueAttributes; private Map<String, Object> singleValueAttributes; protected Map<String, RuleClass> ruleClassMap; /** * Create a new instance. * * @param ruleClass the rule class of the new rule * @param ruleName the name of the new rule. */ public BuildRuleBuilder(String ruleClass, String ruleName) { this(ruleClass, ruleName, getDefaultRuleClassMap()); } protected static Map<String, RuleClass> getDefaultRuleClassMap() { return TestRuleClassProvider.getRuleClassProvider().getRuleClassMap(); } public BuildRuleBuilder(String ruleClass, String ruleName, Map<String, RuleClass> ruleClassMap) { this.ruleClass = ruleClassMap.get(ruleClass); this.ruleName = ruleName; this.multiValueAttributes = LinkedHashMultimap.create(); this.singleValueAttributes = new HashMap<>(); this.ruleClassMap = ruleClassMap; } /** * Sets the value of a single valued attribute */ public BuildRuleBuilder setSingleValueAttribute(String attrName, Object value) { Preconditions.checkState( !singleValueAttributes.containsKey(attrName), "attribute '%s' already set", attrName); singleValueAttributes.put(attrName, value); return this; } /** * Sets the value of a list type attribute */ public BuildRuleBuilder addMultiValueAttributes(String attrName, String... value) { multiValueAttributes.putAll(attrName, Lists.newArrayList(value)); return this; } /** * Generate the rule * * @return a string representation of the rule. */ public String build() { StringBuilder sb = new StringBuilder(); sb.append(ruleClass.getName()).append("("); printNormal(sb, "name", ruleName); for (Map.Entry<String, Collection<String>> entry : multiValueAttributes.asMap().entrySet()) { printArray(sb, entry.getKey(), entry.getValue()); } for (Map.Entry<String, Object> entry : singleValueAttributes.entrySet()) { printNormal(sb, entry.getKey(), entry.getValue()); } sb.append(")\n"); return sb.toString(); } private void printArray(StringBuilder sb, String attr, Collection<String> values) { if (values == null || values.isEmpty()) { return; } sb.append(" ").append(attr).append(" = "); printList(sb, values); sb.append(","); sb.append("\n"); } private void printNormal(StringBuilder sb, String attr, Object value) { if (value == null) { return; } sb.append(" ").append(attr).append(" = "); if (value instanceof Integer) { sb.append(value); } else { sb.append("'").append(value).append("'"); } sb.append(","); sb.append("\n"); } /** * Turns iterable of {a b c} into string "['a', 'b', 'c']", appends to * supplied StringBuilder. */ private void printList(StringBuilder sb, Collection<String> elements) { sb.append("["); Joiner.on(",").appendTo(sb, Iterables.transform(elements, new Function<String, String>() { @Override public String apply(String from) { return "'" + from + "'"; } })); sb.append("]"); } /** * Returns the transitive closure of file names need to be generated in order * for this rule to build. */ public Collection<String> getFilesToGenerate() { return ImmutableList.of(); } /** * Returns the transitive closure of BuildRuleBuilders need to be generated in order * for this rule to build. */ public Collection<BuildRuleBuilder> getRulesToGenerate() { return ImmutableList.of(); } /** * Returns a {@link Dependency} of this {@link BuildRuleBuilder} using attrName. */ public Dependency dependsVia(String attrName) { return new Dependency(this, attrName); } /** * Representing a {@link BuildRuleBuilder} depending on an other rule via a certain attribute. */ public class Dependency { private BuildRuleBuilder buildRuleBuilder; private String attrName; private Dependency(BuildRuleBuilder buildRuleBuilder, String attrName) { this.buildRuleBuilder = buildRuleBuilder; this.attrName = attrName; } /** * Returns this {@link BuildRuleBuilder} with a new dependency on otherRule. */ public BuildRuleBuilder on(BuildRuleBuilder otherRule) { buildRuleBuilder.addMultiValueAttributes(attrName, otherRule.ruleName); return buildRuleBuilder; } } }