blob: 7196e75c279cb608912a7f8280cb0df5e358beb8 [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;
import 'package:yaml/yaml.dart';
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 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 privateInterfaces = <String>{
'DartInputStreamAdapter',
'DartInputStreamAdapterWeakHolder',
'DOBJCObservation',
};
final privateMethods = <String>{
for (final name in privateInterfaces) '$name\$Methods',
};
final privateClasses = privateInterfaces.union(privateMethods);
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());
}
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');
writeDecls('objCBuiltInProtocols', 'objc-protocols');
writeDecls('objCBuiltInCategories', 'objc-categories');
writeDecls('objCBuiltInGlobals', 'globals');
File(out).writeAsStringSync(s.toString());
return exports.difference(privateClasses).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 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));
print('Generating objc_built_in_types.dart...');
final exports = writeBuiltInTypes(objcConfig, builtInTypes);
print('Generating objective_c_bindings_exported.dart...');
writeExports(exports, objcExports);
if (format) {
print('Formatting bindings...');
dartCmd(['format', 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'));
}