aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/testutil/BuildRuleWithDefaultsBuilder.java
blob: 6a24a801d02323e6719152b054b03bb6300c01ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// Copyright 2014 Google Inc. 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.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 isInput = (attrType == BuildType.LABEL || attrType == BuildType.LABEL_LIST);
    String fileName = (isInput ? "dummy_input" : "dummy_output") + 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.popuplateAttributes(rulePkg, true);
    generateRules.put(referencedRuleClass.getName(), builder);
    return referencedRuleName;
  }

  public BuildRuleWithDefaultsBuilder popuplateLabelAttribute(String pkg, Attribute attribute) {
    return popuplateLabelAttribute(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 popuplateLabelAttribute(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 = null;
      if (attribute.getAllowedFileTypesPredicate() == FileTypeSet.ANY_FILE) {
        extension = ".txt";
      } else {
        FileTypeSet fileTypes = attribute.getAllowedFileTypesPredicate();
        // This argument should always hold, if not that means a Blaze design/implementation error
        Preconditions.checkArgument(!fileTypes.getExtensions().isEmpty());
        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 == BuildType.LABEL_LIST || attrType == BuildType.OUTPUT_LIST) {
        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 popuplateStringListAttribute(Attribute attribute) {
    addMultiValueAttributes(attribute.getName(), "x");
    return this;
  }

  public BuildRuleWithDefaultsBuilder popuplateStringAttribute(Attribute attribute) {
    setSingleValueAttribute(attribute.getName(), "x");
    return this;
  }

  public BuildRuleWithDefaultsBuilder popuplateBooleanAttribute(Attribute attribute) {
    setSingleValueAttribute(attribute.getName(), "false");
    return this;
  }

  public BuildRuleWithDefaultsBuilder popuplateIntegerAttribute(Attribute attribute) {
    setSingleValueAttribute(attribute.getName(), 1);
    return this;
  }

  public BuildRuleWithDefaultsBuilder popuplateAttributes(String rulePkg, boolean heuristics) {
    for (Attribute attribute : ruleClass.getAttributes()) {
      if (attribute.isMandatory()) {
        if (attribute.getType() == BuildType.LABEL_LIST
            || attribute.getType() == BuildType.OUTPUT_LIST) {
          if (attribute.isNonEmpty()) {
            popuplateLabelAttribute(rulePkg, attribute);
          } else {
            // TODO(bazel-team): actually here an empty list would be fine, but BuildRuleBuilder
            // doesn't support that, and it makes little sense anyway
            popuplateLabelAttribute(rulePkg, attribute);
          }
        } else if (attribute.getType() == BuildType.LABEL
            || attribute.getType() == BuildType.OUTPUT) {
          popuplateLabelAttribute(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) {
            popuplateStringAttribute(attribute);
          } else if (attribute.getType() == Type.BOOLEAN) {
            popuplateBooleanAttribute(attribute);
          } else if (attribute.getType() == Type.INTEGER) {
            popuplateIntegerAttribute(attribute);
          } else if (attribute.getType() == Type.STRING_LIST) {
            popuplateStringListAttribute(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
      popuplateLabelAttribute(rulePkg, attribute);
    } else if (attribute.getName().equals("main_class") && attribute.getType() == Type.STRING) {
      popuplateStringAttribute(attribute);
    }
  }

  @Override
  public Collection<String> getFilesToGenerate() {
    return generateFiles;
  }

  @Override
  public Collection<BuildRuleBuilder> getRulesToGenerate() {
    return generateRules.values();
  }
}