Skip to content

Commit 50273bb

Browse files
authored
[jnigen] tools: generate_ide_files.dart and pre_commit_checks.dart (#86)
1 parent bfb6f37 commit 50273bb

File tree

4 files changed

+291
-2
lines changed

4 files changed

+291
-2
lines changed

.github/workflows/test-package.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
# for details. All rights reserved. Use of this source code is governed by a
33
# BSD-style license that can be found in the LICENSE file.
44

5+
# Note: There's a script under `jnigen/tool` which runs a subset of these
6+
# tests on local machine. It's a useful little script for checking the
7+
# code before making a PR. If you add a task here, you might want to add
8+
# the equivalent in that script as well.
9+
510
name: Dart CI
611

712
on:
@@ -124,7 +129,7 @@ jobs:
124129
- name: install clang tools
125130
run: |
126131
sudo apt-get update -y
127-
sudo apt-get install -y clang-format
132+
sudo apt-get install -y clang-format build-essential cmake
128133
- run: flutter pub get
129134
- name: Check formatting
130135
run: flutter format --output=none --set-exit-if-changed .
@@ -133,6 +138,10 @@ jobs:
133138
- name: Check C code formatting using clang-format
134139
run: clang-format --dry-run -Werror dartjni.c dartjni.h third_party/*.c third_party/*.h
135140
working-directory: pkg/jni/src
141+
- name: verify that tool/generate_ide_files.dart generates a file
142+
run: |
143+
dart run tool/generate_ide_files.dart
144+
ls src/compile_commands.json
136145
137146
test_jni:
138147
runs-on: ubuntu-latest

pkgs/jni/tool/generate_ide_files.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:args/args.dart';
8+
9+
const makefilesGenerator = 'make';
10+
const ninjaGenerator = 'ninja';
11+
12+
const cmakeGeneratorNames = {
13+
makefilesGenerator: 'Unix Makefiles',
14+
ninjaGenerator: 'Ninja',
15+
};
16+
17+
void runCommand(String exec, List<String> args, String workingDirectory) {
18+
stderr.writeln('+ $exec ${args.join(" ")}');
19+
final process =
20+
Process.runSync(exec, args, workingDirectory: workingDirectory);
21+
if (process.exitCode != 0) {
22+
stdout.writeln(process.stdout);
23+
stderr.writeln(process.stderr);
24+
throw "command failed with exit code ${process.exitCode}";
25+
}
26+
}
27+
28+
void main(List<String> arguments) {
29+
final argParser = ArgParser()
30+
..addOption(
31+
"generator",
32+
abbr: 'G',
33+
help: 'Generator to pass to CMake. (Either "ninja" or "make").',
34+
allowed: [ninjaGenerator, makefilesGenerator],
35+
defaultsTo: Platform.isWindows ? ninjaGenerator : makefilesGenerator,
36+
)
37+
..addFlag('help', abbr: 'h', help: 'Show this usage information.');
38+
final argResults = argParser.parse(arguments);
39+
if (argResults.rest.isNotEmpty || argResults['help']) {
40+
stderr.writeln('This script generates compile_commands.json for '
41+
'C source files in src/');
42+
stderr.writeln(argParser.usage);
43+
exitCode = 1;
44+
return;
45+
}
46+
final generator = cmakeGeneratorNames[argResults['generator']];
47+
final tempDir = Directory.current.createTempSync("clangd_setup_temp_");
48+
final src = Directory.current.uri.resolve("src/");
49+
try {
50+
runCommand(
51+
"cmake",
52+
[
53+
"-DCMAKE_EXPORT_COMPILE_COMMANDS=1",
54+
src.toFilePath(),
55+
"-G",
56+
generator!,
57+
],
58+
tempDir.path);
59+
final createdFile = tempDir.uri.resolve("compile_commands.json");
60+
final target = src.resolve("compile_commands.json");
61+
File.fromUri(createdFile).renameSync(target.toFilePath());
62+
} finally {
63+
tempDir.deleteSync(recursive: true);
64+
}
65+
}

pkgs/jnigen/test/jackson_core_test/generated_files_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ void main() async {
1414
final lib = join(thirdPartyDir, 'lib');
1515
final src = join(thirdPartyDir, 'src');
1616
await generateAndCompareBindings(getConfig(), lib, src);
17-
});
17+
}, timeout: Timeout.factor(2));
1818

1919
test(
2020
'generate and analyze bindings for complete library, '
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This is a script to run a subset of CI checks on development machine before
6+
// submitting PR. This prevents from accidentally forgetting to format or
7+
// remove an unused import.
8+
9+
// This is rough around the edges. This script may give false positives such
10+
// as some gitignore'd temporary files failing the comparison.
11+
12+
// On windows, please install 'diffutils' using your favorite package installer
13+
// for this script to work correctly. Also ensure that clang-format is on your
14+
// PATH.
15+
16+
import 'dart:async';
17+
import 'dart:io';
18+
19+
const ansiRed = '\x1b[31m';
20+
const ansiDefault = '\x1b[39;49m';
21+
22+
void printError(Object? message) {
23+
if (stderr.supportsAnsiEscapes) {
24+
message = '$ansiRed$message$ansiDefault';
25+
}
26+
stderr.writeln(message);
27+
}
28+
29+
class StepFailure implements Exception {
30+
StepFailure(this.name);
31+
String name;
32+
@override
33+
String toString() => 'step failed: $name';
34+
}
35+
36+
abstract class Step {
37+
/// Runs this step, raises an exception if something fails.
38+
Future<void> run();
39+
}
40+
41+
class Callback implements Step {
42+
Callback(this.name, this.function);
43+
String name;
44+
Future<void> Function() function;
45+
@override
46+
Future<void> run() => function();
47+
}
48+
49+
class Command implements Step {
50+
Command(this.exec, this.args, this.workingDirectory);
51+
final String exec;
52+
final List<String> args;
53+
final String workingDirectory;
54+
55+
@override
56+
Future<void> run() async {
57+
final result =
58+
await Process.run(exec, args, workingDirectory: workingDirectory);
59+
if (result.exitCode != 0) {
60+
printError(result.stdout);
61+
printError(result.stderr);
62+
final commandString = "$exec ${args.join(" ")}";
63+
stderr.writeln("failure executing command: $commandString");
64+
throw StepFailure(commandString);
65+
}
66+
}
67+
}
68+
69+
class Runner {
70+
static final gitRoot = getRepositoryRoot();
71+
Runner(this.name, this.defaultWorkingDir);
72+
String name;
73+
String defaultWorkingDir;
74+
final steps = <Step>[];
75+
final cleanupSteps = <Step>[];
76+
77+
void chainCommand(String exec, List<String> args,
78+
{String? workingDirectory}) =>
79+
_addCommand(steps, exec, args, workingDirectory: workingDirectory);
80+
81+
void chainCleanupCommand(String exec, List<String> args,
82+
{String? workingDirectory}) =>
83+
_addCommand(cleanupSteps, exec, args, workingDirectory: workingDirectory);
84+
85+
void _addCommand(List<Step> list, String exec, List<String> args,
86+
{String? workingDirectory}) {
87+
final resolvedWorkingDirectory =
88+
gitRoot.resolve(workingDirectory ?? defaultWorkingDir);
89+
list.add(Command(exec, args, resolvedWorkingDirectory.toFilePath()));
90+
}
91+
92+
void chainCallback(String name, Future<void> Function() callback) {
93+
steps.add(Callback(name, callback));
94+
}
95+
96+
Future<void> run() async {
97+
stderr.writeln("started: $name");
98+
var error = false;
99+
for (var step in steps) {
100+
try {
101+
await step.run();
102+
} on StepFailure catch (e) {
103+
stderr.writeln(e);
104+
error = true;
105+
exitCode = 1;
106+
break;
107+
}
108+
}
109+
stderr.writeln('${error ? "failed" : "complete"}: $name');
110+
for (var step in cleanupSteps) {
111+
try {
112+
await step.run();
113+
} on Exception catch (e) {
114+
printError("ERROR: $e");
115+
}
116+
}
117+
}
118+
}
119+
120+
Uri getRepositoryRoot() {
121+
final gitCommand = Process.runSync("git", ["rev-parse", "--show-toplevel"]);
122+
final output = gitCommand.stdout as String;
123+
return Uri.directory(output.trim());
124+
}
125+
126+
void main() async {
127+
final jniAnalyze = Runner("Analyze JNI", "jni");
128+
jniAnalyze
129+
..chainCommand("dart", ["analyze", "--fatal-infos"])
130+
..chainCommand(
131+
"dart", ["format", "--output=none", "--set-exit-if-changed", "."])
132+
..chainCommand(
133+
"clang-format",
134+
[
135+
"--dry-run",
136+
"-Werror",
137+
"dartjni.c",
138+
"dartjni.h",
139+
"third_party/global_jni_env.c",
140+
"third_party/global_jni_env.h",
141+
],
142+
workingDirectory: "jni/src");
143+
final jniTest = Runner("Test JNI", "jni")
144+
..chainCommand("dart", ["run", "jni:setup"])
145+
..chainCommand("dart", ["test", "-j", "1"]);
146+
unawaited(jniAnalyze.run().then((f) => jniTest.run()));
147+
final ffigenBindingsPath = getRepositoryRoot()
148+
.resolve("jni/lib/src/third_party/jni_bindings_generated.dart");
149+
final ffigenBindings = File.fromUri(ffigenBindingsPath);
150+
final oldBindingsText = ffigenBindings.readAsStringSync();
151+
final ffigenCompare = Runner("Generate & Compare FFIGEN bindings", "jni")
152+
..chainCommand("dart", ["run", "ffigen", "--config", "ffigen.yaml"])
153+
..chainCallback("compare bindings", () async {
154+
final newBindingsText = await ffigenBindings.readAsString();
155+
if (newBindingsText != oldBindingsText) {
156+
await ffigenBindings.writeAsString(oldBindingsText);
157+
throw "new JNI.h bindings differ from old bindings";
158+
}
159+
});
160+
unawaited(ffigenCompare.run());
161+
162+
final jnigenAnalyze = Runner("Analyze jnigen", "jnigen")
163+
..chainCommand("dart", ["analyze", "--fatal-infos"])
164+
..chainCommand(
165+
"dart", ["format", "--output=none", "--set-exit-if-changed", "."])
166+
..chainCommand("dart", ["run", "jnigen:setup"]);
167+
final jnigenTest = Runner("Test jnigen", "jnigen")
168+
..chainCommand("dart", ["test"]);
169+
final compareInAppJavaBindings = Runner(
170+
"Generate & compare InAppJava bindings", "jnigen/example/in_app_java")
171+
..chainCommand("dart", [
172+
"run",
173+
"jnigen",
174+
"--config",
175+
"jnigen.yaml",
176+
"-Dc_root=src_temp",
177+
"-Ddart_root=lib_temp",
178+
])
179+
..chainCommand("diff", ["-qr", "lib/android_utils/", "lib_temp/"])
180+
..chainCommand("diff", ["-qr", "src/android_utils/", "src_temp/"])
181+
..chainCleanupCommand("rm", ["-r", "lib_temp", "src_temp"]);
182+
final comparePdfboxBindings = Runner(
183+
"Generate & compare PdfBox Bindings", "jnigen/example/pdfbox_plugin")
184+
..chainCommand("dart", [
185+
"run",
186+
"jnigen",
187+
"--config",
188+
"jnigen.yaml",
189+
"-Dc_root=src_temp",
190+
"-Ddart_root=lib_temp",
191+
])
192+
..chainCommand("diff", ["-qr", "lib/third_party/", "lib_temp/"])
193+
..chainCommand("diff", ["-qr", "src/", "src_temp/"])
194+
..chainCleanupCommand("rm", ["-r", "lib_temp", "src_temp"]);
195+
final compareNotificationPluginBindings = Runner(
196+
"Generate & compare NotificationPlugin Bindings",
197+
"jnigen/example/notification_plugin")
198+
..chainCommand("dart", [
199+
"run",
200+
"jnigen",
201+
"--config",
202+
"jnigen.yaml",
203+
"-Dc_root=src_temp",
204+
"-Ddart_root=lib_temp",
205+
])
206+
..chainCommand("diff", ["-qr", "lib/", "lib_temp/"])
207+
..chainCommand("diff", ["-qr", "src/", "src_temp/"])
208+
..chainCleanupCommand("rm", ["-r", "lib_temp", "src_temp"]);
209+
unawaited(jnigenAnalyze.run().then((_) {
210+
jnigenTest.run();
211+
compareInAppJavaBindings.run();
212+
comparePdfboxBindings.run();
213+
compareNotificationPluginBindings.run();
214+
}));
215+
}

0 commit comments

Comments
 (0)