aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java24
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java164
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java137
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java86
-rw-r--r--src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java24
8 files changed, 334 insertions, 126 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 0456b44512..750ccf2658 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -1360,7 +1360,7 @@ public final class PackageFactory {
}
byte[] buildFileBytes = maybeGetBuildFileBytes(buildFile, eventHandler);
if (buildFileBytes == null) {
- throw new BuildFileContainsErrorsException(packageId, "IOException occured");
+ throw new BuildFileContainsErrorsException(packageId, "IOException occurred");
}
Globber globber = createLegacyGlobber(buildFile.getParentDirectory(), packageId, locator);
@@ -1507,9 +1507,10 @@ public final class PackageFactory {
*/
private ClassObject newNativeModule() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
- Runtime.BuiltinRegistry builtins = Runtime.getBuiltinRegistry();
- for (String nativeFunction : builtins.getFunctionNames(SkylarkNativeModule.class)) {
- builder.put(nativeFunction, builtins.getFunction(SkylarkNativeModule.class, nativeFunction));
+ SkylarkNativeModule nativeModuleInstance = new SkylarkNativeModule();
+ for (String nativeFunction : FuncallExpression.getMethodNames(SkylarkNativeModule.class)) {
+ builder.put(nativeFunction,
+ FuncallExpression.getBuiltinCallable(nativeModuleInstance, nativeFunction));
}
builder.putAll(ruleFunctions);
builder.put("package", newPackageFunction(packageArguments));
@@ -1529,6 +1530,17 @@ public final class PackageFactory {
// or if not possible, at least make them straight copies from the native module variant.
// or better, use a common Environment.Frame for these common bindings
// (that shares a backing ImmutableMap for the bindings?)
+ Object packageNameFunction;
+ Object repositoryNameFunction;
+ try {
+ packageNameFunction = nativeModule.getValue("package_name");
+ repositoryNameFunction = nativeModule.getValue("repository_name");
+ } catch (EvalException exception) {
+ // This should not occur, as nativeModule.getValue should never throw an exception.
+ throw new IllegalStateException(
+ "error getting package_name or repository_name functions from the native module",
+ exception);
+ }
pkgEnv
.setup("native", nativeModule)
.setup("distribs", newDistribsFunction.apply(context))
@@ -1537,8 +1549,8 @@ public final class PackageFactory {
.setup("exports_files", newExportsFilesFunction.apply())
.setup("package_group", newPackageGroupFunction.apply())
.setup("package", newPackageFunction(packageArguments))
- .setup("package_name", SkylarkNativeModule.packageName)
- .setup("repository_name", SkylarkNativeModule.repositoryName)
+ .setup("package_name", packageNameFunction)
+ .setup("repository_name", repositoryNameFunction)
.setup("environment_group", newEnvironmentGroupFunction.apply(context));
for (Entry<String, BuiltinRuleFunction> entry : ruleFunctions.entrySet()) {
diff --git a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
index 0f23a59ed5..43220320f5 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/SkylarkNativeModule.java
@@ -17,17 +17,15 @@ package com.google.devtools.build.lib.packages;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.skylarkinterface.Param;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
-import com.google.devtools.build.lib.syntax.BuiltinFunction;
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.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
-import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor;
import com.google.devtools.build.lib.syntax.Type.ConversionException;
/**
@@ -36,7 +34,6 @@ import com.google.devtools.build.lib.syntax.Type.ConversionException;
*/
@SkylarkModule(
name = "native",
- namespace = true,
category = SkylarkModuleCategory.BUILTIN,
doc =
"A built-in module to support native rules and other package helper functions. "
@@ -49,10 +46,8 @@ import com.google.devtools.build.lib.syntax.Type.ConversionException;
)
public class SkylarkNativeModule {
- @SkylarkSignature(
+ @SkylarkCallable(
name = "glob",
- objectType = SkylarkNativeModule.class,
- returnType = SkylarkList.class,
doc =
"Glob returns a list of every file in the current package that:<ul>\n"
+ "<li>Matches at least one pattern in <code>include</code>.</li>\n"
@@ -86,24 +81,19 @@ public class SkylarkNativeModule {
useAst = true,
useEnvironment = true
)
- private static final BuiltinFunction glob =
- new BuiltinFunction("glob") {
- public SkylarkList invoke(
- SkylarkList include,
- SkylarkList exclude,
- Integer excludeDirectories,
- FuncallExpression ast,
- Environment env)
- throws EvalException, ConversionException, InterruptedException {
- env.checkLoadingPhase("native.glob", ast.getLocation());
- return PackageFactory.callGlob(null, include, exclude, excludeDirectories != 0, ast, env);
- }
- };
+ public SkylarkList<?> glob(
+ SkylarkList<?> include,
+ SkylarkList<?> exclude,
+ Integer excludeDirectories,
+ FuncallExpression ast,
+ Environment env)
+ throws EvalException, ConversionException, InterruptedException {
+ env.checkLoadingPhase("native.glob", ast.getLocation());
+ return PackageFactory.callGlob(null, include, exclude, excludeDirectories != 0, ast, env);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "existing_rule",
- objectType = SkylarkNativeModule.class,
- returnType = Object.class,
doc =
"Returns a dictionary representing the attributes of a previously defined rule, "
+ "or None if the rule does not exist.",
@@ -113,48 +103,38 @@ public class SkylarkNativeModule {
useAst = true,
useEnvironment = true
)
- private static final BuiltinFunction existingRule =
- new BuiltinFunction("existing_rule") {
- public Object invoke(String name, FuncallExpression ast, Environment env)
- throws EvalException, InterruptedException {
- env.checkLoadingOrWorkspacePhase("native.existing_rule", ast.getLocation());
- SkylarkDict<String, Object> rule = PackageFactory.callGetRuleFunction(name, ast, env);
- if (rule != null) {
- return rule;
- }
+ public Object existingRule(String name, FuncallExpression ast, Environment env)
+ throws EvalException, InterruptedException {
+ env.checkLoadingOrWorkspacePhase("native.existing_rule", ast.getLocation());
+ SkylarkDict<String, Object> rule = PackageFactory.callGetRuleFunction(name, ast, env);
+ if (rule != null) {
+ return rule;
+ }
- return Runtime.NONE;
- }
- };
+ return Runtime.NONE;
+ }
/*
If necessary, we could allow filtering by tag (anytag, alltags), name (regexp?), kind ?
For now, we ignore this, since users can implement it in Skylark.
*/
- @SkylarkSignature(
+ @SkylarkCallable(
name = "existing_rules",
- objectType = SkylarkNativeModule.class,
- returnType = SkylarkDict.class,
doc =
"Returns a dict containing all the rules instantiated so far. "
+ "The map key is the name of the rule. The map value is equivalent to the "
+ "existing_rule output for that rule.",
- parameters = {},
useAst = true,
useEnvironment = true
)
- private static final BuiltinFunction existingRules =
- new BuiltinFunction("existing_rules") {
- public SkylarkDict<String, SkylarkDict<String, Object>> invoke(
- FuncallExpression ast, Environment env)
- throws EvalException, InterruptedException {
- env.checkLoadingOrWorkspacePhase("native.existing_rules", ast.getLocation());
- return PackageFactory.callGetRulesFunction(ast, env);
- }
- };
+ public SkylarkDict<String, SkylarkDict<String, Object>> existingRules(
+ FuncallExpression ast, Environment env)
+ throws EvalException, InterruptedException {
+ env.checkLoadingOrWorkspacePhase("native.existing_rules", ast.getLocation());
+ return PackageFactory.callGetRulesFunction(ast, env);
+ }
- @SkylarkSignature(name = "package_group", objectType = SkylarkNativeModule.class,
- returnType = Runtime.NoneType.class,
+ @SkylarkCallable(name = "package_group",
doc = "This function defines a set of packages and assigns a label to the group. "
+ "The label can be referenced in <code>visibility</code> attributes.",
parameters = {
@@ -167,19 +147,17 @@ public class SkylarkNativeModule {
defaultValue = "[]", named = true, positional = false,
doc = "Other package groups that are included in this one.")},
useAst = true, useEnvironment = true)
- private static final BuiltinFunction packageGroup = new BuiltinFunction("package_group") {
- public Runtime.NoneType invoke(String name, SkylarkList packages, SkylarkList includes,
- FuncallExpression ast, Environment env) throws EvalException, ConversionException {
- env.checkLoadingPhase("native.package_group", ast.getLocation());
- return PackageFactory.callPackageFunction(name, packages, includes, ast, env);
- }
- };
+ public Runtime.NoneType packageGroup(String name, SkylarkList<?> packages,
+ SkylarkList<?> includes,
+ FuncallExpression ast, Environment env) throws EvalException {
+ env.checkLoadingPhase("native.package_group", ast.getLocation());
+ return PackageFactory.callPackageFunction(name, packages, includes, ast, env);
+ }
- @SkylarkSignature(name = "exports_files", objectType = SkylarkNativeModule.class,
- returnType = Runtime.NoneType.class,
- doc = "Specifies a list of files belonging to this package that are exported to other "
- + "packages but not otherwise mentioned.",
- parameters = {
+ @SkylarkCallable(name = "exports_files",
+ doc = "Specifies a list of files belonging to this package that are exported to other "
+ + "packages but not otherwise mentioned.",
+ parameters = {
@Param(name = "srcs", type = SkylarkList.class, generic1 = String.class,
doc = "The list of files to export."),
// TODO(bazel-team): make it possible to express the precise type ListOf(LabelDesignator)
@@ -189,20 +167,16 @@ public class SkylarkNativeModule {
+ "every package."),
@Param(name = "licenses", type = SkylarkList.class, generic1 = String.class, noneable = true,
defaultValue = "None", doc = "Licenses to be specified.")},
- useAst = true, useEnvironment = true)
- private static final BuiltinFunction exportsFiles = new BuiltinFunction("exports_files") {
- public Runtime.NoneType invoke(SkylarkList srcs, Object visibility, Object licenses,
- FuncallExpression ast, Environment env)
- throws EvalException, ConversionException {
- env.checkLoadingPhase("native.exports_files", ast.getLocation());
- return PackageFactory.callExportsFiles(srcs, visibility, licenses, ast, env);
- }
- };
+ useAst = true, useEnvironment = true)
+ public Runtime.NoneType exportsFiles(SkylarkList<?> srcs, Object visibility, Object licenses,
+ FuncallExpression ast, Environment env)
+ throws EvalException {
+ env.checkLoadingPhase("native.exports_files", ast.getLocation());
+ return PackageFactory.callExportsFiles(srcs, visibility, licenses, ast, env);
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "package_name",
- objectType = SkylarkNativeModule.class,
- returnType = String.class,
doc =
"The name of the package being evaluated. "
+ "For example, in the BUILD file <code>some/package/BUILD</code>, its value "
@@ -214,21 +188,16 @@ public class SkylarkNativeModule {
useAst = true,
useEnvironment = true
)
- static final BuiltinFunction packageName =
- new BuiltinFunction("package_name") {
- public String invoke(FuncallExpression ast, Environment env)
- throws EvalException, ConversionException {
- env.checkLoadingPhase("native.package_name", ast.getLocation());
- PackageIdentifier packageId =
- PackageFactory.getContext(env, ast.getLocation()).getBuilder().getPackageIdentifier();
- return packageId.getPackageFragment().getPathString();
- }
- };
+ public String packageName(FuncallExpression ast, Environment env)
+ throws EvalException {
+ env.checkLoadingPhase("native.package_name", ast.getLocation());
+ PackageIdentifier packageId =
+ PackageFactory.getContext(env, ast.getLocation()).getBuilder().getPackageIdentifier();
+ return packageId.getPackageFragment().getPathString();
+ }
- @SkylarkSignature(
+ @SkylarkCallable(
name = "repository_name",
- objectType = SkylarkNativeModule.class,
- returnType = String.class,
doc =
"The name of the repository the rule or build extension is called from. "
+ "For example, in packages that are called into existence by the WORKSPACE stanza "
@@ -240,20 +209,11 @@ public class SkylarkNativeModule {
useLocation = true,
useEnvironment = true
)
- static final BuiltinFunction repositoryName =
- new BuiltinFunction("repository_name") {
- public String invoke(Location location, Environment env)
- throws EvalException, ConversionException {
- env.checkLoadingPhase("native.repository_name", location);
- PackageIdentifier packageId =
- PackageFactory.getContext(env, location).getBuilder().getPackageIdentifier();
- return packageId.getRepository().toString();
- }
- };
-
- public static final SkylarkNativeModule NATIVE_MODULE = new SkylarkNativeModule();
-
- static {
- SkylarkSignatureProcessor.configureSkylarkFunctions(SkylarkNativeModule.class);
+ public String repositoryName(Location location, Environment env)
+ throws EvalException {
+ env.checkLoadingPhase("native.repository_name", location);
+ PackageIdentifier packageId =
+ PackageFactory.getContext(env, location).getBuilder().getPackageIdentifier();
+ return packageId.getRepository().toString();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
index 4e7af5f001..6868a98099 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactory.java
@@ -46,7 +46,6 @@ import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.FunctionSignature;
import com.google.devtools.build.lib.syntax.Mutability;
import com.google.devtools.build.lib.syntax.ParserInputSource;
-import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.Runtime.NoneType;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkSemantics;
@@ -551,9 +550,10 @@ public class WorkspaceFactory {
private static ClassObject newNativeModule(
ImmutableMap<String, BaseFunction> workspaceFunctions, String version) {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
- Runtime.BuiltinRegistry builtins = Runtime.getBuiltinRegistry();
- for (String nativeFunction : builtins.getFunctionNames(SkylarkNativeModule.class)) {
- builder.put(nativeFunction, builtins.getFunction(SkylarkNativeModule.class, nativeFunction));
+ SkylarkNativeModule nativeModuleInstance = new SkylarkNativeModule();
+ for (String nativeFunction : FuncallExpression.getMethodNames(SkylarkNativeModule.class)) {
+ builder.put(nativeFunction,
+ FuncallExpression.getBuiltinCallable(nativeModuleInstance, nativeFunction));
}
for (Map.Entry<String, BaseFunction> function : workspaceFunctions.entrySet()) {
builder.put(function.getKey(), function.getValue());
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
index e445e46cf8..bad903c3df 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
@@ -487,7 +487,7 @@ public abstract class BaseFunction implements SkylarkValue {
Preconditions.checkState(!isConfigured()); // must not be configured yet
this.paramDoc = new ArrayList<>();
- this.signature = SkylarkSignatureProcessor.getSignature(
+ this.signature = SkylarkSignatureProcessor.getSignatureForCallable(
getName(), annotation, unconfiguredDefaultValues, paramDoc, getEnforcedArgumentTypes());
this.objectType = annotation.objectType().equals(Object.class)
? null : annotation.objectType();
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
new file mode 100644
index 0000000000..8419e7fc64
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BuiltinCallable.java
@@ -0,0 +1,137 @@
+// Copyright 2018 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.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.profiler.Profiler;
+import com.google.devtools.build.lib.profiler.ProfilerTask;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
+import com.google.devtools.build.lib.syntax.Environment.LexicalFrame;
+import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * A function-object abstraction on object methods exposed to skylark using {@link SkylarkCallable}.
+ */
+public class BuiltinCallable extends BaseFunction {
+
+ /** Represents a required interpreter parameter as dictated by {@link SkylarkCallable} */
+ public enum ExtraArgKind {
+ LOCATION, // SkylarkCallable.useLocation
+ SYNTAX_TREE, // SkylarkCallable.useAst
+ ENVIRONMENT, // SkylarkCallable.useEnvironment
+ SEMANTICS; // SkylarkCallable.useSemantics
+ }
+
+ // Builtins cannot create or modify variable bindings. So it's sufficient to use a shared
+ // instance.
+ private static final LexicalFrame SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS =
+ LexicalFrame.create(Mutability.IMMUTABLE);
+
+ private final Object obj;
+ private final MethodDescriptor descriptor;
+ private final List<ExtraArgKind> extraArgs;
+ private int innerArgumentCount;
+
+ public BuiltinCallable(String name, Object obj, MethodDescriptor descriptor) {
+ super(name);
+ this.obj = obj;
+ this.descriptor = descriptor;
+ this.extraArgs = getExtraArgs(descriptor.getAnnotation());
+ configure(obj, descriptor);
+ }
+
+ @Override
+ protected int getArgArraySize () {
+ return innerArgumentCount;
+ }
+
+ private static List<ExtraArgKind> getExtraArgs(SkylarkCallable annotation) {
+ ImmutableList.Builder<ExtraArgKind> extraArgs = ImmutableList.builder();
+ if (annotation.useLocation()) {
+ extraArgs.add(ExtraArgKind.LOCATION);
+ }
+ if (annotation.useAst()) {
+ extraArgs.add(ExtraArgKind.SYNTAX_TREE);
+ }
+ if (annotation.useEnvironment()) {
+ extraArgs.add(ExtraArgKind.ENVIRONMENT);
+ }
+ if (annotation.useSkylarkSemantics()) {
+ extraArgs.add(ExtraArgKind.SEMANTICS);
+ }
+ return extraArgs.build();
+ }
+
+ /** Configure a BaseFunction from a @SkylarkCallable-annotated method */
+ private void configure(Object obj, MethodDescriptor descriptor) {
+ Preconditions.checkState(!isConfigured()); // must not be configured yet
+
+ this.paramDoc = new ArrayList<>();
+ this.signature = SkylarkSignatureProcessor.getSignatureForCallable(
+ getName(), descriptor, paramDoc, getEnforcedArgumentTypes());
+ this.objectType = obj.getClass();
+ this.innerArgumentCount = signature.getSignature().getShape().getArguments() + extraArgs.size();
+ configure();
+ }
+
+ @Override
+ @Nullable
+ public Object call(Object[] args,
+ FuncallExpression ast, Environment env)
+ throws EvalException, InterruptedException {
+ Preconditions.checkNotNull(env);
+
+ // ast is null when called from Java (as there's no Skylark call site).
+ Location loc = ast == null ? Location.BUILTIN : ast.getLocation();
+
+ // Add extra arguments, if needed
+ int index = args.length - extraArgs.size();
+ for (ExtraArgKind extraArg : extraArgs) {
+ switch(extraArg) {
+ case LOCATION:
+ args[index] = loc;
+ break;
+
+ case SYNTAX_TREE:
+ args[index] = ast;
+ break;
+
+ case ENVIRONMENT:
+ args[index] = env;
+ break;
+
+ case SEMANTICS:
+ args[index] = env.getSemantics();
+ break;
+ }
+ index++;
+ }
+
+ Profiler.instance().startTask(ProfilerTask.SKYLARK_BUILTIN_FN, getName());
+
+ try {
+ env.enterScope(this, SHARED_LEXICAL_FRAME_FOR_BUILTIN_METHOD_CALLS, ast, env.getGlobals());
+ return FuncallExpression.callMethod(
+ descriptor, getName(), obj, args, ast.getLocation(), env);
+ } finally {
+ Profiler.instance().completeTask(ProfilerTask.SKYLARK_BUILTIN_FN);
+ env.exitScope();
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
index 7a807f5e20..597508f04d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
@@ -318,6 +318,21 @@ public final class FuncallExpression extends Expression {
}
/**
+ * Returns a {@link BuiltinCallable} representing a {@link SkylarkCallable}-annotated instance
+ * method of a given object with the given method name.
+ */
+ public static BuiltinCallable getBuiltinCallable(Object obj, String methodName) {
+ Class<?> objClass = obj.getClass();
+ List<MethodDescriptor> methodDescriptors = getMethods(objClass, methodName);
+ if (methodDescriptors.size() != 1) {
+ throw new IllegalStateException(String.format(
+ "Expected exactly 1 method named '%s' in %s, but found %s",
+ methodName, objClass, methodDescriptors.size()));
+ }
+ return new BuiltinCallable(methodName, obj, methodDescriptors.get(0));
+ }
+
+ /**
* Invokes the given structField=true method and returns the result.
*
* @param methodDescriptor the descriptor of the method to invoke
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
index 6424a47e44..a2dc39a377 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java
@@ -14,10 +14,13 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Booleans;
import com.google.devtools.build.lib.skylarkinterface.Param;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature;
import com.google.devtools.build.lib.syntax.BuiltinFunction.ExtraArgKind;
+import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
@@ -31,8 +34,50 @@ import javax.annotation.Nullable;
* to configure a given field.
*/
public class SkylarkSignatureProcessor {
+
/**
- * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from an annotation
+ * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from a
+ * {@link SkylarkCallable}-annotated method.
+ *
+ * @param name the name of the function
+ * @param descriptor the method descriptor
+ * @param paramDoc an optional list into which to store documentation strings
+ * @param enforcedTypesList an optional list into which to store effective types to enforce
+ */
+ public static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
+ String name, MethodDescriptor descriptor,
+ @Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
+
+ SkylarkCallable annotation = descriptor.getAnnotation();
+
+ // TODO(cparsons): Validate these properties with the annotation processor instead.
+ Preconditions.checkArgument(annotation.name().isEmpty() || name.equals(annotation.name()),
+ "%s != %s", name, annotation.name());
+ boolean documented = annotation.documented();
+ if (annotation.doc().isEmpty() && documented) {
+ throw new RuntimeException(String.format("function %s is undocumented", name));
+ }
+ ImmutableList.Builder<Parameter<Object, SkylarkType>> parameters = ImmutableList.builder();
+
+ Class<?>[] javaMethodSignatureParams = descriptor.getMethod().getParameterTypes();
+
+ for (int paramIndex = 0; paramIndex < annotation.mandatoryPositionals(); paramIndex++) {
+ Parameter<Object, SkylarkType> parameter =
+ new Parameter.Mandatory<Object, SkylarkType>("arg" + paramIndex,
+ SkylarkType.of(javaMethodSignatureParams[paramIndex]));
+ parameters.add(parameter);
+ }
+
+ return getSignatureForCallable(name, documented, parameters.build(), annotation.parameters(),
+ /*extraPositionals=*/null,
+ /*extraKeywords=*/null, /*defaultValues=*/null, paramDoc, enforcedTypesList);
+ }
+
+
+ /**
+ * Extracts a {@code FunctionSignature.WithValues<Object, SkylarkType>} from a
+ * {@link SkylarkSignature} annotation.
+ *
* @param name the name of the function
* @param annotation the annotation
* @param defaultValues an optional list of default values
@@ -42,35 +87,50 @@ public class SkylarkSignatureProcessor {
// NB: the two arguments paramDoc and enforcedTypesList are used to "return" extra values via
// side-effects, and that's ugly
// TODO(bazel-team): use AutoValue to declare a value type to use as return value?
- public static FunctionSignature.WithValues<Object, SkylarkType> getSignature(
+ public static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
String name, SkylarkSignature annotation,
@Nullable Iterable<Object> defaultValues,
@Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
Preconditions.checkArgument(name.equals(annotation.name()),
"%s != %s", name, annotation.name());
+ boolean documented = annotation.documented();
+ if (annotation.doc().isEmpty() && documented) {
+ throw new RuntimeException(String.format("function %s is undocumented", name));
+ }
+ return getSignatureForCallable(name, documented,
+ /*mandatoryPositionals=*/ImmutableList.<Parameter<Object, SkylarkType>>of(),
+ annotation.parameters(),
+ annotation.extraPositionals(),
+ annotation.extraKeywords(), defaultValues, paramDoc, enforcedTypesList);
+ }
+
+ private static FunctionSignature.WithValues<Object, SkylarkType> getSignatureForCallable(
+ String name, boolean documented,
+ ImmutableList<Parameter<Object, SkylarkType>> mandatoryPositionals,
+ Param[] parameters,
+ @Nullable Param extraPositionals, @Nullable Param extraKeywords,
+ @Nullable Iterable<Object> defaultValues,
+ @Nullable List<String> paramDoc, @Nullable List<SkylarkType> enforcedTypesList) {
ArrayList<Parameter<Object, SkylarkType>> paramList = new ArrayList<>();
+ paramList.addAll(mandatoryPositionals);
HashMap<String, SkylarkType> enforcedTypes =
enforcedTypesList == null ? null : new HashMap<>();
HashMap<String, String> doc = new HashMap<>();
- boolean documented = annotation.documented();
- if (annotation.doc().isEmpty() && documented) {
- throw new RuntimeException(String.format("function %s is undocumented", name));
- }
Iterator<Object> defaultValuesIterator = defaultValues == null
? null : defaultValues.iterator();
try {
boolean named = false;
- for (Param param : annotation.parameters()) {
+ for (Param param : parameters) {
boolean mandatory = param.defaultValue() != null && param.defaultValue().isEmpty();
Object defaultValue = mandatory ? null : getDefaultValue(param, defaultValuesIterator);
if (param.named() && !param.positional() && !named) {
named = true;
@Nullable Param starParam = null;
- if (!annotation.extraPositionals().name().isEmpty()) {
- starParam = annotation.extraPositionals();
+ if (extraPositionals != null && !extraPositionals.name().isEmpty()) {
+ starParam = extraPositionals;
}
paramList.add(getParameter(name, starParam, enforcedTypes, doc, documented,
/*mandatory=*/false, /*star=*/true, /*starStar=*/false, /*defaultValue=*/null));
@@ -78,14 +138,14 @@ public class SkylarkSignatureProcessor {
paramList.add(getParameter(name, param, enforcedTypes, doc, documented,
mandatory, /*star=*/false, /*starStar=*/false, defaultValue));
}
- if (!annotation.extraPositionals().name().isEmpty() && !named) {
- paramList.add(getParameter(name, annotation.extraPositionals(), enforcedTypes, doc,
+ if (extraPositionals != null && !extraPositionals.name().isEmpty() && !named) {
+ paramList.add(getParameter(name, extraPositionals, enforcedTypes, doc,
documented, /*mandatory=*/false, /*star=*/true, /*starStar=*/false,
/*defaultValue=*/null));
}
- if (!annotation.extraKeywords().name().isEmpty()) {
+ if (extraKeywords != null && !extraKeywords.name().isEmpty()) {
paramList.add(
- getParameter(name, annotation.extraKeywords(), enforcedTypes, doc, documented,
+ getParameter(name, extraKeywords, enforcedTypes, doc, documented,
/*mandatory=*/false, /*star=*/false, /*starStar=*/true, /*defaultValue=*/null));
}
FunctionSignature.WithValues<Object, SkylarkType> signature =
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index ed8fb91906..ee6fc425bb 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -163,6 +163,7 @@ public class SkylarkEvaluationTest extends EvaluationTest {
return ImmutableMap.of("a", ImmutableList.of("b", "c"));
}
+
@SkylarkCallable(
name = "with_params",
documented = false,
@@ -352,6 +353,18 @@ public class SkylarkEvaluationTest extends EvaluationTest {
+ ")";
}
+ @SkylarkCallable(name = "proxy_methods_object",
+ doc = "Returns a struct containing all callable method objects of this mock",
+ allowReturnNones = true)
+ public ClassObject proxyMethodsObject() {
+ ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
+ for (String nativeFunction : FuncallExpression.getMethodNames(Mock.class)) {
+ builder.put(nativeFunction,
+ FuncallExpression.getBuiltinCallable(this, nativeFunction));
+ }
+ return NativeProvider.STRUCT.create(builder.build(), "no native callable '%s'");
+ }
+
@Override
public String toString() {
return "<mock>";
@@ -921,6 +934,16 @@ public class SkylarkEvaluationTest extends EvaluationTest {
}
@Test
+ public void testProxyMethodsObject() throws Exception {
+ new SkylarkTest()
+ .update("mock", new Mock())
+ .setUp(
+ "m = mock.proxy_methods_object()",
+ "b = m.with_params(1, True, named=True)")
+ .testLookup("b", "with_params(1, true, false, true, false, a)");
+ }
+
+ @Test
public void testJavaCallWithPositionalAndKwargs() throws Exception {
new SkylarkTest()
.update("mock", new Mock())
@@ -1527,6 +1550,7 @@ public class SkylarkEvaluationTest extends EvaluationTest {
"is_empty",
"nullfunc_failing",
"nullfunc_working",
+ "proxy_methods_object",
"return_bad",
"string",
"string_list",