Bug 1378410 - 2. Generate JNI bindings using Python script; r=nalexander draft
authorJim Chen <nchen@mozilla.com>
Thu, 17 Aug 2017 20:12:29 -0400
changeset 648577 5ba28fedb2ae663d4c00745e98eedb62a61deb8b
parent 648576 99241ce857fd57c845394ab39d657593a3c205c7
child 648578 416a5df847dabfa6d7d6168cbbe84e98abe89a6f
push id74799
push userbmo:nchen@mozilla.com
push dateFri, 18 Aug 2017 00:12:52 +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,73 @@ 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()
+    # The generated files go through two preprocessing passes -- the current
+    # pass when the generated files are compared, and another pass when the
+    # generated files are later compiled. Define MOZ_PREPROCESSOR here so we
+    # can distinguish the two passes inside the generated files.
+    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
@@ -1819,8 +1819,47 @@ 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):
+    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'
+
+# Don't perform binding generation when building under gradle because we don't
+# have the local jar files. Bug 1384312 will add support for binding generation
+# under gradle. In addition, don't perform binding generation in artifact mode,
+# because we never use the generated C++ bindings in artifact mode.
+if not CONFIG['MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE'] and not CONFIG['MOZ_ARTIFACT_BUILDS']:
+    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'
+    ])