| // 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.rules; | |
| import static com.google.devtools.build.lib.syntax.SkylarkType.castList; | |
| import com.google.devtools.build.lib.events.Location; | |
| import com.google.devtools.build.lib.packages.Attribute; | |
| import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; | |
| import com.google.devtools.build.lib.packages.Attribute.SkylarkLateBound; | |
| import com.google.devtools.build.lib.packages.Type; | |
| import com.google.devtools.build.lib.packages.Type.ConversionException; | |
| import com.google.devtools.build.lib.syntax.Environment; | |
| import com.google.devtools.build.lib.syntax.EvalException; | |
| import com.google.devtools.build.lib.syntax.FuncallExpression; | |
| import com.google.devtools.build.lib.syntax.Label; | |
| import com.google.devtools.build.lib.syntax.SkylarkBuiltin; | |
| import com.google.devtools.build.lib.syntax.SkylarkBuiltin.Param; | |
| import com.google.devtools.build.lib.syntax.SkylarkCallbackFunction; | |
| import com.google.devtools.build.lib.syntax.SkylarkEnvironment; | |
| import com.google.devtools.build.lib.syntax.SkylarkFunction; | |
| import com.google.devtools.build.lib.syntax.SkylarkList; | |
| import com.google.devtools.build.lib.syntax.SkylarkModule; | |
| import com.google.devtools.build.lib.syntax.UserDefinedFunction; | |
| import com.google.devtools.build.lib.util.FileTypeSet; | |
| import java.util.Map; | |
| /** | |
| * A helper class to provide Attr module in Skylark. | |
| */ | |
| @SkylarkModule(name = "attr", namespace = true, onlyLoadingPhase = true, | |
| doc = "Module for creating new attributes. " | |
| + "They are only for use with the <code>rule</code> function.") | |
| public final class SkylarkAttr { | |
| private static final String MANDATORY_DOC = | |
| "set to True if users have to explicitely specify the value"; | |
| private static final String NON_EMPTY_DOC = | |
| "set to True if the attribute must not be empty"; | |
| private static final String ALLOW_FILES_DOC = | |
| "whether File targets are allowed. Can be True, False (default), or " | |
| + "a FileType filter."; | |
| private static final String ALLOW_RULES_DOC = | |
| "which rule targets (name of the classes) are allowed. This is deprecated (kept only for " | |
| + "compatiblity), use providers instead."; | |
| private static final String FLAGS_DOC = | |
| "deprecated, will be removed"; | |
| private static final String DEFAULT_DOC = | |
| "sets the default value of the attribute."; | |
| private static final String CONFIGURATION_DOC = | |
| "configuration of the attribute. " | |
| + "For example, use DATA_CFG or HOST_CFG."; | |
| private static final String EXECUTABLE_DOC = | |
| "set to True if the labels have to be executable. This means the label must refer to an " | |
| + "executable file, or to a rule that outputs an executable file. Access the labels with " | |
| + "<code>ctx.executable.<attribute_name></code>."; | |
| private static Attribute.Builder<?> createAttribute(Type<?> type, Map<String, Object> arguments, | |
| FuncallExpression ast, SkylarkEnvironment env) throws EvalException, ConversionException { | |
| final Location loc = ast.getLocation(); | |
| // We use an empty name now so that we can set it later. | |
| // This trick makes sense only in the context of Skylark (builtin rules should not use it). | |
| Attribute.Builder<?> builder = Attribute.attr("", type); | |
| Object defaultValue = arguments.get("default"); | |
| if (defaultValue != null) { | |
| if (defaultValue instanceof UserDefinedFunction) { | |
| // Late bound attribute. Non label type attributes already caused a type check error. | |
| builder.value(new SkylarkLateBound( | |
| new SkylarkCallbackFunction((UserDefinedFunction) defaultValue, ast, env))); | |
| } else { | |
| builder.defaultValue(defaultValue); | |
| } | |
| } | |
| for (String flag : castList(arguments.get("flags"), String.class)) { | |
| builder.setPropertyFlag(flag); | |
| } | |
| if (arguments.containsKey("mandatory") && (Boolean) arguments.get("mandatory")) { | |
| builder.setPropertyFlag("MANDATORY"); | |
| } | |
| if (arguments.containsKey("non_empty") && (Boolean) arguments.get("non_empty")) { | |
| builder.setPropertyFlag("NON_EMPTY"); | |
| } | |
| if (arguments.containsKey("executable") && (Boolean) arguments.get("executable")) { | |
| builder.setPropertyFlag("EXECUTABLE"); | |
| } | |
| if (arguments.containsKey("single_file") && (Boolean) arguments.get("single_file")) { | |
| builder.setPropertyFlag("SINGLE_ARTIFACT"); | |
| } | |
| if (arguments.containsKey("allow_files")) { | |
| Object fileTypesObj = arguments.get("allow_files"); | |
| if (fileTypesObj == Boolean.TRUE) { | |
| builder.allowedFileTypes(FileTypeSet.ANY_FILE); | |
| } else if (fileTypesObj == Boolean.FALSE) { | |
| builder.allowedFileTypes(FileTypeSet.NO_FILE); | |
| } else if (fileTypesObj instanceof SkylarkFileType) { | |
| builder.allowedFileTypes(((SkylarkFileType) fileTypesObj).getFileTypeSet()); | |
| } else { | |
| throw new EvalException(loc, "allow_files should be a boolean or a filetype object."); | |
| } | |
| } else if (type.equals(Type.LABEL) || type.equals(Type.LABEL_LIST)) { | |
| builder.allowedFileTypes(FileTypeSet.NO_FILE); | |
| } | |
| Object ruleClassesObj = arguments.get("allow_rules"); | |
| if (ruleClassesObj != null) { | |
| builder.allowedRuleClasses(castList(ruleClassesObj, String.class, | |
| "allowed rule classes for attribute definition")); | |
| } | |
| if (arguments.containsKey("providers")) { | |
| builder.mandatoryProviders(castList(arguments.get("providers"), String.class)); | |
| } | |
| if (arguments.containsKey("cfg")) { | |
| builder.cfg((ConfigurationTransition) arguments.get("cfg")); | |
| } | |
| return builder; | |
| } | |
| private static Object createAttribute(Map<String, Object> kwargs, Type<?> type, | |
| FuncallExpression ast, Environment env) throws EvalException { | |
| try { | |
| return createAttribute(type, kwargs, ast, (SkylarkEnvironment) env); | |
| } catch (ConversionException e) { | |
| throw new EvalException(ast.getLocation(), e.getMessage()); | |
| } | |
| } | |
| @SkylarkBuiltin(name = "int", doc = | |
| "Creates an attribute of type int.", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = Integer.class, | |
| doc = DEFAULT_DOC + " If not specified, default is 0."), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction integer = new SkylarkFunction("int") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.INTEGER, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "string", doc = | |
| "Creates an attribute of type string.", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = String.class, | |
| doc = DEFAULT_DOC + " If not specified, default is \"\"."), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction string = new SkylarkFunction("string") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.STRING, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "label", doc = | |
| "Creates an attribute of type Label. " | |
| + "It is the only way to specify a dependency to another target. " | |
| + "If you need a dependency that the user cannot overwrite, make the attribute " | |
| + "private (starts with <code>_</code>).", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = Label.class, callbackEnabled = true, | |
| doc = DEFAULT_DOC + " If not specified, default is None. " | |
| + "Use the <code>Label</code> function to specify a default value."), | |
| @Param(name = "executable", type = Boolean.class, doc = EXECUTABLE_DOC), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "allow_files", doc = ALLOW_FILES_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "providers", type = SkylarkList.class, generic1 = String.class, | |
| doc = "mandatory providers every dependency has to have"), | |
| @Param(name = "allow_rules", type = SkylarkList.class, generic1 = String.class, | |
| doc = ALLOW_RULES_DOC), | |
| @Param(name = "single_file", doc = | |
| "if True, the label must correspond to a single File. " | |
| + "Access it through <code>ctx.file.<attribute_name></code>."), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction label = new SkylarkFunction("label") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.LABEL, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "string_list", doc = | |
| "Creates an attribute of type list of strings", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = SkylarkList.class, generic1 = String.class, | |
| doc = DEFAULT_DOC + " If not specified, default is []."), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "non_empty", type = Boolean.class, doc = NON_EMPTY_DOC), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, | |
| doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction stringList = new SkylarkFunction("string_list") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.STRING_LIST, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "label_list", doc = | |
| "Creates an attribute of type list of labels. " | |
| + "See <code>label</code> for more information.", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = SkylarkList.class, generic1 = Label.class, | |
| callbackEnabled = true, | |
| doc = DEFAULT_DOC + " If not specified, default is []. " | |
| + "Use the <code>Label</code> function to specify a default value."), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "allow_files", doc = ALLOW_FILES_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "allow_rules", type = SkylarkList.class, generic1 = String.class, | |
| doc = ALLOW_RULES_DOC), | |
| @Param(name = "non_empty", type = Boolean.class, doc = NON_EMPTY_DOC), | |
| @Param(name = "providers", type = SkylarkList.class, generic1 = String.class, | |
| doc = "mandatory providers every dependency has to have"), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction labelList = new SkylarkFunction("label_list") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.LABEL_LIST, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "bool", doc = | |
| "Creates an attribute of type bool. Its default value is False.", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = Boolean.class, doc = DEFAULT_DOC), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction bool = new SkylarkFunction("bool") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.BOOLEAN, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "output", doc = | |
| "Creates an attribute of type output. Its default value is None. " | |
| + "The user provides a file name (string) and the rule must create an action that " | |
| + "generates the file.", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = Label.class, doc = DEFAULT_DOC), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction output = new SkylarkFunction("output") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.OUTPUT, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "output_list", doc = | |
| "Creates an attribute of type list of outputs. Its default value is []. " | |
| + "See <code>output</code> above for more information.", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = SkylarkList.class, generic1 = Label.class, doc = DEFAULT_DOC), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "non_empty", type = Boolean.class, doc = NON_EMPTY_DOC), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction outputList = new SkylarkFunction("output_list") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.OUTPUT_LIST, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "string_dict", doc = | |
| "Creates an attribute of type dictionary, mapping from string to string. " | |
| + "Its default value is {}.", | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", type = Map.class, doc = DEFAULT_DOC), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "non_empty", type = Boolean.class, doc = NON_EMPTY_DOC), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction stringDict = new SkylarkFunction("string_dict") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.STRING_DICT, ast, env); | |
| } | |
| }; | |
| @SkylarkBuiltin(name = "license", doc = | |
| "Creates an attribute of type license. Its default value is NO_LICENSE.", | |
| // TODO(bazel-team): Implement proper license support for Skylark. | |
| objectType = SkylarkAttr.class, | |
| returnType = Attribute.class, | |
| optionalParams = { | |
| @Param(name = "default", doc = DEFAULT_DOC), | |
| @Param(name = "flags", type = SkylarkList.class, generic1 = String.class, doc = FLAGS_DOC), | |
| @Param(name = "mandatory", type = Boolean.class, doc = MANDATORY_DOC), | |
| @Param(name = "cfg", type = ConfigurationTransition.class, doc = CONFIGURATION_DOC)}) | |
| private static SkylarkFunction license = new SkylarkFunction("license") { | |
| @Override | |
| public Object call(Map<String, Object> kwargs, FuncallExpression ast, Environment env) | |
| throws EvalException { | |
| return createAttribute(kwargs, Type.LICENSE, ast, env); | |
| } | |
| }; | |
| } |