blob: fcd40e4e0b7c74263d1ef300f7eaeeeb56b96fc5 [file] [log] [blame] [edit]
// Copyright (c) 2024, 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.
// Runs the ffigen configs, then merges tool/data/extra_methods.dart.in into the
// Objective C bindings.
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:args/args.dart';
import 'package:ffigen/src/executables/ffigen.dart' as ffigen;
const cConfig = 'ffigen_c.yaml';
const objcConfig = 'ffigen_objc.yaml';
const cBindings = 'lib/src/c_bindings_generated.dart';
const objcBindings = 'lib/src/objective_c_bindings_generated.dart';
const extraMethodsFile = 'tool/data/extra_methods.dart.in';
void dartCmd(List<String> args) {
final exec = Platform.resolvedExecutable;
final proc = Process.runSync(exec, args, runInShell: true);
if (proc.exitCode != 0) {
exitCode = proc.exitCode;
print(proc.stdout);
print(proc.stderr);
throw Exception('Command failed: $exec ${args.join(" ")}');
}
}
typedef ClassInfo = ({
String name,
String? ext,
List<String> mix,
List<String> impl,
});
final _clsDecl = RegExp(
r'^class (.*?)(?: extends (.*?))?(?: with (.*?))?(?: implements (.*?))? {');
ClassInfo? parseClassDecl(String line) {
final match = _clsDecl.firstMatch(line);
if (match == null) return null;
return (
name: match[1]!,
ext: match[2],
mix: match[3]?.split(', ') ?? [],
impl: match[4]?.split(', ') ?? []
);
}
typedef ExtraMethods = ({ClassInfo cls, String methods});
Map<String, ExtraMethods> parseExtraMethods(String filename) {
final extraMethods = <String, ExtraMethods>{};
ClassInfo? currentClass;
late StringBuffer methods;
for (final line in File(filename).readAsLinesSync()) {
if (currentClass == null) {
final cls = parseClassDecl(line);
if (cls != null) {
currentClass = cls;
methods = StringBuffer();
}
} else {
if (line == '}') {
extraMethods[currentClass.name] =
(cls: currentClass, methods: methods.toString());
currentClass = null;
} else {
methods.writeln(line);
}
}
}
return extraMethods;
}
String classDecl(
String name, String? ext, List<String> mix, List<String> impl) =>
[
'class $name',
if (ext != null) 'extends $ext',
if (mix.isNotEmpty) 'with ${mix.join(', ')}',
if (impl.isNotEmpty) 'implements ${impl.join(', ')}',
'{',
].join(' ');
void mergeExtraMethods(
String filename, Map<String, ExtraMethods> extraMethods) {
final out = StringBuffer();
for (final line in File(filename).readAsLinesSync()) {
final cls = parseClassDecl(line);
final extra = cls == null ? null : extraMethods[cls.name];
if (cls == null || extra == null) {
out.writeln(line);
} else {
out.writeln(classDecl(cls.name, extra.cls.ext ?? cls.ext,
[...cls.mix, ...extra.cls.mix], [...cls.impl, ...extra.cls.impl]));
out.writeln(extra.methods);
extraMethods.remove(cls.name);
}
}
// Matching classes have been removed from extraMethods. Write all the
// remaining classes separately.
for (final extra in extraMethods.values) {
out.writeln('\n');
out.writeln(classDecl(
extra.cls.name, extra.cls.ext, extra.cls.mix, extra.cls.impl));
out.writeln(extra.methods);
out.writeln('}');
}
File(filename).writeAsStringSync(out.toString());
}
Future<void> run({required bool format}) async {
print('Generating C bindings...');
await ffigen.main(['--no-format', '-v', 'severe', '--config', cConfig]);
print('Generating ObjC bindings...');
await ffigen.main(['--no-format', '-v', 'severe', '--config', objcConfig]);
mergeExtraMethods(objcBindings, parseExtraMethods(extraMethodsFile));
if (format) {
print('Formatting bindings...');
dartCmd(['format', cBindings, objcBindings]);
}
}
Future<void> main(List<String> args) async {
Directory.current = Platform.script.resolve('..').path;
final argResults = (ArgParser()
..addFlag(
'format',
help: 'Format the generated code.',
defaultsTo: true,
negatable: true,
))
.parse(args);
await run(format: argResults.flag('format'));
}