| // 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.Preconditions; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.devtools.build.lib.packages.Attribute; |
| import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.RuleClass; |
| import com.google.devtools.build.lib.syntax.Type; |
| import com.google.devtools.build.lib.syntax.Type.LabelClass; |
| import com.google.devtools.build.lib.syntax.Type.ListType; |
| import com.google.devtools.build.lib.util.FileTypeSet; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * A helper class to generate valid rules with filled attributes if necessary. |
| */ |
| public class BuildRuleWithDefaultsBuilder extends BuildRuleBuilder { |
| |
| private Set<String> generateFiles; |
| private Map<String, BuildRuleBuilder> generateRules; |
| |
| public BuildRuleWithDefaultsBuilder(String ruleClass, String ruleName) { |
| super(ruleClass, ruleName); |
| this.generateFiles = new HashSet<>(); |
| this.generateRules = new HashMap<>(); |
| } |
| |
| private BuildRuleWithDefaultsBuilder(String ruleClass, String ruleName, |
| Map<String, RuleClass> ruleClassMap, Set<String> generateFiles, |
| Map<String, BuildRuleBuilder> generateRules) { |
| super(ruleClass, ruleName, ruleClassMap); |
| this.generateFiles = generateFiles; |
| this.generateRules = generateRules; |
| } |
| |
| /** |
| * Creates a dummy file with the given extension in the given package and returns a valid Blaze |
| * label referring to the file. Note, the created label depends on the package of the rule. |
| */ |
| private String getDummyFileLabel(String rulePkg, String filePkg, String extension, |
| Type<?> attrType) { |
| boolean isOutput = attrType.getLabelClass() == LabelClass.OUTPUT; |
| String fileName = (isOutput ? "dummy_output" : "dummy_input") + extension; |
| generateFiles.add(filePkg + "/" + fileName); |
| if (rulePkg.equals(filePkg)) { |
| return ":" + fileName; |
| } else { |
| return filePkg + ":" + fileName; |
| } |
| } |
| |
| private String getDummyRuleLabel(String rulePkg, RuleClass referencedRuleClass) { |
| String referencedRuleName = ruleName + "_ref_" + referencedRuleClass.getName() |
| .replace("$", "").replace(":", ""); |
| // The new generated rule should have the same generatedFiles and generatedRules |
| // in order to avoid duplications |
| BuildRuleWithDefaultsBuilder builder = new BuildRuleWithDefaultsBuilder( |
| referencedRuleClass.getName(), referencedRuleName, ruleClassMap, generateFiles, |
| generateRules); |
| builder.populateAttributes(rulePkg, true); |
| generateRules.put(referencedRuleClass.getName(), builder); |
| return referencedRuleName; |
| } |
| |
| public BuildRuleWithDefaultsBuilder populateLabelAttribute(String pkg, Attribute attribute) { |
| return populateLabelAttribute(pkg, pkg, attribute); |
| } |
| |
| /** |
| * Populates the label type attribute with generated values. Populates with a file if possible, or |
| * generates an appropriate rule. Note, that the rules are always generated in the same package. |
| */ |
| public BuildRuleWithDefaultsBuilder populateLabelAttribute(String rulePkg, String filePkg, |
| Attribute attribute) { |
| Type<?> attrType = attribute.getType(); |
| String label = null; |
| if (attribute.getAllowedFileTypesPredicate() != FileTypeSet.NO_FILE) { |
| // Try to populate with files first |
| String extension = ""; |
| if (attribute.getAllowedFileTypesPredicate() == FileTypeSet.ANY_FILE) { |
| extension = ".txt"; |
| } else if (attribute.getAllowedFileTypesPredicate() != null) { |
| FileTypeSet fileTypes = attribute.getAllowedFileTypesPredicate(); |
| // This argument should always hold, if not that means a Blaze design/implementation error |
| Preconditions.checkArgument( |
| !fileTypes.getExtensions().isEmpty(), |
| "Attribute %s does not have any allowed file types", |
| attribute.getName()); |
| extension = fileTypes.getExtensions().get(0); |
| } |
| label = getDummyFileLabel(rulePkg, filePkg, extension, attrType); |
| } else { |
| Predicate<RuleClass> allowedRuleClasses = attribute.getAllowedRuleClassesPredicate(); |
| if (allowedRuleClasses != Predicates.<RuleClass>alwaysFalse()) { |
| // See if there is an applicable rule among the already enqueued rules |
| BuildRuleBuilder referencedRuleBuilder = getFirstApplicableRule(allowedRuleClasses); |
| if (referencedRuleBuilder != null) { |
| label = ":" + referencedRuleBuilder.ruleName; |
| } else { |
| RuleClass referencedRuleClass = getFirstApplicableRuleClass(allowedRuleClasses); |
| if (referencedRuleClass != null) { |
| // Generate a rule with the appropriate ruleClass and a label for it in |
| // the original rule |
| label = ":" + getDummyRuleLabel(rulePkg, referencedRuleClass); |
| } |
| } |
| } |
| } |
| if (label != null) { |
| if (attrType instanceof ListType<?>) { |
| addMultiValueAttributes(attribute.getName(), label); |
| } else { |
| setSingleValueAttribute(attribute.getName(), label); |
| } |
| } |
| return this; |
| } |
| |
| private BuildRuleBuilder getFirstApplicableRule(Predicate<RuleClass> allowedRuleClasses) { |
| // There is no direct way to get the set of allowedRuleClasses from the Attribute |
| // The Attribute API probably should not be modified for sole testing purposes |
| for (Map.Entry<String, BuildRuleBuilder> entry : generateRules.entrySet()) { |
| if (allowedRuleClasses.apply(ruleClassMap.get(entry.getKey()))) { |
| return entry.getValue(); |
| } |
| } |
| return null; |
| } |
| |
| private RuleClass getFirstApplicableRuleClass(Predicate<RuleClass> allowedRuleClasses) { |
| // See comments in getFirstApplicableRule(Predicate<RuleClass> allowedRuleClasses) |
| for (RuleClass ruleClass : ruleClassMap.values()) { |
| if (allowedRuleClasses.apply(ruleClass)) { |
| return ruleClass; |
| } |
| } |
| return null; |
| } |
| |
| public BuildRuleWithDefaultsBuilder populateStringListAttribute(Attribute attribute) { |
| addMultiValueAttributes(attribute.getName(), "x"); |
| return this; |
| } |
| |
| public BuildRuleWithDefaultsBuilder populateStringAttribute(Attribute attribute) { |
| setSingleValueAttribute(attribute.getName(), "x"); |
| return this; |
| } |
| |
| public BuildRuleWithDefaultsBuilder populateBooleanAttribute(Attribute attribute) { |
| setSingleValueAttribute(attribute.getName(), "false"); |
| return this; |
| } |
| |
| public BuildRuleWithDefaultsBuilder populateIntegerAttribute(Attribute attribute) { |
| setSingleValueAttribute(attribute.getName(), 1); |
| return this; |
| } |
| |
| public BuildRuleWithDefaultsBuilder populateAttributes(String rulePkg, boolean heuristics) { |
| for (Attribute attribute : ruleClass.getAttributes()) { |
| if (attribute.isMandatory()) { |
| if (BuildType.isLabelType(attribute.getType())) { |
| // TODO(bazel-team): actually an empty list would be fine in the case where |
| // attribute instanceof ListType && !attribute.isNonEmpty(), but BuildRuleBuilder |
| // doesn't support that, and it makes little sense anyway |
| populateLabelAttribute(rulePkg, attribute); |
| } else { |
| // Non label type attributes |
| if (attribute.getAllowedValues() instanceof AllowedValueSet) { |
| Collection<Object> allowedValues = |
| ((AllowedValueSet) attribute.getAllowedValues()).getAllowedValues(); |
| setSingleValueAttribute(attribute.getName(), allowedValues.iterator().next()); |
| } else if (attribute.getType() == Type.STRING) { |
| populateStringAttribute(attribute); |
| } else if (attribute.getType() == Type.BOOLEAN) { |
| populateBooleanAttribute(attribute); |
| } else if (attribute.getType() == Type.INTEGER) { |
| populateIntegerAttribute(attribute); |
| } else if (attribute.getType() == Type.STRING_LIST) { |
| populateStringListAttribute(attribute); |
| } |
| } |
| // TODO(bazel-team): populate for other data types |
| } else if (heuristics) { |
| populateAttributesHeuristics(rulePkg, attribute); |
| } |
| } |
| return this; |
| } |
| |
| // Heuristics which might help to generate valid rules. |
| // This is a bit hackish, but it helps some generated ruleclasses to pass analysis phase. |
| private void populateAttributesHeuristics(String rulePkg, Attribute attribute) { |
| if (attribute.getName().equals("srcs") && attribute.getType() == BuildType.LABEL_LIST) { |
| // If there is a srcs attribute it might be better to populate it even if it's not mandatory |
| populateLabelAttribute(rulePkg, attribute); |
| } else if (attribute.getName().equals("main_class") && attribute.getType() == Type.STRING) { |
| populateStringAttribute(attribute); |
| } |
| } |
| |
| @Override |
| public Collection<String> getFilesToGenerate() { |
| return generateFiles; |
| } |
| |
| @Override |
| public Collection<BuildRuleBuilder> getRulesToGenerate() { |
| return generateRules.values(); |
| } |
| } |