blob: 6a24a801d02323e6719152b054b03bb6300c01ac [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.testutil;
15
16import com.google.common.base.Preconditions;
17import com.google.common.base.Predicate;
18import com.google.common.base.Predicates;
19import com.google.devtools.build.lib.packages.Attribute;
20import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet;
Lukacs Berkiffa73ad2015-09-18 11:40:12 +000021import com.google.devtools.build.lib.packages.BuildType;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010022import com.google.devtools.build.lib.packages.RuleClass;
Lukacs Berkiffa73ad2015-09-18 11:40:12 +000023import com.google.devtools.build.lib.syntax.Type;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010024import com.google.devtools.build.lib.util.FileTypeSet;
25
26import java.util.Collection;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.Map;
30import java.util.Set;
31
32/**
33 * A helper class to generate valid rules with filled attributes if necessary.
34 */
35public class BuildRuleWithDefaultsBuilder extends BuildRuleBuilder {
36
37 private Set<String> generateFiles;
38 private Map<String, BuildRuleBuilder> generateRules;
39
40 public BuildRuleWithDefaultsBuilder(String ruleClass, String ruleName) {
41 super(ruleClass, ruleName);
42 this.generateFiles = new HashSet<>();
43 this.generateRules = new HashMap<>();
44 }
45
46 private BuildRuleWithDefaultsBuilder(String ruleClass, String ruleName,
47 Map<String, RuleClass> ruleClassMap, Set<String> generateFiles,
48 Map<String, BuildRuleBuilder> generateRules) {
49 super(ruleClass, ruleName, ruleClassMap);
50 this.generateFiles = generateFiles;
51 this.generateRules = generateRules;
52 }
53
54 /**
55 * Creates a dummy file with the given extension in the given package and returns a valid Blaze
56 * label referring to the file. Note, the created label depends on the package of the rule.
57 */
58 private String getDummyFileLabel(String rulePkg, String filePkg, String extension,
59 Type<?> attrType) {
Lukacs Berkiffa73ad2015-09-18 11:40:12 +000060 boolean isInput = (attrType == BuildType.LABEL || attrType == BuildType.LABEL_LIST);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010061 String fileName = (isInput ? "dummy_input" : "dummy_output") + extension;
62 generateFiles.add(filePkg + "/" + fileName);
63 if (rulePkg.equals(filePkg)) {
64 return ":" + fileName;
65 } else {
66 return filePkg + ":" + fileName;
67 }
68 }
69
70 private String getDummyRuleLabel(String rulePkg, RuleClass referencedRuleClass) {
71 String referencedRuleName = ruleName + "_ref_" + referencedRuleClass.getName()
72 .replace("$", "").replace(":", "");
73 // The new generated rule should have the same generatedFiles and generatedRules
74 // in order to avoid duplications
75 BuildRuleWithDefaultsBuilder builder = new BuildRuleWithDefaultsBuilder(
76 referencedRuleClass.getName(), referencedRuleName, ruleClassMap, generateFiles,
77 generateRules);
78 builder.popuplateAttributes(rulePkg, true);
79 generateRules.put(referencedRuleClass.getName(), builder);
80 return referencedRuleName;
81 }
82
83 public BuildRuleWithDefaultsBuilder popuplateLabelAttribute(String pkg, Attribute attribute) {
84 return popuplateLabelAttribute(pkg, pkg, attribute);
85 }
86
87 /**
88 * Populates the label type attribute with generated values. Populates with a file if possible, or
89 * generates an appropriate rule. Note, that the rules are always generated in the same package.
90 */
91 public BuildRuleWithDefaultsBuilder popuplateLabelAttribute(String rulePkg, String filePkg,
92 Attribute attribute) {
93 Type<?> attrType = attribute.getType();
94 String label = null;
95 if (attribute.getAllowedFileTypesPredicate() != FileTypeSet.NO_FILE) {
96 // Try to populate with files first
97 String extension = null;
98 if (attribute.getAllowedFileTypesPredicate() == FileTypeSet.ANY_FILE) {
99 extension = ".txt";
100 } else {
101 FileTypeSet fileTypes = attribute.getAllowedFileTypesPredicate();
102 // This argument should always hold, if not that means a Blaze design/implementation error
Ulf Adams795895a2015-03-06 15:58:35 +0000103 Preconditions.checkArgument(!fileTypes.getExtensions().isEmpty());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100104 extension = fileTypes.getExtensions().get(0);
105 }
106 label = getDummyFileLabel(rulePkg, filePkg, extension, attrType);
107 } else {
108 Predicate<RuleClass> allowedRuleClasses = attribute.getAllowedRuleClassesPredicate();
109 if (allowedRuleClasses != Predicates.<RuleClass>alwaysFalse()) {
110 // See if there is an applicable rule among the already enqueued rules
111 BuildRuleBuilder referencedRuleBuilder = getFirstApplicableRule(allowedRuleClasses);
112 if (referencedRuleBuilder != null) {
113 label = ":" + referencedRuleBuilder.ruleName;
114 } else {
115 RuleClass referencedRuleClass = getFirstApplicableRuleClass(allowedRuleClasses);
116 if (referencedRuleClass != null) {
117 // Generate a rule with the appropriate ruleClass and a label for it in
118 // the original rule
119 label = ":" + getDummyRuleLabel(rulePkg, referencedRuleClass);
120 }
121 }
122 }
123 }
124 if (label != null) {
Lukacs Berkiffa73ad2015-09-18 11:40:12 +0000125 if (attrType == BuildType.LABEL_LIST || attrType == BuildType.OUTPUT_LIST) {
Googlerd43d3912015-02-09 15:23:56 +0000126 addMultiValueAttributes(attribute.getName(), label);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100127 } else {
128 setSingleValueAttribute(attribute.getName(), label);
129 }
130 }
131 return this;
132 }
133
134 private BuildRuleBuilder getFirstApplicableRule(Predicate<RuleClass> allowedRuleClasses) {
135 // There is no direct way to get the set of allowedRuleClasses from the Attribute
136 // The Attribute API probably should not be modified for sole testing purposes
137 for (Map.Entry<String, BuildRuleBuilder> entry : generateRules.entrySet()) {
138 if (allowedRuleClasses.apply(ruleClassMap.get(entry.getKey()))) {
139 return entry.getValue();
140 }
141 }
142 return null;
143 }
144
145 private RuleClass getFirstApplicableRuleClass(Predicate<RuleClass> allowedRuleClasses) {
146 // See comments in getFirstApplicableRule(Predicate<RuleClass> allowedRuleClasses)
147 for (RuleClass ruleClass : ruleClassMap.values()) {
148 if (allowedRuleClasses.apply(ruleClass)) {
149 return ruleClass;
150 }
151 }
152 return null;
153 }
154
155 public BuildRuleWithDefaultsBuilder popuplateStringListAttribute(Attribute attribute) {
Googlerd43d3912015-02-09 15:23:56 +0000156 addMultiValueAttributes(attribute.getName(), "x");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100157 return this;
158 }
159
160 public BuildRuleWithDefaultsBuilder popuplateStringAttribute(Attribute attribute) {
161 setSingleValueAttribute(attribute.getName(), "x");
162 return this;
163 }
164
165 public BuildRuleWithDefaultsBuilder popuplateBooleanAttribute(Attribute attribute) {
166 setSingleValueAttribute(attribute.getName(), "false");
167 return this;
168 }
169
170 public BuildRuleWithDefaultsBuilder popuplateIntegerAttribute(Attribute attribute) {
171 setSingleValueAttribute(attribute.getName(), 1);
172 return this;
173 }
174
175 public BuildRuleWithDefaultsBuilder popuplateAttributes(String rulePkg, boolean heuristics) {
176 for (Attribute attribute : ruleClass.getAttributes()) {
177 if (attribute.isMandatory()) {
Lukacs Berkiffa73ad2015-09-18 11:40:12 +0000178 if (attribute.getType() == BuildType.LABEL_LIST
179 || attribute.getType() == BuildType.OUTPUT_LIST) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100180 if (attribute.isNonEmpty()) {
181 popuplateLabelAttribute(rulePkg, attribute);
182 } else {
183 // TODO(bazel-team): actually here an empty list would be fine, but BuildRuleBuilder
184 // doesn't support that, and it makes little sense anyway
185 popuplateLabelAttribute(rulePkg, attribute);
186 }
Lukacs Berkiffa73ad2015-09-18 11:40:12 +0000187 } else if (attribute.getType() == BuildType.LABEL
188 || attribute.getType() == BuildType.OUTPUT) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100189 popuplateLabelAttribute(rulePkg, attribute);
190 } else {
191 // Non label type attributes
192 if (attribute.getAllowedValues() instanceof AllowedValueSet) {
193 Collection<Object> allowedValues =
194 ((AllowedValueSet) attribute.getAllowedValues()).getAllowedValues();
195 setSingleValueAttribute(attribute.getName(), allowedValues.iterator().next());
196 } else if (attribute.getType() == Type.STRING) {
197 popuplateStringAttribute(attribute);
198 } else if (attribute.getType() == Type.BOOLEAN) {
199 popuplateBooleanAttribute(attribute);
200 } else if (attribute.getType() == Type.INTEGER) {
201 popuplateIntegerAttribute(attribute);
202 } else if (attribute.getType() == Type.STRING_LIST) {
203 popuplateStringListAttribute(attribute);
204 }
205 }
206 // TODO(bazel-team): populate for other data types
207 } else if (heuristics) {
208 populateAttributesHeuristics(rulePkg, attribute);
209 }
210 }
211 return this;
212 }
213
214 // Heuristics which might help to generate valid rules.
215 // This is a bit hackish, but it helps some generated ruleclasses to pass analysis phase.
216 private void populateAttributesHeuristics(String rulePkg, Attribute attribute) {
Lukacs Berkiffa73ad2015-09-18 11:40:12 +0000217 if (attribute.getName().equals("srcs") && attribute.getType() == BuildType.LABEL_LIST) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100218 // If there is a srcs attribute it might be better to populate it even if it's not mandatory
219 popuplateLabelAttribute(rulePkg, attribute);
220 } else if (attribute.getName().equals("main_class") && attribute.getType() == Type.STRING) {
221 popuplateStringAttribute(attribute);
222 }
223 }
224
225 @Override
226 public Collection<String> getFilesToGenerate() {
227 return generateFiles;
228 }
229
230 @Override
231 public Collection<BuildRuleBuilder> getRulesToGenerate() {
232 return generateRules.values();
233 }
234}