blob: e2540342c7517e14acd7a878f2852f3cb2acc232 [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.
14
15package com.google.devtools.build.lib.rules;
16
17import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.DATA;
18import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST;
19import static com.google.devtools.build.lib.packages.Attribute.attr;
20import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
21import static com.google.devtools.build.lib.packages.Type.INTEGER;
22import static com.google.devtools.build.lib.packages.Type.LABEL;
23import static com.google.devtools.build.lib.packages.Type.LABEL_LIST;
Florian Weikert70265ec2015-08-31 14:37:27 +000024import static com.google.devtools.build.lib.packages.Type.LICENSE;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import static com.google.devtools.build.lib.packages.Type.STRING;
Laurent Le Brun8d4ffc42015-06-24 15:21:50 +000026import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
Francois-Rene Rideau48670f82015-03-19 17:23:35 +000027import static com.google.devtools.build.lib.syntax.SkylarkType.castList;
28import static com.google.devtools.build.lib.syntax.SkylarkType.castMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010029
30import com.google.common.annotations.VisibleForTesting;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import com.google.common.cache.CacheBuilder;
32import com.google.common.cache.CacheLoader;
33import com.google.common.cache.LoadingCache;
34import com.google.common.collect.ImmutableList;
Lukacs Berki57892bd2015-04-13 14:42:28 +000035import com.google.common.collect.ImmutableMap;
Florian Weikert3f53fbb2015-08-07 22:25:50 +000036import com.google.common.collect.Iterables;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import com.google.devtools.build.lib.analysis.BaseRuleClasses;
38import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
39import com.google.devtools.build.lib.analysis.config.RunUnder;
40import com.google.devtools.build.lib.events.Location;
41import com.google.devtools.build.lib.packages.Attribute;
42import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition;
43import com.google.devtools.build.lib.packages.Attribute.LateBoundLabel;
44import com.google.devtools.build.lib.packages.AttributeMap;
45import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SkylarkImplicitOutputsFunctionWithCallback;
46import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SkylarkImplicitOutputsFunctionWithMap;
47import com.google.devtools.build.lib.packages.Package.NameConflictException;
48import com.google.devtools.build.lib.packages.PackageFactory;
49import com.google.devtools.build.lib.packages.PackageFactory.PackageContext;
50import com.google.devtools.build.lib.packages.Rule;
51import com.google.devtools.build.lib.packages.RuleClass;
52import com.google.devtools.build.lib.packages.RuleClass.Builder;
53import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
54import com.google.devtools.build.lib.packages.RuleFactory;
55import com.google.devtools.build.lib.packages.RuleFactory.InvalidRuleException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010056import com.google.devtools.build.lib.packages.TargetUtils;
57import com.google.devtools.build.lib.packages.TestSize;
58import com.google.devtools.build.lib.packages.Type;
59import com.google.devtools.build.lib.packages.Type.ConversionException;
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000060import com.google.devtools.build.lib.syntax.BaseFunction;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000061import com.google.devtools.build.lib.syntax.BuiltinFunction;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010062import com.google.devtools.build.lib.syntax.ClassObject;
63import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
64import com.google.devtools.build.lib.syntax.Environment;
65import com.google.devtools.build.lib.syntax.Environment.NoSuchVariableException;
66import com.google.devtools.build.lib.syntax.EvalException;
67import com.google.devtools.build.lib.syntax.EvalUtils;
68import com.google.devtools.build.lib.syntax.FuncallExpression;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000069import com.google.devtools.build.lib.syntax.FunctionSignature;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010070import com.google.devtools.build.lib.syntax.Label;
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +000071import com.google.devtools.build.lib.syntax.Runtime;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010072import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction;
73import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010074import com.google.devtools.build.lib.syntax.SkylarkList;
Florian Weikert3f53fbb2015-08-07 22:25:50 +000075import com.google.devtools.build.lib.syntax.SkylarkModuleNameResolver;
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000076import com.google.devtools.build.lib.syntax.SkylarkSignature;
77import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +000078import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
Francois-Rene Rideaua8352e92015-06-30 16:59:42 +000079import com.google.devtools.build.lib.vfs.PathFragment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010080
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010081import java.util.Map;
82import java.util.concurrent.ExecutionException;
83
84/**
85 * A helper class to provide an easier API for Skylark rule definitions.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010086 */
87public class SkylarkRuleClassFunctions {
88
89 //TODO(bazel-team): proper enum support
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000090 @SkylarkSignature(name = "DATA_CFG", returnType = ConfigurationTransition.class,
Laurent Le Brund90db7f2015-04-01 14:16:30 +000091 doc = "Experimental. Specifies a transition to the data configuration.")
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010092 private static final Object dataTransition = ConfigurationTransition.DATA;
93
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +000094 @SkylarkSignature(name = "HOST_CFG", returnType = ConfigurationTransition.class,
Laurent Le Brund90db7f2015-04-01 14:16:30 +000095 doc = "Specifies a transition to the host configuration.")
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010096 private static final Object hostTransition = ConfigurationTransition.HOST;
97
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010098 private static final LateBoundLabel<BuildConfiguration> RUN_UNDER =
99 new LateBoundLabel<BuildConfiguration>() {
100 @Override
101 public Label getDefault(Rule rule, BuildConfiguration configuration) {
102 RunUnder runUnder = configuration.getRunUnder();
103 return runUnder == null ? null : runUnder.getLabel();
104 }
105 };
106
107 // TODO(bazel-team): Copied from ConfiguredRuleClassProvider for the transition from built-in
108 // rules to skylark extensions. Using the same instance would require a large refactoring.
109 // If we don't want to support old built-in rules and Skylark simultaneously
110 // (except for transition phase) it's probably OK.
111 private static LoadingCache<String, Label> labelCache =
112 CacheBuilder.newBuilder().build(new CacheLoader<String, Label>() {
113 @Override
114 public Label load(String from) throws Exception {
115 try {
116 return Label.parseAbsolute(from);
117 } catch (Label.SyntaxException e) {
118 throw new Exception(from);
119 }
120 }
121 });
122
123 // TODO(bazel-team): Remove the code duplication (BaseRuleClasses and this class).
Florian Weikert70265ec2015-08-31 14:37:27 +0000124 /** Parent rule class for non-executable non-test Skylark rules. */
Mark Schaller4fa83ac2015-07-10 16:59:37 +0000125 public static final RuleClass baseRule =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100126 BaseRuleClasses.commonCoreAndSkylarkAttributes(
127 new RuleClass.Builder("$base_rule", RuleClassType.ABSTRACT, true))
128 .add(attr("expect_failure", STRING))
129 .build();
130
Florian Weikert70265ec2015-08-31 14:37:27 +0000131 /** Parent rule class for executable non-test Skylark rules. */
132 public static final RuleClass binaryBaseRule =
133 new RuleClass.Builder("$binary_base_rule", RuleClassType.ABSTRACT, true, baseRule)
134 .add(
135 attr("args", STRING_LIST)
136 .nonconfigurable("policy decision: should be consistent across configurations"))
137 .add(attr("output_licenses", LICENSE))
138 .build();
139
Mark Schaller4fa83ac2015-07-10 16:59:37 +0000140 /** Parent rule class for test Skylark rules. */
141 public static final RuleClass testBaseRule =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100142 new RuleClass.Builder("$test_base_rule", RuleClassType.ABSTRACT, true, baseRule)
143 .add(attr("size", STRING).value("medium").taggable()
144 .nonconfigurable("used in loading phase rule validation logic"))
145 .add(attr("timeout", STRING).taggable()
146 .nonconfigurable("used in loading phase rule validation logic").value(
147 new Attribute.ComputedDefault() {
148 @Override
149 public Object getDefault(AttributeMap rule) {
150 TestSize size = TestSize.getTestSize(rule.get("size", Type.STRING));
151 if (size != null) {
152 String timeout = size.getDefaultTimeout().toString();
153 if (timeout != null) {
154 return timeout;
155 }
156 }
157 return "illegal";
158 }
159 }))
160 .add(attr("flaky", BOOLEAN).value(false).taggable()
161 .nonconfigurable("taggable - called in Rule.getRuleTags"))
162 .add(attr("shard_count", INTEGER).value(-1))
163 .add(attr("local", BOOLEAN).value(false).taggable()
164 .nonconfigurable("policy decision: this should be consistent across configurations"))
Laurent Le Brun8d4ffc42015-06-24 15:21:50 +0000165 .add(attr("args", STRING_LIST)
166 .nonconfigurable("policy decision: should be consistent across configurations"))
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100167 .add(attr("$test_runtime", LABEL_LIST).cfg(HOST).value(ImmutableList.of(
168 labelCache.getUnchecked("//tools/test:runtime"))))
169 .add(attr(":run_under", LABEL).cfg(DATA).value(RUN_UNDER))
170 .build();
171
172 /**
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000173 * In native code, private values start with $.
174 * In Skylark, private values start with _, because of the grammar.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100175 */
176 private static String attributeToNative(String oldName, Location loc, boolean isLateBound)
177 throws EvalException {
178 if (oldName.isEmpty()) {
179 throw new EvalException(loc, "Attribute name cannot be empty");
180 }
181 if (isLateBound) {
182 if (oldName.charAt(0) != '_') {
183 throw new EvalException(loc, "When an attribute value is a function, "
184 + "the attribute must be private (start with '_')");
185 }
186 return ":" + oldName.substring(1);
187 }
188 if (oldName.charAt(0) == '_') {
189 return "$" + oldName.substring(1);
190 }
191 return oldName;
192 }
193
194 // TODO(bazel-team): implement attribute copy and other rule properties
195
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000196 @SkylarkSignature(name = "rule", doc =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100197 "Creates a new rule. Store it in a global value, so that it can be loaded and called "
198 + "from BUILD files.",
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +0000199 returnType = BaseFunction.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000200 mandatoryPositionals = {
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +0000201 @Param(name = "implementation", type = BaseFunction.class,
Laurent Le Brunfaf78412015-07-28 16:13:00 +0000202 doc = "the function implementing this rule, must have exactly one parameter: "
David Chen24f2d992015-08-17 17:25:46 +0000203 + "<a href=\"ctx.html\">ctx</a>. The function is called during the analysis phase "
Laurent Le Brunfaf78412015-07-28 16:13:00 +0000204 + "for each instance of the rule. It can access the attributes provided by the user. "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000205 + "It must create actions to generate all the declared outputs.")
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100206 },
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000207 optionalPositionals = {
208 @Param(name = "test", type = Boolean.class, defaultValue = "False",
209 doc = "Whether this rule is a test rule. "
Laurent Le Brunfaf78412015-07-28 16:13:00 +0000210 + "If True, the rule must end with <code>_test</code> (otherwise it must not), "
Laurent Le Brun9178bc22015-06-19 16:00:34 +0000211 + "and there must be an action that generates <code>ctx.outputs.executable</code>."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000212 @Param(name = "attrs", type = Map.class, noneable = true, defaultValue = "None", doc =
213 "dictionary to declare all the attributes of the rule. It maps from an attribute name "
David Chen24f2d992015-08-17 17:25:46 +0000214 + "to an attribute object (see <a href=\"attr.html\">attr</a> module). "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000215 + "Attributes starting with <code>_</code> are private, and can be used to add "
Laurent Le Brun9ba067d2015-05-22 13:55:23 +0000216 + "an implicit dependency on a label. The attribute <code>name</code> is implicitly "
217 + "added and must not be specified. Attributes <code>visibility</code>, "
218 + "<code>deprecation</code>, <code>tags</code>, <code>testonly</code>, and "
219 + "<code>features</code> are implicitly added and might be overriden."),
220 // TODO(bazel-team): need to give the types of these builtin attributes
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000221 @Param(name = "outputs", type = Map.class, callbackEnabled = true, noneable = true,
222 defaultValue = "None", doc = "outputs of this rule. "
223 + "It is a dictionary mapping from string to a template name. "
Brian Silvermane0adfc62015-08-03 09:04:31 +0000224 + "For example: <code>{\"ext\": \"%{name}.ext\"}</code>. <br>"
Laurent Le Brunfaf78412015-07-28 16:13:00 +0000225 + "The dictionary key becomes an attribute in <code>ctx.outputs</code>. "
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000226 // TODO(bazel-team): Make doc more clear, wrt late-bound attributes.
227 + "It may also be a function (which receives <code>ctx.attr</code> as argument) "
228 + "returning such a dictionary."),
229 @Param(name = "executable", type = Boolean.class, defaultValue = "False",
Laurent Le Brunfaf78412015-07-28 16:13:00 +0000230 doc = "whether this rule is marked as executable or not. If True, "
231 + "there must be an action that generates <code>ctx.outputs.executable</code>."),
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000232 @Param(name = "output_to_genfiles", type = Boolean.class, defaultValue = "False",
233 doc = "If true, the files will be generated in the genfiles directory instead of the "
Florian Weikert3f53fbb2015-08-07 22:25:50 +0000234 + "bin directory. This is used for compatibility with existing rules."),
235 @Param(name = "fragments", type = SkylarkList.class, generic1 = String.class,
236 defaultValue = "[]",
237 doc = "List of names of configuration fragments that the rule requires.")},
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000238 useAst = true, useEnvironment = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000239 private static final BuiltinFunction rule = new BuiltinFunction("rule") {
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000240 @SuppressWarnings({"rawtypes", "unchecked"}) // castMap produces
241 // an Attribute.Builder instead of a Attribute.Builder<?> but it's OK.
242 public BaseFunction invoke(BaseFunction implementation, Boolean test, Object attrs,
243 Object implicitOutputs, Boolean executable, Boolean outputToGenfiles,
244 SkylarkList fragments, FuncallExpression ast, Environment funcallEnv)
245 throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100246
Francois-Rene Rideau3a8ddcc2015-08-26 22:30:38 +0000247 funcallEnv.checkLoadingPhase("rule", ast.getLocation());
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000248 RuleClassType type = test ? RuleClassType.TEST : RuleClassType.NORMAL;
Florian Weikert70265ec2015-08-31 14:37:27 +0000249 RuleClass parent = test ? testBaseRule : (executable ? binaryBaseRule : baseRule);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100250
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000251 // We'll set the name later, pass the empty string for now.
Florian Weikert70265ec2015-08-31 14:37:27 +0000252 RuleClass.Builder builder = new RuleClass.Builder("", type, true, parent);
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000253
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000254 if (attrs != Runtime.NONE) {
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000255 for (Map.Entry<String, Attribute.Builder> attr : castMap(
256 attrs, String.class, Attribute.Builder.class, "attrs").entrySet()) {
257 Attribute.Builder<?> attrBuilder = (Attribute.Builder<?>) attr.getValue();
258 String attrName = attributeToNative(attr.getKey(), ast.getLocation(),
259 attrBuilder.hasLateBoundValue());
260 builder.addOrOverrideAttribute(attrBuilder.build(attrName));
261 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000262 }
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000263 if (executable || test) {
264 builder.addOrOverrideAttribute(
265 attr("$is_executable", BOOLEAN).value(true)
266 .nonconfigurable("Called from RunCommand.isExecutable, which takes a Target")
267 .build());
268 builder.setOutputsDefaultExecutable();
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000269 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100270
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000271 if (implicitOutputs != Runtime.NONE) {
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000272 if (implicitOutputs instanceof BaseFunction) {
273 BaseFunction func = (BaseFunction) implicitOutputs;
274 final SkylarkCallbackFunction callback =
275 new SkylarkCallbackFunction(func, ast, (SkylarkEnvironment) funcallEnv);
276 builder.setImplicitOutputsFunction(
277 new SkylarkImplicitOutputsFunctionWithCallback(callback, ast.getLocation()));
278 } else {
279 builder.setImplicitOutputsFunction(new SkylarkImplicitOutputsFunctionWithMap(
280 ImmutableMap.copyOf(castMap(implicitOutputs, String.class, String.class,
281 "implicit outputs of the rule class"))));
282 }
283 }
Laurent Le Brunc2efe452015-04-08 16:27:21 +0000284
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000285 if (outputToGenfiles) {
286 builder.setOutputToGenfiles();
287 }
Florian Weikert3f53fbb2015-08-07 22:25:50 +0000288
Han-Wen Nienhuysd39e1e12015-08-20 13:52:19 +0000289 if (!fragments.isEmpty()) {
290 builder.requiresConfigurationFragments(
291 new SkylarkModuleNameResolver(),
292 Iterables.toArray(castList(fragments, String.class), String.class));
293 }
294
295 builder.setConfiguredTargetFunction(implementation);
296 builder.setRuleDefinitionEnvironment(
297 ((SkylarkEnvironment) funcallEnv).getGlobalEnvironment());
298 return new RuleFunction(builder, type);
299 }
300 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100301
302 // This class is needed for testing
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000303 static final class RuleFunction extends BaseFunction {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100304 // Note that this means that we can reuse the same builder.
305 // This is fine since we don't modify the builder from here.
306 private final RuleClass.Builder builder;
307 private final RuleClassType type;
Francois-Rene Rideaua8352e92015-06-30 16:59:42 +0000308 private PathFragment skylarkFile;
309 private String ruleClassName;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100310
311 public RuleFunction(Builder builder, RuleClassType type) {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000312 super("rule", FunctionSignature.KWARGS);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100313 this.builder = builder;
314 this.type = type;
315 }
316
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000317 @Override
Francois-Rene Rideau76023b92015-04-17 15:31:59 +0000318 @SuppressWarnings("unchecked") // the magic hidden $pkg_context variable is guaranteed
319 // to be a PackageContext
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000320 public Object call(Object[] args, FuncallExpression ast, Environment env)
321 throws EvalException, InterruptedException, ConversionException {
Francois-Rene Rideau3a8ddcc2015-08-26 22:30:38 +0000322 env.checkLoadingPhase(getName(), ast.getLocation());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100323 try {
Francois-Rene Rideaua8352e92015-06-30 16:59:42 +0000324 if (ruleClassName == null || skylarkFile == null) {
325 throw new EvalException(ast.getLocation(),
326 "Invalid rule class hasn't been exported by a Skylark file");
327 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100328 if (type == RuleClassType.TEST != TargetUtils.isTestRuleName(ruleClassName)) {
329 throw new EvalException(ast.getLocation(), "Invalid rule class name '" + ruleClassName
330 + "', test rule class names must end with '_test' and other rule classes must not");
331 }
332 RuleClass ruleClass = builder.build(ruleClassName);
333 PackageContext pkgContext = (PackageContext) env.lookup(PackageFactory.PKG_CONTEXT);
Florian Weikert6a663392015-09-02 14:04:33 +0000334 return RuleFactory.createAndAddRule(
335 pkgContext, ruleClass, (Map<String, Object>) args[0], ast, env.getStackTrace());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100336 } catch (InvalidRuleException | NameConflictException | NoSuchVariableException e) {
337 throw new EvalException(ast.getLocation(), e.getMessage());
338 }
339 }
340
Francois-Rene Rideaua8352e92015-06-30 16:59:42 +0000341 /**
Francois-Rene Rideaua8352e92015-06-30 16:59:42 +0000342 * Export a RuleFunction from a Skylark file with a given name.
343 */
344 void export(PathFragment skylarkFile, String ruleClassName) {
345 this.skylarkFile = skylarkFile;
346 this.ruleClassName = ruleClassName;
347 }
348
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100349 @VisibleForTesting
350 RuleClass.Builder getBuilder() {
351 return builder;
352 }
353 }
354
Francois-Rene Rideaua8352e92015-06-30 16:59:42 +0000355 public static void exportRuleFunctions(SkylarkEnvironment env, PathFragment skylarkFile) {
356 for (String name : env.getDirectVariableNames()) {
357 try {
358 Object value = env.lookup(name);
359 if (value instanceof RuleFunction) {
360 RuleFunction function = (RuleFunction) value;
361 if (function.skylarkFile == null) {
362 function.export(skylarkFile, name);
363 }
364 }
365 } catch (NoSuchVariableException e) {
366 throw new AssertionError(e);
367 }
368 }
369 }
370
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000371 @SkylarkSignature(name = "Label", doc = "Creates a Label referring to a BUILD target. Use "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100372 + "this function only when you want to give a default value for the label attributes. "
Laurent Le Brunfaf78412015-07-28 16:13:00 +0000373 + "The argument must refer to an absolute label. "
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100374 + "Example: <br><pre class=language-python>Label(\"//tools:default\")</pre>",
375 returnType = Label.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000376 mandatoryPositionals = {@Param(name = "label_string", type = String.class,
377 doc = "the label string")},
378 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000379 private static final BuiltinFunction label = new BuiltinFunction("Label") {
380 public Label invoke(String labelString,
381 Location loc) throws EvalException, ConversionException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100382 try {
383 return labelCache.get(labelString);
384 } catch (ExecutionException e) {
385 throw new EvalException(loc, "Illegal absolute label syntax: " + labelString);
386 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000387 }
388 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100389
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000390 @SkylarkSignature(name = "FileType",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100391 doc = "Creates a file filter from a list of strings. For example, to match files ending "
392 + "with .cc or .cpp, use: <pre class=language-python>FileType([\".cc\", \".cpp\"])</pre>",
393 returnType = SkylarkFileType.class,
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000394 mandatoryPositionals = {
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000395 @Param(name = "types", type = SkylarkList.class, generic1 = String.class, defaultValue = "[]",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100396 doc = "a list of the accepted file extensions")})
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000397 private static final BuiltinFunction fileType = new BuiltinFunction("FileType") {
398 public SkylarkFileType invoke(SkylarkList types) throws ConversionException {
399 return SkylarkFileType.of(castList(types, String.class));
400 }
401 };
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100402
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000403 @SkylarkSignature(name = "to_proto",
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100404 doc = "Creates a text message from the struct parameter. This method only works if all "
405 + "struct elements (recursively) are strings, ints, booleans, other structs or a "
406 + "list of these types. Quotes and new lines in strings are escaped. "
407 + "Examples:<br><pre class=language-python>"
408 + "struct(key=123).to_proto()\n# key: 123\n\n"
409 + "struct(key=True).to_proto()\n# key: true\n\n"
410 + "struct(key=[1, 2, 3]).to_proto()\n# key: 1\n# key: 2\n# key: 3\n\n"
411 + "struct(key='text').to_proto()\n# key: \"text\"\n\n"
412 + "struct(key=struct(inner_key='text')).to_proto()\n"
413 + "# key {\n# inner_key: \"text\"\n# }\n\n"
414 + "struct(key=[struct(inner_key=1), struct(inner_key=2)]).to_proto()\n"
415 + "# key {\n# inner_key: 1\n# }\n# key {\n# inner_key: 2\n# }\n\n"
416 + "struct(key=struct(inner_key=struct(inner_inner_key='text'))).to_proto()\n"
417 + "# key {\n# inner_key {\n# inner_inner_key: \"text\"\n# }\n# }\n</pre>",
Francois-Rene Rideaua3ac2022015-04-20 18:35:05 +0000418 objectType = SkylarkClassObject.class, returnType = String.class,
419 mandatoryPositionals = {
420 // TODO(bazel-team): shouldn't we accept any ClassObject?
421 @Param(name = "self", type = SkylarkClassObject.class,
422 doc = "this struct")},
423 useLocation = true)
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000424 private static final BuiltinFunction toProto = new BuiltinFunction("to_proto") {
425 public String invoke(SkylarkClassObject self, Location loc) throws EvalException {
426 StringBuilder sb = new StringBuilder();
427 printTextMessage(self, sb, 0, loc);
428 return sb.toString();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100429 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100430
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000431 private void printTextMessage(ClassObject object, StringBuilder sb,
432 int indent, Location loc) throws EvalException {
433 for (String key : object.getKeys()) {
434 printTextMessage(key, object.getValue(key), sb, indent, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100435 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000436 }
437
438 private void printSimpleTextMessage(String key, Object value, StringBuilder sb,
439 int indent, Location loc, String container) throws EvalException {
440 if (value instanceof ClassObject) {
441 print(sb, key + " {", indent);
442 printTextMessage((ClassObject) value, sb, indent + 1, loc);
443 print(sb, "}", indent);
444 } else if (value instanceof String) {
445 print(sb, key + ": \"" + escape((String) value) + "\"", indent);
446 } else if (value instanceof Integer) {
447 print(sb, key + ": " + value, indent);
448 } else if (value instanceof Boolean) {
449 // We're relying on the fact that Java converts Booleans to Strings in the same way
450 // as the protocol buffers do.
451 print(sb, key + ": " + value, indent);
452 } else {
453 throw new EvalException(loc,
454 "Invalid text format, expected a struct, a string, a bool, or an int but got a "
455 + EvalUtils.getDataTypeName(value) + " for " + container + " '" + key + "'");
456 }
457 }
458
459 private void printTextMessage(String key, Object value, StringBuilder sb,
460 int indent, Location loc) throws EvalException {
461 if (value instanceof SkylarkList) {
462 for (Object item : ((SkylarkList) value)) {
463 // TODO(bazel-team): There should be some constraint on the fields of the structs
464 // in the same list but we ignore that for now.
465 printSimpleTextMessage(key, item, sb, indent, loc, "list element in struct field");
466 }
467 } else {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100468 printSimpleTextMessage(key, value, sb, indent, loc, "struct field");
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000469 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100470 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100471
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000472 private String escape(String string) {
473 // TODO(bazel-team): use guava's SourceCodeEscapers when it's released.
474 return string.replace("\"", "\\\"").replace("\n", "\\n");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100475 }
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000476
477 private void print(StringBuilder sb, String text, int indent) {
478 for (int i = 0; i < indent; i++) {
479 sb.append(" ");
480 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100481 sb.append(text);
482 sb.append("\n");
Francois-Rene Rideau537a90b2015-04-22 06:47:31 +0000483 }
484 };
485
486 static {
487 SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkRuleClassFunctions.class);
488 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100489}