Skip to content

Commit 2393cca

Browse files
[jnigen] Remove C-based bindings (#1091)
1 parent 0f41ebb commit 2393cca

File tree

96 files changed

+4672
-28509
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+4672
-28509
lines changed

.github/workflows/jnigen.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,6 @@ jobs:
105105
working-directory: ./pkgs/jnigen/java
106106
- name: Build summarizer
107107
run: dart run jnigen:setup
108-
- name: Generate runtime tests
109-
run: dart run tool/generate_runtime_tests.dart
110108
- name: Run VM tests
111109
run: dart test --test-randomize-ordering-seed random
112110
- name: Install coverage
@@ -258,8 +256,6 @@ jobs:
258256
- run: dart run jnigen:setup
259257
- name: Build summarizer
260258
run: dart run jnigen:setup
261-
- name: Generate runtime tests
262-
run: dart run tool/generate_runtime_tests.dart
263259
- name: Run tests
264260
run: dart test --test-randomize-ordering-seed random
265261

@@ -309,8 +305,6 @@ jobs:
309305
- run: dart pub get
310306
- name: Build summarizer
311307
run: dart run jnigen:setup
312-
- name: Generate runtime tests
313-
run: dart run tool/generate_runtime_tests.dart
314308
- name: Run tests
315309
run: dart test --test-randomize-ordering-seed random
316310

pkgs/jni/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.9.0-wip
2+
3+
- No changes yet.
4+
15
## 0.8.0
26

37
- **Breaking Change** ([#981](https://github.com/dart-lang/native/issues/981)):

pkgs/jni/lib/src/third_party/global_env_extensions.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// This is generated from JNI header in Android NDK. License for the same is
44
// provided below.
55

6+
// Generation logic resides in `tool/wrapper_generators`.
7+
// To regenerate, run `dart run tool/generate_ffi_bindings.dart`.
8+
69
/*
710
* Copyright (C) 2006 The Android Open Source Project
811
*

pkgs/jni/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
name: jni
66
description: A library to access JNI from Dart and Flutter that acts as a support library for package:jnigen.
7-
version: 0.8.0
7+
version: 0.9.0-wip
88
repository: https://github.com/dart-lang/native/tree/main/pkgs/jni
99

1010
topics:

pkgs/jni/src/third_party/global_jni_env.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// This is generated from JNI header in Android NDK. License for the same is
44
// provided below.
55

6+
// Generation logic resides in `tool/wrapper_generators`.
7+
// To regenerate, run `dart run tool/generate_ffi_bindings.dart`.
8+
69
/*
710
* Copyright (C) 2006 The Android Open Source Project
811
*

pkgs/jni/src/third_party/global_jni_env.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// This is generated from JNI header in Android NDK. License for the same is
44
// provided below.
55

6+
// Generation logic resides in `tool/wrapper_generators`.
7+
// To regenerate, run `dart run tool/generate_ffi_bindings.dart`.
8+
69
/*
710
* Copyright (C) 2006 The Android Open Source Project
811
*

pkgs/jni/tool/wrapper_generators/ffigen_util.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ const preamble = '''
2424
// This is generated from JNI header in Android NDK. License for the same is
2525
// provided below.
2626
27+
// Generation logic resides in `tool/wrapper_generators`.
28+
// To regenerate, run `dart run tool/generate_ffi_bindings.dart`.
29+
2730
/*
2831
* Copyright (C) 2006 The Android Open Source Project
2932
*

pkgs/jnigen/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
## 0.8.1-wip
1+
## 0.9.0-wip
22

3+
- **Breaking Change** ([#660](https://github.com/dart-lang/native/issues/660)):
4+
Removed C-based bindings. Now all bindings are Dart-only.
35
- Expand constraint on `package:cli_config` to allow `^0.2.0`.
46
- Ignore `use_super_parameters` lint in generated files.
57

pkgs/jnigen/README.md

Lines changed: 40 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,20 @@
66
## Introduction
77
Experimental bindings generator for Java bindings through dart:ffi and JNI.
88

9-
`jnigen` scans compiled JAR files or Java source code to generate a description of the API, then uses it to generate Dart annd C bindings. The Dart bindings call the C bindings, which in-turn call the Java functions through JNI. Shared functionality and base classes are provided through the support library, `package:jni`.
9+
`jnigen` scans compiled JAR files or Java source code to generate a description of the API, then uses it to generate Dart bindings. The Dart bindings call the C bindings, which in-turn call the Java functions through JNI. Shared functionality and base classes are provided through the support library, `package:jni`.
1010

1111
The configuration for binding generation is usually provided through YAML.
1212

1313
Three configuration details are needed to generate the bindings. Everything else is optional:
1414

1515
* _Inputs_: input can be Java source files (`source_path`), or compiled classes / JARs (`class_path`). Some maven / gradle based tooling is also provided to simplify obtaining dependencies.
1616

17-
* _Outputs_: Output can be generated in package-structured (one file per class) or single file bindings. Target path to write C and Dart bindings needs to be specified.
17+
* _Outputs_: Output can be generated in package-structured (one file per class) or single file bindings. Target path to write Dart bindings needs to be specified.
1818

1919
* _Classes_: Specify which classes or packages you need bindings for. Specifying a package includes all classes inside it recursively.
2020

2121
Check out the [examples](jnigen/example/) to see some sample configurations.
2222

23-
C code is always generated into a directory with it's own build configuration. It's built as a separate dynamic library.
24-
25-
Lastly, [dart_only bindings](#pure-dart-bindings) mode is also available as a proof-of-concept. It does not need intermediate C bindings, only a dependency on the support library `package:jni`.
26-
2723
## Example
2824
It's possible to generate bindings for JAR libraries, or Java source files.
2925

@@ -53,53 +49,50 @@ This produces the following boilerplate:
5349

5450
```dart
5551
/// Some boilerplate is omitted for clarity.
56-
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym) jniLookup =
57-
ProtectedJniExtensions.initGeneratedLibrary("android_utils");
58-
59-
/// from: com.example.in_app_java.AndroidUtils
6052
class AndroidUtils extends jni.JObject {
61-
AndroidUtils.fromReference(JReference reference) : super.fromReference(reference);
53+
@override
54+
late final jni.JObjType<AndroidUtils> $type = type;
55+
56+
AndroidUtils.fromReference(
57+
jni.JReference reference,
58+
) : super.fromReference(reference);
59+
60+
static final _class =
61+
jni.JClass.forName(r"com/example/in_app_java/AndroidUtils");
6262
63-
static final _showToast = jniLookup<
63+
/// The type which includes information such as the signature of this class.
64+
static const type = $AndroidUtilsType();
65+
66+
static final _id_showToast = _class.staticMethodId(
67+
r"showToast",
68+
r"(Landroid/app/Activity;Ljava/lang/CharSequence;I)V",
69+
);
70+
71+
static final _showToast = ProtectedJniExtensions.lookup<
6472
ffi.NativeFunction<
65-
jni.JniResult Function(ffi.Pointer<ffi.Void>,
66-
ffi.Pointer<ffi.Void>, ffi.Int32)>>("AndroidUtils__showToast")
73+
jni.JThrowablePtr Function(
74+
ffi.Pointer<ffi.Void>,
75+
jni.JMethodIDPtr,
76+
ffi.VarArgs<
77+
(
78+
ffi.Pointer<ffi.Void>,
79+
ffi.Pointer<ffi.Void>,
80+
ffi.Int64
81+
)>)>>("globalEnv_CallStaticVoidMethod")
6782
.asFunction<
68-
jni.JniResult Function(
83+
jni.JThrowablePtr Function(ffi.Pointer<ffi.Void>, jni.JMethodIDPtr,
6984
ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int)>();
7085
7186
/// from: static public void showToast(android.app.Activity mainActivity, java.lang.CharSequence text, int duration)
7287
static void showToast(
73-
jni.JObject mainActivity, jni.JObject text, int duration) =>
74-
_showToast(mainActivity.reference, text.reference, duration).check();
75-
}
76-
```
77-
78-
#### C Bindings:
79-
80-
```c
81-
// Some boilerplate is omitted for clarity.
82-
83-
// com.example.in_app_java.AndroidUtils
84-
jclass _c_AndroidUtils = NULL;
85-
86-
jmethodID _m_AndroidUtils__showToast = NULL;
87-
FFI_PLUGIN_EXPORT
88-
JniResult AndroidUtils__showToast(jobject mainActivity,
89-
jobject text,
90-
int32_t duration) {
91-
load_env();
92-
load_class_gr(&_c_AndroidUtils, "com/example/in_app_java/AndroidUtils");
93-
if (_c_AndroidUtils == NULL)
94-
return (JniResult){.result = {.j = 0}, .exception = check_exception()};
95-
load_static_method(_c_AndroidUtils, &_m_AndroidUtils__showToast, "showToast",
96-
"(Landroid/app/Activity;Ljava/lang/CharSequence;I)V");
97-
if (_m_AndroidUtils__showToast == NULL)
98-
return (JniResult){.result = {.j = 0}, .exception = check_exception()};
99-
(*jniEnv)->CallStaticVoidMethod(jniEnv, _c_AndroidUtils,
100-
_m_AndroidUtils__showToast, mainActivity,
101-
text, duration);
102-
return (JniResult){.result = {.j = 0}, .exception = check_exception()};
88+
jni.JObject mainActivity,
89+
jni.JObject text,
90+
int duration,
91+
) {
92+
_showToast(_class.reference.pointer, _id_showToast as jni.JMethodIDPtr,
93+
mainActivity.reference.pointer, text.reference.pointer, duration)
94+
.check();
95+
}
10396
}
10497
```
10598

@@ -110,9 +103,6 @@ android_sdk_config:
110103
add_gradle_deps: true
111104

112105
output:
113-
c:
114-
library_name: android_utils
115-
path: src/android_utils/
116106
dart:
117107
path: lib/android_utils.dart
118108
structure: single_file
@@ -145,8 +135,6 @@ More advanced features such as callbacks are not supported yet. Support for thes
145135

146136
On Flutter targets, native libraries are built automatically and bundled. On standalone platforms, no such infrastructure exists yet. As a stopgap solution, running `dart run jni:setup` in a target directory builds all JNI native dependencies of the package into `build/jni_libs`.
147137

148-
By default `jni:setup` goes through pubspec configuration and builds all JNI dependencies of the project. It can be overridden to build a custom directory using `-s` switch, which can be useful when output configuration for C bindings does not follow standard FFI plugin layout.
149-
150138
The build directory has to be passed to `Jni.spawn` call. It's assumed that all dependencies are built into the same target directory, so that once JNI is initialized, generated bindings can load their respective C libraries automatically.
151139

152140
## Requirements
@@ -171,10 +159,8 @@ $env:Path += ";${env:JAVA_HOME}\bin\server".
171159

172160
If JAVA_HOME not set, find the `java.exe` executable and set the environment variable in Control Panel. If java is installed through a package manager, there may be a more automatic way to do this. (Eg: `scoop reset`).
173161

174-
### C/C++ tooling
175-
CMake and a standard C toolchain are required to build `package:jni` and C bindings generated by `jnigen`.
176-
177-
It's recommended to have `clang-format` installed for formatting the generated C bindings. On Windows, it's part of LLVM installation. On most Linux distributions it is available as a separate package. On MacOS, it can be installed using Homebrew.
162+
### C tooling
163+
CMake and a standard C toolchain are required to build `package:jni`.
178164

179165
## FAQs
180166

@@ -225,11 +211,6 @@ A `*` denotes required configuration.
225211
| `classes` * | List of qualified class / package names | List of qualified class / package names. `source_path` will be scanned assuming the sources follow standard java-ish hierarchy. That is a.b.c either maps to a directory `a/b/c` or a class file `a/b/c.java`. |
226212
| `enable_experiment` | List of experiment names:<br><ul><li>`interface_implementation`</li></ul> | List of enabled experiments. These features are still in development and their API might break. |
227213
| `output:` | (Subsection) | This subsection will contain configuration related to output files. |
228-
| `output:` >> `bindings_type` | `c_based` (default) or `dart_only` | Binding generation strategy. [Trade-offs](#pure-dart-bindings) are explained at the end of this document. |
229-
| `output:` >> `c:` | (Subsection) | This subsection specified C output configuration. Required if `bindings_type` is `c_based`. |
230-
| `output:` >> `c:` >> path * | Directory path | Directory to write C bindings. Usually `src/` in case of an FFI plugin template. |
231-
| `output:` >> `c:` >> subdir | Directory path | If specified, C bindings will be written to `subdir` resolved relative to `path`. This is useful when bindings are supposed to be under source's license, and written to a subdirectory such as `third_party`. |
232-
| `output:` >> `c:` >> `library_name` *| Identifier (snake_case) | Name for generated C library.
233214
| `output:` >> `dart:` | (Subsection) | This subsection specifies Dart output configuration. |
234215
| `output:` >> `dart:` >> `structure` | `package_structure` / `single_file` | Whether to map resulting dart bindings to file-per-class source layout, or write all bindings to single file.
235216
| `output:` >> `dart:` >> `path` * | Directory path or File path | Path to write Dart bindings. Should end in `.dart` for `single_file` configurations, and end in `/` for `package_structure` (default) configuration. |
@@ -255,19 +236,6 @@ It's possible to use the programmatic API instead of YAML.
255236
* import `package:jnigen/jnigen.dart`
256237
* construct a `Config` object and pass it to `generateJniBindings` function. The parameters are similar to the ones described above.
257238
258-
## Pure dart Bindings
259-
It's possible to generate bindings that do not rely on an intermediate layer of C code. Bindings will still depend on `package:jni` and its support library written in C. But this approach avoids large C bindings.
260-
261-
To enable pure dart bindings, specify
262-
```
263-
output:
264-
bindings_type: dart_only
265-
```
266-
267-
Any C output configuration will be ignored.
268-
269-
However, pure dart bindings will require additional allocations and check runtimeType of the arguments. This will be the case until Variadic arguments land in Dart FFI.
270-
271239
## Android core libraries
272240
These days, Android projects depend heavily on AndroidX and other libraries downloaded via gradle. We have a tracking issue to improve detection of android SDK and dependencies. (#31). Currently we can fetch the JAR dependencies of an android project, by running a gradle stub, if `android_sdk_config` >> `add_gradle_deps` is specified.
273241

pkgs/jnigen/android_test_runner/android/app/build.gradle

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,6 @@ android {
5959
signingConfig signingConfigs.debug
6060
}
6161
}
62-
externalNativeBuild {
63-
cmake {
64-
path 'CMakeLists.txt'
65-
}
66-
}
6762
}
6863

6964
flutter {

pkgs/jnigen/android_test_runner/integration_test/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Generated file. Do not edit or check-in to version control.
2+
3+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
4+
// for details. All rights reserved. Use of this source code is governed by a
5+
// BSD-style license that can be found in the LICENSE file.
6+
7+
import "package:flutter_test/flutter_test.dart";
8+
9+
import "../../test/jackson_core_test/runtime_test_registrant.dart"
10+
as jackson_core_test;
11+
import "../../test/simple_package_test/runtime_test_registrant.dart"
12+
as simple_package_test;
13+
import "../../test/kotlin_test/runtime_test_registrant.dart" as kotlin_test;
14+
15+
typedef TestCaseCallback = void Function();
16+
17+
void test(String description, TestCaseCallback testCase) {
18+
testWidgets(description, (widgetTester) async => testCase());
19+
}
20+
21+
void main() {
22+
jackson_core_test.registerTests("jackson_core_test", test);
23+
simple_package_test.registerTests("simple_package_test", test);
24+
kotlin_test.registerTests("kotlin_test", test);
25+
}

pkgs/jnigen/cmake/CMakeLists.txt.tmpl

Lines changed: 0 additions & 32 deletions
This file was deleted.

pkgs/jnigen/cmake/README.md

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
# In-App Java Example
22

3-
This example shows how to write custom java code in `android/app/src` and call it using `jnigen` generated bindings.
3+
This example shows how to write custom java code in `android/app/src` and call
4+
it using `jnigen` generated bindings.
45

56
#### How to run this example:
6-
* Run `flutter run` to run the app.
77

8-
* To regenerate bindings after changing Java code, run `flutter pub run jnigen --config jnigen.yaml`. This requires at least one APK build to have been run before, so that it's possible for `jnigen` to obtain classpaths of Android Gradle libraries. Therefore, once run `flutter build apk` before generating bindings for the first time, or after a `flutter clean`.
8+
- Run `flutter run` to run the app.
99

10-
#### General steps
11-
These are general steps to integrate Java code into a flutter project using `jnigen`.
12-
13-
* Write Java code in suitable package folder, under `android/` subproject of the flutter app.
14-
15-
* Create A jnigen config like `jnigen.yaml` in this example.
10+
- To regenerate bindings after changing Java code, run
11+
`flutter pub run jnigen --config jnigen.yaml`. This requires at least one APK
12+
build to have been run before, so that it's possible for `jnigen` to obtain
13+
classpaths of Android Gradle libraries. Therefore, once run
14+
`flutter build apk` before generating bindings for the first time, or after a
15+
`flutter clean`.
1616

17-
* Generate bindings using jnigen config.
18-
19-
* Add an `externalNativeBuild` to gradle script (see `android/app/build.gradle` in this example).
20-
21-
* Add proguard rules to exclude your custom classes from tree shaking, since they are always accessed reflectively in JNI.
17+
#### General steps
2218

23-
* Build and run the app.
19+
These are general steps to integrate Java code into a flutter project using
20+
`jnigen`.
2421

22+
- Write Java code in suitable package folder, under `android/` subproject of the
23+
flutter app.
24+
- Create A jnigen config like `jnigen.yaml` in this example.
25+
- Generate bindings using jnigen config.
26+
- Add proguard rules to exclude your custom classes from tree shaking, since
27+
they are always accessed reflectively in JNI.
28+
- Build and run the app.

0 commit comments

Comments
 (0)