blob: 13e25fe3c90d99281d7c40e2bc33d5054833b19b [file] [log] [blame]
// 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();
}
}