[jnigen] Add regenerate script (https://github.com/dart-lang/jnigen/issues/106)

diff --git a/pkgs/jnigen/tool/command_runner.dart b/pkgs/jnigen/tool/command_runner.dart
new file mode 100644
index 0000000..7bf925e
--- /dev/null
+++ b/pkgs/jnigen/tool/command_runner.dart
@@ -0,0 +1,109 @@
+import 'dart:io';
+
+const ansiRed = '\x1b[31m';
+const ansiDefault = '\x1b[39;49m';
+
+void printError(Object? message) {
+  if (stderr.supportsAnsiEscapes) {
+    message = '$ansiRed$message$ansiDefault';
+  }
+  stderr.writeln(message);
+}
+
+class StepFailure implements Exception {
+  StepFailure(this.name);
+  String name;
+  @override
+  String toString() => 'step failed: $name';
+}
+
+abstract class Step {
+  /// Runs this step, raises an exception if something fails.
+  Future<void> run();
+}
+
+class Callback implements Step {
+  Callback(this.name, this.function);
+  String name;
+  Future<void> Function() function;
+  @override
+  Future<void> run() => function();
+}
+
+class Command implements Step {
+  Command(this.exec, this.args, this.workingDirectory);
+  final String exec;
+  final List<String> args;
+  final Uri workingDirectory;
+
+  @override
+  Future<void> run() async {
+    final result = await Process.run(
+      exec,
+      args,
+      workingDirectory: workingDirectory.toFilePath(),
+      runInShell: true,
+    );
+    if (result.exitCode != 0) {
+      printError(result.stdout);
+      printError(result.stderr);
+      final commandString = "$exec ${args.join(" ")}";
+      stderr.writeln("failure executing command: $commandString");
+      throw StepFailure(commandString);
+    }
+  }
+}
+
+class Runner {
+  static final gitRoot = getRepositoryRoot();
+  Runner(this.name, this.defaultWorkingDir);
+  String name;
+  Uri defaultWorkingDir;
+  final steps = <Step>[];
+  final cleanupSteps = <Step>[];
+
+  void chainCommand(String exec, List<String> args, {Uri? workingDirectory}) =>
+      _addCommand(steps, exec, args, workingDirectory: workingDirectory);
+
+  void chainCleanupCommand(String exec, List<String> args,
+          {Uri? workingDirectory}) =>
+      _addCommand(cleanupSteps, exec, args, workingDirectory: workingDirectory);
+
+  void _addCommand(List<Step> list, String exec, List<String> args,
+      {Uri? workingDirectory}) {
+    list.add(Command(exec, args, (workingDirectory ?? defaultWorkingDir)));
+  }
+
+  void chainCallback(String name, Future<void> Function() callback) {
+    steps.add(Callback(name, callback));
+  }
+
+  Future<void> run() async {
+    stderr.writeln("started: $name");
+    var error = false;
+    for (var step in steps) {
+      try {
+        await step.run();
+      } on StepFailure catch (e) {
+        stderr.writeln(e);
+        error = true;
+        exitCode = 1;
+        break;
+      }
+    }
+    stderr.writeln('${error ? "failed" : "complete"}: $name');
+    for (var step in cleanupSteps) {
+      try {
+        await step.run();
+      } on Exception catch (e) {
+        printError("ERROR: $e");
+      }
+    }
+  }
+}
+
+Uri getRepositoryRoot() {
+  final gitCommand = Process.runSync("git", ["rev-parse", "--show-toplevel"]);
+  final output = gitCommand.stdout as String;
+  return Uri.directory(output.trim());
+}
diff --git a/pkgs/jnigen/tool/pre_commit_checks.dart b/pkgs/jnigen/tool/pre_commit_checks.dart
index 6575e8a..07eb0b6 100644
--- a/pkgs/jnigen/tool/pre_commit_checks.dart
+++ b/pkgs/jnigen/tool/pre_commit_checks.dart
@@ -16,115 +16,26 @@
 import 'dart:async';
 import 'dart:io';
 
-const ansiRed = '\x1b[31m';
-const ansiDefault = '\x1b[39;49m';
-
-void printError(Object? message) {
-  if (stderr.supportsAnsiEscapes) {
-    message = '$ansiRed$message$ansiDefault';
-  }
-  stderr.writeln(message);
-}
-
-class StepFailure implements Exception {
-  StepFailure(this.name);
-  String name;
-  @override
-  String toString() => 'step failed: $name';
-}
-
-abstract class Step {
-  /// Runs this step, raises an exception if something fails.
-  Future<void> run();
-}
-
-class Callback implements Step {
-  Callback(this.name, this.function);
-  String name;
-  Future<void> Function() function;
-  @override
-  Future<void> run() => function();
-}
-
-class Command implements Step {
-  Command(this.exec, this.args, this.workingDirectory);
-  final String exec;
-  final List<String> args;
-  final String workingDirectory;
-
-  @override
-  Future<void> run() async {
-    final result =
-        await Process.run(exec, args, workingDirectory: workingDirectory);
-    if (result.exitCode != 0) {
-      printError(result.stdout);
-      printError(result.stderr);
-      final commandString = "$exec ${args.join(" ")}";
-      stderr.writeln("failure executing command: $commandString");
-      throw StepFailure(commandString);
-    }
-  }
-}
-
-class Runner {
-  static final gitRoot = getRepositoryRoot();
-  Runner(this.name, this.defaultWorkingDir);
-  String name;
-  String defaultWorkingDir;
-  final steps = <Step>[];
-  final cleanupSteps = <Step>[];
-
-  void chainCommand(String exec, List<String> args,
-          {String? workingDirectory}) =>
-      _addCommand(steps, exec, args, workingDirectory: workingDirectory);
-
-  void chainCleanupCommand(String exec, List<String> args,
-          {String? workingDirectory}) =>
-      _addCommand(cleanupSteps, exec, args, workingDirectory: workingDirectory);
-
-  void _addCommand(List<Step> list, String exec, List<String> args,
-      {String? workingDirectory}) {
-    final resolvedWorkingDirectory =
-        gitRoot.resolve(workingDirectory ?? defaultWorkingDir);
-    list.add(Command(exec, args, resolvedWorkingDirectory.toFilePath()));
-  }
-
-  void chainCallback(String name, Future<void> Function() callback) {
-    steps.add(Callback(name, callback));
-  }
-
-  Future<void> run() async {
-    stderr.writeln("started: $name");
-    var error = false;
-    for (var step in steps) {
-      try {
-        await step.run();
-      } on StepFailure catch (e) {
-        stderr.writeln(e);
-        error = true;
-        exitCode = 1;
-        break;
-      }
-    }
-    stderr.writeln('${error ? "failed" : "complete"}: $name');
-    for (var step in cleanupSteps) {
-      try {
-        await step.run();
-      } on Exception catch (e) {
-        printError("ERROR: $e");
-      }
-    }
-  }
-}
-
-Uri getRepositoryRoot() {
-  final gitCommand = Process.runSync("git", ["rev-parse", "--show-toplevel"]);
-  final output = gitCommand.stdout as String;
-  return Uri.directory(output.trim());
-}
+import 'command_runner.dart';
 
 void main() async {
-  final jniAnalyze = Runner("Analyze JNI", "jni");
+  final gitRoot = getRepositoryRoot();
+
+  // change to project root
+  Directory.current = gitRoot.toFilePath();
+
+  final tempDir = Directory.current.createTempSync('jnigen_checks_clone_');
+  final tempJniPath = tempDir.uri.resolve("jni/");
+  final tempJnigenPath = tempDir.uri.resolve("jnigen/");
+  final gitClone = Runner("Clone jni", Directory.current.uri)
+    ..chainCommand('git', ['clone', '.', tempDir.path])
+    ..chainCommand("flutter", ["pub", "get", "--offline"],
+        workingDirectory: tempJniPath)
+    ..chainCommand("dart", ["pub", "get", "--offline"],
+        workingDirectory: tempJnigenPath);
+  await gitClone.run();
+
+  final jniAnalyze = Runner("Analyze JNI", tempJniPath);
   jniAnalyze
     ..chainCommand("dart", ["analyze", "--fatal-infos"])
     ..chainCommand(
@@ -139,35 +50,44 @@
           "third_party/global_jni_env.c",
           "third_party/global_jni_env.h",
         ],
-        workingDirectory: "jni/src");
-  final jniTest = Runner("Test JNI", "jni")
+        workingDirectory: tempJniPath.resolve("src/"));
+  final jniTest = Runner("Test JNI", tempJniPath)
     ..chainCommand("dart", ["run", "jni:setup"])
     ..chainCommand("dart", ["test", "-j", "1"]);
   unawaited(jniAnalyze.run().then((f) => jniTest.run()));
