Skip to content

Commit a86fcd9

Browse files
authored
[jnigen] Initial code generator support (#19)
1 parent e4d1e43 commit a86fcd9

File tree

104 files changed

+12414
-101
lines changed

Some content is hidden

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

104 files changed

+12414
-101
lines changed

.github/workflows/test-package.yml

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ env:
1717
PUB_ENVIRONMENT: bot.github
1818

1919
jobs:
20-
# Check code formatting and static analysis on a single OS (linux)
21-
# against Dart stable.
20+
check_java_format:
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v2 # v2 minimum required
24+
- uses: axel-op/googlejavaformat-action@v3
25+
with:
26+
args: "--set-exit-if-changed"
27+
2228
analyze_jni_gen:
2329
runs-on: ubuntu-latest
2430
defaults:
@@ -60,6 +66,11 @@ jobs:
6066
- uses: dart-lang/[email protected]
6167
with:
6268
sdk: stable
69+
- uses: actions/setup-java@v2
70+
with:
71+
distribution: 'zulu'
72+
java-version: '11'
73+
cache: maven
6374
- name: Install dependencies
6475
run: dart pub get
6576
- name: Run VM tests
@@ -80,6 +91,20 @@ jobs:
8091
## i.e do not rerun analyze and format steps, and do not require flutter.
8192
## IssueRef: https://github.com/dart-lang/jni_gen/issues/15
8293

94+
test_summarizer:
95+
runs-on: ubuntu-latest
96+
defaults:
97+
run:
98+
working-directory: pkg/jni_gen/java
99+
steps:
100+
- uses: actions/checkout@v2
101+
- uses: actions/setup-java@v2
102+
with:
103+
distribution: 'zulu'
104+
java-version: '11'
105+
- name: run tests using maven surefire
106+
run: mvn surefire:test
107+
83108
test_jni:
84109
runs-on: ubuntu-latest
85110
defaults:
@@ -139,12 +164,9 @@ jobs:
139164
- run: |
140165
sudo apt-get update -y
141166
sudo apt-get install -y ninja-build libgtk-3-dev
142-
- run: dart pub get
143-
working-directory: pkg/jni
144-
- run: dart run bin/setup.dart
145-
working-directory: pkg/jni
146167
- run: flutter config --enable-linux-desktop
147168
- run: flutter pub get
169+
- run: dart run jni:setup
148170
- run: flutter test
149171
- run: flutter build linux
150172

pkgs/jni/android/build.gradle

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,9 @@ android {
3131

3232
// Bumping the plugin ndkVersion requires all clients of this plugin to bump
3333
// the version in their app and to download a newer version of the NDK.
34-
35-
// Note(MaheshH): Seems 22 is lowest one can get through SDKManager now?
36-
//
37-
// It's more of a logistic issue, I can't download NDK 21, so keeping it
38-
// 22. You might get a warning to bump some versions.
39-
ndkVersion "22.1.7171670"
34+
// Note(MaheshH) - Flutter seems to download minimum NDK of flutter when
35+
// below line is commented out.
36+
// How about leaving it?
4037
// ndkVersion "21.1.6352462"
4138

4239
// Invoke the shared CMake build with the Android Gradle Plugin.
Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
package dev.dart.jni;
22

3+
import android.app.Activity;
4+
import android.content.Context;
35
import androidx.annotation.Keep;
46
import androidx.annotation.NonNull;
5-
import android.util.Log;
6-
import android.app.Activity;
7-
import io.flutter.plugin.common.PluginRegistry.Registrar;
87
import io.flutter.embedding.engine.plugins.FlutterPlugin;
98
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
109
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
11-
12-
import android.content.Context;
10+
import io.flutter.plugin.common.PluginRegistry.Registrar;
1311

1412
@Keep
1513
public class JniPlugin implements FlutterPlugin, ActivityAware {
16-
14+
1715
@Override
18-
public void
19-
onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
20-
setup(binding.getApplicationContext());
16+
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
17+
setup(binding.getApplicationContext());
2118
}
2219

2320
public static void registerWith(Registrar registrar) {
2421
JniPlugin plugin = new JniPlugin();
25-
plugin.setup(registrar.activeContext());
22+
plugin.setup(registrar.activeContext());
2623
}
2724

2825
private void setup(Context context) {
29-
initializeJni(context, getClass().getClassLoader());
26+
initializeJni(context, getClass().getClassLoader());
3027
}
3128

3229
@Override
@@ -35,27 +32,27 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
3532
// Activity handling methods
3633
@Override
3734
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
38-
Activity activity = binding.getActivity();
39-
setJniActivity(activity, activity.getApplicationContext());
35+
Activity activity = binding.getActivity();
36+
setJniActivity(activity, activity.getApplicationContext());
4037
}
4138

4239
@Override
4340
public void onDetachedFromActivityForConfigChanges() {}
4441

4542
@Override
4643
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
47-
Activity activity = binding.getActivity();
48-
setJniActivity(activity, activity.getApplicationContext());
44+
Activity activity = binding.getActivity();
45+
setJniActivity(activity, activity.getApplicationContext());
4946
}
5047

