From 14f6da1d58d23bb0b726e703dd92827a5dafeceb Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 9 Jan 2024 09:09:00 +0100 Subject: [PATCH] Support `@Native` fields and `addressOf` (#860) --- .github/workflows/ffigen.yml | 9 +- .github/workflows/ffigen_weekly.yml | 2 +- .github/workflows/health.yaml | 1 + .github/workflows/publish.yaml | 2 +- pkgs/ffigen/CHANGELOG.md | 6 + pkgs/ffigen/example/ffinative/config.yaml | 12 ++ .../example/ffinative/headers/example.h | 7 ++ .../ffinative/lib/generated_bindings.dart | 48 +++++--- pkgs/ffigen/example/ffinative/pubspec.yaml | 2 +- .../lib/src/code_generator/compound.dart | 15 +-- pkgs/ffigen/lib/src/code_generator/func.dart | 21 +++- .../ffigen/lib/src/code_generator/global.dart | 76 ++++++++---- .../lib/src/code_generator/imports.dart | 1 + .../lib/src/code_generator/library.dart | 79 ++++++------- .../lib/src/code_generator/pointer.dart | 14 ++- pkgs/ffigen/lib/src/code_generator/utils.dart | 37 ++++++ .../ffigen/lib/src/code_generator/writer.dart | 87 ++++++++++++-- .../header_parser/sub_parsers/var_parser.dart | 16 +-- .../type_extractor/extractor.dart | 12 +- pkgs/ffigen/lib/src/header_parser/utils.dart | 4 +- pkgs/ffigen/pubspec.yaml | 6 +- .../code_generator_test.dart | 108 ++++++++++++++---- ..._expected_function_ffiNative_bindings.dart | 14 ++- .../_expected_global_bindings.dart | 5 + .../_expected_global_native_bindings.dart | 32 ++++++ .../_expected_native_symbol_bindings.dart | 17 +++ .../function_n_struct_test.dart | 9 +- 27 files changed, 483 insertions(+), 159 deletions(-) create mode 100644 pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_native_bindings.dart create mode 100644 pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_native_symbol_bindings.dart diff --git a/.github/workflows/ffigen.yml b/.github/workflows/ffigen.yml index 945cca21c..4c290e779 100644 --- a/.github/workflows/ffigen.yml +++ b/.github/workflows/ffigen.yml @@ -28,7 +28,8 @@ jobs: strategy: fail-fast: false matrix: - sdk: [3.2.0] + sdk: [dev] +# sdk: [3.3.0] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d @@ -56,7 +57,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: - sdk: 3.2.0 + sdk: dev #3.3.0 - name: Install dependencies run: dart pub get - name: Install libclang-14-dev @@ -77,7 +78,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: - sdk: 3.2.0 + sdk: dev #3.3.0 - name: Install dependencies run: dart pub get - name: Build test dylib and bindings @@ -110,7 +111,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: - sdk: 3.2.0 + sdk: dev #3.3.0 - name: Install dependencies run: dart pub get - name: Build test dylib and bindings diff --git a/.github/workflows/ffigen_weekly.yml b/.github/workflows/ffigen_weekly.yml index 4d8d1060a..80488b7ba 100644 --- a/.github/workflows/ffigen_weekly.yml +++ b/.github/workflows/ffigen_weekly.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d with: - sdk: 3.2.0 + sdk: dev #3.3.0 - name: Install dependencies run: dart pub get - name: Build test dylib and bindings diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml index 0e0c19460..72e04d29d 100644 --- a/.github/workflows/health.yaml +++ b/.github/workflows/health.yaml @@ -10,5 +10,6 @@ jobs: coverage_web: false checks: "version,changelog,license,do-not-submit,breaking" use-flutter: true + sdk: master permissions: pull-requests: write diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 4c00b141b..c7f1d2cde 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -17,4 +17,4 @@ jobs: pull-requests: write # Required for writing the pull request note with: write-comments: false - sdk: beta + sdk: dev # use beta/stable after 3.3.0 diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index cc50b1d9f..c65a61a47 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -1,3 +1,9 @@ +## 12.0.0-dev + +- Global variables are now compatible with the `ffi-native` option. +- Exposing symbol addresses of functions and globals is now compatible with the + `ffi-native` option. + ## 11.0.0 - Any compiler errors/warnings in source header files will now result in diff --git a/pkgs/ffigen/example/ffinative/config.yaml b/pkgs/ffigen/example/ffinative/config.yaml index 1bb3dac0c..002d7feda 100644 --- a/pkgs/ffigen/example/ffinative/config.yaml +++ b/pkgs/ffigen/example/ffinative/config.yaml @@ -9,4 +9,16 @@ headers: entry-points: - 'headers/example.h' preamble: | + // Copyright (c) 2020, 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. + // ignore_for_file: deprecated_member_use +functions: + symbol-address: + include: + - sum +globals: + symbol-address: + include: + - library_version diff --git a/pkgs/ffigen/example/ffinative/headers/example.h b/pkgs/ffigen/example/ffinative/headers/example.h index 44056dd7d..82df9c7bd 100644 --- a/pkgs/ffigen/example/ffinative/headers/example.h +++ b/pkgs/ffigen/example/ffinative/headers/example.h @@ -16,3 +16,10 @@ float *divide(int a, int b); /** Divides 2 floats, returns a pointer to double. */ double *dividePrecision(float a, float b); + +int log_level = -1; + +const int array[5] = {0, 1, 2, 3, 4}; + +/** Version of the native C library */ +const char* const library_version = "1.0.0-native"; diff --git a/pkgs/ffigen/example/ffinative/lib/generated_bindings.dart b/pkgs/ffigen/example/ffinative/lib/generated_bindings.dart index 13c7d2b9b..39e098469 100644 --- a/pkgs/ffigen/example/ffinative/lib/generated_bindings.dart +++ b/pkgs/ffigen/example/ffinative/lib/generated_bindings.dart @@ -1,51 +1,71 @@ +// Copyright (c) 2020, 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. + // ignore_for_file: deprecated_member_use // AUTO GENERATED FILE, DO NOT EDIT. // // Generated by `package:ffigen`. // ignore_for_file: type=lint +@ffi.DefaultAsset('package:ffinative_example/generated_bindings.dart') +library; + import 'dart:ffi' as ffi; +import '' as self; /// Adds 2 integers. -@ffi.Native( - symbol: 'sum', assetId: 'package:ffinative_example/generated_bindings.dart') +@ffi.Native() external int sum( int a, int b, ); /// Subtracts 2 integers. -@ffi.Native( - symbol: 'subtract', - assetId: 'package:ffinative_example/generated_bindings.dart') +@ffi.Native() external int subtract( int a, int b, ); /// Multiplies 2 integers, returns pointer to an integer,. -@ffi.Native Function(ffi.Int, ffi.Int)>( - symbol: 'multiply', - assetId: 'package:ffinative_example/generated_bindings.dart') +@ffi.Native Function(ffi.Int, ffi.Int)>() external ffi.Pointer multiply( int a, int b, ); /// Divides 2 integers, returns pointer to a float. -@ffi.Native Function(ffi.Int, ffi.Int)>( - symbol: 'divide', - assetId: 'package:ffinative_example/generated_bindings.dart') +@ffi.Native Function(ffi.Int, ffi.Int)>() external ffi.Pointer divide( int a, int b, ); /// Divides 2 floats, returns a pointer to double. -@ffi.Native Function(ffi.Float, ffi.Float)>( - symbol: 'dividePrecision', - assetId: 'package:ffinative_example/generated_bindings.dart') +@ffi.Native Function(ffi.Float, ffi.Float)>() external ffi.Pointer dividePrecision( double a, double b, ); + +@ffi.Native() +external int log_level; + +@ffi.Array.multi([5]) +@ffi.Native>() +external ffi.Array array; + +/// Version of the native C library +@ffi.Native>() +external final ffi.Pointer library_version; + +const addresses = _SymbolAddresses(); + +class _SymbolAddresses { + const _SymbolAddresses(); + ffi.Pointer> get sum => + ffi.Native.addressOf(self.sum); + ffi.Pointer> get library_version => + ffi.Native.addressOf(self.library_version); +} diff --git a/pkgs/ffigen/example/ffinative/pubspec.yaml b/pkgs/ffigen/example/ffinative/pubspec.yaml index e97103113..226c26cb6 100644 --- a/pkgs/ffigen/example/ffinative/pubspec.yaml +++ b/pkgs/ffigen/example/ffinative/pubspec.yaml @@ -5,7 +5,7 @@ name: ffinative_example environment: - sdk: '>=3.2.0 <4.0.0' + sdk: '>=3.3.0-252.0.dev <4.0.0' dependencies: ffi: ^2.0.1 diff --git a/pkgs/ffigen/lib/src/code_generator/compound.dart b/pkgs/ffigen/lib/src/code_generator/compound.dart index 1f9706e30..1cc369787 100644 --- a/pkgs/ffigen/lib/src/code_generator/compound.dart +++ b/pkgs/ffigen/lib/src/code_generator/compound.dart @@ -80,16 +80,6 @@ abstract class Compound extends BindingType { } } - List _getArrayDimensionLengths(Type type) { - final array = []; - var startType = type; - while (startType is ConstantArray) { - array.add(startType.length); - startType = startType.child; - } - return array; - } - String _getInlineArrayTypeString(Type type, Writer w) { if (type is ConstantArray) { return '${w.ffiLibraryPrefix}.Array<' @@ -132,9 +122,8 @@ abstract class Compound extends BindingType { s.writeAll(m.dartDoc!.split('\n'), '\n$depth/// '); s.write('\n'); } - if (m.type is ConstantArray) { - s.write('$depth@${w.ffiLibraryPrefix}.Array.multi('); - s.write('${_getArrayDimensionLengths(m.type)})\n'); + if (m.type case final ConstantArray arrayType) { + s.writeln(makeArrayAnnotation(w, arrayType)); s.write('${depth}external ${_getInlineArrayTypeString(m.type, w)} '); s.write('${m.name};\n\n'); } else { diff --git a/pkgs/ffigen/lib/src/code_generator/func.dart b/pkgs/ffigen/lib/src/code_generator/func.dart index 43f17eb2b..1c518f9f6 100644 --- a/pkgs/ffigen/lib/src/code_generator/func.dart +++ b/pkgs/ffigen/lib/src/code_generator/func.dart @@ -145,13 +145,15 @@ class Func extends LookUpBinding { } if (ffiNativeConfig.enabled) { - final assetString = ffiNativeConfig.assetId != null - ? ", assetId: '${ffiNativeConfig.assetId}'" - : ''; - final isLeafString = isLeaf ? ', isLeaf:true' : ''; final nativeFuncName = needsWrapper ? funcVarName : enclosingFuncName; s.write(''' -@${w.ffiLibraryPrefix}.Native<$cType>(symbol: '$originalName'$assetString$isLeafString) +${makeNativeAnnotation( + w, + nativeType: cType, + dartName: nativeFuncName, + nativeSymbolName: originalName, + isLeaf: isLeaf, + )} external $ffiReturnType $nativeFuncName($ffiArgDeclString); '''); @@ -164,6 +166,15 @@ $dartReturnType $enclosingFuncName($libArg$dartArgDeclString) => $funcImplCall; '''); } + + if (exposeSymbolAddress) { + // Add to SymbolAddress in writer. + w.symbolAddressWriter.addNativeSymbol( + type: + '${w.ffiLibraryPrefix}.Pointer<${w.ffiLibraryPrefix}.NativeFunction<$cType>>', + name: name, + ); + } } else { funcPointerName = w.wrapperLevelUniqueNamer.makeUnique('_${name}Ptr'); final isLeafString = isLeaf ? 'isLeaf:true' : ''; diff --git a/pkgs/ffigen/lib/src/code_generator/global.dart b/pkgs/ffigen/lib/src/code_generator/global.dart index 47aee0de1..439a2f326 100644 --- a/pkgs/ffigen/lib/src/code_generator/global.dart +++ b/pkgs/ffigen/lib/src/code_generator/global.dart @@ -2,9 +2,11 @@ // 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 '../config_provider/config_types.dart'; import 'binding.dart'; import 'binding_string.dart'; import 'compound.dart'; +import 'pointer.dart'; import 'type.dart'; import 'utils.dart'; import 'writer.dart'; @@ -22,6 +24,7 @@ import 'writer.dart'; class Global extends LookUpBinding { final Type type; final bool exposeSymbolAddress; + final FfiNativeConfig nativeConfig; final bool constant; Global({ @@ -32,6 +35,7 @@ class Global extends LookUpBinding { super.dartDoc, this.exposeSymbolAddress = false, this.constant = false, + this.nativeConfig = const FfiNativeConfig(enabled: false), }); @override @@ -41,35 +45,63 @@ class Global extends LookUpBinding { if (dartDoc != null) { s.write(makeDartDoc(dartDoc!)); } - final pointerName = w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName'); final dartType = type.getFfiDartType(w); final cType = type.getCType(w); - s.write( - "late final ${w.ffiLibraryPrefix}.Pointer<$cType> $pointerName = ${w.lookupFuncIdentifier}<$cType>('$originalName');\n\n"); - final baseTypealiasType = type.typealiasType; - if (baseTypealiasType is Compound) { - if (baseTypealiasType.isOpaque) { - s.write( - '${w.ffiLibraryPrefix}.Pointer<$cType> get $globalVarName => $pointerName;\n\n'); - } else { - s.write('$dartType get $globalVarName => $pointerName.ref;\n\n'); + if (nativeConfig.enabled) { + if (type case final ConstantArray arr) { + s.writeln(makeArrayAnnotation(w, arr)); + } + + s + ..writeln(makeNativeAnnotation( + w, + nativeType: cType, + dartName: globalVarName, + nativeSymbolName: originalName, + isLeaf: false, + )) + ..write('external '); + if (constant) { + s.write('final '); + } + + s.writeln('$dartType $globalVarName;\n'); + + if (exposeSymbolAddress) { + w.symbolAddressWriter.addNativeSymbol( + type: '${w.ffiLibraryPrefix}.Pointer<$cType>', name: name); } } else { - s.write('$dartType get $globalVarName => $pointerName.value;\n\n'); - if (!constant) { - s.write( - 'set $globalVarName($dartType value) => $pointerName.value = value;\n\n'); + final pointerName = + w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName'); + + s.write( + "late final ${w.ffiLibraryPrefix}.Pointer<$cType> $pointerName = ${w.lookupFuncIdentifier}<$cType>('$originalName');\n\n"); + final baseTypealiasType = type.typealiasType; + if (baseTypealiasType is Compound) { + if (baseTypealiasType.isOpaque) { + s.write( + '${w.ffiLibraryPrefix}.Pointer<$cType> get $globalVarName => $pointerName;\n\n'); + } else { + s.write('$dartType get $globalVarName => $pointerName.ref;\n\n'); + } + } else { + s.write('$dartType get $globalVarName => $pointerName.value;\n\n'); + if (!constant) { + s.write( + 'set $globalVarName($dartType value) => $pointerName.value = value;\n\n'); + } } - } - if (exposeSymbolAddress) { - // Add to SymbolAddress in writer. - w.symbolAddressWriter.addSymbol( - type: '${w.ffiLibraryPrefix}.Pointer<$cType>', - name: name, - ptrName: pointerName, - ); + if (exposeSymbolAddress) { + // Add to SymbolAddress in writer. + w.symbolAddressWriter.addSymbol( + type: '${w.ffiLibraryPrefix}.Pointer<$cType>', + name: name, + ptrName: pointerName, + ); + } } return BindingString(type: BindingStringType.global, string: s.toString()); diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart index e9dda1ccd..f620909b6 100644 --- a/pkgs/ffigen/lib/src/code_generator/imports.dart +++ b/pkgs/ffigen/lib/src/code_generator/imports.dart @@ -76,6 +76,7 @@ class SelfImportedType extends Type { final ffiImport = LibraryImport('ffi', 'dart:ffi'); final ffiPkgImport = LibraryImport('pkg_ffi', 'package:ffi/ffi.dart'); +final self = LibraryImport('self', ''); final voidType = ImportedType(ffiImport, 'Void', 'void'); diff --git a/pkgs/ffigen/lib/src/code_generator/library.dart b/pkgs/ffigen/lib/src/code_generator/library.dart index 75cbdebc9..60ad7c3ff 100644 --- a/pkgs/ffigen/lib/src/code_generator/library.dart +++ b/pkgs/ffigen/lib/src/code_generator/library.dart @@ -5,13 +5,13 @@ import 'dart:io'; import 'package:cli_util/cli_util.dart'; +import 'package:collection/collection.dart'; import 'package:ffigen/src/code_generator.dart'; import 'package:ffigen/src/config_provider/config_types.dart'; import 'package:logging/logging.dart'; import 'package:path/path.dart' as p; import 'package:yaml_edit/yaml_edit.dart'; -import '../strings.dart' as strings; import 'utils.dart'; import 'writer.dart'; @@ -34,25 +34,13 @@ class Library { StructPackingOverride? packingOverride, Set? libraryImports, }) { - /// Get all dependencies (includes itself). - final dependencies = {}; - for (final b in bindings) { - b.addDependencies(dependencies); - } - - /// Save bindings. - this.bindings = dependencies.toList(); - - if (sort) { - _sort(); - } + _findBindings(bindings, sort); /// Handle any declaration-declaration name conflicts and emit warnings. final declConflictHandler = UniqueNamer({}); for (final b in this.bindings) { _warnIfPrivateDeclaration(b); _resolveIfNameConflicts(declConflictHandler, b); - _warnIfExposeSymbolAddressAndFfiNative(b); } // Override pack values according to config. We do this after declaration @@ -66,23 +54,31 @@ class Library { } // Seperate bindings which require lookup. - final lookUpBindings = this.bindings.whereType().where((e) { - if (e is Func) { - return !e.ffiNativeConfig.enabled; - } - return true; - }).toList(); - final ffiNativeBindings = this - .bindings - .whereType() - .where((e) => e.ffiNativeConfig.enabled) - .toList(); + final lookupBindings = []; + final nativeBindings = []; + FfiNativeConfig? nativeConfig; + + for (final binding in this.bindings.whereType()) { + final nativeConfigForBinding = switch (binding) { + Func() => binding.ffiNativeConfig, + Global() => binding.nativeConfig, + _ => null, + }; + + // At the moment, all bindings share their native config. + nativeConfig ??= nativeConfigForBinding; + + final usesLookup = + nativeConfigForBinding == null || !nativeConfigForBinding.enabled; + (usesLookup ? lookupBindings : nativeBindings).add(binding); + } final noLookUpBindings = this.bindings.whereType().toList(); _writer = Writer( - lookUpBindings: lookUpBindings, - ffiNativeBindings: ffiNativeBindings, + lookUpBindings: lookupBindings, + ffiNativeBindings: nativeBindings, + nativeAssetId: nativeConfig?.assetId, noLookUpBindings: noLookUpBindings, className: name, classDocComment: description, @@ -91,6 +87,20 @@ class Library { ); } + void _findBindings(List original, bool sort) { + /// Get all dependencies (includes itself). + final dependencies = {}; + for (final b in original) { + b.addDependencies(dependencies); + } + + /// Save bindings. + bindings = dependencies.toList(); + if (sort) { + bindings.sortBy((b) => b.name); + } + } + /// Logs a warning if generated declaration will be private. void _warnIfPrivateDeclaration(Binding b) { if (b.name.startsWith('_') && !b.isInternal) { @@ -113,21 +123,6 @@ class Library { } } - /// Logs a warning if generated declaration will be private. - void _warnIfExposeSymbolAddressAndFfiNative(Binding b) { - if (b is Func) { - if (b.exposeSymbolAddress && b.ffiNativeConfig.enabled) { - _logger.warning( - "Ignoring ${strings.symbolAddress} for '${b.name}' because it is generated as FfiNative."); - } - } - } - - /// Sort all bindings in alphabetical order. - void _sort() { - bindings.sort((b1, b2) => b1.name.compareTo(b2.name)); - } - /// Generates [file] by generating C bindings. /// /// If format is true(default), the formatter will be called to format the generated file. diff --git a/pkgs/ffigen/lib/src/code_generator/pointer.dart b/pkgs/ffigen/lib/src/code_generator/pointer.dart index 30e2e6ba7..b3f6cfb62 100644 --- a/pkgs/ffigen/lib/src/code_generator/pointer.dart +++ b/pkgs/ffigen/lib/src/code_generator/pointer.dart @@ -45,7 +45,10 @@ class PointerType extends Type { /// Represents a constant array, which has a fixed size. class ConstantArray extends PointerType { final int length; - ConstantArray(this.length, Type child) : super._(child); + final bool useArrayType; + + ConstantArray(this.length, Type child, {required this.useArrayType}) + : super._(child); @override Type get baseArrayType => child.baseArrayType; @@ -58,6 +61,15 @@ class ConstantArray extends PointerType { @override String cacheKey() => '${child.cacheKey()}[$length]'; + + @override + String getCType(Writer w) { + if (useArrayType) { + return '${w.ffiLibraryPrefix}.Array<${child.getCType(w)}>'; + } + + return super.getCType(w); + } } /// Represents an incomplete array, which has an unknown size. diff --git a/pkgs/ffigen/lib/src/code_generator/utils.dart b/pkgs/ffigen/lib/src/code_generator/utils.dart index 5c18c7f62..4bad8e3dd 100644 --- a/pkgs/ffigen/lib/src/code_generator/utils.dart +++ b/pkgs/ffigen/lib/src/code_generator/utils.dart @@ -1,4 +1,11 @@ +// Copyright (c) 2023, 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 'dart_keywords.dart'; +import 'pointer.dart'; +import 'type.dart'; +import 'writer.dart'; class UniqueNamer { final Set _usedUpNames; @@ -75,3 +82,33 @@ String makeDoc(String text) { return s.toString(); } + +String makeNativeAnnotation( + Writer w, { + required String? nativeType, + required String dartName, + required String nativeSymbolName, + bool isLeaf = false, +}) { + final args = <(String, String)>[]; + if (dartName != nativeSymbolName) { + args.add(('symbol', '"$nativeSymbolName"')); + } + if (isLeaf) { + args.add(('isLeaf', 'true')); + } + + final combinedArgs = args.map((e) => '${e.$1}: ${e.$2}').join(', '); + return '@${w.ffiLibraryPrefix}.Native<$nativeType>($combinedArgs)'; +} + +String makeArrayAnnotation(Writer w, ConstantArray arrayType) { + final dimensions = []; + Type type = arrayType; + while (type is ConstantArray) { + dimensions.add(type.length); + type = type.child; + } + + return '@${w.ffiLibraryPrefix}.Array.multi([${dimensions.join(', ')}])'; +} diff --git a/pkgs/ffigen/lib/src/code_generator/writer.dart b/pkgs/ffigen/lib/src/code_generator/writer.dart index d20db66c1..8193de1c0 100644 --- a/pkgs/ffigen/lib/src/code_generator/writer.dart +++ b/pkgs/ffigen/lib/src/code_generator/writer.dart @@ -23,6 +23,9 @@ class Writer { /// Holds bindings which don't lookup symbols. final List noLookUpBindings; + /// The default asset id to use for [ffiNativeBindings]. + final String? nativeAssetId; + /// Manages the `_SymbolAddress` class. final symbolAddressWriter = SymbolAddressWriter(); @@ -57,6 +60,13 @@ class Writer { return _ffiPkgLibraryPrefix = import.prefix; } + late String selfImportPrefix = () { + final import = _usedImports + .firstWhere((element) => element.name == self.name, orElse: () => self); + _usedImports.add(import); + return import.prefix; + }(); + final Set _usedImports = {}; late String _lookupFuncIdentifier; @@ -92,6 +102,7 @@ class Writer { required this.ffiNativeBindings, required this.noLookUpBindings, required String className, + required this.nativeAssetId, Set? additionalImports, this.classDocComment, this.header, @@ -217,6 +228,17 @@ class Writer { result.write(makeDoc('ignore_for_file: type=lint')); } + // If there are any @Native bindings, the file needs to have an + // `@DefaultAsset` annotation for the symbols to resolve properly. This + // avoids duplicating the asset on every element. + // Since the annotation goes on a `library;` directive, it needs to appear + // before other definitions in the file. + if (ffiNativeBindings.isNotEmpty && nativeAssetId != null) { + result + ..writeln("@$ffiLibraryPrefix.DefaultAsset('$nativeAssetId')") + ..writeln('library;\n'); + } + /// Write [lookUpBindings]. if (lookUpBindings.isNotEmpty) { // Write doc comment for wrapper class. @@ -250,8 +272,14 @@ class Writer { s.write('}\n\n'); } - for (final b in ffiNativeBindings) { - s.write(b.toBindingString(this).string); + if (ffiNativeBindings.isNotEmpty) { + for (final b in ffiNativeBindings) { + s.write(b.toBindingString(this).string); + } + + if (symbolAddressWriter.shouldGenerate) { + s.write(symbolAddressWriter.writeObject(this)); + } } if (symbolAddressWriter.shouldGenerate) { @@ -329,29 +357,61 @@ class SymbolAddressWriter { /// Used to check if we need to generate `_SymbolAddress` class. bool get shouldGenerate => _addresses.isNotEmpty; + bool get hasNonNativeAddress => _addresses.any((e) => !e.native); + void addSymbol({ required String type, required String name, required String ptrName, }) { - _addresses.add(_SymbolAddressUnit(type, name, ptrName)); + _addresses.add(_SymbolAddressUnit(type, name, ptrName, false)); + } + + void addNativeSymbol({required String type, required String name}) { + _addresses.add(_SymbolAddressUnit(type, name, '', true)); } String writeObject(Writer w) { - return 'late final ${w._symbolAddressVariableName} = ${w._symbolAddressClassName}(this);'; + final className = w._symbolAddressClassName; + final fieldName = w._symbolAddressVariableName; + + if (hasNonNativeAddress) { + return 'late final $fieldName = $className(this);'; + } else { + return 'const $fieldName = $className();'; + } } String writeClass(Writer w) { final sb = StringBuffer(); sb.write('class ${w._symbolAddressClassName} {\n'); - // Write Library object. - sb.write('final ${w._className} ${w._symbolAddressLibraryVarName};\n'); - // Write Constructor. - sb.write( - '${w._symbolAddressClassName}(this.${w._symbolAddressLibraryVarName});\n'); - for (final address in _addresses) { + + if (hasNonNativeAddress) { + // Write Library object. + sb.write('final ${w._className} ${w._symbolAddressLibraryVarName};\n'); + // Write Constructor. sb.write( - '${address.type} get ${address.name} => ${w._symbolAddressLibraryVarName}.${address.ptrName};\n'); + '${w._symbolAddressClassName}(this.${w._symbolAddressLibraryVarName});\n'); + } else { + // Native bindings are top-level, so we don't need a field here. + sb.write('const ${w._symbolAddressClassName}();'); + } + + for (final address in _addresses) { + sb.write('${address.type} get ${address.name} => '); + + if (address.native) { + // For native fields and functions, we can use Native.addressOf to look + // up their address. + // The name of address getter shadows the actual element in the library, + // so we need to use a self-import. + final arg = '${w.selfImportPrefix}.${address.name}'; + sb.writeln('${w.ffiLibraryPrefix}.Native.addressOf($arg);'); + } else { + // For other elements, the generator will write a private field of type + // Pointer which we can reference here. + sb.writeln('${w._symbolAddressLibraryVarName}.${address.ptrName};'); + } } sb.write('}\n'); return sb.toString(); @@ -362,5 +422,8 @@ class SymbolAddressWriter { class _SymbolAddressUnit { final String type, name, ptrName; - _SymbolAddressUnit(this.type, this.name, this.ptrName); + /// Whether the symbol we're looking up has been declared with `@Native`. + final bool native; + + _SymbolAddressUnit(this.type, this.name, this.ptrName, this.native); } diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart index bea3e8091..e77ff76a4 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart @@ -26,7 +26,11 @@ Global? parseVarDeclaration(clang_types.CXCursor cursor) { _logger.fine('++++ Adding Global: ${cursor.completeStringRepr()}'); final cType = cursor.type(); - final type = cType.toCodeGenType(); + + final type = cType.toCodeGenType( + // Native fields can be arrays, but if we use the lookup based method of + // reading fields there's no way to turn a Pointer into an array. + supportNonInlineArray: config.ffiNativeConfig.enabled); if (type.baseType is UnimplementedType) { _logger.fine('---- Removed Global, reason: unsupported type: ' '${cursor.completeStringRepr()}'); @@ -34,21 +38,17 @@ Global? parseVarDeclaration(clang_types.CXCursor cursor) { return null; } - if (config.ffiNativeConfig.enabled) { - _logger - .warning("Skipped global variable '$name', not supported in Natives."); - return null; - } - final global = Global( originalName: name, name: config.globals.renameUsingConfig(name), usr: usr, type: type, dartDoc: getCursorDocComment(cursor), - exposeSymbolAddress: config.functionDecl.shouldIncludeSymbolAddress(name), + exposeSymbolAddress: config.globals.shouldIncludeSymbolAddress(name), constant: cType.isConstQualified, + nativeConfig: config.ffiNativeConfig, ); bindingsIndex.addGlobalVarToSeen(usr, global); + return global; } diff --git a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart index 5eb7a0259..079e6573f 100644 --- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart +++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart @@ -39,6 +39,7 @@ Type getCodeGenType( /// Cursor of the declaration, currently this is useful only to extract /// parameter names in function types. clang_types.CXCursor? originalCursor, + bool supportNonInlineArray = false, }) { _logger.fine('${_padding}getCodeGenType ${cxtype.completeStringRepr()}'); @@ -121,12 +122,17 @@ Type getCodeGenType( case clang_types.CXTypeKind.CXType_ConstantArray: // Primarily used for constant array in struct members. final numElements = clang.clang_getNumElements(cxtype); - final elementType = - clang.clang_getArrayElementType(cxtype).toCodeGenType(); + final elementType = clang + .clang_getArrayElementType(cxtype) + .toCodeGenType(supportNonInlineArray: supportNonInlineArray); // Handle numElements being 0 as an incomplete array. return numElements == 0 ? IncompleteArray(elementType) - : ConstantArray(numElements, elementType); + : ConstantArray( + numElements, + elementType, + useArrayType: supportNonInlineArray, + ); case clang_types.CXTypeKind.CXType_IncompleteArray: // Primarily used for incomplete array in function parameters. return IncompleteArray( diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart index d142370f2..9e80414df 100644 --- a/pkgs/ffigen/lib/src/header_parser/utils.dart +++ b/pkgs/ffigen/lib/src/header_parser/utils.dart @@ -273,8 +273,8 @@ String? removeRawCommentMarkups(String? string) { extension CXTypeExt on clang_types.CXType { /// Get code_gen [Type] representation of [clang_types.CXType]. - Type toCodeGenType() { - return getCodeGenType(this); + Type toCodeGenType({bool supportNonInlineArray = false}) { + return getCodeGenType(this, supportNonInlineArray: supportNonInlineArray); } /// Spelling for a [clang_types.CXTypeKind], useful for debug purposes. diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index d0bea7683..20703e331 100644 --- a/pkgs/ffigen/pubspec.yaml +++ b/pkgs/ffigen/pubspec.yaml @@ -3,7 +3,7 @@ # BSD-style license that can be found in the LICENSE file. name: ffigen -version: 11.0.0 +version: 12.0.0-dev description: > Generator for FFI bindings, using LibClang to parse C, Objective-C, and Swift files. @@ -14,7 +14,7 @@ topics: - codegen environment: - sdk: ">=3.2.0 <4.0.0" + sdk: '>=3.3.0-252.0.dev <4.0.0' dependencies: ffi: ^2.0.1 @@ -28,8 +28,10 @@ dependencies: file: ^7.0.0 package_config: ^2.1.0 yaml_edit: ^2.0.3 + collection: ^1.18.0 dev_dependencies: lints: ^2.0.1 test: ^1.16.2 json_schema: ^5.1.1 + meta: ^1.11.0 diff --git a/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart b/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart index c4dd1a120..c9de2e13a 100644 --- a/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart +++ b/pkgs/ffigen/test/code_generator_tests/code_generator_test.dart @@ -4,7 +4,9 @@ import 'package:ffigen/src/code_generator.dart'; import 'package:ffigen/src/config_provider/config_types.dart'; +import 'package:meta/meta.dart'; import 'package:test/test.dart'; + import '../test_utils.dart'; void main() { @@ -15,13 +17,24 @@ void main() { '''; group('code_generator: ', () { - void functionBindings(bool enableFfiNative) { + @isTestGroup + void withAndWithoutNative( + String description, void Function(FfiNativeConfig) runTest) { + group(description, () { + test('without Native', () => runTest(FfiNativeConfig(enabled: false))); + test('with Native', + () => runTest(FfiNativeConfig(enabled: true, assetId: 'test'))); + }); + } + + withAndWithoutNative('Function Binding (primitives, pointers)', + (nativeConfig) { final library = Library( name: 'Bindings', header: licenseHeader, bindings: [ Func( - ffiNativeConfig: FfiNativeConfig(enabled: enableFfiNative), + ffiNativeConfig: nativeConfig, name: 'noParam', dartDoc: 'Just a test function\nheres another line', returnType: NativeType( @@ -29,7 +42,7 @@ void main() { ), ), Func( - ffiNativeConfig: FfiNativeConfig(enabled: enableFfiNative), + ffiNativeConfig: nativeConfig, name: 'withPrimitiveParam', parameters: [ Parameter( @@ -50,7 +63,7 @@ void main() { ), ), Func( - ffiNativeConfig: FfiNativeConfig(enabled: enableFfiNative), + ffiNativeConfig: nativeConfig, name: 'withPointerParam', parameters: [ Parameter( @@ -79,7 +92,7 @@ void main() { ), ), Func( - ffiNativeConfig: FfiNativeConfig(enabled: enableFfiNative), + ffiNativeConfig: nativeConfig, isLeaf: true, name: 'leafFunc', dartDoc: 'A function with isLeaf: true', @@ -98,15 +111,8 @@ void main() { ], ); - _matchLib(library, enableFfiNative ? 'function_ffiNative' : 'function'); - } - - test('Function Binding (primitives, pointers)', () { - functionBindings(false); - }); - - test('Function Binding (primitives, pointers) (ffiNative)', () { - functionBindings(true); + _matchLib( + library, nativeConfig.enabled ? 'function_ffiNative' : 'function'); }); test('Struct Binding (primitives, pointers)', () { @@ -250,7 +256,8 @@ void main() { _matchLib(library, 'function_n_struct'); }); - test('global (primitives, pointers, pointer to struct)', () { + withAndWithoutNative('global (primitives, pointers, pointer to struct)', + (nativeConfig) { final structSome = Struct( name: 'Some', ); @@ -261,12 +268,14 @@ void main() { header: licenseHeader, bindings: [ Global( + nativeConfig: nativeConfig, name: 'test1', type: NativeType( SupportedNativeType.Int32, ), ), Global( + nativeConfig: nativeConfig, name: 'test2', type: PointerType( NativeType( @@ -275,18 +284,35 @@ void main() { ), constant: true, ), + Global( + nativeConfig: nativeConfig, + name: 'test3', + type: ConstantArray( + 10, + NativeType( + SupportedNativeType.Float, + ), + useArrayType: nativeConfig.enabled, + ), + constant: true, + ), structSome, Global( + nativeConfig: nativeConfig, name: 'test5', type: PointerType( structSome, ), ), emptyGlobalStruct, - Global(name: 'globalStruct', type: emptyGlobalStruct), + Global( + nativeConfig: nativeConfig, + name: 'globalStruct', + type: emptyGlobalStruct, + ), ], ); - _matchLib(library, 'global'); + _matchLib(library, nativeConfig.enabled ? 'global_native' : 'global'); }); test('constant', () { @@ -329,6 +355,7 @@ void main() { ); _matchLib(library, 'enumclass'); }); + test('Internal conflict resolution', () { final library = Library( name: 'init_dylib', @@ -361,6 +388,9 @@ void main() { NativeType( SupportedNativeType.Int8, ), + // This flag is ignored for struct fields, which always use + // inline arrays. + useArrayType: true, ), ), ], @@ -376,6 +406,30 @@ void main() { ); _matchLib(library, 'internal_conflict_resolution'); }); + + test('Adds Native symbol on mismatch', () { + final nativeConfig = FfiNativeConfig(enabled: true); + final library = Library( + name: 'init_dylib', + header: + '$licenseHeader\n// ignore_for_file: unused_element, camel_case_types, non_constant_identifier_names\n', + bindings: [ + Func( + ffiNativeConfig: nativeConfig, + name: 'test', + originalName: '_test', + returnType: NativeType(SupportedNativeType.Void), + ), + Global( + nativeConfig: nativeConfig, + name: 'testField', + originalName: '_testField', + type: NativeType(SupportedNativeType.Int16), + ), + ], + ); + _matchLib(library, 'native_symbol'); + }); }); test('boolean_dartBool', () { final library = Library( @@ -467,10 +521,22 @@ void main() { Member(name: 'd', type: PointerType(struct1)), ]), Union(name: 'WithArray', members: [ - Member(name: 'a', type: ConstantArray(10, charType)), - Member(name: 'b', type: ConstantArray(10, union1)), - Member(name: 'b', type: ConstantArray(10, struct1)), - Member(name: 'c', type: ConstantArray(10, PointerType(union1))), + Member( + name: 'a', + type: ConstantArray(10, charType, useArrayType: true), + ), + Member( + name: 'b', + type: ConstantArray(10, union1, useArrayType: true), + ), + Member( + name: 'b', + type: ConstantArray(10, struct1, useArrayType: true), + ), + Member( + name: 'c', + type: ConstantArray(10, PointerType(union1), useArrayType: true), + ), ]), ], ); diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_function_ffiNative_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_function_ffiNative_bindings.dart index 9b4ebf4f5..e77b71487 100644 --- a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_function_ffiNative_bindings.dart +++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_function_ffiNative_bindings.dart @@ -6,30 +6,32 @@ // // Generated by `package:ffigen`. // ignore_for_file: type=lint +@ffi.DefaultAsset('test') +library; + import 'dart:ffi' as ffi; /// Just a test function /// heres another line -@ffi.Native(symbol: 'noParam') +@ffi.Native() external int noParam(); -@ffi.Native( - symbol: 'withPrimitiveParam') +@ffi.Native() external int withPrimitiveParam( int a, int b, ); @ffi.Native< - ffi.Pointer Function(ffi.Pointer, - ffi.Pointer>)>(symbol: 'withPointerParam') + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer>)>() external ffi.Pointer withPointerParam( ffi.Pointer a, ffi.Pointer> b, ); /// A function with isLeaf: true -@ffi.Native(symbol: 'leafFunc', isLeaf: true) +@ffi.Native(isLeaf: true) external int leafFunc( int a, ); diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_bindings.dart index 85b3b1843..066f9ee0b 100644 --- a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_bindings.dart +++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_bindings.dart @@ -33,6 +33,11 @@ class Bindings { ffi.Pointer get test2 => _test2.value; + late final ffi.Pointer> _test3 = + _lookup>('test3'); + + ffi.Pointer get test3 => _test3.value; + late final ffi.Pointer> _test5 = _lookup>('test5'); diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_native_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_native_bindings.dart new file mode 100644 index 000000000..601c079d5 --- /dev/null +++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_global_native_bindings.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2023, 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. + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +@ffi.DefaultAsset('test') +library; + +import 'dart:ffi' as ffi; + +@ffi.Native() +external int test1; + +@ffi.Native>() +external final ffi.Pointer test2; + +@ffi.Array.multi([10]) +@ffi.Native>() +external final ffi.Array test3; + +@ffi.Native>() +external ffi.Pointer test5; + +@ffi.Native() +external EmptyStruct globalStruct; + +final class Some extends ffi.Opaque {} + +final class EmptyStruct extends ffi.Opaque {} diff --git a/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_native_symbol_bindings.dart b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_native_symbol_bindings.dart new file mode 100644 index 000000000..234cf2c96 --- /dev/null +++ b/pkgs/ffigen/test/code_generator_tests/expected_bindings/_expected_native_symbol_bindings.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2023, 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. + +// ignore_for_file: unused_element, camel_case_types, non_constant_identifier_names + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +@ffi.Native(symbol: "_test") +external void test(); + +@ffi.Native(symbol: "_testField") +external int testField; diff --git a/pkgs/ffigen/test/header_parser_tests/function_n_struct_test.dart b/pkgs/ffigen/test/header_parser_tests/function_n_struct_test.dart index 5ddd58afa..028da3d7f 100644 --- a/pkgs/ffigen/test/header_parser_tests/function_n_struct_test.dart +++ b/pkgs/ffigen/test/header_parser_tests/function_n_struct_test.dart @@ -118,7 +118,14 @@ Library expectedLibrary() { Struct(name: 'Struct4'), Struct(name: 'Struct5'), Struct(name: 'Struct6', members: [ - Member(name: 'a', type: ConstantArray(2, ConstantArray(10, intType))) + Member( + name: 'a', + type: ConstantArray( + 2, + ConstantArray(10, intType, useArrayType: false), + useArrayType: false, + ), + ) ]), Struct(name: 'Struct7'), ],