+
   final ffigenBindingsPath = getRepositoryRoot()
       .resolve("jni/lib/src/third_party/jni_bindings_generated.dart");
   final ffigenBindings = File.fromUri(ffigenBindingsPath);
   final oldBindingsText = ffigenBindings.readAsStringSync();
-  final ffigenCompare = Runner("Generate & Compare FFIGEN bindings", "jni")
-    ..chainCommand("dart", ["run", "ffigen", "--config", "ffigen.yaml"])
-    ..chainCallback("compare bindings", () async {
-      final newBindingsText = await ffigenBindings.readAsString();
-      if (newBindingsText != oldBindingsText) {
-        await ffigenBindings.writeAsString(oldBindingsText);
-        throw "new JNI.h bindings differ from old bindings";
-      }
-    });
+  final ffigenCompare =
+      Runner("Generate & Compare FFIGEN bindings", tempJniPath)
+        ..chainCommand("dart", ["run", "ffigen", "--config", "ffigen.yaml"])
+        ..chainCallback("compare bindings", () async {
+          final newBindingsText = await ffigenBindings.readAsString();
+          if (newBindingsText != oldBindingsText) {
+            await ffigenBindings.writeAsString(oldBindingsText);
+            throw "new JNI.h bindings differ from old bindings";
+          }
+        });
   unawaited(ffigenCompare.run());
 
