[ffigen] First bits of Objective C support (#281)
diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart index ab5e6c0..e620626 100644 --- a/pkgs/ffigen/lib/src/code_generator/imports.dart +++ b/pkgs/ffigen/lib/src/code_generator/imports.dart
@@ -2,6 +2,8 @@ // 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. +import 'struc.dart'; + /// A library import which will be written as an import in the generated file. class LibraryImport { final String name; @@ -51,3 +53,6 @@ final sizeType = ImportedType(ffiPkgImport, 'Size', 'int'); final wCharType = ImportedType(ffiPkgImport, 'WChar', 'int'); + +final objCObjectType = Struc(name: 'ObjCObject'); +final objCSelType = Struc(name: 'ObjCSel');
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index 7acb2c8..29673a6 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -23,9 +23,14 @@ String get libclangDylib => _libclangDylib; late String _libclangDylib; - /// output file name. + /// Output file name. String get output => _output; late String _output; + + /// Language that ffigen is consuming. + Language get language => _language; + late Language _language; + // Holds headers and filters for header. Headers get headers => _headers; late Headers _headers; @@ -220,6 +225,13 @@ extractor: outputExtractor, extractedResult: (dynamic result) => _output = result as String, ), + [strings.language]: Specification<Language>( + requirement: Requirement.no, + validator: languageValidator, + extractor: languageExtractor, + defaultValue: () => Language.c, + extractedResult: (dynamic result) => _language = result as Language, + ), [strings.headers]: Specification<Headers>( requirement: Requirement.yes, validator: headersValidator,
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart index d973013..1f0c7a6 100644 --- a/pkgs/ffigen/lib/src/config_provider/config_types.dart +++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -9,6 +9,8 @@ import 'path_finder.dart'; +enum Language { c, objc } + class CommentType { CommentStyle style; CommentLength length;
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart index e5f0f41..aff84b2 100644 --- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart +++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -453,6 +453,28 @@ bool outputValidator(List<String> name, dynamic value) => checkType<String>(name, value); +Language languageExtractor(dynamic value) { + if (value == strings.langC) { + return Language.c; + } else if (value == strings.langObjC) { + return Language.objc; + } + return Language.c; +} + +bool languageValidator(List<String> name, dynamic value) { + if (value is String) { + if (value == strings.langC || value == strings.langObjC) { + return true; + } + _logger.severe("'$name' must be one of the following - " + "{${strings.langC}, ${strings.langObjC}}"); + return false; + } + _logger.severe("Expected value of key '$name' to be a String."); + return false; +} + /// Returns true if [str] is not a full name. /// /// E.g `abc` is a full name, `abc.*` is not.
diff --git a/pkgs/ffigen/lib/src/header_parser/includer.dart b/pkgs/ffigen/lib/src/header_parser/includer.dart index aaad693..0b3505b 100644 --- a/pkgs/ffigen/lib/src/header_parser/includer.dart +++ b/pkgs/ffigen/lib/src/header_parser/includer.dart
@@ -5,6 +5,8 @@ /// Utility functions to check whether a binding should be parsed or not /// based on filters. +import '../config_provider/config_types.dart'; +import '../strings.dart' as strings; import 'data.dart'; bool _shouldIncludeDecl(String usr, String name, @@ -66,6 +68,12 @@ return false; } + // Objective C has some extra system headers that have a non-empty sourceFile. + if (config.language == Language.objc && + sourceFile.startsWith(strings.clangDefaultObjCSystemHeaderPath)) { + return false; + } + // Add header to seen if it's not. if (!bindingsIndex.isSeenHeader(sourceFile)) { bindingsIndex.addHeaderToSeen(
diff --git a/pkgs/ffigen/lib/src/header_parser/parser.dart b/pkgs/ffigen/lib/src/header_parser/parser.dart index ffcfc8f..33c7072 100644 --- a/pkgs/ffigen/lib/src/header_parser/parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/parser.dart
@@ -56,17 +56,27 @@ final index = clang.clang_createIndex(0, 0); Pointer<Pointer<Utf8>> clangCmdArgs = nullptr; - var cmdLen = 0; + final compilerOpts = List<String>.from(config.compilerOpts); /// Add compiler opt for comment parsing for clang based on config. if (config.commentType.length != CommentLength.none && config.commentType.style == CommentStyle.any) { - config.compilerOpts.add(strings.fparseAllComments); + compilerOpts.add(strings.fparseAllComments); } - _logger.fine('CompilerOpts used: ${config.compilerOpts}'); - clangCmdArgs = createDynamicStringArray(config.compilerOpts); - cmdLen = config.compilerOpts.length; + /// If the config targets Objective C, add a compiler opt for it. + if (config.language == Language.objc) { + compilerOpts.addAll(strings.clangLangObjC); + compilerOpts.add('-I' + strings.clangDefaultObjCSystemHeaderPath); + for (final sysHdr in strings.clangDefaultObjCSystemHeaders) { + compilerOpts.add(strings.clangInclude); + compilerOpts.add(sysHdr); + } + } + + _logger.fine('CompilerOpts used: $compilerOpts'); + clangCmdArgs = createDynamicStringArray(compilerOpts); + final cmdLen = compilerOpts.length; // Contains all bindings. A set ensures we never have duplicates. final bindings = <Binding>{}; @@ -111,7 +121,7 @@ // Parse all saved macros. bindings.addAll(parseSavedMacros()!); - clangCmdArgs.dispose(config.compilerOpts.length); + clangCmdArgs.dispose(cmdLen); clang.clang_disposeIndex(index); return bindings.toList(); }
diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart index 8ea5d96..3c2f2dc 100644 --- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart +++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -8,6 +8,7 @@ import 'package:ffigen/src/strings.dart' as strings; import 'package:logging/logging.dart'; +import '../../config_provider/config_types.dart'; import '../clang_bindings/clang_bindings.dart' as clang_types; import '../data.dart'; import '../sub_parsers/compounddecl_parser.dart'; @@ -37,6 +38,20 @@ ignoreFilter: ignoreFilter, pointerReference: pointerReference); } + // Objective C types skip the cache, and are conditional on the language flag. + if (config.language == Language.objc) { + switch (cxtype.kind) { + case clang_types.CXTypeKind.CXType_ObjCObjectPointer: + case clang_types.CXTypeKind.CXType_BlockPointer: + case clang_types.CXTypeKind.CXType_ObjCId: + return Type.pointer(Type.struct(objCObjectType)); + case clang_types.CXTypeKind.CXType_ObjCSel: + return Type.pointer(Type.struct(objCSelType)); + case clang_types.CXTypeKind.CXType_ObjCClass: + return Type.struct(objCObjectType); + } + } + // If the type has a declaration cursor, then use the BindingsIndex to break // any potential cycles, and dedupe the Type. final cursor = clang.clang_getTypeDeclaration(cxtype);
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index 58e9b5d..2e57de7 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart
@@ -29,6 +29,19 @@ const output = 'output'; +const language = 'language'; + +// String mappings for the Language enum. +const langC = 'c'; +const langObjC = 'objc'; + +// Clang command line args for Objective C. +const clangLangObjC = ['-x', 'objective-c']; +const clangInclude = '-include'; +const clangDefaultObjCSystemHeaderPath = + '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include'; +const clangDefaultObjCSystemHeaders = ['objc/NSObject.h']; + const headers = 'headers'; // Sub-fields of headers
diff --git a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_basic_types_bindings.dart b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_basic_types_bindings.dart new file mode 100644 index 0000000..5cd8096 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_objc_basic_types_bindings.dart
@@ -0,0 +1,30 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; +import 'package:ffi/ffi.dart' as pkg_ffi; + +class Foo extends ffi.Struct { + @BOOL() + external int someBool; + + external ffi.Pointer<ObjCObject> anId; + + external ffi.Pointer<ObjCSel> selector; + + external ffi.Pointer<ObjCObject> object; + + external ffi.Pointer<ObjCObject> clazz; + + external ffi.Pointer<ObjCObject> blockThatReturnsAnInt; +} + +typedef BOOL = pkg_ffi.SignedChar; + +class ObjCObject extends ffi.Opaque {} + +class ObjCSel extends ffi.Opaque {} + +const int true1 = 1; + +const int false1 = 0;
diff --git a/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h b/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h new file mode 100644 index 0000000..a2f7fc9 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/objc_basic_types.h
@@ -0,0 +1,8 @@ +struct Foo { + BOOL someBool; + id anId; + SEL selector; + NSObject* object; + Class* clazz; + int32_t (^blockThatReturnsAnInt)(void); +};
diff --git a/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart b/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart new file mode 100644 index 0000000..8ee93f4 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/objc_basic_types_test.dart
@@ -0,0 +1,51 @@ +// Copyright (c) 2022, 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. + +// Objective C support is only available on mac. +@TestOn('mac-os') + +import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/config_provider.dart'; +import 'package:ffigen/src/header_parser.dart' as parser; +import 'package:ffigen/src/strings.dart' as strings; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart' as yaml; + +import '../test_utils.dart'; + +late Library actual; +void main() { + group('objc_basic_types_test', () { + setUpAll(() { + logWarnings(Level.SEVERE); + actual = parser.parse( + Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'NativeLibrary' +${strings.description}: 'Opaque Dependencies Test' +${strings.output}: 'unused' +${strings.language}: '${strings.langObjC}' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/objc_basic_types.h' +${strings.structs}: + ${strings.include}: + - 'Foo' + ''') as yaml.YamlMap), + ); + }); + test('Expected bindings', () { + matchLibraryWithExpected(actual, [ + 'test', + 'debug_generated', + 'header_parser_objc_basic_types_test_output.dart' + ], [ + 'test', + 'header_parser_tests', + 'expected_bindings', + '_expected_objc_basic_types_bindings.dart' + ]); + }); + }); +}