Bug 1378410 - 2. Generate JNI bindings using Python script; r?nalexander draft
authorJim Chen <nchen@mozilla.com>
Mon, 14 Aug 2017 15:46:24 -0400
changeset 646064 896d45a1f9f6e24531653143785a699f26f73b49
parent 646063 d9f7768ee48a5af80e77f610637e30b5829dfefc
child 646065 686f01427eb14b86815c3fc2a7f4e6f26b80055a
push id73986
push userbmo:nchen@mozilla.com
push dateMon, 14 Aug 2017 19:46:39 +0000
reviewersnalexander
bugs1378410
milestone57.0a1
Bug 1378410 - 2. Generate JNI bindings using Python script; r?nalexander Generate JNI bindings through a Python script using GENERATED_FILES. The script calls the AnnotationProcessor Java program to generate the bindings, and then compares the new version against the existing in-tree version. Comparison is done after preprocessing to accommodate features hidden by build flags. MozReview-Commit-ID: CmBdpjEXc0W
mobile/android/base/generate_build_config.py
mobile/android/base/moz.build
--- a/mobile/android/base/generate_build_config.py
+++ b/mobile/android/base/generate_build_config.py
@@ -17,17 +17,19 @@ transition to Gradle.
 '''
 
 from __future__ import (
     print_function,
     unicode_literals,
 )
 
 from collections import defaultdict
+from io import BytesIO, StringIO
 import os
+import subprocess
 import sys
 
 import buildconfig
 
 from mozbuild import preprocessor
 from mozbuild.android_version_code import android_version_code
 
 
@@ -144,8 +146,68 @@ def generate_java(output_file, input_fil
 
 def generate_android_manifest(output_file, input_filename):
     includes = preprocessor.preprocess(includes=[input_filename],
                                        defines=_defines(),
                                        output=output_file,
                                        marker='#')
     includes.add(os.path.join(buildconfig.topobjdir, 'buildid.h'))
     return includes
+
+def generate_jni_bindings(output_file, ap_jar, *args):
+    # jar names and flags are separated by '-'
+    dash = args.index('-')
+    target_jars = args[:dash]
+    classpath, srcpath, prefix, update_cmd = args[dash + 1:]
+
+    # Generate new JNI bindings.
+    CONFIG = buildconfig.substs
+    sdk_jar = os.path.join(CONFIG['ANDROID_SDK'], 'android.jar')
+    subprocess.check_call((
+        CONFIG['JAVA'],
+        '-cp', ':'.join([ap_jar, sdk_jar, classpath]),
+        'org.mozilla.gecko.annotationProcessors.AnnotationProcessor',
+        prefix,
+    ) + target_jars)
+
+    # Preprocess bindings and compare new version against existing version.
+    DEFINES = _defines()
+    DEFINES['MOZ_PREPROCESSOR'] = 1
+    def preprocess(files):
+        with StringIO() if isinstance('', str) else BytesIO() as output:
+            preprocessor.preprocess(includes=files,
+                                    defines=DEFINES,
+                                    output=output,
+                                    marker='#')
+            return output.getvalue()
+
+    new_files = [prefix + suffix for suffix in (
+        'JNIWrappers.cpp',
+        'JNIWrappers.h',
+        'JNINatives.h',
+    )]
+    src_files = [os.path.join(srcpath, f) for f in new_files]
+
+    if not all(os.path.exists(f) for f in new_files):
+        raise Exception('Not all files generated')
+
+    try:
+        if preprocess(new_files) == preprocess(src_files):
+            # Preprocessed new version matches preprocessed existing version.
+            return
+    except:
+        pass
+
+    sys.stderr.write(
+        '                                                     \n'
+        '*****************************************************\n'
+        '***   ERROR: The generated JNI code has changed   ***\n'
+        '* To update generated code in the tree, please run  *\n'
+        '                                                     \n'
+        '  make -C {curdir} {target}\n'
+        '                                                     \n'
+        '* Repeat the build, and check in any changes.       *\n'
+        '*****************************************************\n'
+        '                                                     \n'
+        .format(curdir=os.path.abspath(os.path.curdir),
+                target=update_cmd))
+    sys.stderr.flush()
+    raise Exception('JNI binding mismatch')
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -1817,8 +1817,44 @@ if CONFIG['MOZ_ANDROID_HLS_SUPPORT']:
         'util/XmlPullParserUtil.java',
         'video/AvcConfig.java',
         'video/ColorInfo.java',
         'video/HevcConfig.java',
         'video/MediaCodecVideoRenderer.java',
         'video/VideoFrameReleaseTimeHelper.java',
         'video/VideoRendererEventListener.java',
     ]]
+
+# Generate separate JNI bindings for GeckoView and for Fennec.
+def generate_jni_bindings(generated_files, srcpath, prefix, update_cmd, jars):
+    set = {0}.__class__
+
+    ap_jar = '!/build/annotationProcessors/annotationProcessors.jar'
+    target_jars = ['!' + jar.name + '.jar' for jar in jars]
+
+    # classpath includes the target jars' dependencies.
+    classpath = ':'.join(set().union(*(jar.extra_jars for jar in jars)))
+
+    # Use a dummy file as the "output" file because our model for generating
+    # JNI bindings doesn't exactly match the build system's expectation for
+    # GENERATED_FILES. In particular, if we write to the output file outside of
+    # the Python file object, which is the case when using the annotation
+    # processor, the build system can get confused and end up deleting the
+    # output file. Using a dummy file avoids this issue.
+    output = prefix + '.bindings'
+
+    generated_files += [output]
+    generated_files[output].script = 'generate_build_config.py:generate_jni_bindings'
+    generated_files[output].inputs = [ap_jar] + target_jars
+    # inputs and flags are passed to the script together, so use '-' as a separator.
+    generated_files[output].flags = ['-', classpath, srcpath, prefix, update_cmd]
+    generated_files[output].tier = 'libs'
+
+generate_jni_bindings(GENERATED_FILES, TOPSRCDIR + '/widget/android',
+                      'Generated', 'update-generated-wrappers', [
+    gujar, # 'gecko-util'
+    gvjar, # 'gecko-view'
+])
+
+generate_jni_bindings(GENERATED_FILES, TOPSRCDIR + '/widget/android/fennec',
+                      'Fennec', 'update-fennec-wrappers', [
+    gbjar, # 'gecko-browser'
+])