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