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