Skip to content

[ffigen] Method filtering #1511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
124 changes: 109 additions & 15 deletions pkgs/ffigen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ dart run ffigen --compiler-opts "-I/headers
</td>
</tr>
<tr>
<td>compiler-opts-automatic -> macos -> include-c-standard-library</td>
<td>compiler-opts-automatic.macos.include-c-standard-library</td>
<td>Tries to automatically find and add C standard library path to
compiler-opts on macos.<br>
<b>Default: true</b>
Expand All @@ -213,7 +213,8 @@ compiler-opts-automatic:
Options -<br>
- Include/Exclude declarations.<br>
- Rename declarations.<br>
- Rename enum and struct members.<br>
- Rename enum, struct, and union members, function parameters, and ObjC
interface and protocol methods and properties.<br>
- Expose symbol-address for functions and globals.<br>
</td>
<td>
Expand Down Expand Up @@ -313,7 +314,7 @@ include-unused-typedefs: true
</td>
</tr>
<tr>
<td>functions -> expose-typedefs</td>
<td>functions.expose-typedefs</td>
<td>Generate the typedefs to Native and Dart type of a function<br>
<b>Default: Inline types are used and no typedefs to Native/Dart
type are generated.</b>
Expand All @@ -336,7 +337,7 @@ functions:
</td>
</tr>
<tr>
<td>functions -> leaf</td>
<td>functions.leaf</td>
<td>Set isLeaf:true for functions.<br>
<b>Default: all functions are excluded.</b>
</td>
Expand All @@ -358,7 +359,7 @@ functions:
</td>
</tr>
<tr>
<td>functions -> variadic-arguments</td>
<td>functions.variadic-arguments</td>
<td>Generate multiple functions with different variadic arguments.<br>
<b>Default: var args for any function are ignored.</b>
</td>
Expand All @@ -378,7 +379,7 @@ functions:
</td>
</tr>
<tr>
<td>structs -> pack</td>
<td>structs.pack</td>
<td>Override the @Packed(X) annotation for generated structs.<br><br>
<i>Options - none, 1, 2, 4, 8, 16</i><br>
You can use RegExp to match with the <b>generated</b> names.<br><br>
Expand Down Expand Up @@ -417,8 +418,8 @@ comments:
</td>
</tr>
<tr>
<td>structs -> dependency-only<br><br>
unions -> dependency-only
<td>structs.dependency-only<br><br>
unions.dependency-only
</td>
<td>If `opaque`, generates empty `Opaque` structs/unions if they
were not included in config (but were added since they are a dependency) and
Expand Down Expand Up @@ -613,7 +614,7 @@ language: 'objc'
</td>
</tr>
<tr>
<td>output -> objc-bindings</td>
<td>output.objc-bindings</td>
<td>
Choose where the generated ObjC code (if any) is placed. The default path
is `'${output.bindings}.m'`, so if your Dart bindings are in
Expand All @@ -635,7 +636,7 @@ output:
</td>
</tr>
<tr>
<td>output -> symbol-file</td>
<td>output.symbol-file</td>
<td>Generates a symbol file yaml containing all types defined in the generated output.</td>
<td>

Expand All @@ -651,7 +652,7 @@ output:
</td>
</tr>
<tr>
<td>import -> symbol-files</td>
<td>import.symbol-files</td>
<td>Import symbols from a symbol file. Used for sharing type definitions from other pacakges.</td>
<td>

Expand Down Expand Up @@ -751,7 +752,7 @@ objc-protocols:

<tr>
<td>
objc-interfaces -> module<br><br>objc-protocols -> module
objc-interfaces.module<br><br>objc-protocols.module
</td>
<td>
Adds a module prefix to the interface/protocol name when loading it
Expand All @@ -778,6 +779,37 @@ objc-interfaces:

</td>
</tr>

<tr>
<td>
objc-interfaces.member-filter<br><br>objc-protocols.member-filter
</td>
<td>
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
<a href="#how-does-objc-method-filtering-work">below</a> for more details.
</td>
<td>

```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.
```

</td>
</tr>
</tbody>
</table>

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -968,12 +1000,74 @@ 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).

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`.
14 changes: 14 additions & 0 deletions pkgs/ffigen/ffigen.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@
"member-rename": {
"$ref": "#/$defs/memberRename"
},
"member-filter": {
"$ref": "#/$defs/memberFilter"
},
"module": {
"$ref": "#/$defs/objcModule"
}
Expand All @@ -362,6 +365,9 @@
"member-rename": {
"$ref": "#/$defs/memberRename"
},
"member-filter": {
"$ref": "#/$defs/memberFilter"
},
"module": {
"$ref": "#/$defs/objcModule"
}
Expand Down Expand Up @@ -561,6 +567,14 @@
"opaque"
]
},
"memberFilter": {
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/$defs/includeExclude"
}
}
},
"objcModule": {
"type": "object",
"patternProperties": {
Expand Down
9 changes: 6 additions & 3 deletions pkgs/ffigen/lib/src/code_generator/objc_methods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Parameter> params;
Expand All @@ -177,6 +179,7 @@ class ObjCMethod {

ObjCMethod({
required this.originalName,
required this.name,
this.property,
this.dartDoc,
required this.kind,
Expand Down Expand Up @@ -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!;
}
Expand All @@ -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) {
Expand Down
10 changes: 9 additions & 1 deletion pkgs/ffigen/lib/src/config_provider/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,21 +305,29 @@ 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,
shouldIncludeSymbolAddressFunc:
shouldIncludeSymbolAddress ?? (_) => false,
renameFunc: rename ?? (declaration) => declaration.originalName,
renameMemberFunc: renameMember ?? (_, member) => member,
shouldIncludeMemberFunc: shouldIncludeMember ?? (_, __) => true,
);

static final excludeAll = DeclarationFilters();
Expand Down
7 changes: 7 additions & 0 deletions pkgs/ffigen/lib/src/config_provider/config_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
Loading