Bug 1378410 - 1. Support BuildFlag annotation for generated bindings; r=snorp draft
authorJim Chen <nchen@mozilla.com>
Fri, 01 Sep 2017 14:02:29 -0400
changeset 657614 6ebc82d11fd1aa4aeb57a46262e678480d23de83
parent 657613 075a61e7c9eba9f24dd0b11d0fc07cdb1a5f1b75
child 657615 5cfe8080ec333a1eca70cd3edba2aaaff6406820
push id77575
push userbmo:nchen@mozilla.com
push dateFri, 01 Sep 2017 18:04:14 +0000
reviewerssnorp
bugs1378410
milestone57.0a1
Bug 1378410 - 1. Support BuildFlag annotation for generated bindings; r=snorp Add a BuildFlag annotation, which when specified for classes, will wrap generated code in `#ifdef` or `#ifndef` blocks. This functionality is used for conditionally excluding generated code when NIghtly becomes Beta, without the need to regenerate bindings. MozReview-Commit-ID: L2NFM8CHKqF
build/annotationProcessors/AnnotationProcessor.java
build/annotationProcessors/CodeGenerator.java
build/annotationProcessors/SDKProcessor.java
build/annotationProcessors/classloader/ClassWithOptions.java
build/annotationProcessors/classloader/JarClassIterator.java
build/annotationProcessors/utils/GeneratableElementIterator.java
build/annotationProcessors/utils/Utils.java
mobile/android/base/moz.build
mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/BuildFlag.java
--- a/build/annotationProcessors/AnnotationProcessor.java
+++ b/build/annotationProcessors/AnnotationProcessor.java
@@ -24,17 +24,17 @@ public class AnnotationProcessor {
             "\n";
 
     private static final StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
     private static final StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
     private static final StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
 
     public static void main(String[] args) {
         // We expect a list of jars on the commandline. If missing, whinge about it.
-        if (args.length <= 2) {
+        if (args.length < 2) {
             System.err.println("Usage: java AnnotationProcessor outprefix jarfiles ...");
             System.exit(1);
         }
 
         final String OUTPUT_PREFIX = args[0];
         final String SOURCE_FILE = OUTPUT_PREFIX + "JNIWrappers.cpp";
         final String HEADER_FILE = OUTPUT_PREFIX + "JNIWrappers.h";
         final String NATIVES_FILE = OUTPUT_PREFIX + "JNINatives.h";
@@ -51,36 +51,42 @@ public class AnnotationProcessor {
 
         // Get an iterator over the classes in the jar files given...
         Iterator<ClassWithOptions> jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(jars);
 
         headerFile.append(
                 "#ifndef " + getHeaderGuardName(HEADER_FILE) + "\n" +
                 "#define " + getHeaderGuardName(HEADER_FILE) + "\n" +
                 "\n" +
+                "#ifndef MOZ_PREPROCESSOR\n" +
                 "#include \"mozilla/jni/Refs.h\"\n" +
+                "#endif\n" +
                 "\n" +
                 "namespace mozilla {\n" +
                 "namespace java {\n" +
                 "\n");
 
         implementationFile.append(
+                "#ifndef MOZ_PREPROCESSOR\n" +
                 "#include \"" + HEADER_FILE + "\"\n" +
                 "#include \"mozilla/jni/Accessors.h\"\n" +
+                "#endif\n" +
                 "\n" +
                 "namespace mozilla {\n" +
                 "namespace java {\n" +
                 "\n");
 
         nativesFile.append(
                 "#ifndef " + getHeaderGuardName(NATIVES_FILE) + "\n" +
                 "#define " + getHeaderGuardName(NATIVES_FILE) + "\n" +
                 "\n" +
+                "#ifndef MOZ_PREPROCESSOR\n" +
                 "#include \"" + HEADER_FILE + "\"\n" +
                 "#include \"mozilla/jni/Natives.h\"\n" +
+                "#endif\n" +
                 "\n" +
                 "namespace mozilla {\n" +
                 "namespace java {\n" +
                 "\n");
 
         while (jarClassIterator.hasNext()) {
             generateClass(jarClassIterator.next());
         }
--- a/build/annotationProcessors/CodeGenerator.java
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -22,43 +22,48 @@ public class CodeGenerator {
     // Buffers holding the strings to ultimately be written to the output files.
     private final StringBuilder cpp = new StringBuilder();
     private final StringBuilder header = new StringBuilder();
     private final StringBuilder natives = new StringBuilder();
     private final StringBuilder nativesInits = new StringBuilder();
 
     private final Class<?> cls;
     private final String clsName;
+    private final ClassWithOptions options;
     private AnnotationInfo.CallingThread callingThread = null;
     private int numNativesInits;
 
     private final HashSet<String> takenMethodNames = new HashSet<String>();
 
     public CodeGenerator(ClassWithOptions annotatedClass) {
         this.cls = annotatedClass.wrappedClass;
         this.clsName = annotatedClass.generatedName;
+        this.options = annotatedClass;
 
         final String unqualifiedName = Utils.getUnqualifiedName(clsName);
         header.append(
+                Utils.getIfdefHeader(annotatedClass.ifdef) +
                 "class " + clsName + " : public mozilla::jni::ObjectBase<" +
                         unqualifiedName + ">\n" +
                 "{\n" +
                 "public:\n" +
                 "    static const char name[];\n" +
                 "\n" +
                 "    explicit " + unqualifiedName + "(const Context& ctx) : ObjectBase<" +
                         unqualifiedName + ">(ctx) {}\n" +
                 "\n");
 
         cpp.append(
+                Utils.getIfdefHeader(annotatedClass.ifdef) +
                 "const char " + clsName + "::name[] =\n" +
                 "        \"" + cls.getName().replace('.', '/') + "\";\n" +
                 "\n");
 
         natives.append(
+                Utils.getIfdefHeader(annotatedClass.ifdef) +
                 "template<class Impl>\n" +
                 "class " + clsName + "::Natives : " +
                         "public mozilla::jni::NativeImpl<" + unqualifiedName + ", Impl>\n" +
                 "{\n" +
                 "public:\n");
     }
 
     private String getTraitsName(String uniqueName, boolean includeScope) {
@@ -551,16 +556,18 @@ public class CodeGenerator {
     }
 
     /**
      * Get the finalised bytes to go into the generated wrappers file.
      *
      * @return The bytes to be written to the wrappers file.
      */
     public String getWrapperFileContents() {
+        cpp.append(
+                Utils.getIfdefFooter(options.ifdef));
         return cpp.toString();
     }
 
     /**
      * Get the finalised bytes to go into the generated header file.
      *
      * @return The bytes to be written to the header file.
      */
@@ -575,17 +582,18 @@ public class CodeGenerator {
                 "\n");
 
         if (nativesInits.length() > 0) {
             header.append(
                     "    template<class Impl> class Natives;\n");
         }
         header.append(
                 "};\n" +
-                "\n");
+                "\n" +
+                Utils.getIfdefFooter(options.ifdef));
         return header.toString();
     }
 
     /**
      * Get the finalised bytes to go into the generated natives header file.
      *
      * @return The bytes to be written to the header file.
      */
@@ -595,12 +603,13 @@ public class CodeGenerator {
         }
         natives.append(
                 "    static const JNINativeMethod methods[" + numNativesInits + "];\n" +
                 "};\n" +
                 "\n" +
                 "template<class Impl>\n" +
                 "const JNINativeMethod " + clsName + "::Natives<Impl>::methods[] = {" + nativesInits + '\n' +
                 "};\n" +
-                "\n");
+                "\n" +
+                Utils.getIfdefFooter(options.ifdef));
         return natives.toString();
     }
 }
--- a/build/annotationProcessors/SDKProcessor.java
+++ b/build/annotationProcessors/SDKProcessor.java
@@ -407,17 +407,18 @@ public class SDKProcessor {
     }
 
     private static void generateClass(Class<?> clazz,
                                       ClassInfo clsInfo,
                                       StringBuilder implementationFile,
                                       StringBuilder headerFile) throws ParseException {
         String generatedName = clazz.getSimpleName();
 
-        CodeGenerator generator = new CodeGenerator(new ClassWithOptions(clazz, generatedName));
+        CodeGenerator generator = new CodeGenerator(
+                new ClassWithOptions(clazz, generatedName, /* ifdef */ ""));
 
         generateMembers(generator, clsInfo,
                         sortAndFilterMembers(clazz, clazz.getConstructors()));
         generateMembers(generator, clsInfo, sortAndFilterMembers(clazz, clazz.getMethods()));
         generateMembers(generator, clsInfo, sortAndFilterMembers(clazz, clazz.getFields()));
 
         headerFile.append(generator.getHeaderFileContents());
         implementationFile.append(generator.getWrapperFileContents());
--- a/build/annotationProcessors/classloader/ClassWithOptions.java
+++ b/build/annotationProcessors/classloader/ClassWithOptions.java
@@ -2,14 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.annotationProcessors.classloader;
 
 public class ClassWithOptions {
     public final Class<?> wrappedClass;
     public final String generatedName;
+    public final String ifdef;
 
-    public ClassWithOptions(Class<?> someClass, String name) {
+    public ClassWithOptions(Class<?> someClass, String name, String ifdef) {
         wrappedClass = someClass;
         generatedName = name;
+        this.ifdef = ifdef;
     }
 }
--- a/build/annotationProcessors/classloader/JarClassIterator.java
+++ b/build/annotationProcessors/classloader/JarClassIterator.java
@@ -1,14 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.annotationProcessors.classloader;
 
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
 import java.util.Iterator;
 
 /**
  * Class for iterating over an IterableJarLoadingURLClassLoader's classes.
  *
  * This class is not thread safe: use it only from a single thread.
  */
 public class JarClassIterator implements Iterator<ClassWithOptions> {
@@ -62,17 +64,37 @@ public class JarClassIterator implements
             }
 
             if (enclosingClass != null) {
                 // Anonymous inner class - unsupported.
                 // Or named inner class, which will be processed when we process the outer class.
                 return fillLookAheadIfPossible();
             }
 
-            lookAhead = new ClassWithOptions(ret, ret.getSimpleName());
+            String ifdef = "";
+            for (final Annotation annotation : ret.getDeclaredAnnotations()) {
+                Class<? extends Annotation> annotationType = annotation.annotationType();
+                if (!annotationType.getName().equals(
+                        "org.mozilla.gecko.annotation.BuildFlag")) {
+                    continue;
+                }
+
+                try {
+                    final Method valueMethod = annotationType.getDeclaredMethod("value");
+                    valueMethod.setAccessible(true);
+                    ifdef = (String) valueMethod.invoke(annotation);
+                    break;
+                } catch (final Exception e) {
+                    System.err.println("Unable to read BuildFlag annotation.");
+                    e.printStackTrace(System.err);
+                    System.exit(1);
+                }
+            }
+
+            lookAhead = new ClassWithOptions(ret, ret.getSimpleName(), ifdef);
             return true;
         } catch (ClassNotFoundException e) {
             System.err.println("Unable to enumerate class: " + className + ". Corrupted jar file?");
             e.printStackTrace();
             System.exit(2);
         }
         return false;
     }
--- a/build/annotationProcessors/utils/GeneratableElementIterator.java
+++ b/build/annotationProcessors/utils/GeneratableElementIterator.java
@@ -72,18 +72,18 @@ public class GeneratableElementIterator 
     }
 
     private Class<?>[] getFilteredInnerClasses() {
         // Go through all inner classes and see which ones we want to generate.
         final Class<?>[] candidates = mClass.wrappedClass.getDeclaredClasses();
         int count = 0;
 
         for (int i = 0; i < candidates.length; ++i) {
-            final GeneratableElementIterator testIterator
-                    = new GeneratableElementIterator(new ClassWithOptions(candidates[i], null));
+            final GeneratableElementIterator testIterator = new GeneratableElementIterator(
+                    new ClassWithOptions(candidates[i], null, /* ifdef */ ""));
             if (testIterator.hasNext()
                     || testIterator.getFilteredInnerClasses() != null) {
                 count++;
                 continue;
             }
             // Clear out ones that don't match.
             candidates[i] = null;
         }
@@ -103,17 +103,19 @@ public class GeneratableElementIterator 
             }
         }
 
         final ClassWithOptions[] ret = new ClassWithOptions[count];
         count = 0;
         for (Class<?> candidate : candidates) {
             if (candidate != null) {
                 ret[count++] = new ClassWithOptions(
-                        candidate, mClass.generatedName + "::" + candidate.getSimpleName());
+                        candidate,
+                        mClass.generatedName + "::" + candidate.getSimpleName(),
+                        /* ifdef */ "");
             }
         }
         assert ret.length == count;
 
         Arrays.sort(ret, new Comparator<ClassWithOptions>() {
             @Override public int compare(ClassWithOptions lhs, ClassWithOptions rhs) {
                 return lhs.generatedName.compareTo(rhs.generatedName);
             }
--- a/build/annotationProcessors/utils/Utils.java
+++ b/build/annotationProcessors/utils/Utils.java
@@ -322,9 +322,25 @@ public class Utils {
             System.err.println("*** Invalid value \"" + name + "\" for " + type.getSimpleName());
             System.err.println("*** Specify one of " + names.toString());
             System.err.println("***");
             e.printStackTrace(System.err);
             System.exit(1);
             return null;
         }
     }
+
+    public static String getIfdefHeader(String ifdef) {
+        if (ifdef.isEmpty()) {
+            return "";
+        } else if (ifdef.startsWith("!")) {
+            return "#ifndef " + ifdef.substring(1) + "\n";
+        }
+        return "#ifdef " + ifdef + "\n";
+    }
+
+    public static String getIfdefFooter(String ifdef) {
+        if (ifdef.isEmpty()) {
+            return "";
+        }
+        return "#endif // " + ifdef + "\n";
+    }
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -143,16 +143,17 @@ z.inputs += ['AndroidManifest.xml.in']
 include('android-services.mozbuild')
 
 geckoview_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/main/'
 geckoview_thirdparty_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/thirdparty/'
 thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
 
 constants_jar = add_java_jar('constants')
 constants_jar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
+    'annotation/BuildFlag.java',
     'annotation/JNITarget.java',
     'annotation/ReflectionTarget.java',
     'annotation/RobocopTarget.java',
     'annotation/WebRTCJNITarget.java',
     'annotation/WrapForJNI.java',
     'SysInfo.java',
 ]]
 constants_jar.sources += ['java/org/mozilla/gecko/' + x for x in [
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/BuildFlag.java
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to tag classes that are conditionally built behind
+ * build flags. Any generated JNI bindings will incorporate the specified build
+ * flags.
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BuildFlag {
+    /**
+     * Preprocessor macro for conditionally building the generated bindings.
+     * "MOZ_FOO" wraps generated bindings in "#ifdef MOZ_FOO / #endif"
+     * "!MOZ_FOO" wraps generated bindings in "#ifndef MOZ_FOO / #endif"
+     */
+    String value() default "";
+}