• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import("//build/config/android/config.gni")
6import("//build/config/compiler/compiler.gni")
7
8_JAVAP_PATH = "//third_party/jdk/current/bin/javap"
9
10declare_args() {
11  # Enables JNI multiplexing to reduce JNI native methods overhead.
12  allow_jni_multiplexing = false
13
14  # Use hashed symbol names to reduce JNI symbol overhead.
15  use_hashed_jni_names = !is_java_debug
16}
17
18# Use a dedicated include dir so that files can #include headers from other
19# toolchains without affecting non-JNI #includes.
20if (target_os == "android") {
21  jni_headers_dir = "$root_build_dir/gen/jni_headers"
22} else {
23  # Chrome OS builds cannot share gen/ directories because is_android=false
24  # within default_toolchain.
25  jni_headers_dir = "$root_gen_dir/jni_headers"
26}
27
28_jni_zero_dir = "//third_party/jni_zero"
29
30template("jni_sources_list") {
31  generated_file(target_name) {
32    forward_variables_from(invoker,
33                           TESTONLY_AND_VISIBILITY + [
34                                 "deps",
35                                 "walk_keys",
36                               ])
37    outputs = [ invoker.output ]
38    data_keys = [ "jni_source_files" ]
39    rebase = root_build_dir
40    metadata = {
41      # This target is just collecting source files used - this is not a
42      # legitimate dependency.
43      shared_libraries_barrier = []
44    }
45  }
46}
47
48template("_invoke_jni_zero") {
49  action(target_name) {
50    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
51    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
52
53    script = "//third_party/jni_zero/jni_zero.py"
54    if (!defined(inputs)) {
55      inputs = []
56    }
57    inputs += rebase_path([
58                            "codegen/placeholder_gen_jni_java.py",
59                            "codegen/proxy_impl_java.py",
60                            "common.py",
61                            "java_lang_classes.py",
62                            "java_types.py",
63                            "jni_generator.py",
64                            "jni_registration_generator.py",
65                            "jni_zero.py",
66                            "parse.py",
67                            "proxy.py",
68                          ],
69                          ".",
70                          _jni_zero_dir)
71  }
72}
73
74# Declare a jni registration target.
75#
76# This target generates a srcjar containing a copy of GEN_JNI.java, which has
77# the native methods of all dependent java files. It can also create a .h file
78# for use with manual JNI registration.
79#
80# The script does not scan any generated sources (those within .srcjars, or
81# within root_build_dir). This could be fixed by adding deps & logic to scan
82# .srcjars, but isn't currently needed.
83#
84# See third_party/jni_zero/jni_registration_generator.py for more info
85# about the format of the header file.
86#
87# Variables
88#   java_targets: List of android_* targets that comprise your app.
89#   native_deps: List of shared_library targets that comprise your app.
90#   manual_jni_registration: Manually do JNI registration - required for feature
91#     splits which provide their own native library. (optional)
92#   namespace: Registration functions will be wrapped into this. (optional)
93#   require_native_mocks: Enforce that any native calls using
94#     org.chromium.base.annotations.NativeMethods must have a mock set
95#     (optional).
96#   enable_native_mocks: Allow native calls using
97#     org.chromium.base.annotations.NativeMethods to be mocked in tests
98#     (optional).
99#
100# Example
101#   generate_jni_registration("chrome_jni_registration") {
102#     java_targets = [ ":chrome_public_apk" ]
103#     manual_jni_registration = false
104#   }
105template("generate_jni_registration") {
106  forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
107  if (defined(invoker.native_deps)) {
108    _native_sources_list = "$target_gen_dir/$target_name.nativesources.txt"
109    jni_sources_list("${target_name}__native_sources") {
110      deps = invoker.native_deps
111      output = _native_sources_list
112    }
113  }
114
115  _java_sources_list = "$target_gen_dir/$target_name.javasources.txt"
116  jni_sources_list("${target_name}__java_sources") {
117    deps = invoker.java_targets
118    output = _java_sources_list
119
120    # When apk or bundle module targets are uses, do not pull metadata from
121    # their native library deps.
122    walk_keys = [ "java_walk_keys" ]
123  }
124
125  _invoke_jni_zero(target_name) {
126    # Cannot depend on jni_sources_list targets since they likely depend on
127    # this target via srcjar_deps. Depfiles are used to add the dep instead.
128    deps = []
129    _srcjar_output = "$target_gen_dir/$target_name.srcjar"
130    outputs = [ _srcjar_output ]
131    depfile = "$target_gen_dir/$target_name.d"
132
133    java_target_deps = []
134    if (defined(invoker.java_targets)) {
135      foreach(java_targets_dep, invoker.java_targets) {
136        java_target_deps +=
137            [ get_label_info(java_targets_dep, "label_no_toolchain") ]
138      }
139    }
140    metadata = {
141      java_deps = java_target_deps
142    }
143    args = [
144      "generate-final",
145      "--srcjar-path",
146      rebase_path(_srcjar_output, root_build_dir),
147      "--depfile",
148      rebase_path(depfile, root_build_dir),
149      "--java-sources-file",
150      rebase_path(_java_sources_list, root_build_dir),
151    ]
152
153    if (defined(_native_sources_list)) {
154      args += [
155        "--native-sources-file",
156        rebase_path(_native_sources_list, root_build_dir),
157      ]
158    }
159
160    if (defined(invoker.include_testonly)) {
161      _include_testonly = invoker.include_testonly
162    } else {
163      _include_testonly = defined(testonly) && testonly
164    }
165    if (_include_testonly) {
166      args += [ "--include-test-only" ]
167    }
168
169    if (use_hashed_jni_names) {
170      args += [ "--use-proxy-hash" ]
171    }
172
173    if (defined(invoker.enable_native_mocks) && invoker.enable_native_mocks) {
174      args += [ "--enable-proxy-mocks" ]
175
176      if (defined(invoker.require_native_mocks) &&
177          invoker.require_native_mocks) {
178        args += [ "--require-mocks" ]
179      }
180    }
181
182    if (defined(invoker.remove_uncalled_jni) && invoker.remove_uncalled_jni) {
183      args += [ "--remove-uncalled-methods" ]
184    }
185    if (defined(invoker.add_stubs_for_missing_jni) &&
186        invoker.add_stubs_for_missing_jni) {
187      args += [ "--add-stubs-for-missing-native" ]
188    }
189
190    _manual_jni_registration = defined(invoker.manual_jni_registration) &&
191                               invoker.manual_jni_registration
192    _enable_jni_multiplexing = defined(invoker.enable_jni_multiplexing) &&
193                               invoker.enable_jni_multiplexing
194    if (_manual_jni_registration) {
195      args += [ "--manual-jni-registration" ]
196    }
197    if (_enable_jni_multiplexing) {
198      args += [ "--enable-jni-multiplexing" ]
199    }
200
201    if (_manual_jni_registration || _enable_jni_multiplexing) {
202      _subdir = rebase_path(target_gen_dir, root_gen_dir)
203      _jni_header_output =
204          "$jni_headers_dir/$_subdir/${target_name}_generated.h"
205      outputs += [ _jni_header_output ]
206      args += [
207        "--header-path",
208        rebase_path(_jni_header_output, root_build_dir),
209      ]
210
211      public_configs = [
212        # This gives targets depending on this registration access to our generated header.
213        "//third_party/jni_zero:jni_include_dir",
214
215        # For now, we unconditionally use chromium's base. We could move this
216        # to depend on an invoker variable like we do with generate_jni_impl
217        # if we want this to be configurable.
218        "//third_party/jni_zero:use_chromium_base_define",
219      ]
220    }
221
222    if (defined(invoker.namespace)) {
223      args += [ "--namespace=${invoker.namespace}" ]
224    }
225
226    if (defined(invoker.module_name)) {
227      args += [ "--module-name=${invoker.module_name}" ]
228    }
229  }
230}
231
232# JNI target implementation. See generate_jni or generate_jar_jni for usage.
233template("generate_jni_impl") {
234  public_configs = []
235  if (!defined(invoker.use_chromium_base) || invoker.use_chromium_base) {
236    public_configs += [ "//third_party/jni_zero:use_chromium_base_define" ]
237  }
238  _jni_zero_action_target_name = target_name + "__action"
239  if (current_toolchain != default_toolchain && target_os == "android") {
240    # Rather than regenerating .h files in secondary toolchains, re-use the
241    # ones from the primary toolchain by depending on it and adding the
242    # root gen directory to the include paths.
243    # https://crbug.com/1369398
244    group(target_name) {
245      forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
246      not_needed(invoker, "*")
247      public_configs +=
248          [ "//third_party/jni_zero:jni_include_dir($default_toolchain)" ]
249
250      # Depending on the action name to avoid cross-toolchain native deps.
251      public_deps = [ ":$_jni_zero_action_target_name($default_toolchain)" ]
252      deps = [ "//third_party/jni_zero:jni_zero_utils" ]
253      metadata = {
254        shared_libraries_barrier = []
255      }
256    }
257  } else {
258    _final_target_name = target_name
259    _invoke_jni_zero(_jni_zero_action_target_name) {
260      _subdir = rebase_path(target_gen_dir, root_gen_dir)
261      _jni_output_dir = "$jni_headers_dir/$_subdir/$_final_target_name"
262      if (defined(invoker.jni_generator_include)) {
263        _jni_generator_include = invoker.jni_generator_include
264        _jni_generator_include_deps = []
265      } else {
266        _jni_generator_include = "//third_party/jni_zero/jni_zero_helper.h"
267        _jni_generator_include_deps = [
268          # Using //base/android/jni_generator/jni_generator_helper.h introduces
269          # a dependency on buildflags targets indirectly through
270          # base/android/jni_android.h, which is part of the //base target.
271          # This can't depend directly on //base without causing a dependency
272          # cycle, though.
273          "//base:debugging_buildflags",
274          "//base:logging_buildflags",
275          "//build:chromeos_buildflags",
276        ]
277      }
278
279      # The sources aren't compiled so don't check their dependencies.
280      check_includes = false
281      forward_variables_from(invoker,
282                             [
283                               "deps",
284                               "metadata",
285                               "public_deps",
286                             ])
287      if (!defined(public_deps)) {
288        public_deps = []
289      }
290      public_deps += _jni_generator_include_deps
291
292      public_configs += [ "//third_party/jni_zero:jni_include_dir" ]
293
294      inputs = []
295      outputs = []
296      args = []
297      if (defined(invoker.classes)) {
298        args += [ "from-jar" ]
299      } else {
300        args += [ "from-source" ]
301      }
302      args += [
303        "--output-dir",
304        rebase_path(_jni_output_dir, root_build_dir),
305        "--extra-include",
306        rebase_path(_jni_generator_include, _jni_output_dir),
307      ]
308
309      if (defined(invoker.classes)) {
310        if (is_robolectric) {
311          not_needed(invoker, [ "jar_file" ])
312        } else {
313          if (defined(invoker.jar_file)) {
314            _jar_file = invoker.jar_file
315          } else {
316            _jar_file = android_sdk_jar
317          }
318          inputs += [
319            _jar_file,
320            _JAVAP_PATH,
321          ]
322          args += [
323            "--jar-file",
324            rebase_path(_jar_file, root_build_dir),
325            "--javap",
326            rebase_path(_JAVAP_PATH, root_build_dir),
327          ]
328        }
329        _input_args = invoker.classes
330        _input_names = invoker.classes
331        if (defined(invoker.unchecked_exceptions) &&
332            invoker.unchecked_exceptions) {
333          args += [ "--unchecked-exceptions" ]
334        }
335      } else {
336        assert(defined(invoker.sources))
337
338        # Using final_target_name to make srcjar_deps work.
339        _srcjar_output = "$target_gen_dir/$_final_target_name.srcjar"
340        args += [
341          "--srcjar-path",
342          rebase_path(_srcjar_output, root_build_dir),
343        ]
344        outputs += [ _srcjar_output ]
345        inputs += invoker.sources
346        _input_args = rebase_path(invoker.sources, root_build_dir)
347        _input_names = invoker.sources
348        if (use_hashed_jni_names) {
349          args += [ "--use-proxy-hash" ]
350        }
351
352        if (defined(invoker.enable_jni_multiplexing) &&
353            invoker.enable_jni_multiplexing) {
354          args += [ "--enable-jni-multiplexing" ]
355        }
356        if (defined(invoker.namespace)) {
357          args += [ "--namespace=${invoker.namespace}" ]
358        }
359      }
360      if (defined(invoker.split_name)) {
361        args += [ "--split-name=${invoker.split_name}" ]
362      }
363
364      foreach(_name, _input_names) {
365        _name = get_path_info(_name, "name") + "_jni.h"
366        outputs += [ "$_jni_output_dir/$_name" ]
367
368        # Avoid passing GN lists because not all webrtc embedders use //build.
369        args += [
370          "--output-name",
371          _name,
372        ]
373      }
374
375      foreach(_input, _input_args) {
376        args += [ "--input-file=$_input" ]
377      }
378    }
379
380    # This group exists to allow for users of generate_jni() to get our object
381    # files included in their executables without explicitly depending on our
382    # targets in jni_zero/BUILD.gn.
383    group(_final_target_name) {
384      deps = [ "//third_party/jni_zero:jni_zero_utils" ]
385      public_deps = [ ":$_jni_zero_action_target_name" ]
386      forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
387      if (defined(visibility)) {
388        visibility += [ ":$target_name" ]
389      }
390    }
391  }
392}
393
394# Declare a jni target
395#
396# This target generates the native jni bindings for a set of .java files.
397#
398# See third_party/jni_zero/jni_generator.py for more info about the
399# format of generating JNI bindings.
400#
401# Variables
402#   sources: list of .java files to generate jni for
403#   namespace: Specify the namespace for the generated header file.
404#   deps, public_deps: As normal
405#
406# Example
407#   # Target located in base/BUILD.gn.
408#   generate_jni("foo_jni") {
409#     # Generates gen/base/foo_jni/Foo_jni.h
410#     # To use: #include "base/foo_jni/Foo_jni.h"
411#     sources = [
412#       "android/java/src/org/chromium/foo/Foo.java",
413#       ...,
414#     ]
415#   }
416template("generate_jni") {
417  generate_jni_impl(target_name) {
418    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
419    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
420    metadata = {
421      jni_source_files = sources
422    }
423  }
424}
425
426# Declare a jni target for a prebuilt jar
427#
428# This target generates the native jni bindings for a set of classes in a .jar.
429#
430# See third_party/jni_zero/jni_generator.py for more info about the
431# format of generating JNI bindings.
432#
433# Variables
434#   classes: list of .class files in the jar to generate jni for. These should
435#     include the full path to the .class file.
436#   jar_file: the path to the .jar. If not provided, will default to the sdk's
437#     android.jar
438#   unchecked_exceptions: Don't CHECK() for exceptions in generated stubs.
439#     This behaves as if every method had @CalledByNativeUnchecked.
440#   deps, public_deps: As normal
441#
442# Example
443#   # Target located in base/BUILD.gn.
444#   generate_jar_jni("foo_jni") {
445#     # Generates gen/base/foo_jni/Runnable_jni.h
446#     # To use: #include "base/foo_jni/Runnable_jni.h"
447#     classes = [
448#       "android/view/Foo.class",
449#     ]
450#   }
451template("generate_jar_jni") {
452  generate_jni_impl(target_name) {
453    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
454    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
455  }
456}
457
458# This is a wrapper around an underlying native target which inserts JNI
459# registration.
460#
461# The registration is based on the closure of the native target's generate_jni
462# transitive dependencies. Additionally, we use provided java_targets to assert
463# that our native and Java sides line up.
464#
465# In order to depend on the JNI registration, use
466# <native-target-name>__jni_registration.
467template("native_with_jni") {
468  _needs_native_dep =
469      (defined(invoker.manual_jni_registration) &&
470       invoker.manual_jni_registration) || allow_jni_multiplexing
471  if (_needs_native_dep || current_toolchain == default_toolchain) {
472    _jni_registration_target_name = "${target_name}__jni_registration"
473  }
474
475  if (current_toolchain == default_toolchain) {
476    generate_jni_registration(_jni_registration_target_name) {
477      forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
478      native_deps = invoker.deps
479
480      if (allow_jni_multiplexing) {
481        enable_jni_multiplexing = true
482      }
483      if (defined(invoker.testonly) && invoker.testonly) {
484        enable_native_mocks = true
485        add_stubs_for_missing_jni = true
486        remove_uncalled_jni = true
487      }
488      forward_variables_from(invoker,
489                             [
490                               "add_stubs_for_missing_jni",
491                               "java_targets",
492                               "manual_jni_registration",
493                               "module_name",
494                               "namespace",
495                               "remove_uncalled_jni",
496                             ])
497    }
498  } else {
499    not_needed(invoker,
500               [
501                 "add_stubs_for_missing_jni",
502                 "java_targets",
503                 "manual_jni_registration",
504                 "module_name",
505                 "namespace",
506                 "remove_uncalled_jni",
507               ])
508  }
509
510  if (!defined(invoker.enable_target) || invoker.enable_target) {
511    if (defined(invoker.target_type_import)) {
512      import(invoker.target_type_import)
513    }
514    target(invoker.target_type, target_name) {
515      deps = invoker.deps
516      if (_needs_native_dep) {
517        deps += [ ":$_jni_registration_target_name($default_toolchain)" ]
518      }
519      forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
520      forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY + [ "deps" ])
521    }
522  } else {
523    not_needed(invoker, "*")
524    if (current_toolchain != default_toolchain) {
525      not_needed([ "target_name" ])
526    }
527  }
528}
529
530# native_with_jni for shared libraries - see native_with_jni for details.
531template("shared_library_with_jni") {
532  native_with_jni(target_name) {
533    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
534    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
535    target_type = "shared_library"
536  }
537}
538set_defaults("shared_library_with_jni") {
539  configs = default_shared_library_configs
540}
541
542# native_with_jni for components - see native_with_jni for details.
543template("component_with_jni") {
544  native_with_jni(target_name) {
545    forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
546    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
547    target_type = "component"
548  }
549}
550set_defaults("component_with_jni") {
551  configs = default_component_configs
552}
553