Skip to content
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

[ffigen] Add flags controlling how transitive deps are pulled in #1687

Merged
merged 11 commits into from
Oct 31, 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
6 changes: 6 additions & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
- https://github.com/dart-lang/native/issues/1582
- https://github.com/dart-lang/native/issues/1594
- https://github.com/dart-lang/native/issues/1595
- Add `includeTransitiveObjCInterfaces` and `includeTransitiveObjCProtocols`
config flags, which control whether transitively included ObjC interfaces and
protocols are generated.
- __Breaking change__: `includeTransitiveObjCInterfaces` defaults to false,
which changes the default behavior from pulling in all transitive deps, to
generating them as stubs.

## 15.0.0

Expand Down
25 changes: 25 additions & 0 deletions pkgs/ffigen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,31 @@ objc-protocols:

</td>
</tr>

<tr>
<td>
include-transitive-objc-interfaces<br><br>
include-transitive-objc-protocols
</td>
<td>
By default, Objective-C interfaces and protocols that are not directly
included by the inclusion rules, but are transitively depended on by
the inclusions, are not fully code genned. Transitively included
interfaces are generated as stubs, and transitive protocols are omitted.
<br>
If these flags are enabled, transitively included interfaces and protocols
are fully code genned.
<br>
<b>Default: false</b>
</td>
<td>

```yaml
include-transitive-objc-interfaces: true
include-transitive-objc-protocols: true
```
</td>
</tr>
</tbody>
</table>

