blob: 6575e8abf863907eadf265573daee8e33858ebf0 [file]
// 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.
// This is a script to run a subset of CI checks on development machine before
// submitting PR. This prevents from accidentally forgetting to format or
// remove an unused import.
// This is rough around the edges. This script may give false positives such
// as some gitignore'd temporary files failing the comparison.
// On windows, please install 'diffutils' using your favorite package installer
// for this script to work correctly. Also ensure that clang-format is on your
// PATH.
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());
}
void main() async {
final jniAnalyze = Runner("Analyze JNI", "jni");
jniAnalyze
..chainCommand("dart", ["analyze", "--fatal-infos"])
..chainCommand(
"dart", ["format", "--output=none", "--set-exit-if-changed", "."])
..chainCommand(
"clang-format",
[
"--dry-run",
"-Werror",
"dartjni.c",
"dartjni.h",
"third_party/global_jni_env.c",
"third_party/global_jni_env.h",
],
workingDirectory: "jni/src");
final jniTest = Runner("Test JNI", "jni")
..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";
}
});
unawaited(ffigenCompare.run());
final jnigenAnalyze = Runner("Analyze jnigen", "jnigen")
..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"]);
final compareInAppJavaBindings = Runner(
"Generate & compare InAppJava bindings", "jnigen/example/in_app_java")
..chainCommand("dart", [
"run",
"jnigen",
"--config",
"jnigen.yaml",
"-Doutput.c.path=src_temp/",
"-Doutput.dart.path=_temp.dart",
])
..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")
..chainCommand("dart", [
"run",
"jnigen",
"--config",
"jnigen.yaml",
"-Doutput.c.path=src_temp/",
"-Doutput.dart.path=lib_temp/",
])
..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")
..chainCommand("dart", [
"run",
"jnigen",
"--config",
"jnigen.yaml",
"-Doutput.c.path=src_temp/",
"-Doutput.dart.path=_temp.dart",
])
..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();
}));
}