blob: 3e90345299e54c1eedb354ed8025fe4299afd534 [file] [log] [blame]
// 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.
import 'dart:io';
import 'package:args/args.dart';
import 'package:ffigen/src/executables/ffigen.dart' as ffigen;
import 'package:yaml/yaml.dart';
const runtimeConfig = 'ffigen_runtime.yaml';
const cConfig = 'ffigen_c.yaml';
const objcConfig = 'ffigen_objc.yaml';
const runtimeBindings = 'lib/src/runtime_bindings_generated.dart';
const cBindings = 'lib/src/c_bindings_generated.dart';
const objcBindings = 'lib/src/objective_c_bindings_generated.dart';
const objcExports = 'lib/src/objective_c_bindings_exported.dart';
const extraMethodsFile = 'tool/data/extra_methods.dart.in';
const builtInTypes =
'../ffigen/lib/src/code_generator/objc_built_in_types.dart';
const interfaceListTest = 'test/interface_lists_test.dart';
const ffigenFlags = ['--no-format', '-v', 'severe', '--config'];
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(" ")}');
}
}
final _clsDecl = RegExp(r'^extension type (\w+)\W');
String? parseClassDecl(String line) => _clsDecl.firstMatch(line)?[1];
Map<String, String> parseExtraMethods(String filename) {
final extraMethods = <String, String>{};
String? 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] = methods.toString();
currentClass = null;
} else {
methods.writeln(line);
}
}
}
return extraMethods;
}
void mergeExtraMethods(String filename, Map<String, String> extraMethods) {
final out = StringBuffer();
for (final line in File(filename).readAsLinesSync()) {
out.writeln(line);
final cls = parseClassDecl(line);
final extra = cls == null ? null : extraMethods[cls];
if (cls != null && extra != null) {
out.writeln(extra);
extraMethods.remove(cls);
}
}
assert(extraMethods.isEmpty);
File(filename).writeAsStringSync(out.toString());
}
List<String> writeBuiltInTypes(String config, String out) {
final yaml = loadYaml(File(config).readAsStringSync()) as YamlMap;
final s = StringBuffer();
final exports = <String>{};
s.write('''
// Copyright (c) 2025, 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.
// Generated by package:objective_c's tool/generate_code.dart.
''');
Iterable<String> writeDecls(String name, String key) {
final decls = yaml[key] as YamlMap;
final renames = decls['rename'] as YamlMap? ?? YamlMap();
final includes = decls['include'] as YamlList;
final names = <String, String>{
for (final inc in includes.map<String>((i) => i as String))
inc: renames[inc] as String? ?? inc,
};
exports.addAll(names.values);
final anyRenames = names.entries.any((kv) => kv.key != kv.value);
final elements = anyRenames
? names.entries.map((kv) => " '${kv.key}': '${kv.value}',")
: names.keys.map((key) => " '$key',");
s.write('''
const $name = {
${elements.join('\n')}
};
''');
return names.values;
}
final interfaces = writeDecls('objCBuiltInInterfaces', 'objc-interfaces');
exports.addAll([for (final name in interfaces) '$name\$Methods']);
writeDecls('objCBuiltInCompounds', 'structs');
writeDecls('objCBuiltInEnums', 'enums');
final protocols = writeDecls('objCBuiltInProtocols', 'objc-protocols');
exports.addAll([for (final name in protocols) '$name\$Methods']);
exports.addAll([for (final name in protocols) '$name\$Builder']);
writeDecls('objCBuiltInCategories', 'objc-categories');
File(out).writeAsStringSync(s.toString());
return exports.toList()..sort();
}
void writeExports(List<String> exports, String out) {
File(out).writeAsStringSync('''
// Copyright (c) 2025, 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.
// Generated by package:objective_c's tool/generate_code.dart.
export 'objective_c_bindings_generated.dart'
show
${exports.join(',\n ')};
''');
}
Future<void> run({required bool format}) async {
print('Generating runtime bindings...');
await ffigen.main([...ffigenFlags, runtimeConfig]);
print('Generating C bindings...');
await ffigen.main([...ffigenFlags, cConfig]);
print('Generating ObjC bindings...');
await ffigen.main([...ffigenFlags, objcConfig]);
mergeExtraMethods(objcBindings, parseExtraMethods(extraMethodsFile));
print('Generating objc_built_in_types.dart...');
final exports = writeBuiltInTypes(objcConfig, builtInTypes);
print('Generating objc_bindings_exported.dart...');
writeExports(exports, objcExports);
if (format) {
print('Formatting bindings...');
dartCmd([
'format',
runtimeBindings,
cBindings,
objcBindings,
builtInTypes,
objcExports,
]);
}
print('Running tests...');
dartCmd(['test', interfaceListTest]);
}
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'));
}