diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md
index c5ead5fd9b..476dc7cba2 100644
--- a/pkgs/ffigen/CHANGELOG.md
+++ b/pkgs/ffigen/CHANGELOG.md
@@ -28,6 +28,10 @@
classes as opposed to `abstract` classes.
- Fix some bugs in the way ObjC method families and ownership annotations were
being handled: https://github.com/dart-lang/native/issues/1446
+- Apply the existing `member-rename` option to ObjC interface and protocol
+ methods and properties.
+- Add a `member-filter` option that filters ObjC interface and protocol methods
+ and properties.
## 13.0.0
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md
index 35826db724..4ddf5255a9 100644
--- a/pkgs/ffigen/README.md
+++ b/pkgs/ffigen/README.md
@@ -190,7 +190,7 @@ dart run ffigen --compiler-opts "-I/headers
- compiler-opts-automatic -> macos -> include-c-standard-library |
+ compiler-opts-automatic.macos.include-c-standard-library |
Tries to automatically find and add C standard library path to
compiler-opts on macos.
Default: true
@@ -213,7 +213,8 @@ compiler-opts-automatic:
Options -
- Include/Exclude declarations.
- Rename declarations.
- - Rename enum and struct members.
+ - Rename enum, struct, and union members, function parameters, and ObjC
+ interface and protocol methods and properties.
- Expose symbol-address for functions and globals.
|
@@ -313,7 +314,7 @@ include-unused-typedefs: true
|
- functions -> expose-typedefs |
+ functions.expose-typedefs |
Generate the typedefs to Native and Dart type of a function
Default: Inline types are used and no typedefs to Native/Dart
type are generated.
@@ -336,7 +337,7 @@ functions:
|
- functions -> leaf |
+ functions.leaf |
Set isLeaf:true for functions.
Default: all functions are excluded.
|
@@ -358,7 +359,7 @@ functions:
- functions -> variadic-arguments |
+ functions.variadic-arguments |
Generate multiple functions with different variadic arguments.
Default: var args for any function are ignored.
|
@@ -378,7 +379,7 @@ functions:
- structs -> pack |
+ structs.pack |
Override the @Packed(X) annotation for generated structs.
Options - none, 1, 2, 4, 8, 16
You can use RegExp to match with the generated names.
@@ -417,8 +418,8 @@ comments:
|
- structs -> dependency-only
- unions -> dependency-only
+ | structs.dependency-only
+ unions.dependency-only
|
If `opaque`, generates empty `Opaque` structs/unions if they
were not included in config (but were added since they are a dependency) and
@@ -613,7 +614,7 @@ language: 'objc'
|
- output -> objc-bindings |
+ output.objc-bindings |
Choose where the generated ObjC code (if any) is placed. The default path
is `'${output.bindings}.m'`, so if your Dart bindings are in
@@ -635,7 +636,7 @@ output:
|
- output -> symbol-file |
+ output.symbol-file |
Generates a symbol file yaml containing all types defined in the generated output. |
@@ -651,7 +652,7 @@ output:
|
- import -> symbol-files |
+ import.symbol-files |
Import symbols from a symbol file. Used for sharing type definitions from other pacakges. |
@@ -751,7 +752,7 @@ objc-protocols:
|
- objc-interfaces -> module
objc-protocols -> module
+ objc-interfaces.module
objc-protocols.module
|
Adds a module prefix to the interface/protocol name when loading it
@@ -778,6 +779,37 @@ objc-interfaces:
|
+
+
+
+ objc-interfaces.member-filter
objc-protocols.member-filter
+ |
+
+ Filters interface and protocol methods and properties. This is a map from
+ interface name to a list of method include and exclude rules. The
+ interface name can be a regexp. The include and exclude rules work exactly
+ like any other declaration. See
+ below for more details.
+ |
+
+
+```yaml
+objc-interfaces:
+ member-filter:
+ MyInterface:
+ include:
+ - "someMethod:withArg:"
+ # Since MyInterface has an include rule, all other methods
+ # are excluded by default.
+objc-protocols:
+ member-filter:
+ NS.*: # Matches all protocols starting with NS.
+ exclude:
+ - copy.* # Remove all copy methods from these protocols.
+```
+
+ |
+
@@ -884,7 +916,7 @@ be future-proof against new additions to the enums.
This happens when an excluded struct/union is a dependency to some included declaration.
(A dependency means a struct is being passed/returned by a function or is member of another struct in some way)
-Note: If you supply `structs` -> `dependency-only` as `opaque` ffigen will generate
+Note: If you supply `structs.dependency-only` as `opaque` ffigen will generate
these struct dependencies as `Opaque` if they were only passed by reference(pointer).
```yaml
structs:
@@ -968,8 +1000,8 @@ Ffigen can sometimes generate a lot of logs, especially when it's parsing a lot
### How can type definitions be shared?
Ffigen can share type definitions using symbol files.
-- A package can generate a symbol file using the `output -> symbol-file` config.
-- And another package can then import this, using `import -> symbol-files` config.
+- A package can generate a symbol file using the `output.symbol-file` config.
+- And another package can then import this, using `import.symbol-files` config.
- Doing so will reuse all the types such as Struct/Unions, and will automatically
exclude generating other types (E.g functions, enums, macros).
@@ -977,3 +1009,65 @@ Checkout `examples/shared_bindings` for details.
For manually reusing definitions from another package, the `library-imports`
and `type-map` config can be used.
+
+### How does ObjC method filtering work?
+
+Methods and properties on ObjC interfaces and protocols can be filtered using
+the `member-filter` option under `objc-interfaces` and `objc-protocols`. For
+simplicity we'll focus on interface methods, but the same rules apply to
+properties and protocols. There are two parts to the filtering process: matching
+the interface, and then filtering the method.
+
+The syntax of `member-filter` is a YAML map from a pattern to some
+`include`/`exclude` rules, and `include` and `exclude` are each a list of
+patterns.
+
+```yaml
+objc-interfaces:
+ member-filter:
+ MyInterface: # Matches an interface.
+ include:
+ - "someMethod:withArg:" # Matches a method.
+ exclude:
+ - someOtherMethod # Matches a method.
+```
+
+The interface matching logic is the same as the matching logic for the
+`member-rename` option:
+
+- The pattern is compared against the original name of the interface (before any
+ renaming is applied).
+- The pattern may be a string or a regexp, but in either case they must match
+ the entire interface name.
+- If the pattern contains only alphanumeric characters, or `_`, it is treated as
+ a string rather than a regex.
+- String patterns take precedence over regexps. That is, if an interface matches
+ both a regexp pattern, and a string pattern, it uses the string pattern's
+ `include`/`exclude` rules.
+
+The method filtering logic uses the same `include`/`exclude` rules as the rest
+of the config:
+
+- `include` and `exclude` are a list of patterns.
+- The patterns are compared against the original name of the method, before
+ renaming.
+- The patterns can be strings or regexps, but must match the entire method name.
+- The method name is in ObjC selector syntax, which means that the method name
+ and all the external parameter names are concatenated together with `:`
+ characters. This is the same name you'll see in ObjC's API documentation.
+- **NOTE:** Since the pattern must match the entire method name, and most ObjC
+ method names end with a `:`, it's a good idea to surround the pattern with
+ quotes, `"`. Otherwise YAML will think you're defining a map key.
+- If no `include` or `exclude` rules are defined, all methods are included,
+ regardless of the top level `exclude-all-by-default` rule.
+- If only `include` rules are `defined`, all non-matching methods are excluded.
+- If only `exclude` rules are `defined`, all non-matching methods are included.
+- If both `include` and `exclude` rules are defined, the `exclude` rules take
+ precedence. That is, if a method name matches both an `include` rule and an
+ `exclude` rule, the method is excluded. All non-matching methods are also
+ excluded.
+
+The property filtering rules live in the same `objc-interfaces.member-filter`
+option as the methods. There is no distinction between methods and properties in
+the filters. The protocol filtering rules live in
+`objc-protocols.member-filter`.
diff --git a/pkgs/ffigen/ffigen.schema.json b/pkgs/ffigen/ffigen.schema.json
index 4303ffec21..8cb1049065 100644
--- a/pkgs/ffigen/ffigen.schema.json
+++ b/pkgs/ffigen/ffigen.schema.json
@@ -341,6 +341,9 @@
"member-rename": {
"$ref": "#/$defs/memberRename"
},
+ "member-filter": {
+ "$ref": "#/$defs/memberFilter"
+ },
"module": {
"$ref": "#/$defs/objcModule"
}
@@ -362,6 +365,9 @@
"member-rename": {
"$ref": "#/$defs/memberRename"
},
+ "member-filter": {
+ "$ref": "#/$defs/memberFilter"
+ },
"module": {
"$ref": "#/$defs/objcModule"
}
@@ -561,6 +567,14 @@
"opaque"
]
},
+ "memberFilter": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "$ref": "#/$defs/includeExclude"
+ }
+ }
+ },
"objcModule": {
"type": "object",
"patternProperties": {
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_methods.dart b/pkgs/ffigen/lib/src/code_generator/objc_methods.dart
index ec91eb19f5..11eaee6011 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_methods.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_methods.dart
@@ -154,14 +154,16 @@ enum ObjCMethodFamily {
class ObjCProperty {
final String originalName;
+ final String name;
String? dartName;
- ObjCProperty(this.originalName);
+ ObjCProperty({required this.originalName, required this.name});
}
class ObjCMethod {
final String? dartDoc;
final String originalName;
+ final String name;
final ObjCProperty? property;
Type returnType;
final List params;
@@ -177,6 +179,7 @@ class ObjCMethod {
ObjCMethod({
required this.originalName,
+ required this.name,
this.property,
this.dartDoc,
required this.kind,
@@ -232,7 +235,7 @@ class ObjCMethod {
// just run the name through uniqueNamer. Instead they need to share
// the dartName, which is run through uniqueNamer.
if (property!.dartName == null) {
- property!.dartName = uniqueNamer.makeUnique(property!.originalName);
+ property!.dartName = uniqueNamer.makeUnique(property!.name);
}
return property!.dartName!;
}
@@ -241,7 +244,7 @@ class ObjCMethod {
// foo:
// foo:someArgName:
// So replace all ':' with '_'.
- return uniqueNamer.makeUnique(originalName.replaceAll(':', '_'));
+ return uniqueNamer.makeUnique(name.replaceAll(':', '_'));
}
bool sameAs(ObjCMethod other) {
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart
index 2f410aee4f..915dc43e29 100644
--- a/pkgs/ffigen/lib/src/config_provider/config.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -305,14 +305,21 @@ abstract interface class DeclarationFilters {
/// Applies renaming and returns the result.
String rename(Declaration declaration);
- /// Applies member renaming and returns the result.
+ /// Applies member renaming and returns the result. Used for struct/union
+ /// fields, enum elements, function params, and ObjC
+ /// interface/protocol methods/properties.
String renameMember(Declaration declaration, String member);
+ /// Whether a member of a declaration should be included. Used for ObjC
+ /// interface/protocol methods/properties.
+ bool shouldIncludeMember(Declaration declaration, String member);
+
factory DeclarationFilters({
bool Function(Declaration declaration)? shouldInclude,
bool Function(Declaration declaration)? shouldIncludeSymbolAddress,
String Function(Declaration declaration)? rename,
String Function(Declaration declaration, String member)? renameMember,
+ bool Function(Declaration declaration, String member)? shouldIncludeMember,
}) =>
DeclarationFiltersImpl(
shouldIncludeFunc: shouldInclude ?? (_) => false,
@@ -320,6 +327,7 @@ abstract interface class DeclarationFilters {
shouldIncludeSymbolAddress ?? (_) => false,
renameFunc: rename ?? (declaration) => declaration.originalName,
renameMemberFunc: renameMember ?? (_, member) => member,
+ shouldIncludeMemberFunc: shouldIncludeMember ?? (_, __) => true,
);
static final excludeAll = DeclarationFilters();
diff --git a/pkgs/ffigen/lib/src/config_provider/config_impl.dart b/pkgs/ffigen/lib/src/config_provider/config_impl.dart
index 8516b5bf3e..1346d7d895 100644
--- a/pkgs/ffigen/lib/src/config_provider/config_impl.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config_impl.dart
@@ -249,10 +249,17 @@ class DeclarationFiltersImpl implements DeclarationFilters {
shouldIncludeSymbolAddressFunc(declaration);
final bool Function(Declaration declaration) shouldIncludeSymbolAddressFunc;
+ @override
+ bool shouldIncludeMember(Declaration declaration, String member) =>
+ shouldIncludeMemberFunc(declaration, member);
+ final bool Function(Declaration declaration, String member)
+ shouldIncludeMemberFunc;
+
DeclarationFiltersImpl({
required this.renameFunc,
required this.renameMemberFunc,
required this.shouldIncludeFunc,
required this.shouldIncludeSymbolAddressFunc,
+ required this.shouldIncludeMemberFunc,
});
}
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart
index cfe63f8422..e8cd386b8b 100644
--- a/pkgs/ffigen/lib/src/config_provider/config_types.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -107,6 +107,7 @@ class YamlDeclarationFilters implements DeclarationFilters {
final YamlRenamer _renamer;
final YamlMemberRenamer _memberRenamer;
final YamlIncluder _symbolAddressIncluder;
+ final YamlMemberIncluder _memberIncluder;
final bool excludeAllByDefault;
YamlDeclarationFilters({
@@ -114,12 +115,14 @@ class YamlDeclarationFilters implements DeclarationFilters {
YamlRenamer? renamer,
YamlMemberRenamer? memberRenamer,
YamlIncluder? symbolAddressIncluder,
+ YamlMemberIncluder? memberIncluder,
required this.excludeAllByDefault,
}) : _includer = includer ?? YamlIncluder(),
_renamer = renamer ?? YamlRenamer(),
_memberRenamer = memberRenamer ?? YamlMemberRenamer(),
_symbolAddressIncluder =
- symbolAddressIncluder ?? YamlIncluder.excludeByDefault();
+ symbolAddressIncluder ?? YamlIncluder.excludeByDefault(),
+ _memberIncluder = memberIncluder ?? YamlMemberIncluder();
/// Applies renaming and returns the result.
@override
@@ -140,6 +143,11 @@ class YamlDeclarationFilters implements DeclarationFilters {
@override
bool shouldIncludeSymbolAddress(Declaration declaration) =>
_symbolAddressIncluder.shouldInclude(declaration.originalName);
+
+ /// Checks if a member is allowed by a filter.
+ @override
+ bool shouldIncludeMember(Declaration declaration, String member) =>
+ _memberIncluder.shouldInclude(declaration.originalName, member);
}
/// Matches `$`, value can be accessed in group 1 of match.
@@ -188,61 +196,54 @@ class RegExpRenamer {
}
}
+/// Filter pack used for both the includer and excluder of [YamlIncluder].
+class YamlFilter {
+ final List _matchers;
+ final Set _full;
+
+ YamlFilter({
+ List? matchers,
+ Set? full,
+ }) : _full = full ?? {},
+ _matchers = matchers ?? [];
+
+ bool get isEmpty => _full.isEmpty && _matchers.isEmpty;
+
+ bool matches(String name) =>
+ _full.contains(name) ||
+ _matchers.any((re) => quiver.matchesFull(re, name));
+}
+
/// Handles `include/exclude` logic for a declaration.
class YamlIncluder {
- final List _includeMatchers;
- final Set _includeFull;
- final List _excludeMatchers;
- final Set _excludeFull;
+ final YamlFilter _include;
+ final YamlFilter _exclude;
YamlIncluder({
List? includeMatchers,
Set? includeFull,
List? excludeMatchers,
Set? excludeFull,
- }) : _includeMatchers = includeMatchers ?? [],
- _includeFull = includeFull ?? {},
- _excludeMatchers = excludeMatchers ?? [],
- _excludeFull = excludeFull ?? {};
+ }) : _include = YamlFilter(matchers: includeMatchers, full: includeFull),
+ _exclude = YamlFilter(matchers: excludeMatchers, full: excludeFull);
YamlIncluder.excludeByDefault()
- : _includeMatchers = [],
- _includeFull = {},
- _excludeMatchers = [RegExp('.*', dotAll: true)],
- _excludeFull = {};
+ : _include = YamlFilter(),
+ _exclude = YamlFilter(matchers: [RegExp('.*', dotAll: true)]);
/// Returns true if [name] is allowed.
///
/// Exclude overrides include.
bool shouldInclude(String name, [bool excludeAllByDefault = false]) {
- if (_excludeFull.contains(name)) {
- return false;
- }
-
- for (final em in _excludeMatchers) {
- if (quiver.matchesFull(em, name)) {
- return false;
- }
- }
-
- if (_includeFull.contains(name)) {
- return true;
- }
-
- for (final im in _includeMatchers) {
- if (quiver.matchesFull(im, name)) {
- return true;
- }
- }
+ if (_exclude.matches(name)) return false;
+ if (_include.matches(name)) return true;
// If user has provided 'include' field in the filter, then default
// matching is false.
- if (_includeMatchers.isNotEmpty || _includeFull.isNotEmpty) {
- return false;
- } else {
- // Otherwise, fall back to the default behavior for empty filters.
- return !excludeAllByDefault;
- }
+ if (!_include.isEmpty) return false;
+
+ // Otherwise, fall back to the default behavior for empty filters.
+ return !excludeAllByDefault;
}
}
@@ -336,6 +337,33 @@ class YamlMemberRenamer {
}
}
+class YamlMemberIncluder {
+ final Map _memberIncluderFull;
+ final List<(RegExp, YamlIncluder)> _memberIncluderMatchers;
+
+ YamlMemberIncluder({
+ Map? memberIncluderFull,
+ List<(RegExp, YamlIncluder)>? memberIncluderMatchers,
+ }) : _memberIncluderFull = memberIncluderFull ?? {},
+ _memberIncluderMatchers = memberIncluderMatchers ?? [];
+
+ bool shouldInclude(String declaration, String member) {
+ // Full matches take priority.
+ final fullMatch = _memberIncluderFull[declaration];
+ if (fullMatch != null) return fullMatch.shouldInclude(member);
+
+ // Check regex matchers.
+ for (final (re, includer) in _memberIncluderMatchers) {
+ if (quiver.matchesFull(re, declaration)) {
+ return includer.shouldInclude(member);
+ }
+ }
+
+ // By default, include all members.
+ return true;
+ }
+}
+
List defaultCompilerOpts({bool macIncludeStdLib = true}) => [
if (Platform.isMacOS && macIncludeStdLib)
...getCStandardLibraryHeadersForMac(),
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
index e9816dc3e7..491f552fd2 100644
--- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
+++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -562,7 +562,6 @@ YamlDeclarationFilters declarationConfigExtractor(
final memberRename =
yamlMap[strings.memberRename] as Map>?;
-
if (memberRename != null) {
for (final key in memberRename.keys) {
final decl = key.toString();
@@ -598,6 +597,21 @@ YamlDeclarationFilters declarationConfigExtractor(
}
}
+ final memberIncluderMatchers = <(RegExp, YamlIncluder)>[];
+ final memberIncluderFull = {};
+ final memberFilter =
+ yamlMap[strings.memberFilter] as Map?;
+ if (memberFilter != null) {
+ for (final entry in memberFilter.entries) {
+ final decl = entry.key.toString();
+ if (isFullDeclarationName(decl)) {
+ memberIncluderFull[decl] = entry.value;
+ } else {
+ memberIncluderMatchers.add((RegExp(decl, dotAll: true), entry.value));
+ }
+ }
+ }
+
return YamlDeclarationFilters(
includer: includer,
renamer: YamlRenamer(
@@ -608,6 +622,10 @@ YamlDeclarationFilters declarationConfigExtractor(
memberRenameFull: memberRenamerFull,
memberRenamePattern: memberRenamePatterns,
),
+ memberIncluder: YamlMemberIncluder(
+ memberIncluderFull: memberIncluderFull,
+ memberIncluderMatchers: memberIncluderMatchers,
+ ),
symbolAddressIncluder: symbolIncluder,
excludeAllByDefault: excludeAllByDefault,
);
diff --git a/pkgs/ffigen/lib/src/config_provider/yaml_config.dart b/pkgs/ffigen/lib/src/config_provider/yaml_config.dart
index 1b179da1a8..0f03b5f953 100644
--- a/pkgs/ffigen/lib/src/config_provider/yaml_config.dart
+++ b/pkgs/ffigen/lib/src/config_provider/yaml_config.dart
@@ -644,6 +644,7 @@ class YamlConfig implements Config {
..._includeExcludeProperties(),
..._renameProperties(),
..._memberRenameProperties(),
+ _memberFilterProperty(),
HeterogeneousMapEntry(
key: strings.objcModule,
valueConfigSpec: _objcModuleObject(),
@@ -664,6 +665,7 @@ class YamlConfig implements Config {
..._includeExcludeProperties(),
..._renameProperties(),
..._memberRenameProperties(),
+ _memberFilterProperty(),
HeterogeneousMapEntry(
key: strings.objcModule,
valueConfigSpec: _objcModuleObject(),
@@ -1068,6 +1070,21 @@ class YamlConfig implements Config {
];
}
+ HeterogeneousMapEntry _memberFilterProperty() {
+ return HeterogeneousMapEntry(
+ key: strings.memberFilter,
+ valueConfigSpec: MapConfigSpec>(
+ schemaDefName: 'memberFilter',
+ keyValueConfigSpecs: [
+ (
+ keyRegexp: '.*',
+ valueConfigSpec: _includeExcludeObject(),
+ ),
+ ],
+ ),
+ );
+ }
+
List _enumIntProperties() => [
HeterogeneousMapEntry(
key: strings.enumAsInt,
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
index 018aa0e5cc..ac3dff85bb 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcinterfacedecl_parser.dart
@@ -5,6 +5,7 @@
import 'package:logging/logging.dart';
import '../../code_generator.dart';
+import '../../config_provider/config.dart';
import '../../config_provider/config_types.dart';
import '../clang_bindings/clang_bindings.dart' as clang_types;
import '../data.dart';
@@ -69,17 +70,18 @@ void fillObjCInterfaceMethodsIfNeeded(
}
void _fillInterface(ObjCInterface itf, clang_types.CXCursor cursor) {
+ final itfDecl = Declaration(usr: itf.usr, originalName: itf.originalName);
cursor.visitChildren((child) {
switch (child.kind) {
case clang_types.CXCursorKind.CXCursor_ObjCSuperClassRef:
_parseSuperType(child, itf);
break;
case clang_types.CXCursorKind.CXCursor_ObjCPropertyDecl:
- _parseProperty(child, itf);
+ _parseProperty(child, itf, itfDecl);
break;
case clang_types.CXCursorKind.CXCursor_ObjCInstanceMethodDecl:
case clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl:
- _parseInterfaceMethod(child, itf);
+ _parseInterfaceMethod(child, itf, itfDecl);
break;
}
});
@@ -110,7 +112,8 @@ void _parseSuperType(clang_types.CXCursor cursor, ObjCInterface itf) {
}
}
-void _parseProperty(clang_types.CXCursor cursor, ObjCInterface itf) {
+void _parseProperty(
+ clang_types.CXCursor cursor, ObjCInterface itf, Declaration itfDecl) {
final fieldName = cursor.spelling();
final fieldType = cursor.type().toCodeGenType();
@@ -125,6 +128,10 @@ void _parseProperty(clang_types.CXCursor cursor, ObjCInterface itf) {
return;
}
+ if (!config.objcInterfaces.shouldIncludeMember(itfDecl, fieldName)) {
+ return;
+ }
+
final dartDoc = getCursorDocComment(cursor);
final propertyAttributes =
@@ -137,7 +144,10 @@ void _parseProperty(clang_types.CXCursor cursor, ObjCInterface itf) {
0;
final isOptionalMethod = clang.clang_Cursor_isObjCOptional(cursor) != 0;
- final property = ObjCProperty(fieldName);
+ final property = ObjCProperty(
+ originalName: fieldName,
+ name: config.objcInterfaces.renameMember(itfDecl, fieldName),
+ );
_logger.fine(' > Property: '
'$fieldType $fieldName ${cursor.completeStringRepr()}');
@@ -146,6 +156,7 @@ void _parseProperty(clang_types.CXCursor cursor, ObjCInterface itf) {
clang.clang_Cursor_getObjCPropertyGetterName(cursor).toStringAndDispose();
final getter = ObjCMethod(
originalName: getterName,
+ name: getterName,
property: property,
dartDoc: dartDoc,
kind: ObjCMethodKind.propertyGetter,
@@ -162,6 +173,7 @@ void _parseProperty(clang_types.CXCursor cursor, ObjCInterface itf) {
.toStringAndDispose();
final setter = ObjCMethod(
originalName: setterName,
+ name: setterName,
property: property,
dartDoc: dartDoc,
kind: ObjCMethodKind.propertySetter,
@@ -176,14 +188,16 @@ void _parseProperty(clang_types.CXCursor cursor, ObjCInterface itf) {
}
}
-void _parseInterfaceMethod(clang_types.CXCursor cursor, ObjCInterface itf) {
- final method = parseObjCMethod(cursor, itf.originalName);
+void _parseInterfaceMethod(
+ clang_types.CXCursor cursor, ObjCInterface itf, Declaration itfDecl) {
+ final method = parseObjCMethod(cursor, itfDecl, config.objcInterfaces);
if (method != null) {
itf.addMethod(method);
}
}
-ObjCMethod? parseObjCMethod(clang_types.CXCursor cursor, String itfName) {
+ObjCMethod? parseObjCMethod(clang_types.CXCursor cursor, Declaration itfDecl,
+ DeclarationFilters filters) {
final methodName = cursor.spelling();
final isClassMethod =
cursor.kind == clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl;
@@ -191,18 +205,24 @@ ObjCMethod? parseObjCMethod(clang_types.CXCursor cursor, String itfName) {
final returnType = clang.clang_getCursorResultType(cursor).toCodeGenType();
if (returnType.isIncompleteCompound) {
_logger.warning('Method "$methodName" in instance '
- '"$itfName" has incomplete '
+ '"${itfDecl.originalName}" has incomplete '
'return type: $returnType.');
return null;
}
if (!isApiAvailable(cursor)) {
- _logger.info('Omitting deprecated method $itfName.$methodName');
+ _logger
+ .info('Omitting deprecated method ${itfDecl.originalName}.$methodName');
+ return null;
+ }
+
+ if (!filters.shouldIncludeMember(itfDecl, methodName)) {
return null;
}
final method = ObjCMethod(
originalName: methodName,
+ name: filters.renameMember(itfDecl, methodName),
dartDoc: getCursorDocComment(cursor),
kind: ObjCMethodKind.method,
isClassMethod: isClassMethod,
@@ -216,7 +236,7 @@ ObjCMethod? parseObjCMethod(clang_types.CXCursor cursor, String itfName) {
cursor.visitChildren((child) {
switch (child.kind) {
case clang_types.CXCursorKind.CXCursor_ParmDecl:
- if (!_parseMethodParam(child, itfName, method)) {
+ if (!_parseMethodParam(child, itfDecl.originalName, method)) {
hasError = true;
}
break;
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart
index 7b9dccb74e..a1ff1107d0 100644
--- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart
+++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/objcprotocoldecl_parser.dart
@@ -69,7 +69,7 @@ ObjCProtocol? parseObjCProtocolDeclaration(clang_types.CXCursor cursor,
break;
case clang_types.CXCursorKind.CXCursor_ObjCInstanceMethodDecl:
case clang_types.CXCursorKind.CXCursor_ObjCClassMethodDecl:
- final method = parseObjCMethod(child, name);
+ final method = parseObjCMethod(child, decl, config.objcProtocols);
if (method != null) {
protocol.addMethod(method);
}
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart
index fde8952d3a..112217f850 100644
--- a/pkgs/ffigen/lib/src/strings.dart
+++ b/pkgs/ffigen/lib/src/strings.dart
@@ -88,6 +88,7 @@ const include = 'include';
const exclude = 'exclude';
const rename = 'rename';
const memberRename = 'member-rename';
+const memberFilter = 'member-filter';
const symbolAddress = 'symbol-address';
// Nested under `functions`
diff --git a/pkgs/ffigen/test/native_objc_test/method_filtering_config.yaml b/pkgs/ffigen/test/native_objc_test/method_filtering_config.yaml
new file mode 100644
index 0000000000..8277fc9fd2
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/method_filtering_config.yaml
@@ -0,0 +1,33 @@
+name: MethodFilteringTestLibrary
+description: 'Tests method filtering'
+language: objc
+output: 'method_filtering_bindings.dart'
+exclude-all-by-default: true
+objc-interfaces:
+ include:
+ - MethodFilteringTestInterface
+ member-filter:
+ Metho.*ngTe.*rface: # The full match rule below takes precedence.
+ include:
+ - .*
+ MethodFilteringTestInterface:
+ include:
+ - includedStaticMethod
+ - excludedStaticMethod # The exclude rule takes precedence.
+ - inc.*Ins.*Me.*od:wi.* # Methods use original names with : delimiters.
+ - includedProperty
+ exclude:
+ - excluded.*
+objc-protocols:
+ include:
+ - MethodFilteringTestProtocol
+ member-filter:
+ Met.*ringT.*ocol:
+ include:
+ - includedProtocolMethod
+ # If include is non-empty, everything else is excluded.
+headers:
+ entry-points:
+ - 'method_filtering_test.m'
+preamble: |
+ // ignore_for_file: camel_case_types, non_constant_identifier_names, unnecessary_non_null_assertion, unused_element, unused_field
diff --git a/pkgs/ffigen/test/native_objc_test/method_filtering_test.dart b/pkgs/ffigen/test/native_objc_test/method_filtering_test.dart
new file mode 100644
index 0000000000..8e887bba71
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/method_filtering_test.dart
@@ -0,0 +1,50 @@
+// 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.
+
+// Objective C support is only available on mac.
+@TestOn('mac-os')
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:ffi/ffi.dart';
+import 'package:ffigen/ffigen.dart';
+import 'package:ffigen/src/config_provider/config.dart';
+import 'package:ffigen/src/config_provider/config_types.dart';
+import 'package:logging/logging.dart';
+import 'package:pub_semver/pub_semver.dart';
+import 'package:test/test.dart';
+import '../test_utils.dart';
+import 'util.dart';
+
+void main() {
+ group('method filtering', () {
+ late final String bindings;
+ group('no version info', () {
+ late final String bindings;
+ setUpAll(() {
+ // TODO(https://github.com/dart-lang/native/issues/1068): Remove this.
+ generateBindingsForCoverage('method_filtering');
+ bindings = File('test/native_objc_test/method_filtering_bindings.dart')
+ .readAsStringSync();
+ });
+
+ test('interfaces', () {
+ expect(bindings, contains('MethodFilteringTestInterface'));
+ expect(bindings, contains('includedStaticMethod'));
+ expect(bindings, contains('includedInstanceMethod'));
+ expect(bindings, contains('includedProperty'));
+ expect(bindings, isNot(contains('excludedStaticMethod')));
+ expect(bindings, isNot(contains('excludedInstanceMethod')));
+ expect(bindings, isNot(contains('excludedProperty')));
+ });
+
+ test('protocols', () {
+ expect(bindings, contains('MethodFilteringTestProtocol'));
+ expect(bindings, contains('includedProtocolMethod'));
+ expect(bindings, isNot(contains('excludedProtocolMethod')));
+ });
+ });
+ });
+}
diff --git a/pkgs/ffigen/test/native_objc_test/method_filtering_test.m b/pkgs/ffigen/test/native_objc_test/method_filtering_test.m
new file mode 100644
index 0000000000..d76fd3526a
--- /dev/null
+++ b/pkgs/ffigen/test/native_objc_test/method_filtering_test.m
@@ -0,0 +1,19 @@
+// 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.
+
+#import
+
+@interface MethodFilteringTestInterface : NSObject {}
++ (instancetype)includedStaticMethod;
++ (instancetype)excludedStaticMethod;
+- (instancetype)includedInstanceMethod: (int32_t)arg with: (int32_t)otherArg;
+- (instancetype)excludedInstanceMethod: (int32_t)arg with: (int32_t)otherArg;
+@property (assign) NSObject* includedProperty;
+@property (assign) NSObject* excludedProperty;
+@end
+
+@protocol MethodFilteringTestProtocol
+- (instancetype)includedProtocolMethod;
+- (instancetype)excludedProtocolMethod;
+@end
diff --git a/pkgs/ffigen/test/native_objc_test/rename_config.yaml b/pkgs/ffigen/test/native_objc_test/rename_config.yaml
index 55e6281a53..d62e7e700b 100644
--- a/pkgs/ffigen/test/native_objc_test/rename_config.yaml
+++ b/pkgs/ffigen/test/native_objc_test/rename_config.yaml
@@ -9,13 +9,17 @@
name: RenameLibrary
description: 'Rename test'
language: objc
-output: 'rename_test_bindings.dart'
+output: 'rename_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- _Renamed
rename:
'_(.*)': '$1'
+ member-rename:
+ '_Renamed':
+ 'renamedMethod:otherArg:': 'fooBarBaz'
+ 'ren.*P.*rty': 'reProp'
structs:
include:
- CollidingStructName
diff --git a/pkgs/ffigen/test/native_objc_test/rename_test.dart b/pkgs/ffigen/test/native_objc_test/rename_test.dart
index 2bc5227343..2cd3830e6e 100644
--- a/pkgs/ffigen/test/native_objc_test/rename_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/rename_test.dart
@@ -10,7 +10,7 @@ import 'dart:io';
import 'package:test/test.dart';
import '../test_utils.dart';
-import 'rename_test_bindings.dart';
+import 'rename_bindings.dart';
import 'util.dart';
void main() {
@@ -44,5 +44,18 @@ void main() {
expect(renamed.CollidingStructName1(), 456);
});
+
+ test('Renamed method', () {
+ final renamed = Renamed.new1();
+
+ expect(renamed.fooBarBaz(123, 456), 579);
+ });
+
+ test('Renamed property', () {
+ final renamed = Renamed.new1();
+
+ renamed.reProp = 2468;
+ expect(renamed.reProp, 2468);
+ });
});
}
diff --git a/pkgs/ffigen/test/native_objc_test/rename_test.m b/pkgs/ffigen/test/native_objc_test/rename_test.m
index 5038242715..44e2f7365d 100644
--- a/pkgs/ffigen/test/native_objc_test/rename_test.m
+++ b/pkgs/ffigen/test/native_objc_test/rename_test.m
@@ -12,6 +12,8 @@ @interface _Renamed : NSObject
@property int32_t property;
-(NSString*)toString;
-(int32_t)CollidingStructName;
+-(int32_t)renamedMethod:(int32_t)x otherArg:(int32_t)y;
+@property int32_t renamedProperty;
@end
@implementation _Renamed
@@ -26,4 +28,9 @@ -(int32_t)CollidingStructName {
return 456;
}
+// Method that will be renamed.
+-(int32_t)renamedMethod:(int32_t)x otherArg:(int32_t)y {
+ return x + y;
+}
+
@end