# Copyright 2023 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import("//build/config/android/config.gni") import("//build/config/compiler/compiler.gni") if (is_android) { import("//build/config/android/rules.gni") } _JAVAP_PATH = "//third_party/jdk/current/bin/javap" declare_args() { # Enables JNI multiplexing to reduce JNI native methods overhead. # When we want to "enable" this, we can use this line instead/ # allow_jni_multiplexing = !is_java_debug && is_component_build == false allow_jni_multiplexing = false # Use hashed symbol names to reduce JNI symbol overhead. use_hashed_jni_names = !is_java_debug } # Use a dedicated include dir so that files can #include headers from other # toolchains without affecting non-JNI #includes. if (target_os == "android") { jni_headers_dir = "$root_build_dir/gen/jni_headers" } else { # Chrome OS builds cannot share gen/ directories because is_android=false # within default_toolchain. jni_headers_dir = "$root_gen_dir/jni_headers" } _jni_zero_dir = "//third_party/jni_zero" template("jni_sources_list") { generated_file(target_name) { forward_variables_from(invoker, TESTONLY_AND_VISIBILITY + [ "deps", "walk_keys", ]) outputs = [ invoker.output ] data_keys = [ "jni_source_files" ] rebase = root_build_dir metadata = { # This target is just collecting source files used - this is not a # legitimate dependency. shared_libraries_barrier = [] } } } template("_invoke_jni_zero") { action(target_name) { forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) script = "//third_party/jni_zero/jni_zero.py" if (!defined(inputs)) { inputs = [] } inputs += rebase_path([ "codegen/called_by_native_header.py", "codegen/convert_type.py", "codegen/header_common.py", "codegen/natives_header.py", "codegen/placeholder_gen_jni_java.py", "codegen/placeholder_java_type.py", "codegen/proxy_impl_java.py", "common.py", "java_lang_classes.py", "java_types.py", "jni_generator.py", "jni_registration_generator.py", "jni_zero.py", "parse.py", "proxy.py", ], ".", _jni_zero_dir) } } # Declare a jni registration target. # # This target generates a srcjar containing a copy of GEN_JNI.java, which has # the native methods of all dependent java files. It can also create a .h file # for use with manual JNI registration. # # The script does not scan any generated sources (those within .srcjars, or # within root_build_dir). This could be fixed by adding deps & logic to scan # .srcjars, but isn't currently needed. # # See third_party/jni_zero/jni_registration_generator.py for more info # about the format of the header file. # # Variables # java_targets: List of android_* targets that comprise your app. # native_deps: List of shared_library targets that comprise your app. # manual_jni_registration: Manually do JNI registration - required for feature # splits which provide their own native library. (optional) # namespace: Registration functions will be wrapped into this. (optional) # require_native_mocks: Enforce that any native calls using # org.jni_zero.NativeMethods must have a mock set # (optional). # enable_native_mocks: Allow native calls using # org.jni_zero.NativeMethods to be mocked in tests # (optional). # # Example # generate_jni_registration("chrome_jni_registration") { # java_targets = [ ":chrome_public_apk" ] # manual_jni_registration = false # } template("generate_jni_registration") { forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) if (defined(invoker.native_deps)) { _native_sources_list = "$target_gen_dir/$target_name.nativesources.txt" jni_sources_list("${target_name}__native_sources") { deps = invoker.native_deps output = _native_sources_list } } _java_sources_list = "$target_gen_dir/$target_name.javasources.txt" jni_sources_list("${target_name}__java_sources") { deps = invoker.java_targets output = _java_sources_list # When apk or bundle module targets are uses, do not pull metadata from # their native library deps. walk_keys = [ "java_walk_keys" ] } _invoke_jni_zero(target_name) { # Cannot depend on jni_sources_list targets since they likely depend on # this target via srcjar_deps. Depfiles are used to add the dep instead. deps = [] _srcjar_output = "$target_gen_dir/$target_name.srcjar" outputs = [ _srcjar_output ] depfile = "$target_gen_dir/$target_name.d" java_target_deps = [] if (defined(invoker.java_targets)) { foreach(java_targets_dep, invoker.java_targets) { java_target_deps += [ get_label_info(java_targets_dep, "label_no_toolchain") ] } } args = [ "generate-final", "--srcjar-path", rebase_path(_srcjar_output, root_build_dir), "--depfile", rebase_path(depfile, root_build_dir), "--java-sources-file", rebase_path(_java_sources_list, root_build_dir), ] if (defined(_native_sources_list)) { args += [ "--native-sources-file", rebase_path(_native_sources_list, root_build_dir), ] } if (defined(invoker.include_testonly)) { _include_testonly = invoker.include_testonly } else { _include_testonly = defined(testonly) && testonly } if (_include_testonly) { args += [ "--include-test-only" ] } if (use_hashed_jni_names) { args += [ "--use-proxy-hash" ] } if (defined(invoker.enable_native_mocks) && invoker.enable_native_mocks) { args += [ "--enable-proxy-mocks" ] if (defined(invoker.require_native_mocks) && invoker.require_native_mocks) { args += [ "--require-mocks" ] } } if (defined(invoker.remove_uncalled_jni) && invoker.remove_uncalled_jni) { args += [ "--remove-uncalled-methods" ] } if (defined(invoker.add_stubs_for_missing_jni) && invoker.add_stubs_for_missing_jni) { args += [ "--add-stubs-for-missing-native" ] } _manual_jni_registration = defined(invoker.manual_jni_registration) && invoker.manual_jni_registration _enable_jni_multiplexing = defined(invoker.enable_jni_multiplexing) && invoker.enable_jni_multiplexing if (_manual_jni_registration) { args += [ "--manual-jni-registration" ] } if (_enable_jni_multiplexing) { args += [ "--enable-jni-multiplexing" ] } if (_manual_jni_registration || _enable_jni_multiplexing) { _cpp_codegen_output = invoker.cpp_codegen_output outputs += [ _cpp_codegen_output ] args += [ "--header-path", rebase_path(_cpp_codegen_output, root_build_dir), ] public_configs = [ # This gives targets depending on this registration access to our # generated C++ file. "//third_party/jni_zero:jni_include_dir", ] } if (defined(invoker.namespace)) { args += [ "--namespace=${invoker.namespace}" ] } if (defined(invoker.module_name)) { args += [ "--module-name=${invoker.module_name}" ] } } } # JNI target implementation. See generate_jni or generate_jar_jni for usage. template("generate_jni_impl") { public_configs = [] # A hack to prevent GN from treating this dep as a java dep, since we depend # onto the invoke_jni_zero action from a java_library, which unfortunately # checks to see if a given dep is a java dep by searching for the strings # "java" or "junit". _target_name_without_java_or_junit = string_replace(string_replace(target_name, "_java", "_J"), "_junit", "_U") _jni_zero_action_target_name = _target_name_without_java_or_junit + "__action" if (current_toolchain != default_toolchain && target_os == "android") { # Rather than regenerating .h files in secondary toolchains, re-use the # ones from the primary toolchain by depending on it and adding the # root gen directory to the include paths. # https://crbug.com/1369398 group(target_name) { forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) not_needed(invoker, "*") public_configs += [ "//third_party/jni_zero:jni_include_dir($default_toolchain)" ] # Depending on the action name to avoid cross-toolchain native deps. public_deps = [ ":$_jni_zero_action_target_name($default_toolchain)" ] deps = [ "//third_party/jni_zero" ] metadata = { shared_libraries_barrier = [] } } } else { _final_target_name = target_name if (defined(invoker.classes)) { _from_source = false } else { _from_source = true # Using final_target_name to make srcjar_deps work. _srcjar_output = "$target_gen_dir/$_final_target_name.srcjar" _placeholder_srcjar_output = "$target_gen_dir/${_final_target_name}_placeholder.srcjar" } _invoke_jni_zero(_jni_zero_action_target_name) { _subdir = rebase_path(target_gen_dir, root_gen_dir) _jni_output_dir = "$jni_headers_dir/$_subdir/$_final_target_name" if (defined(invoker.jni_generator_include)) { _jni_generator_include = invoker.jni_generator_include } else { _jni_generator_include = "//third_party/jni_zero/jni_zero_internal.h" } # The sources aren't compiled so don't check their dependencies. check_includes = false forward_variables_from(invoker, [ "deps", "metadata", "public_deps", ]) if (!defined(public_deps)) { public_deps = [] } public_configs += [ "//third_party/jni_zero:jni_include_dir" ] inputs = [] outputs = [] args = [] if (_from_source) { args += [ "from-source" ] } else { args += [ "from-jar" ] } args += [ "--output-dir", rebase_path(_jni_output_dir, root_build_dir), "--extra-include", rebase_path(_jni_generator_include, _jni_output_dir), ] if (_from_source) { assert(defined(invoker.sources)) args += [ "--srcjar-path", rebase_path(_srcjar_output, root_build_dir), "--placeholder-srcjar-path", rebase_path(_placeholder_srcjar_output, root_build_dir), ] outputs += [ _srcjar_output, _placeholder_srcjar_output, ] inputs += invoker.sources _input_args = rebase_path(invoker.sources, root_build_dir) _input_names = invoker.sources if (use_hashed_jni_names) { args += [ "--use-proxy-hash" ] } if (defined(invoker.enable_jni_multiplexing) && invoker.enable_jni_multiplexing) { args += [ "--enable-jni-multiplexing" ] } if (defined(invoker.namespace)) { args += [ "--namespace=${invoker.namespace}" ] } } else { if (is_robolectric) { not_needed(invoker, [ "jar_file" ]) } else { if (defined(invoker.jar_file)) { _jar_file = invoker.jar_file } else { _jar_file = android_sdk_jar } inputs += [ _jar_file, _JAVAP_PATH, ] args += [ "--jar-file", rebase_path(_jar_file, root_build_dir), "--javap", rebase_path(_JAVAP_PATH, root_build_dir), ] } _input_args = invoker.classes _input_names = invoker.classes if (defined(invoker.unchecked_exceptions) && invoker.unchecked_exceptions) { args += [ "--unchecked-exceptions" ] } } if (defined(invoker.split_name)) { args += [ "--split-name=${invoker.split_name}" ] } foreach(_name, _input_names) { _name = get_path_info(_name, "name") + "_jni.h" outputs += [ "$_jni_output_dir/$_name" ] # Avoid passing GN lists because not all webrtc embedders use //build. args += [ "--output-name", _name, ] } foreach(_input, _input_args) { args += [ "--input-file=$_input" ] } } if (_from_source) { java_library("${_final_target_name}_java") { forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) srcjars = [ _srcjar_output, _placeholder_srcjar_output, ] supports_android = true jar_included_patterns = [ "*Jni.class", "*Jni\$*.class", ] prevent_excluded_classes_from_classpath = true deps = [ ":$_jni_zero_action_target_name", "//third_party/jni_zero:jni_zero_java", ] } } # This group exists to allow for users of generate_jni() to get our object # files included in their executables without explicitly depending on our # targets in jni_zero/BUILD.gn. group(_final_target_name) { public_deps = [ ":$_jni_zero_action_target_name" ] forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) if (defined(visibility)) { visibility += [ ":$target_name" ] } } } } # Declare a jni target # # This target generates the native jni bindings for a set of .java files. # # See third_party/jni_zero/jni_generator.py for more info about the # format of generating JNI bindings. # # Variables # sources: list of .java files to generate jni for # namespace: Specify the namespace for the generated header file. # deps, public_deps: As normal # # Example # # Target located in base/BUILD.gn. # generate_jni("foo_jni") { # # Generates gen/base/foo_jni/Foo_jni.h # # To use: #include "base/foo_jni/Foo_jni.h" # sources = [ # "android/java/src/org/chromium/foo/Foo.java", # ..., # ] # } template("generate_jni") { generate_jni_impl(target_name) { forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) metadata = { jni_source_files = sources # This field is only used by Cronet team during the translation # of the GN build targets to Soong modules. From a pure # GN perspective, it is unused. jni_source_files_abs = get_path_info(sources, "abspath") } } } # Declare a jni target for a prebuilt jar # # This target generates the native jni bindings for a set of classes in a .jar. # # See third_party/jni_zero/jni_generator.py for more info about the # format of generating JNI bindings. # # Variables # classes: list of .class files in the jar to generate jni for. These should # include the full path to the .class file. # jar_file: the path to the .jar. If not provided, will default to the sdk's # android.jar # unchecked_exceptions: Don't CHECK() for exceptions in generated stubs. # This behaves as if every method had @CalledByNativeUnchecked. # deps, public_deps: As normal # # Example # # Target located in base/BUILD.gn. # generate_jar_jni("foo_jni") { # # Generates gen/base/foo_jni/Runnable_jni.h # # To use: #include "base/foo_jni/Runnable_jni.h" # classes = [ # "android/view/Foo.class", # ] # } template("generate_jar_jni") { generate_jni_impl(target_name) { forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) } } # This is a wrapper around an underlying native target which inserts JNI # registration. # # The registration is based on the closure of the native target's generate_jni # transitive dependencies. Additionally, we use provided java_targets to assert # that our native and Java sides line up. # # In order to depend on the JNI registration, use # __jni_registration. template("native_with_jni") { _enable_underlying_native = !defined(invoker.enable_target) || invoker.enable_target _manual_jni_registration = defined(invoker.manual_jni_registration) && invoker.manual_jni_registration _needs_cpp_codegen = (_manual_jni_registration || allow_jni_multiplexing) && !(defined(invoker.collect_inputs_only) && invoker.collect_inputs_only) _needs_native_dep = _enable_underlying_native && _needs_cpp_codegen if ((_needs_cpp_codegen && current_toolchain == default_toolchain) || _needs_native_dep) { _subdir = rebase_path(target_gen_dir, root_gen_dir) _registration_cpp_codegen_output = "$jni_headers_dir/$_subdir/${target_name}__jni_registration_generated" # Make it a header for jni_registration (where we need to #include it) and # .cc when it's multiplexing. if (_manual_jni_registration) { _registration_cpp_codegen_output += ".h" } else { _registration_cpp_codegen_output += ".cc" } } if (_needs_native_dep || current_toolchain == default_toolchain) { _jni_registration_target_name = "${target_name}__jni_registration" } if (current_toolchain == default_toolchain) { if (defined(invoker.visibility)) { _target_name_for_visibility = target_name } generate_jni_registration(_jni_registration_target_name) { forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) if (defined(visibility)) { visibility += [ ":$_target_name_for_visibility" ] } native_deps = invoker.deps if (defined(invoker.testonly) && invoker.testonly) { enable_native_mocks = true if (!defined(invoker.add_stubs_for_missing_jni)) { add_stubs_for_missing_jni = true } if (!defined(invoker.remove_uncalled_jni)) { remove_uncalled_jni = true } } if (_needs_cpp_codegen) { if (allow_jni_multiplexing) { enable_jni_multiplexing = true } cpp_codegen_output = _registration_cpp_codegen_output } forward_variables_from(invoker, [ "add_stubs_for_missing_jni", "java_targets", "manual_jni_registration", "module_name", "namespace", "remove_uncalled_jni", ]) } } else { not_needed(invoker, [ "add_stubs_for_missing_jni", "java_targets", "manual_jni_registration", "module_name", "namespace", "remove_uncalled_jni", ]) } if (_enable_underlying_native) { if (defined(invoker.target_type_import)) { import(invoker.target_type_import) } target(invoker.target_type, target_name) { deps = invoker.deps if (defined(invoker.sources)) { sources = invoker.sources } # Need to overwrite configs, which have defaults. We assume we have # already set the correct defaults in the invoker. configs = [] configs = invoker.configs if (_needs_native_dep) { configs += [ "//third_party/jni_zero:jni_include_dir($default_toolchain)" ] deps += [ ":$_jni_registration_target_name($default_toolchain)" ] if (!defined(sources)) { sources = [] } sources += [ _registration_cpp_codegen_output ] } forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY + [ "configs", "deps", "sources", ]) } } else { not_needed(invoker, "*") if (current_toolchain != default_toolchain) { not_needed([ "target_name" ]) } } } # native_with_jni for shared libraries - see native_with_jni for details. template("shared_library_with_jni") { native_with_jni(target_name) { forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) target_type = "shared_library" } } set_defaults("shared_library_with_jni") { configs = default_shared_library_configs } # native_with_jni for components - see native_with_jni for details. template("component_with_jni") { native_with_jni(target_name) { forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY) forward_variables_from(invoker, TESTONLY_AND_VISIBILITY) target_type = "component" } } set_defaults("component_with_jni") { configs = default_component_configs }