5148
@Override
5249
public void onDetachedFromActivity() {}
5350

5451
native void initializeJni(Context context, ClassLoader classLoader);
52+
5553
native void setJniActivity(Activity activity, Context context);
5654

5755
static {
58-
System.loadLibrary("dartjni");
56+
System.loadLibrary("dartjni");
5957
}
6058
}
61-

pkgs/jni/bin/setup.dart

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,27 @@ import 'package:package_config/package_config.dart';
55

66
const _buildDir = "build-dir";
77
const _srcDir = "source-dir";
8+
const _packageName = 'package-name';
89
const _verbose = "verbose";
910
const _cmakeArgs = "cmake-args";
1011
const _clean = "clean";
1112

13+
const _cmakeTemporaryFiles = [
14+
'CMakeCache.txt',
15+
'CMakeFiles/',
16+
'cmake_install.cmake',
17+
'Makefile'
18+
];
19+
20+
void deleteCMakeTemps(Uri buildDir) async {
21+
for (var filename in _cmakeTemporaryFiles) {
22+
if (options.verbose) {
23+
stderr.writeln('remove $filename');
24+
}
25+
await File(buildDir.resolve(filename).toFilePath()).delete(recursive: true);
26+
}
27+
}
28+
1229
// Sets up input output channels and maintains state.
1330
class CommandRunner {
1431
CommandRunner({this.printCmds = false});
@@ -25,7 +42,7 @@ class CommandRunner {
2542
}
2643
final process = await Process.start(exec, args,
2744
workingDirectory: workingDir,
28-
runInShell: Platform.isWindows,
45+
runInShell: true,
2946
mode: ProcessStartMode.inheritStdio);
3047
final exitCode = await process.exitCode;
3148
if (exitCode != 0) {
@@ -39,11 +56,14 @@ class Options {
3956
Options(ArgResults arg)
4057
: buildDir = arg[_buildDir],
4158
srcDir = arg[_srcDir],
59+
packageName = arg[_packageName] ?? 'jni',
4260
cmakeArgs = arg[_cmakeArgs],
4361
verbose = arg[_verbose] ?? false,
4462
clean = arg[_clean] ?? false;
4563

46-
String? buildDir, srcDir, cmakeArgs;
64+
String? buildDir, srcDir;
65+
String packageName;
66+
List<String> cmakeArgs;
4767
bool verbose, clean;
4868
}
4969

@@ -63,7 +83,7 @@ Future<String?> findSources() async {
6383
}
6484
final packages = packageConfig.packages;
6585
for (var package in packages) {
66-
if (package.name == 'jni') {
86+
if (package.name == options.packageName) {
6787
return package.root.resolve("src/").toFilePath();
6888
}
6989
}
@@ -76,14 +96,18 @@ void main(List<String> arguments) async {
7696
abbr: 'B', help: 'Directory to place built artifacts')
7797
..addOption(_srcDir,
7898
abbr: 'S', help: 'alternative path to package:jni sources')
99+
..addOption(_packageName,
100+
abbr: 'p',
101+
help: 'package for which native'
102+
'library should be built',
103+
defaultsTo: 'jni')
79104
..addFlag(_verbose, abbr: 'v', help: 'Enable verbose output')
80105
..addFlag(_clean,
81106
negatable: false,
82107
abbr: 'C',
83108
help: 'Clear built artifacts instead of running a build')
84-
..addOption(_cmakeArgs,
85-
abbr: 'm',
86-
help: 'additional space separated arguments to pass to CMake');
109+
..addMultiOption(_cmakeArgs,
110+
abbr: 'm', help: 'additional argument to pass to CMake');
87111
final cli = parser.parse(arguments);
88112
options = Options(cli);
89113
final rest = cli.rest;
@@ -114,7 +138,7 @@ void main(List<String> arguments) async {
114138

115139
final currentDirUri = Uri.file(".");
116140
final buildPath =
117-
options.buildDir ?? currentDirUri.resolve("src/build").toFilePath();
141+
options.buildDir ?? currentDirUri.resolve("build/jni_libs").toFilePath();
118142
final buildDir = Directory(buildPath);
119143
await buildDir.create(recursive: true);
120144
log("buildPath: $buildPath");
@@ -136,15 +160,15 @@ void main(List<String> arguments) async {
136160
Future<void> build(Options options, String srcPath, String buildPath) async {
137161
final runner = CommandRunner(printCmds: true);
138162
final cmakeArgs = <String>[];
139-
if (options.cmakeArgs != null) {
140-
cmakeArgs.addAll(options.cmakeArgs!.split(" "));
141-
}
163+
cmakeArgs.addAll(options.cmakeArgs);
142164
cmakeArgs.add(srcPath);
143165
await runner.run("cmake", cmakeArgs, buildPath);
144166
await runner.run("cmake", ["--build", "."], buildPath);
145167
if (Platform.isWindows) {
146168
await runner.run("move", ["Debug\\dartjni.dll", "."], buildPath);
147169
}
170+
// delete cmakeTemporaryArtifacts
171+
deleteCMakeTemps(Uri.directory(buildPath));
148172
}
149173

150174
Future<void> cleanup(Options options, String srcPath, String buildPath) async {
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
11
package dev.dart.jni_example;
22

33
import android.app.Activity;
4-
import android.os.Handler;
54
import android.content.Context;
65
import android.widget.Toast;
76
import androidx.annotation.Keep;
87

98
@Keep
109
class AnyToast {
11-
static AnyToast makeText(Activity mainActivity, Context context, CharSequence text, int duration) {
12-
AnyToast toast = new AnyToast();
13-
toast.mainActivity = mainActivity;
14-
toast.context = context;
15-
toast.text = text;
16-
toast.duration = duration;
17-
return toast;
18-
}
10+
static AnyToast makeText(
11+
Activity mainActivity, Context context, CharSequence text, int duration) {
12+
AnyToast toast = new AnyToast();
13+
toast.mainActivity = mainActivity;
14+
toast.context = context;
15+
toast.text = text;
16+
toast.duration = duration;
17+
return toast;
18+
}
1919

20-
void show() {
21-
mainActivity.runOnUiThread(() -> Toast.makeText(context, text, duration).show());
22-
}
20+
void show() {
21+
mainActivity.runOnUiThread(() -> Toast.makeText(context, text, duration).show());
22+
}
2323

24-
Activity mainActivity;
25-
Context context;
26-
CharSequence text;
27-
int duration;
24+
Activity mainActivity;
25+
Context context;
26+
CharSequence text;
27+
int duration;
2828
}

pkgs/jni/example/test/widget_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'package:jni_example/main.dart';
1414

1515
void main() {
1616
if (!Platform.isAndroid) {
17-
Jni.spawn(helperDir: "../src/build");
17+
Jni.spawn(helperDir: "build/jni_libs");
1818
}
1919
final jni = Jni.getInstance();
2020
testWidgets("simple toString example", (tester) async {

pkgs/jni/lib/jni.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,4 @@ export 'src/jvalues.dart' hide JValueArgs, toJValues;
6464
export 'src/extensions.dart'
6565
show StringMethodsForJni, CharPtrMethodsForJni, AdditionalJniEnvMethods;
6666
export 'src/jni_exceptions.dart';
67+
export 'src/jl_object.dart';

pkgs/jni/lib/src/jl_object.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import 'dart:ffi';
2+
3+
import 'package:ffi/ffi.dart';
4+
5+
import 'third_party/jni_bindings_generated.dart';
6+
import 'jni_exceptions.dart';
7+
import 'jni.dart';
8+
9+
/// A container for a global [JObject] reference.
10+
class JlObject {
11+
/// Constructs a `JlObject` from JNI reference.
12+
JlObject.fromRef(this.reference);
13+
14+
/// Stored JNI global reference to the object.
15+
JObject reference;
16+
17+
bool _deleted = false;
18+
19+
/// Deletes the underlying JNI reference.
20+
///
21+
/// Must be called after this object is no longer needed.
22+
void delete() {
23+
if (_deleted) {
24+
throw DoubleFreeException(this, reference);
25+
}
26+
_deleted = true;
27+
// TODO(#12): this should be done in jni-thread-safe way
28+
// will be solved when #12 is implemented.
29+
Jni.getInstance().getEnv().DeleteGlobalRef(reference);
30+
}
31+
}
32+
33+
/// A container for JNI strings, with convertion to & from dart strings.
34+
class JlString extends JlObject {
35+
JlString.fromRef(JString reference) : super.fromRef(reference);
36+
37+
static JString _toJavaString(String s) {
38+
final chars = s.toNativeUtf8().cast<Char>();
39+
final jstr = Jni.getInstance().toJavaString(chars);
40+
malloc.free(chars);
41+
return jstr;
42+
}
43+
44+
JlString.fromString(String s) : super.fromRef(_toJavaString(s));
45+
46+
String toDartString() {
47+
final jni = Jni.getInstance();
48+
if (reference == nullptr) {
49+
throw NullJlStringException();
50+
}
51+
final chars = jni.getJavaStringChars(reference);
52+
final result = chars.cast<Utf8>().toDartString();
53+
jni.releaseJavaStringChars(reference, chars);
54+
return result;
55+
}
56+
57+
late final _dartString = toDartString();
58+
59+
@override
60+
String toString() => _dartString;
61+
}

0 commit comments

Comments
 (0)