blob: 20111cedd614e8e6fa4c8196ca6d7aa08861302e [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.syntax;
import com.google.common.collect.ImmutableList;
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.SkylarkList.MutableList;
import java.util.List;
/**
* A helper class containing additional built in functions for Bazel (BUILD files and .bzl files).
*/
public class BazelLibrary {
@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,
useEnvironment = true
)
private static final BuiltinFunction depset =
new BuiltinFunction("depset") {
public SkylarkNestedSet invoke(
Object items,
String orderString,
Object direct,
Object transitive,
Location loc,
Environment env)
throws EvalException {
Order order;
try {
order = Order.parse(
orderString, env.getSemantics().incompatibleDisallowSetConstructor());
} catch (IllegalArgumentException ex) {
throw new EvalException(loc, ex);
}
if (transitive == Runtime.NONE && direct == Runtime.NONE) {
// Legacy behavior.
return new SkylarkNestedSet(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();
}
@SkylarkSignature(
name = "set",
returnType = SkylarkNestedSet.class,
documentationReturnType = SkylarkNestedSet.LegacySet.class,
doc =
"A temporary alias for <a href=\"#depset\">depset</a>. "
+ "Deprecated in favor of <code>depset</code>.",
parameters = {
@Param(
name = "items",
type = Object.class,
defaultValue = "[]",
doc = "Same as for <a href=\"#depset\">depset</a>."
),
@Param(
name = "order",
type = String.class,
defaultValue = "\"default\"",
doc = "Same as for <a href=\"#depset\">depset</a>."
)
},
useLocation = true,
useEnvironment = true
)
private static final BuiltinFunction set =
new BuiltinFunction("set") {
public SkylarkNestedSet invoke(Object items, String order, Location loc, Environment env)
throws EvalException {
if (env.getSemantics().incompatibleDisallowSetConstructor()) {
throw new EvalException(
loc,
"The `set` constructor for depsets is deprecated and will be removed. Please use "
+ "the `depset` constructor instead. You can temporarily enable the "
+ "deprecated `set` constructor by passing the flag "
+ "--incompatible_disallow_set_constructor=false");
}
try {
return new SkylarkNestedSet(
Order.parse(order, /*forbidDeprecatedOrderNames=*/false),
items, loc);
} catch (IllegalArgumentException ex) {
throw new EvalException(loc, ex);
}
}
};
@SkylarkSignature(
name = "union",
objectType = SkylarkNestedSet.class,
returnType = SkylarkNestedSet.class,
doc =
"<i>(Deprecated)</i> Returns a new <a href=\"depset.html\">depset</a> that is the merge "
+ "of the given depset and <code>new_elements</code>. This is the same as the <code>+"
+ "</code> operator.",
parameters = {
@Param(name = "input", type = SkylarkNestedSet.class, doc = "The input depset."),
@Param(name = "new_elements", type = Object.class, doc = "The elements to be added.")
},
useLocation = true
)
private static final BuiltinFunction union =
new BuiltinFunction("union") {
@SuppressWarnings("unused")
public SkylarkNestedSet invoke(SkylarkNestedSet input, Object newElements, Location loc)
throws EvalException {
// newElements' type is Object because of the polymorphism on unioning two
// SkylarkNestedSets versus a set and another kind of iterable.
// Can't use EvalUtils#toIterable since that would discard this information.
return new SkylarkNestedSet(input, newElements, loc);
}
};
@SkylarkSignature(
name = "to_list",
objectType = SkylarkNestedSet.class,
returnType = MutableList.class,
doc =
"Returns a list of the elements, without duplicates, in the depset's traversal order. "
+ "Note that order is unspecified (but deterministic) for elements that were added "
+ "more than once to the depset. Order is also unspecified for <code>\"default\""
+ "</code>-ordered depsets, and for elements of child depsets whose order differs "
+ "from that of the parent depset. The list is a copy; modifying it has no effect "
+ "on the depset and vice versa.",
parameters = {@Param(name = "input", type = SkylarkNestedSet.class, doc = "The input depset.")},
useEnvironment = true
)
private static final BuiltinFunction toList =
new BuiltinFunction("to_list") {
@SuppressWarnings("unused")
public MutableList<Object> invoke(SkylarkNestedSet input, Environment env) {
return MutableList.copyOf(env, input.toCollection());
}
};
/**
* Returns a function-value implementing "select" (i.e. configurable attributes) in the specified
* package context.
*/
@SkylarkSignature(
name = "select",
doc = "Creates a select from the dict parameter, usable for setting configurable attributes.",
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));
}
};
private static Environment.Frame createGlobals() {
List<BaseFunction> bazelGlobalFunctions = ImmutableList.of(select, depset, set, type);
try (Mutability mutability = Mutability.create("BUILD")) {
Environment env = Environment.builder(mutability)
.useDefaultSemantics()
.build();
Runtime.setupConstants(env);
Runtime.setupMethodEnvironment(env, MethodLibrary.defaultGlobalFunctions);
Runtime.setupMethodEnvironment(env, bazelGlobalFunctions);
return env.getGlobals();
}
}
public static final Environment.Frame GLOBALS = createGlobals();
static {
SkylarkSignatureProcessor.configureSkylarkFunctions(BazelLibrary.class);
}
}