Expand Down
6 changes: 6 additions & 0 deletions pkgs/ffigen/ffigen.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,12 @@
"include-unused-typedefs": {
"type": "boolean"
},
"include-transitive-objc-interfaces": {
"type": "boolean"
},
"include-transitive-objc-protocols": {
"type": "boolean"
},
"generate-for-package-objective-c": {
"type": "boolean"
},
Expand Down
45 changes: 39 additions & 6 deletions pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ class ObjCBuiltInFunctions {
// for float return types we need objc_msgSend_fpret.
final _msgSendFuncs = <String, ObjCMsgSendFunc>{};
ObjCMsgSendFunc getMsgSendFunc(Type returnType, List<Parameter> params) {
params = _methodSigParams(params);
returnType = _methodSigType(returnType);
final id = _methodSigId(returnType, params);
return _msgSendFuncs[id] ??= ObjCMsgSendFunc(
'_objc_msgSend_${fnvHash32(id).toRadixString(36)}',
Expand All @@ -161,19 +163,50 @@ class ObjCBuiltInFunctions {

String _methodSigId(Type returnType, List<Parameter> params) {
final paramIds = <String>[];
for (final param in params) {
final retainFunc = param.type.generateRetain('');

for (final p in params) {
// The trampoline ID is based on the getNativeType of the param. Objects
// and blocks both have `id` as their native type, but need separate
// trampolines since they have different retain functions. So add the
// retainFunc (if any) to all the param IDs.
paramIds.add('${param.getNativeType()}-${retainFunc ?? ''}');
// retain function (if any) to all the param IDs.
paramIds.add(p.getNativeType(varName: p.type.generateRetain('') ?? ''));
}
final rt = '${returnType.getNativeType()}-${returnType.generateRetain('')}';
final rt =
returnType.getNativeType(varName: returnType.generateRetain('') ?? '');
return '$rt,${paramIds.join(',')}';
}

Type _methodSigType(Type t) {
if (t is FunctionType) {
return FunctionType(
returnType: _methodSigType(t.returnType),
parameters: _methodSigParams(t.parameters),
varArgParameters: _methodSigParams(t.varArgParameters),
);
} else if (t is ObjCBlock) {
return ObjCBlockPointer();
} else if (t is ObjCInterface) {
return ObjCObjectPointer();
} else if (t is ConstantArray) {
return ConstantArray(
t.length,
_methodSigType(t.child),
useArrayType: t.useArrayType,
);
} else if (t is PointerType) {
return PointerType(_methodSigType(t.child));
} else if (t is ObjCNullable) {
return _methodSigType(t.child);
} else if (t is Typealias) {
return _methodSigType(t.type);
}
return t;
}

List<Parameter> _methodSigParams(List<Parameter> params) => params
.map((p) =>
Parameter(type: _methodSigType(p.type), objCConsumed: p.objCConsumed))
.toList();

final _blockTrampolines = <String, ObjCListenerBlockTrampoline>{};
ObjCListenerBlockTrampoline? getListenerBlockTrampoline(ObjCBlock block) {
final id = _methodSigId(block.returnType, block.params);
Expand Down
54 changes: 37 additions & 17 deletions pkgs/ffigen/lib/src/code_generator/objc_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class ObjCInterface extends BindingType with ObjCMethods {
@override
final ObjCBuiltInFunctions builtInFunctions;

// Filled by ListBindingsVisitation.
bool generateAsStub = false;

ObjCInterface({
super.usr,
required String super.originalName,
Expand Down Expand Up @@ -53,27 +56,23 @@ class ObjCInterface extends BindingType with ObjCMethods {

@override
BindingString toBindingString(Writer w) {
String paramsToString(List<Parameter> params) {
final stringParams = <String>[
for (final p in params)
'${_getConvertedType(p.type, w, name)} ${p.name}',
];
return '(${stringParams.join(", ")})';
}

final s = StringBuffer();
s.write('\n');
if (generateAsStub) {
s.write('''
/// WARNING: $name is a stub. To generate bindings for this class, include
/// $name in your config's objc-interfaces list.
///
''');
}
s.write(makeDartDoc(dartDoc ?? originalName));

final methodNamer = createMethodRenamer(w);

final rawObjType = PointerType(objCObjectType).getCType(w);
final wrapObjType = ObjCBuiltInFunctions.objectBase.gen(w);

final superTypeIsInPkgObjc = superType == null;

// Class declaration.
s.write('''class $name extends ${superType?.getDartType(w) ?? wrapObjType} {
s.write('''
class $name extends ${superType?.getDartType(w) ?? wrapObjType} {
$name._($rawObjType pointer,
{bool retain = false, bool release = false}) :
${superTypeIsInPkgObjc ? 'super' : 'super.castFromPointer'}
Expand All @@ -88,6 +87,30 @@ class ObjCInterface extends BindingType with ObjCMethods {
{bool retain = false, bool release = false}) :
this._(other, retain: retain, release: release);

${generateAsStub ? '' : _generateMethods(w)}
}

''');

return BindingString(
type: BindingStringType.objcInterface, string: s.toString());
}

String _generateMethods(Writer w) {
String paramsToString(List<Parameter> params) {
final stringParams = <String>[
for (final p in params)
'${_getConvertedType(p.type, w, name)} ${p.name}',
];
return '(${stringParams.join(", ")})';
}

final methodNamer = createMethodRenamer(w);
final wrapObjType = ObjCBuiltInFunctions.objectBase.gen(w);
final s = StringBuffer();

// Class declaration.
s.write('''
/// Returns whether [obj] is an instance of [$name].
static bool isInstance($wrapObjType obj) {
return ${_isKindOfClassMsgSend.invoke(
Expand Down Expand Up @@ -215,10 +238,7 @@ class ObjCInterface extends BindingType with ObjCMethods {
s.write('\n }\n');
}

s.write('}\n\n');

return BindingString(
type: BindingStringType.objcInterface, string: s.toString());
return s.toString();
}

@override
Expand Down
17 changes: 17 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/pointer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class PointerType extends Type {
factory PointerType(Type child) {
if (child == objCObjectType) {
return ObjCObjectPointer();
} else if (child == objCBlockType) {
return ObjCBlockPointer();
}
return PointerType._(child);
}
Expand Down Expand Up @@ -108,6 +110,7 @@ class ObjCObjectPointer extends PointerType {
factory ObjCObjectPointer() => _inst;

static final _inst = ObjCObjectPointer._();
ObjCObjectPointer.__(super.child) : super._();
ObjCObjectPointer._() : super._(objCObjectType);

@override
Expand Down Expand Up @@ -143,3 +146,17 @@ class ObjCObjectPointer extends PointerType {
@override
String? generateRetain(String value) => 'objc_retain($value)';
}

/// A pointer to an Objective C block.
class ObjCBlockPointer extends ObjCObjectPointer {
factory ObjCBlockPointer() => _inst;

static final _inst = ObjCBlockPointer._();
ObjCBlockPointer._() : super.__(objCBlockType);

@override
String getDartType(Writer w) => '${w.objcPkgPrefix}.ObjCBlockBase';

@override
String? generateRetain(String value) => 'objc_retainBlock($value)';
}
16 changes: 16 additions & 0 deletions pkgs/ffigen/lib/src/config_provider/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ abstract interface class Config {
/// If enabled, unused typedefs will also be generated.
bool get includeUnusedTypedefs;

/// If enabled, Objective C interfaces that are not explicitly included by
/// the [DeclarationFilters], but are transitively included by other bindings,
/// will be code-genned as if they were included. If disabled, these
/// transitively included interfaces will be generated as stubs instead.
bool get includeTransitiveObjCInterfaces;

/// If enabled, Objective C protocols that are not explicitly included by
/// the [DeclarationFilters], but are transitively included by other bindings,
/// will be code-genned as if they were included. If disabled, these
/// transitively included protocols will not be generated at all.
bool get includeTransitiveObjCProtocols;

/// Undocumented option that changes code generation for package:objective_c.
/// The main difference is whether NSObject etc are imported from
/// package:objective_c (the default) or code genned like any other class.
Expand Down Expand Up @@ -195,6 +207,8 @@ abstract interface class Config {
DeclarationFilters? objcInterfaces,
DeclarationFilters? objcProtocols,
bool includeUnusedTypedefs = false,
bool includeTransitiveObjCInterfaces = false,
bool includeTransitiveObjCProtocols = false,
bool generateForPackageObjectiveC = false,
bool sort = false,
bool useSupportedTypedefs = true,
Expand Down Expand Up @@ -250,6 +264,8 @@ abstract interface class Config {
objcInterfaces: objcInterfaces ?? DeclarationFilters.excludeAll,
objcProtocols: objcProtocols ?? DeclarationFilters.excludeAll,
includeUnusedTypedefs: includeUnusedTypedefs,
includeTransitiveObjCInterfaces: includeTransitiveObjCInterfaces,
includeTransitiveObjCProtocols: includeTransitiveObjCProtocols,
generateForPackageObjectiveC: generateForPackageObjectiveC,
sort: sort,
useSupportedTypedefs: useSupportedTypedefs,
Expand Down
8 changes: 8 additions & 0 deletions pkgs/ffigen/lib/src/config_provider/config_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ class ConfigImpl implements Config {
@override
final bool includeUnusedTypedefs;

@override
final bool includeTransitiveObjCInterfaces;

@override
final bool includeTransitiveObjCProtocols;

@override
final bool generateForPackageObjectiveC;

Expand Down Expand Up @@ -198,6 +204,8 @@ class ConfigImpl implements Config {
required this.objcInterfaces,
required this.objcProtocols,
required this.includeUnusedTypedefs,
required this.includeTransitiveObjCInterfaces,
required this.includeTransitiveObjCProtocols,
required this.generateForPackageObjectiveC,
required this.sort,
required this.useSupportedTypedefs,
Expand Down
30 changes: 30 additions & 0 deletions pkgs/ffigen/lib/src/config_provider/yaml_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,22 @@ class YamlConfig implements Config {
bool get includeUnusedTypedefs => _includeUnusedTypedefs;
late bool _includeUnusedTypedefs;

/// If enabled, Objective C interfaces that are not explicitly included by
/// the [DeclarationFilters], but are transitively included by other bindings,
/// will be code-genned as if they were included. If disabled, these
/// transitively included interfaces will be generated as stubs instead.
@override
bool get includeTransitiveObjCInterfaces => _includeTransitiveObjCInterfaces;
late bool _includeTransitiveObjCInterfaces;

/// If enabled, Objective C protocols that are not explicitly included by
/// the [DeclarationFilters], but are transitively included by other bindings,
/// will be code-genned as if they were included. If disabled, these
/// transitively included protocols will not be generated at all.
@override
bool get includeTransitiveObjCProtocols => _includeTransitiveObjCProtocols;
late bool _includeTransitiveObjCProtocols;

/// Undocumented option that changes code generation for package:objective_c.
/// The main difference is whether NSObject etc are imported from
/// package:objective_c (the default) or code genned like any other class.
Expand Down Expand Up @@ -753,6 +769,20 @@ class YamlConfig implements Config {
resultOrDefault: (node) =>
_includeUnusedTypedefs = node.value as bool,
),
HeterogeneousMapEntry(
key: strings.includeTransitiveObjCInterfaces,
valueConfigSpec: BoolConfigSpec(),
defaultValue: (node) => false,
resultOrDefault: (node) =>
_includeTransitiveObjCInterfaces = node.value as bool,
),
HeterogeneousMapEntry(
key: strings.includeTransitiveObjCProtocols,
valueConfigSpec: BoolConfigSpec(),
defaultValue: (node) => false,
resultOrDefault: (node) =>
_includeTransitiveObjCProtocols = node.value as bool,
),
HeterogeneousMapEntry(
key: strings.generateForPackageObjectiveC,
valueConfigSpec: BoolConfigSpec(),
Expand Down
Loading
Loading