-  final jnigenAnalyze = Runner("Analyze jnigen", "jnigen")
+  final jnigenAnalyze = Runner("Analyze jnigen", tempJnigenPath)
     ..chainCommand("dart", ["analyze", "--fatal-infos"])
     ..chainCommand(
         "dart", ["format", "--output=none", "--set-exit-if-changed", "."])
     ..chainCommand("dart", ["run", "jnigen:setup"]);
-  final jnigenTest = Runner("Test jnigen", "jnigen")
-    ..chainCommand("dart", ["test"]);
+
+  // Tests may need more time when running on systems with less cores.
+  // So '--timeout 2x' is specified.
+  final jnigenTest = Runner("Test jnigen", gitRoot.resolve("jnigen/"))
+    ..chainCommand("dart", ["test", "--timeout", "2x"]);
+
+  // Note: Running in_app_java and notification_plugin checks on source dir
+  // itself, because running flutter build in cloned dir will take time.
   final compareInAppJavaBindings = Runner(
-      "Generate & compare InAppJava bindings", "jnigen/example/in_app_java")
+      "Generate & compare InAppJava bindings",
+      gitRoot.resolve("jnigen/example/in_app_java"))
     ..chainCommand("dart", [
       "run",
       "jnigen",
@@ -179,8 +99,9 @@
     ..chainCommand("diff", ["lib/android_utils.dart", "_temp.dart"])
     ..chainCommand("diff", ["-qr", "src/android_utils/", "src_temp/"])
     ..chainCleanupCommand("rm", ["-r", "_temp.dart", "src_temp"]);
-  final comparePdfboxBindings = Runner(
-      "Generate & compare PdfBox Bindings", "jnigen/example/pdfbox_plugin")
+
+  final comparePdfboxBindings = Runner("Generate & compare PdfBox Bindings",
+      gitRoot.resolve("jnigen/example/pdfbox_plugin"))
     ..chainCommand("dart", [
       "run",
       "jnigen",
@@ -192,9 +113,10 @@
     ..chainCommand("diff", ["-qr", "lib/src/third_party/", "lib_temp/"])
     ..chainCommand("diff", ["-qr", "src/", "src_temp/"])
     ..chainCleanupCommand("rm", ["-r", "lib_temp", "src_temp"]);
+
   final compareNotificationPluginBindings = Runner(
       "Generate & compare NotificationPlugin Bindings",
-      "jnigen/example/notification_plugin")
+      gitRoot.resolve("jnigen/example/notification_plugin"))
     ..chainCommand("dart", [
       "run",
       "jnigen",
@@ -206,10 +128,14 @@
     ..chainCommand("diff", ["lib/notifications.dart", "_temp.dart"])
     ..chainCommand("diff", ["-qr", "src/", "src_temp/"])
     ..chainCleanupCommand("rm", ["-r", "_temp.dart", "src_temp"]);
+
   unawaited(jnigenAnalyze.run().then((_) {
-    jnigenTest.run();
-    compareInAppJavaBindings.run();
-    comparePdfboxBindings.run();
-    compareNotificationPluginBindings.run();
+    final test = jnigenTest.run();
+    final inAppJava = compareInAppJavaBindings.run();
+    final pdfBox = comparePdfboxBindings.run();
+    final notificationPlugin = compareNotificationPluginBindings.run();
+    return Future.wait([test, inAppJava, pdfBox, notificationPlugin]);
+  }).then((_) {
+    tempDir.deleteSync(recursive: true);
   }));
 }
diff --git a/pkgs/jnigen/tool/regenerate_all_bindings.dart b/pkgs/jnigen/tool/regenerate_all_bindings.dart
new file mode 100644
index 0000000..b99fb01
--- /dev/null
+++ b/pkgs/jnigen/tool/regenerate_all_bindings.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Run this script after any change which affects generated bindings.
+//
+// This will update all generated bindings in the whole repository.
+
+import 'dart:io';
+
+import 'command_runner.dart';
+
+const scripts = [
+  "test/jackson_core_test/generate.dart",
+  "test/simple_package_test/generate.dart",
+];
+
+const yamlBasedExamples = [
+  "example/in_app_java",
+  "example/pdfbox_plugin",
+  "example/notification_plugin",
+];
+
+void main() async {
+  final runners = <Runner>[];
+  final current = Directory.current.uri;
+  for (var script in scripts) {
+    runners.add(Runner("Run generate script: $script", current)
+      ..chainCommand("dart", ["run", script]));
+  }
+
+  for (var yamlDir in yamlBasedExamples) {
+    runners.add(
+        Runner("Regenerate bindings in $yamlDir", current.resolve(yamlDir))
+          ..chainCommand("dart", ["run", "jnigen", "--config", "jnigen.yaml"]));
+  }
+
+  await Future.wait(runners.map((runner) => runner.run()).toList());
+}