blob: 205f98f167c78225f6ac7eff41c0176e0b35f615 [file] [log] [blame]
// Copyright 2016 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.packages;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
import com.google.devtools.build.lib.syntax.BaseFunction;
import com.google.devtools.build.lib.syntax.BuiltinFunction;
import com.google.devtools.build.lib.syntax.Environment.GlobalFrame;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.EvalUtils;
import com.google.devtools.build.lib.syntax.MethodLibrary;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SelectorList;
import com.google.devtools.build.lib.syntax.SelectorValue;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
import com.google.devtools.build.lib.syntax.SkylarkType;
/**
* A helper class containing additional built in functions for Bazel (BUILD files and .bzl files).
*/
public class BazelLibrary {
// TODO(bazel-team): Move to MethodLibrary alongside other pure-Skylark builtins.
@SkylarkSignature(
name = "type",
returnType = String.class,
doc =
"Returns the type name of its argument. This is useful for debugging and "
+ "type-checking. Examples:"
+ "<pre class=\"language-python\">"
+ "type(2) == \"int\"\n"
+ "type([1]) == \"list\"\n"
+ "type(struct(a = 2)) == \"struct\""
+ "</pre>"
+ "This function might change in the future. To write Python-compatible code and "
+ "be future-proof, use it only to compare return values: "
+ "<pre class=\"language-python\">"
+ "if type(x) == type([]): # if x is a list"
+ "</pre>",
parameters = {@Param(name = "x", doc = "The object to check type of.")}
)
private static final BuiltinFunction type =
new BuiltinFunction("type") {
public String invoke(Object object) {
// There is no 'type' type in Skylark, so we return a string with the type name.
return EvalUtils.getDataTypeName(object, false);
}
};
@SkylarkSignature(
name = "depset",
returnType = SkylarkNestedSet.class,
doc =
"Creates a <a href=\"depset.html\">depset</a>. The <code>direct</code> parameter is a list "
+ "of direct elements of the depset, and <code>transitive</code> parameter is "
+ "a list of depsets whose elements become indirect elements of the created depset. "
+ "The order in which elements are returned when the depset is converted to a list "
+ "is specified by the <code>order</code> parameter. "
+ "See the <a href=\"../depsets.md\">Depsets overview</a> for more information. "
+ "<p> All elements (direct and indirect) of a depset must be of the same type. "
+ "<p> The order of the created depset should be <i>compatible</i> with the order of "
+ "its <code>transitive</code> depsets. <code>\"default\"</code> order is compatible "
+ "with any other order, all other orders are only compatible with themselves."
+ "<p> Note on backward/forward compatibility. This function currently accepts a "
+ "positional <code>items</code> parameter. It is deprecated and will be removed "
+ "in the future, and after its removal <code>direct</code> will become a sole "
+ "positional parameter of the <code>depset</code> function. Thus, both of the "
+ "following calls are equivalent and future-proof:<br>"
+ "<pre class=language-python>"
+ "depset(['a', 'b'], transitive = [...])\n"
+ "depset(direct = ['a', 'b'], transitive = [...])\n"
+ "</pre>",
parameters = {
@Param(
name = "items",
type = Object.class,
defaultValue = "[]",
doc =
"Deprecated: Either an iterable whose items become the direct elements of "
+ "the new depset, in left-to-right order, or else a depset that becomes "
+ "a transitive element of the new depset. In the latter case, "
+ "<code>transitive</code> cannot be specified."
),
@Param(
name = "order",
type = String.class,
defaultValue = "\"default\"",
doc =
"The traversal strategy for the new depset. See <a href=\"depset.html\">here</a> for "
+ "the possible values."
),
@Param(
name = "direct",
type = SkylarkList.class,
defaultValue = "None",
positional = false,
named = true,
noneable = true,
doc = "A list of <i>direct</i> elements of a depset."
),
@Param(
name = "transitive",
named = true,
positional = false,
type = SkylarkList.class,
generic1 = SkylarkNestedSet.class,
noneable = true,
doc = "A list of depsets whose elements will become indirect elements of the depset.",
defaultValue = "None"
)
},
useLocation = true
)
private static final BuiltinFunction depset =
new BuiltinFunction("depset") {
public SkylarkNestedSet invoke(
Object items, String orderString, Object direct, Object transitive, Location loc)
throws EvalException {
Order order;
try {
order = Order.parse(orderString);
} catch (IllegalArgumentException ex) {
throw new EvalException(loc, ex);
}
if (transitive == Runtime.NONE && direct == Runtime.NONE) {
// Legacy behavior.
return SkylarkNestedSet.of(order, items, loc);
}
if (direct != Runtime.NONE && !isEmptySkylarkList(items)) {
throw new EvalException(
loc, "Do not pass both 'direct' and 'items' argument to depset constructor.");
}
// Non-legacy behavior: either 'transitive' or 'direct' were specified.
Iterable<Object> directElements;
if (direct != Runtime.NONE) {
directElements = ((SkylarkList<?>) direct).getContents(Object.class, "direct");
} else {
SkylarkType.checkType(items, SkylarkList.class, "items");
directElements = ((SkylarkList<?>) items).getContents(Object.class, "items");
}
Iterable<SkylarkNestedSet> transitiveList;
if (transitive != Runtime.NONE) {
SkylarkType.checkType(transitive, SkylarkList.class, "transitive");
transitiveList =
((SkylarkList<?>) transitive).getContents(SkylarkNestedSet.class, "transitive");
} else {
transitiveList = ImmutableList.of();
}
SkylarkNestedSet.Builder builder = SkylarkNestedSet.builder(order, loc);
for (Object directElement : directElements) {
builder.addDirect(directElement);
}
for (SkylarkNestedSet transitiveSet : transitiveList) {
builder.addTransitive(transitiveSet);
}
return builder.build();
}
};
private static boolean isEmptySkylarkList(Object o) {
return o instanceof SkylarkList && ((SkylarkList) o).isEmpty();
}
/**
* Returns a function-value implementing "select" (i.e. configurable attributes) in the specified
* package context.
*/
@SkylarkSignature(
name = "select",
doc =
"<code>select()</code> is the helper function that makes a rule attribute "
+ "<a href=\"$BE_ROOT/common-definitions.html#configurable-attributes\">configurable</a>. "
+ "See <a href=\"$BE_ROOT/functions.html#select\">build encyclopedia</a> for details.",
parameters = {
@Param(name = "x", type = SkylarkDict.class, doc = "The parameter to convert."),
@Param(
name = "no_match_error",
type = String.class,
defaultValue = "''",
doc = "Optional custom error to report if no condition matches."
)
},
useLocation = true
)
private static final BuiltinFunction select =
new BuiltinFunction("select") {
public Object invoke(SkylarkDict<?, ?> dict, String noMatchError, Location loc)
throws EvalException {
for (Object key : dict.keySet()) {
if (!(key instanceof String)) {
throw new EvalException(
loc, String.format("Invalid key: %s. select keys must be label references", key));
}
}
return SelectorList.of(new SelectorValue(dict, noMatchError));
}
};
/** Adds bindings for all the builtin functions of this class to the given map builder. */
public static void addBindingsToBuilder(ImmutableMap.Builder<String, Object> builder) {
for (BaseFunction function : allFunctions) {
builder.put(function.getName(), function);
}
}
private static final ImmutableList<BaseFunction> allFunctions =
ImmutableList.of(select, depset, type);
/** A global frame containing pure Skylark builtins and some Bazel builtins. */
public static final GlobalFrame GLOBALS = createGlobals();
private static GlobalFrame createGlobals() {
ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
Runtime.addConstantsToBuilder(builder);
MethodLibrary.addBindingsToBuilder(builder);
BazelLibrary.addBindingsToBuilder(builder);
return GlobalFrame.createForBuiltins(builder.build());
}
static {
SkylarkSignatureProcessor.configureSkylarkFunctions(BazelLibrary.class);
}
}