// 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 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 parseExtraMethods(String filename) { final extraMethods = {}; 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 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 writeBuiltInTypes(String config, String out) { final yaml = loadYaml(File(config).readAsStringSync()) as YamlMap; final s = StringBuffer(); final exports = {}; 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 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 = { for (final inc in includes.map((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 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 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 main(List 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')); }