Add an objective-C module prefix option (#438)
* Add an objective-C module prefix option
* Fix analysis
* Update readme
* Update readme again
diff --git a/README.md b/README.md
index 437ba54..6442b42 100644
--- a/README.md
+++ b/README.md
@@ -186,7 +186,7 @@
<tr>
<td>
functions<br><br>structs<br><br>unions<br><br>enums<br><br>
- unnamed-enums<br><br>macros<br><br>globals<br><br>objc-interfaces
+ unnamed-enums<br><br>macros<br><br>globals
</td>
<td>Filters for declarations.<br><b>Default: all are included.</b><br><br>
Options -<br>
@@ -507,6 +507,81 @@
</tbody>
</table>
+### Objective-C config options
+
+<table>
+<thead>
+ <tr>
+ <th>Key</th>
+ <th>Explaination</th>
+ <th>Example</th>
+ </tr>
+ <colgroup>
+ <col>
+ <col style="width: 100px;">
+ </colgroup>
+</thead>
+<tbody>
+ <tr>
+ <td>
+ objc-interfaces
+ </td>
+ <td>
+ Filters for interface declarations. This option works the same as other
+ declaration filters like `functions` and `structs`.
+ </td>
+ <td>
+
+```yaml
+objc-interfaces:
+ include:
+ # Includes a specific interface.
+ - 'MyInterface'
+ # Includes all interfaces starting with "NS".
+ - 'NS.*'
+ exclude:
+ # Override the above NS.* inclusion, to exclude NSURL.
+ - 'NSURL'
+ rename:
+ # Removes '_' prefix from interface names.
+ '_(.*)': '$1'
+```
+
+ </td>
+ </tr>
+
+ <tr>
+ <td>
+ objc-interfaces -> module
+ </td>
+ <td>
+ Adds a module prefix to the class name when loading the class
+ from the dylib. This is only relevent for ObjC headers that are generated
+ wrappers for a Swift library. See example/swift for more information.
+ </td>
+ <td>
+
+```yaml
+headers:
+ entry-points:
+ # Generated by swiftc to wrap foo_lib.swift.
+ - 'foo_lib-Swift.h'
+objc-interfaces:
+ include:
+ # Eg, foo_lib contains a set of classes prefixed with FL.
+ - 'FL.*'
+ module:
+ # Use 'foo_lib' as the module name for all the FL.* classes.
+ # We don't match .* here because other classes like NSString
+ # shouldn't be given a module prefix.
+ 'FL.*': 'foo_lib'
+```
+
+ </td>
+ </tr>
+</tbody>
+</table>
+
## Trying out examples
1. `cd examples/<example_u_want_to_run>`, Run `dart pub get`.
2. Run `dart run ffigen`.
diff --git a/lib/src/code_generator/objc_interface.dart b/lib/src/code_generator/objc_interface.dart
index 61e88ef..a6099b8 100644
--- a/lib/src/code_generator/objc_interface.dart
+++ b/lib/src/code_generator/objc_interface.dart
@@ -41,6 +41,7 @@
final methods = <String, ObjCMethod>{};
bool filled = false;
+ final String lookupName;
final ObjCBuiltInFunctions builtInFunctions;
final bool isBuiltIn;
late final ObjCInternalGlobal _classObject;
@@ -51,6 +52,7 @@
String? usr,
required String originalName,
required String name,
+ required this.lookupName,
String? dartDoc,
required this.builtInFunctions,
required this.isBuiltIn,
@@ -228,7 +230,7 @@
_classObject = ObjCInternalGlobal(
'_class_$originalName',
- (Writer w) => '${builtInFunctions.getClass.name}("$originalName")',
+ (Writer w) => '${builtInFunctions.getClass.name}("$lookupName")',
builtInFunctions.getClass)
..addDependencies(dependencies);
_isKindOfClass = builtInFunctions.getSelObject('isKindOfClass:');
diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index de60997..56b2df1 100644
--- a/lib/src/config_provider/config.dart
+++ b/lib/src/config_provider/config.dart
@@ -125,6 +125,10 @@
StructPackingOverride get structPackingOverride => _structPackingOverride;
late StructPackingOverride _structPackingOverride;
+ /// Module prefixes for ObjC interfaces.
+ ObjCModulePrefixer get objcModulePrefixer => _objcModulePrefixer;
+ late ObjCModulePrefixer _objcModulePrefixer;
+
/// Name of the wrapper class.
String get wrapperName => _wrapperName;
late String _wrapperName;
@@ -341,6 +345,15 @@
_objcInterfaces = result as Declaration;
},
),
+ [strings.objcInterfaces, strings.objcModule]:
+ Specification<Map<String, String>>(
+ requirement: Requirement.no,
+ validator: stringStringMapValidator,
+ extractor: stringStringMapExtractor,
+ defaultValue: () => <String, String>{},
+ extractedResult: (dynamic result) => _objcModulePrefixer =
+ ObjCModulePrefixer(result as Map<String, String>),
+ ),
[strings.libraryImports]: Specification<Map<String, LibraryImport>>(
validator: libraryImportsValidator,
extractor: libraryImportsExtractor,
diff --git a/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index f602976..8146917 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/lib/src/config_provider/config_types.dart
@@ -373,3 +373,32 @@
return [];
}
}
+
+class _ObjCModulePrefixerEntry {
+ final RegExp pattern;
+ final String moduleName;
+
+ _ObjCModulePrefixerEntry(this.pattern, this.moduleName);
+}
+
+/// Handles applying module prefixes to ObjC classes.
+class ObjCModulePrefixer {
+ final _prefixes = <_ObjCModulePrefixerEntry>[];
+
+ ObjCModulePrefixer(Map<String, String> prefixes) {
+ for (final entry in prefixes.entries) {
+ _prefixes.add(_ObjCModulePrefixerEntry(RegExp(entry.key), entry.value));
+ }
+ }
+
+ /// If any of the prefixing patterns match, applies that module prefix.
+ /// Otherwise returns the class name unmodified.
+ String applyPrefix(String className) {
+ for (final entry in _prefixes) {
+ if (quiver.matchesFull(entry.pattern, className)) {
+ return '${entry.moduleName}.$className';
+ }
+ }
+ return className;
+ }
+}
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index 015d92e..ac154ce 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -179,6 +179,29 @@
return result;
}
+Map<String, String> stringStringMapExtractor(dynamic yamlConfig) {
+ final resultMap = <String, String>{};
+ final inputMap = yamlConfig as YamlMap?;
+ if (inputMap != null) {
+ for (final key in inputMap.keys) {
+ resultMap[key as String] = inputMap[key] as String;
+ }
+ }
+ return resultMap;
+}
+
+bool stringStringMapValidator(List<String> name, dynamic yamlConfig) {
+ if (!checkType<YamlMap>(name, yamlConfig)) {
+ return false;
+ }
+ for (final key in (yamlConfig as YamlMap).keys) {
+ if (!checkType<String>([...name, key as String], yamlConfig[key])) {
+ return false;
+ }
+ }
+ return true;
+}
+
Map<String, ImportedType> makeImportTypeMapping(
Map<String, List<String>> rawTypeMappings,
Map<String, LibraryImport> libraryImportsMap) {
diff --git a/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart b/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
index 3c8aab3..bace1c4 100644
--- a/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
@@ -51,6 +51,7 @@
usr: itfUsr,
originalName: name,
name: config.objcInterfaces.renameUsingConfig(name),
+ lookupName: config.objcModulePrefixer.applyPrefix(name),
dartDoc: getCursorDocComment(cursor),
builtInFunctions: objCBuiltInFunctions,
isBuiltIn: cursor.isInSystemHeader(),
diff --git a/lib/src/strings.dart b/lib/src/strings.dart
index 8094df1..6995ada 100644
--- a/lib/src/strings.dart
+++ b/lib/src/strings.dart
@@ -87,6 +87,9 @@
const exposeFunctionTypedefs = 'expose-typedefs';
const leafFunctions = 'leaf';
+// Sub-fields of ObjC interfaces.
+const objcModule = 'module';
+
const dependencyOnly = 'dependency-only';
// Values for `compoundDependencies`.
const fullCompoundDependencies = 'full';
diff --git a/test/native_objc_test/.gitignore b/test/native_objc_test/.gitignore
index 62dd4b5..ad4daf6 100644
--- a/test/native_objc_test/.gitignore
+++ b/test/native_objc_test/.gitignore
@@ -1 +1,2 @@
*_bindings.dart
+*-Swift.h
diff --git a/test/native_objc_test/setup.dart b/test/native_objc_test/setup.dart
index 2e6701b..5eff12c 100644
--- a/test/native_objc_test/setup.dart
+++ b/test/native_objc_test/setup.dart
@@ -27,6 +27,27 @@
print('Generated file: $output');
}
+Future<void> _buildSwift(
+ String input, String outputHeader, String outputLib) async {
+ final args = [
+ '-c',
+ input,
+ '-emit-objc-header-path',
+ outputHeader,
+ '-emit-library',
+ '-o',
+ outputLib,
+ ];
+ final process = await Process.start('swiftc', args);
+ unawaited(stdout.addStream(process.stdout));
+ unawaited(stderr.addStream(process.stderr));
+ final result = await process.exitCode;
+ if (result != 0) {
+ throw ProcessException('swiftc', args, 'Build failed', result);
+ }
+ print('Generated files: $outputHeader and $outputLib');
+}
+
Future<void> _generateBindings(String config) async {
final args = [
'run',
@@ -60,7 +81,19 @@
Future<void> build(List<String> testNames) async {
print('Building Dynamic Library for Objective C Native Tests...');
for (final name in testNames) {
- await _buildLib('${name}_test.m', '${name}_test.dylib');
+ final mFile = '${name}_test.m';
+ if (await File(mFile).exists()) {
+ await _buildLib(mFile, '${name}_test.dylib');
+ }
+ }
+
+ print('Building Dynamic Library and Header for Swift Tests...');
+ for (final name in testNames) {
+ final swiftFile = '${name}_test.swift';
+ if (await File(swiftFile).exists()) {
+ await _buildSwift(
+ swiftFile, '${name}_test-Swift.h', '${name}_test.dylib');
+ }
}
print('Generating Bindings for Objective C Native Tests...');
diff --git a/test/native_objc_test/swift_class_config.yaml b/test/native_objc_test/swift_class_config.yaml
new file mode 100644
index 0000000..e845ed1
--- /dev/null
+++ b/test/native_objc_test/swift_class_config.yaml
@@ -0,0 +1,15 @@
+name: SwiftClassTestLibrary
+description: 'Tests Swift classes'
+language: objc
+output: 'test/native_objc_test/swift_class_bindings.dart'
+exclude-all-by-default: true
+objc-interfaces:
+ include:
+ - MySwiftClass
+ module:
+ 'MySwiftClass': 'swift_class_test'
+headers:
+ entry-points:
+ - 'test/native_objc_test/swift_class_test-Swift.h'
+preamble: |
+ // ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/swift_class_test.dart b/test/native_objc_test/swift_class_test.dart
new file mode 100644
index 0000000..09a2714
--- /dev/null
+++ b/test/native_objc_test/swift_class_test.dart
@@ -0,0 +1,34 @@
+// 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 'dart:ffi';
+import 'dart:io';
+
+import 'package:test/test.dart';
+import '../test_utils.dart';
+import 'swift_class_bindings.dart';
+import 'util.dart';
+
+void main() {
+ late SwiftClassTestLibrary lib;
+ group('swift_class_test', () {
+ setUpAll(() {
+ logWarnings();
+ final dylib = File('test/native_objc_test/swift_class_test.dylib');
+ verifySetupFile(dylib);
+ lib = SwiftClassTestLibrary(DynamicLibrary.open(dylib.absolute.path));
+ generateBindingsForCoverage('swift_class');
+ });
+
+ test('Renamed class', () {
+ final swiftObject = MySwiftClass.new1(lib);
+ expect(swiftObject.getValue(), 123);
+ swiftObject.setValueWithX_(456);
+ expect(swiftObject.getValue(), 456);
+ });
+ });
+}
diff --git a/test/native_objc_test/swift_class_test.swift b/test/native_objc_test/swift_class_test.swift
new file mode 100644
index 0000000..26ae016
--- /dev/null
+++ b/test/native_objc_test/swift_class_test.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+@objc public class MySwiftClass: NSObject {
+ var val = 123;
+ @objc public func getValue() -> Int {
+ return val;
+ }
+ @objc public func setValue(x: Int) {
+ val = x;
+ }
+}