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