| // 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')); |
| } |