• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2023 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""A bindings generator for JNI on Android."""
6
7import argparse
8import os
9import shutil
10import sys
11
12import common
13import jni_generator
14import jni_registration_generator
15
16# jni_zero.py requires Python 3.8+.
17_MIN_PYTHON_MINOR = 8
18
19
20def _add_io_args(parser, *, is_final=False, is_javap=False):
21  inputs = parser.add_argument_group(title='Inputs')
22  outputs = parser.add_argument_group(title='Outputs')
23  if is_final:
24    inputs.add_argument(
25        '--java-sources-file',
26        required=True,
27        help='Newline-separated file containing paths to .java or .jni.pickle '
28        'files, taken from Java dependency tree.')
29    inputs.add_argument(
30        '--priority-java-sources-file',
31        help='Same format as java-sources-file, only used by multiplexing to '
32        'pick certain methods to be the first N numbers in the switch table.')
33    inputs.add_argument(
34        '--never-omit-switch-num',
35        action='store_true',
36        help='Only used by multiplexing. Whether to disable optimization of '
37        'omitting switch_num for unique signatures.')
38    inputs.add_argument(
39        '--native-sources-file',
40        help='Newline-separated file containing paths to .java or .jni.pickle '
41        'files, taken from Native dependency tree.')
42  else:
43    if is_javap:
44      inputs.add_argument(
45          '--jar-file',
46          help='Extract the list of input files from a specified jar file. '
47          'Uses javap to extract the methods from a pre-compiled class.')
48
49      help_text = 'Paths within the .jar'
50    else:
51      help_text = 'Paths to .java files to parse.'
52    inputs.add_argument('--input-file',
53                        action='append',
54                        required=True,
55                        dest='input_files',
56                        help=help_text)
57    outputs.add_argument('--output-name',
58                         action='append',
59                         required=True,
60                         dest='output_names',
61                         help='Output filenames within output directory.')
62    outputs.add_argument('--output-dir',
63                         required=True,
64                         help='Output directory. '
65                         'Existing .h files in this directory will be assumed '
66                         'stale and removed.')
67    outputs.add_argument('--placeholder-srcjar-path',
68                         help='Path to output srcjar with placeholders for '
69                         'all referenced classes in |input_files|')
70
71  outputs.add_argument('--header-path', help='Path to output header file.')
72
73  if is_javap:
74    inputs.add_argument('--javap', help='The path to javap command.')
75  else:
76    outputs.add_argument(
77        '--srcjar-path',
78        help='Path to output srcjar for GEN_JNI.java (and J/N.java if proxy'
79        ' hash is enabled).')
80    outputs.add_argument('--jni-pickle',
81                         help='Path to write intermediate .jni.pickle file.')
82  if is_final:
83    outputs.add_argument(
84        '--depfile', help='Path to depfile (for use with ninja build system)')
85
86
87def _add_codegen_args(parser, *, is_final=False, is_javap=False):
88  group = parser.add_argument_group(title='Codegen Options')
89  mode_group = parser.add_mutually_exclusive_group()
90  group.add_argument(
91      '--module-name',
92      help='Only look at natives annotated with a specific module name.')
93  if is_final:
94    group.add_argument(
95        '--add-stubs-for-missing-native',
96        action='store_true',
97        help='Adds stub methods for any --java-sources-file which are missing '
98        'from --native-sources-files. If not passed, we will assert that none '
99        'of these exist.')
100    group.add_argument(
101        '--remove-uncalled-methods',
102        action='store_true',
103        help='Removes --native-sources-files which are not in '
104        '--java-sources-file. If not passed, we will assert that none of these '
105        'exist.')
106    group.add_argument(
107        '--namespace',
108        help='Native namespace to wrap the registration functions into.')
109    group.add_argument('--manual-jni-registration',
110                       action='store_true',
111                       help='Generate a call to RegisterNatives()')
112    group.add_argument('--include-test-only',
113                       action='store_true',
114                       help='Whether to maintain ForTesting JNI methods.')
115  else:
116    group.add_argument('--extra-include',
117                       action='append',
118                       dest='extra_includes',
119                       help='Header file to #include in the generated header.')
120    group.add_argument(
121        '--split-name',
122        help='Split name that the Java classes should be loaded from.')
123    mode_group.add_argument(
124        '--per-file-natives',
125        action='store_true',
126        help='Generate .srcjar and .h such that a final generate-final '
127        'step is not necessary')
128
129  if is_javap:
130    group.add_argument('--unchecked-exceptions',
131                       action='store_true',
132                       help='Do not check that no exceptions were thrown.')
133  else:
134    mode_group.add_argument(
135        '--use-proxy-hash',
136        action='store_true',
137        help='Enables hashing of the native declaration for methods in '
138        'a @NativeMethods interface')
139    mode_group.add_argument(
140        '--enable-jni-multiplexing',
141        action='store_true',
142        help='Enables JNI multiplexing for Java native methods')
143    group.add_argument(
144        '--package-prefix',
145        help='Adds a prefix to the classes fully qualified-name. Effectively '
146        'changing a class name from foo.bar -> prefix.foo.bar')
147    group.add_argument(
148        '--package-prefix-filter',
149        help=
150        ': separated list of java packages to apply the --package-prefix to.')
151
152  if not is_final:
153    if is_javap:
154      instead_msg = 'instead of the javap class name.'
155    else:
156      instead_msg = 'when there is no @JNINamespace set'
157
158    group.add_argument('--namespace',
159                       help='Uses as a namespace in the generated header ' +
160                       instead_msg)
161
162
163def _maybe_relaunch_with_newer_python():
164  # If "python3" is < python3.8, but a newer version is available, then use
165  # that.
166  py_version = sys.version_info
167  if py_version < (3, _MIN_PYTHON_MINOR):
168    if os.environ.get('JNI_ZERO_RELAUNCHED'):
169      sys.stderr.write('JNI_ZERO_RELAUNCHED failure.\n')
170      sys.exit(1)
171    for i in range(_MIN_PYTHON_MINOR, 30):
172      name = f'python3.{i}'
173      if shutil.which(name):
174        cmd = [name] + sys.argv
175        env = os.environ.copy()
176        env['JNI_ZERO_RELAUNCHED'] = '1'
177        os.execvpe(cmd[0], cmd, env)
178    sys.stderr.write(
179        f'jni_zero requires Python 3.{_MIN_PYTHON_MINOR} or greater.\n')
180    sys.exit(1)
181
182
183def _add_args(parser, *, is_final=False, is_javap=False):
184  _add_io_args(parser, is_final=is_final, is_javap=is_javap)
185  _add_codegen_args(parser, is_final=is_final, is_javap=is_javap)
186
187
188def main():
189  parser = argparse.ArgumentParser(description=__doc__)
190  subparsers = parser.add_subparsers(required=True)
191
192  subp = subparsers.add_parser(
193      'from-source', help='Generates files for a set of .java sources.')
194  _add_args(subp)
195  subp.set_defaults(func=jni_generator.GenerateFromSource)
196
197  subp = subparsers.add_parser(
198      'from-jar', help='Generates files from a .jar of .class files.')
199  _add_args(subp, is_javap=True)
200  subp.set_defaults(func=jni_generator.GenerateFromJar)
201
202  subp = subparsers.add_parser(
203      'generate-final',
204      help='Generates files that require knowledge of all intermediates.')
205  _add_args(subp, is_final=True)
206  subp.set_defaults(func=jni_registration_generator.main)
207
208  # Default to showing full help text when no args are passed.
209  if len(sys.argv) == 1:
210    parser.print_help()
211  elif len(sys.argv) == 2 and sys.argv[1] in subparsers.choices:
212    parser.parse_args(sys.argv[1:] + ['-h'])
213  else:
214    args = parser.parse_args()
215    bool_arg = lambda name: getattr(args, name, False)
216    jni_mode = common.JniMode(is_hashing=bool_arg('use_proxy_hash'),
217                              is_muxing=bool_arg('enable_jni_multiplexing'),
218                              is_per_file=bool_arg('per_file_natives'))
219    args.func(parser, args, jni_mode)
220
221if __name__ == '__main__':
222  _maybe_relaunch_with_newer_python()
223  main()
224