blob: 44f97df2cf443e45ba2e700fba9c3986ca1ba7ec [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generate JNI registration entry points
Creates a header file with two static functions: RegisterMainDexNatives() and
RegisterNonMainDexNatives(). Together, these will use manual JNI registration
to register all native methods that exist within an application."""
import argparse
import jni_generator
import multiprocessing
import os
import string
import sys
from util import build_utils
# All but FULL_CLASS_NAME, which is used only for sorting.
MERGEABLE_KEYS = [
'CLASS_PATH_DECLARATIONS',
'FORWARD_DECLARATIONS',
'JNI_NATIVE_METHOD',
'JNI_NATIVE_METHOD_ARRAY',
'REGISTER_MAIN_DEX_NATIVES',
'REGISTER_NON_MAIN_DEX_NATIVES',
]
def GenerateJNIHeader(java_file_paths, output_file, args):
"""Generate a header file including two registration functions.
Forward declares all JNI registration functions created by jni_generator.py.
Calls the functions in RegisterMainDexNatives() if they are main dex. And
calls them in RegisterNonMainDexNatives() if they are non-main dex.
Args:
java_file_paths: A list of java file paths.
output_file: A relative path to output file.
args: All input arguments.
"""
# Without multiprocessing, script takes ~13 seconds for chrome_public_apk
# on a z620. With multiprocessing, takes ~2 seconds.
pool = multiprocessing.Pool()
paths = (p for p in java_file_paths if p not in args.no_register_java)
results = [d for d in pool.imap_unordered(_DictForPath, paths) if d]
pool.close()
# Sort to make output deterministic.
results.sort(key=lambda d: d['FULL_CLASS_NAME'])
combined_dict = {}
for key in MERGEABLE_KEYS:
combined_dict[key] = ''.join(d.get(key, '') for d in results)
combined_dict['HEADER_GUARD'] = \
os.path.splitext(output_file)[0].replace('/', '_').upper() + '_'
combined_dict['NAMESPACE'] = args.namespace
header_content = CreateFromDict(combined_dict)
if output_file:
jni_generator.WriteOutput(output_file, header_content)
else:
print header_content
def _DictForPath(path):
with open(path) as f:
contents = jni_generator.RemoveComments(f.read())
natives = jni_generator.ExtractNatives(contents, 'long')
if len(natives) == 0:
return None
namespace = jni_generator.ExtractJNINamespace(contents)
fully_qualified_class = jni_generator.ExtractFullyQualifiedJavaClassName(
path, contents)
jni_params = jni_generator.JniParams(fully_qualified_class)
jni_params.ExtractImportsAndInnerClasses(contents)
main_dex = jni_generator.IsMainDexJavaClass(contents)
header_generator = HeaderGenerator(
namespace, fully_qualified_class, natives, jni_params, main_dex)
return header_generator.Generate()
def CreateFromDict(registration_dict):
"""Returns the content of the header file."""
template = string.Template("""\
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is autogenerated by
// base/android/jni_generator/jni_registration_generator.py
// Please do not change its content.
#ifndef ${HEADER_GUARD}
#define ${HEADER_GUARD}
#include <jni.h>
#include "base/android/jni_generator/jni_generator_helper.h"
#include "base/android/jni_int_wrapper.h"
// Step 1: Forward declarations (classes).
${CLASS_PATH_DECLARATIONS}
// Step 2: Forward declarations (methods).
${FORWARD_DECLARATIONS}
// Step 3: Method declarations.
${JNI_NATIVE_METHOD_ARRAY}
${JNI_NATIVE_METHOD}
// Step 4: Main dex and non-main dex registration functions.
namespace ${NAMESPACE} {
bool RegisterMainDexNatives(JNIEnv* env) {
${REGISTER_MAIN_DEX_NATIVES}
return true;
}
bool RegisterNonMainDexNatives(JNIEnv* env) {
${REGISTER_NON_MAIN_DEX_NATIVES}
return true;
}
} // namespace ${NAMESPACE}
#endif // ${HEADER_GUARD}
""")
if len(registration_dict['FORWARD_DECLARATIONS']) == 0:
return ''
return template.substitute(registration_dict)
class HeaderGenerator(object):
"""Generates an inline header file for JNI registration."""
def __init__(self, namespace, fully_qualified_class, natives, jni_params,
main_dex):
self.namespace = namespace
self.natives = natives
self.fully_qualified_class = fully_qualified_class
self.jni_params = jni_params
self.class_name = self.fully_qualified_class.split('/')[-1]
self.main_dex = main_dex
self.helper = jni_generator.HeaderFileGeneratorHelper(
self.class_name, fully_qualified_class)
self.registration_dict = None
def Generate(self):
self.registration_dict = {'FULL_CLASS_NAME': self.fully_qualified_class}
self._AddClassPathDeclarations()
self._AddForwardDeclaration()
self._AddJNINativeMethodsArrays()
self._AddRegisterNativesCalls()
self._AddRegisterNativesFunctions()
return self.registration_dict
def _SetDictValue(self, key, value):
self.registration_dict[key] = jni_generator.WrapOutput(value)
def _AddClassPathDeclarations(self):
classes = self.helper.GetUniqueClasses(self.natives)
self._SetDictValue('CLASS_PATH_DECLARATIONS',
self.helper.GetClassPathLines(classes, declare_only=True))
def _AddForwardDeclaration(self):
"""Add the content of the forward declaration to the dictionary."""
template = string.Template("""\
JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(
JNIEnv* env,
${PARAMS_IN_STUB});
""")
forward_declaration = ''
for native in self.natives:
value = {
'RETURN': jni_generator.JavaDataTypeToC(native.return_type),
'STUB_NAME': self.helper.GetStubName(native),
'PARAMS_IN_STUB': jni_generator.GetParamsInStub(native),
}
forward_declaration += template.substitute(value)
self._SetDictValue('FORWARD_DECLARATIONS', forward_declaration)
def _AddRegisterNativesCalls(self):
"""Add the body of the RegisterNativesImpl method to the dictionary."""
template = string.Template("""\
if (!${REGISTER_NAME}(env))
return false;
""")
value = {
'REGISTER_NAME':
jni_generator.GetRegistrationFunctionName(
self.fully_qualified_class)
}
register_body = template.substitute(value)
if self.main_dex:
self._SetDictValue('REGISTER_MAIN_DEX_NATIVES', register_body)
else:
self._SetDictValue('REGISTER_NON_MAIN_DEX_NATIVES', register_body)
def _AddJNINativeMethodsArrays(self):
"""Returns the implementation of the array of native methods."""
template = string.Template("""\
static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
${KMETHODS}
};
""")
open_namespace = ''
close_namespace = ''
if self.namespace:
parts = self.namespace.split('::')
all_namespaces = ['namespace %s {' % ns for ns in parts]
open_namespace = '\n'.join(all_namespaces) + '\n'
all_namespaces = ['} // namespace %s' % ns for ns in parts]
all_namespaces.reverse()
close_namespace = '\n'.join(all_namespaces) + '\n\n'
body = self._SubstituteNativeMethods(template)
self._SetDictValue('JNI_NATIVE_METHOD_ARRAY',
''.join((open_namespace, body, close_namespace)))
def _GetKMethodsString(self, clazz):
ret = []
for native in self.natives:
if (native.java_class_name == clazz or
(not native.java_class_name and clazz == self.class_name)):
ret += [self._GetKMethodArrayEntry(native)]
return '\n'.join(ret)
def _GetKMethodArrayEntry(self, native):
template = string.Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' +
'reinterpret_cast<void*>(${STUB_NAME}) },')
values = {
'NAME': native.name,
'JNI_SIGNATURE': self.jni_params.Signature(
native.params, native.return_type),
'STUB_NAME': self.helper.GetStubName(native)
}
return template.substitute(values)
def _SubstituteNativeMethods(self, template):
"""Substitutes NAMESPACE, JAVA_CLASS and KMETHODS in the provided
template."""
ret = []
all_classes = self.helper.GetUniqueClasses(self.natives)
all_classes[self.class_name] = self.fully_qualified_class
for clazz, full_clazz in all_classes.iteritems():
kmethods = self._GetKMethodsString(clazz)
namespace_str = ''
if self.namespace:
namespace_str = self.namespace + '::'
if kmethods:
values = {'NAMESPACE': namespace_str,
'JAVA_CLASS': jni_generator.GetBinaryClassName(full_clazz),
'KMETHODS': kmethods}
ret += [template.substitute(values)]
if not ret: return ''
return '\n'.join(ret)
def GetJNINativeMethodsString(self):
"""Returns the implementation of the array of native methods."""
template = string.Template("""\
static const JNINativeMethod kMethods_${JAVA_CLASS}[] = {
${KMETHODS}
};
""")
return self._SubstituteNativeMethods(template)
def _AddRegisterNativesFunctions(self):
"""Returns the code for RegisterNatives."""
natives = self._GetRegisterNativesImplString()
if not natives:
return ''
template = string.Template("""\
JNI_REGISTRATION_EXPORT bool ${REGISTER_NAME}(JNIEnv* env) {
${NATIVES}\
return true;
}
""")
values = {
'REGISTER_NAME': jni_generator.GetRegistrationFunctionName(
self.fully_qualified_class),
'NATIVES': natives
}
self._SetDictValue('JNI_NATIVE_METHOD', template.substitute(values))
def _GetRegisterNativesImplString(self):
"""Returns the shared implementation for RegisterNatives."""
template = string.Template("""\
const int kMethods_${JAVA_CLASS}Size =
arraysize(${NAMESPACE}kMethods_${JAVA_CLASS});
if (env->RegisterNatives(
${JAVA_CLASS}_clazz(env),
${NAMESPACE}kMethods_${JAVA_CLASS},
kMethods_${JAVA_CLASS}Size) < 0) {
jni_generator::HandleRegistrationError(env,
${JAVA_CLASS}_clazz(env),
__FILE__);
return false;
}
""")
return self._SubstituteNativeMethods(template)
def main(argv):
arg_parser = argparse.ArgumentParser()
build_utils.AddDepfileOption(arg_parser)
arg_parser.add_argument('--sources_files',
help='A list of .sources files which contain Java '
'file paths. Must be used with --output.')
arg_parser.add_argument('--output',
help='The output file path.')
arg_parser.add_argument('--no_register_java',
default=[],
help='A list of Java files which should be ignored '
'by the parser.')
arg_parser.add_argument('--namespace',
default='',
help='Namespace to wrap the registration functions '
'into.')
args = arg_parser.parse_args(build_utils.ExpandFileArgs(argv[1:]))
args.sources_files = build_utils.ParseGnList(args.sources_files)
if not args.sources_files:
print '\nError: Must specify --sources_files.'
return 1
java_file_paths = []
for f in args.sources_files:
# java_file_paths stores each Java file path as a string.
java_file_paths += build_utils.ReadSourcesList(f)
output_file = args.output
GenerateJNIHeader(java_file_paths, output_file, args)
if args.depfile:
build_utils.WriteDepfile(args.depfile, output_file,
args.sources_files + java_file_paths,
add_pydeps=False)
if __name__ == '__main__':
sys.exit(main(sys.argv))