aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/skylarkinterface
diff options
context:
space:
mode:
authorGravatar cparsons <cparsons@google.com>2018-02-12 14:10:17 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-02-12 14:12:40 -0800
commitb87a47a090aa04b7df256a229ecdbf2ec319c03c (patch)
treef03da1df72ce4348679152094d56fdaeafed12d5 /src/main/java/com/google/devtools/build/lib/skylarkinterface
parentdd4ddfd4a78e187f3fd39978d569f9b2ae17968b (diff)
Create a basic annotation processor for validating SkylarkCallable uses at compile time.
RELNOTES: None. PiperOrigin-RevId: 185432867
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skylarkinterface')
-rw-r--r--src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/BUILD25
-rw-r--r--src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/SkylarkCallableProcessor.java95
2 files changed, 120 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/BUILD b/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/BUILD
new file mode 100644
index 0000000000..c5a14392fa
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/BUILD
@@ -0,0 +1,25 @@
+# Description:
+# A preprocessor for skylarkinterface annotations.
+package(default_visibility = [
+ "//src/main/java/com/google/devtools/build/lib:__pkg__",
+ "//src/test/java/com/google/devtools/build/lib/skylarkinterface/processor:__pkg__",
+])
+
+licenses(["notice"]) # Apache 2.0
+
+filegroup(
+ name = "srcs",
+ srcs = glob(
+ ["**"],
+ ),
+)
+
+java_plugin(
+ name = "annotation_preprocessor",
+ srcs = glob(["*.java"]),
+ processor_class = "com.google.devtools.build.lib.skylarkinterface.processor.SkylarkCallableProcessor",
+ deps = [
+ "//src/main/java/com/google/devtools/build/lib:skylarkinterface_internal",
+ "//third_party:guava",
+ ],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/SkylarkCallableProcessor.java b/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/SkylarkCallableProcessor.java
new file mode 100644
index 0000000000..cc98597932
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skylarkinterface/processor/SkylarkCallableProcessor.java
@@ -0,0 +1,95 @@
+// 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.skylarkinterface.processor;
+
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+
+/**
+ * Annotation processor for {@link SkylarkCallable}.
+ *
+ * <p>Checks the following invariants about {@link SkylarkCallable}-annotated methods:
+ * <ul>
+ * <li>The method must be public.</li>
+ * <li>The number of method parameters much match the number of annotation-declared parameters.</li>
+ * <li>If structField=true, there must be zero arguments.</li>
+ * </ul>
+ *
+ * <p>These properties can be relied upon at runtime without additional checks.
+ */
+@SupportedAnnotationTypes({"com.google.devtools.build.lib.skylarkinterface.SkylarkCallable"})
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public final class SkylarkCallableProcessor extends AbstractProcessor {
+
+ private Messager messager;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ messager = processingEnv.getMessager();
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ for (Element element : roundEnv.getElementsAnnotatedWith(SkylarkCallable.class)) {
+ // Only methods are annotated with SkylarkCallable. This is verified by the
+ // @Target(ElementType.METHOD) annotation.
+ ExecutableElement methodElement = (ExecutableElement) element;
+ SkylarkCallable annotation = methodElement.getAnnotation(SkylarkCallable.class);
+
+ if (!methodElement.getModifiers().contains(Modifier.PUBLIC)) {
+ error(methodElement, "@SkylarkCallable annotated methods must be public.");
+ }
+ if (annotation.parameters().length > 0 || annotation.mandatoryPositionals() >= 0) {
+ int numDeclaredArgs = annotation.parameters().length
+ + Math.max(0, annotation.mandatoryPositionals());
+ if (methodElement.getParameters().size() != numDeclaredArgs) {
+ error(methodElement, String.format(
+ "@SkylarkCallable annotated method has %d parameters, but annotation declared %d.",
+ methodElement.getParameters().size(), numDeclaredArgs));
+ }
+ }
+ if (annotation.structField()) {
+ if (!methodElement.getParameters().isEmpty()) {
+ error(methodElement,
+ "@SkylarkCallable annotated methods with structField=true must have zero arguments.");
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Prints an error message & fails the compilation.
+ *
+ * @param e The element which has caused the error. Can be null
+ * @param msg The error message
+ */
+ public void error(Element e, String msg) {
+ messager.printMessage(Diagnostic.Kind.ERROR, msg, e);
+ }
+}