[ffigen] Support for generating Variadic functions (#515)
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index a9e26eb..f577aa5 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,7 @@ +# 8.0.0-dev.3 + +- Added support for variadic functions using config `functions -> variadic-arguments`. + # 8.0.0-dev.2 - Use `@Native` syntax instead of deprecated `@FfiNative` syntax.
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md index 5195ad9..674c347 100644 --- a/pkgs/ffigen/README.md +++ b/pkgs/ffigen/README.md
@@ -335,6 +335,26 @@ </td> </tr> <tr> + <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> + <td> + +```yaml +functions: + variadic-arguments: + myfunc: + // Native C types are supported + - [int, unsigned char, long*, float**] + // Common C typedefs (stddef.h) are supported too + - [uint8_t, intptr_t, size_t, wchar_t*] + // Structs/Unions/Typedefs from generated code or a library import can be referred too. + - [MyStruct*, my_custom_lib.CustomUnion] +``` + </td> + </tr> + <tr> <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>
diff --git a/pkgs/ffigen/lib/src/code_generator/func.dart b/pkgs/ffigen/lib/src/code_generator/func.dart index 55aec86..f8d32e2 100644 --- a/pkgs/ffigen/lib/src/code_generator/func.dart +++ b/pkgs/ffigen/lib/src/code_generator/func.dart
@@ -58,6 +58,7 @@ String? dartDoc, required Type returnType, List<Parameter>? parameters, + List<Parameter>? varArgParameters, this.exposeSymbolAddress = false, this.exposeFunctionTypedefs = false, this.isLeaf = false, @@ -66,6 +67,7 @@ }) : functionType = FunctionType( returnType: returnType, parameters: parameters ?? const [], + varArgParameters: varArgParameters ?? const [], ), super( usr: usr, @@ -109,7 +111,7 @@ } // Resolve name conflicts in function parameter names. final paramNamer = UniqueNamer({}); - for (final p in functionType.parameters) { + for (final p in functionType.dartTypeParameters) { p.name = paramNamer.makeUnique(p.name); } @@ -130,7 +132,7 @@ s.write( 'external ${functionType.returnType.getDartType(w)} $enclosingFuncName(\n'); - for (final p in functionType.parameters) { + for (final p in functionType.dartTypeParameters) { s.write(' ${p.type.getDartType(w)} ${p.name},\n'); } s.write(');\n\n'); @@ -138,14 +140,14 @@ // Write enclosing function. s.write( '${functionType.returnType.getDartType(w)} $enclosingFuncName(\n'); - for (final p in functionType.parameters) { + for (final p in functionType.dartTypeParameters) { s.write(' ${p.type.getDartType(w)} ${p.name},\n'); } s.write(') {\n'); s.write('return $funcVarName'); s.write('(\n'); - for (final p in functionType.parameters) { + for (final p in functionType.dartTypeParameters) { s.write(' ${p.name},\n'); } s.write(' );\n');
diff --git a/pkgs/ffigen/lib/src/code_generator/func_type.dart b/pkgs/ffigen/lib/src/code_generator/func_type.dart index 5402028..d4c74f6 100644 --- a/pkgs/ffigen/lib/src/code_generator/func_type.dart +++ b/pkgs/ffigen/lib/src/code_generator/func_type.dart
@@ -11,13 +11,19 @@ class FunctionType extends Type { final Type returnType; final List<Parameter> parameters; + final List<Parameter> varArgParameters; + + /// Get all the parameters for generating the dart type. This includes both + /// [parameters] and [varArgParameters]. + List<Parameter> get dartTypeParameters => parameters + varArgParameters; FunctionType({ required this.returnType, required this.parameters, + this.varArgParameters = const [], }); - String _getTypeString( + String _getCacheKeyString( bool writeArgumentNames, String Function(Type) typeToString) { final sb = StringBuffer(); @@ -35,18 +41,51 @@ } @override - String getCType(Writer w, {bool writeArgumentNames = true}) => - _getTypeString(writeArgumentNames, (Type t) => t.getCType(w)); + String getCType(Writer w, {bool writeArgumentNames = true}) { + final sb = StringBuffer(); + + // Write return Type. + sb.write(returnType.getCType(w)); + + // Write Function. + sb.write(' Function('); + sb.write((parameters).map<String>((p) { + return '${p.type.getCType(w)} ${writeArgumentNames ? p.name : ""}'; + }).join(', ')); + if (varArgParameters.isNotEmpty) { + sb.write(", ${w.ffiLibraryPrefix}.VarArgs<("); + sb.write((varArgParameters).map<String>((p) { + return '${p.type.getCType(w)} ${writeArgumentNames ? p.name : ""}'; + }).join(', ')); + sb.write(",)>"); + } + sb.write(')'); + + return sb.toString(); + } @override - String getDartType(Writer w, {bool writeArgumentNames = true}) => - _getTypeString(writeArgumentNames, (Type t) => t.getDartType(w)); + String getDartType(Writer w, {bool writeArgumentNames = true}) { + final sb = StringBuffer(); + + // Write return Type. + sb.write(returnType.getDartType(w)); + + // Write Function. + sb.write(' Function('); + sb.write(dartTypeParameters.map<String>((p) { + return '${p.type.getDartType(w)} ${writeArgumentNames ? p.name : ""}'; + }).join(', ')); + sb.write(')'); + + return sb.toString(); + } @override - String toString() => _getTypeString(false, (Type t) => t.toString()); + String toString() => _getCacheKeyString(false, (Type t) => t.toString()); @override - String cacheKey() => _getTypeString(false, (Type t) => t.cacheKey()); + String cacheKey() => _getCacheKeyString(false, (Type t) => t.cacheKey()); @override void addDependencies(Set<Binding> dependencies) {
diff --git a/pkgs/ffigen/lib/src/code_generator/imports.dart b/pkgs/ffigen/lib/src/code_generator/imports.dart index 41f7161..d6dcd06 100644 --- a/pkgs/ffigen/lib/src/code_generator/imports.dart +++ b/pkgs/ffigen/lib/src/code_generator/imports.dart
@@ -49,6 +49,25 @@ String? getDefaultValue(Writer w, String nativeLib) => defaultValue; } +/// An unchecked type similar to [ImportedType] which exists in the generated +/// binding itself. +class SelfImportedType extends Type { + final String cType; + final String dartType; + final String? defaultValue; + + SelfImportedType(this.cType, this.dartType, [this.defaultValue]); + + @override + String getCType(Writer w) => cType; + + @override + String getDartType(Writer w) => dartType; + + @override + String toString() => cType; +} + final ffiImport = LibraryImport('ffi', 'dart:ffi'); final ffiPkgImport = LibraryImport('pkg_ffi', 'package:ffi/ffi.dart');
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index e012e56..12a7e69 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -53,6 +53,10 @@ List<String> get compilerOpts => _compilerOpts; late List<String> _compilerOpts; + /// VarArg function handling. + Map<String, List<VarArgFunction>> get varArgFunctions => _varArgFunctions; + late Map<String, List<VarArgFunction>> _varArgFunctions = {}; + /// Declaration config for Functions. Declaration get functionDecl => _functionDecl; late Declaration _functionDecl; @@ -446,6 +450,17 @@ result as Map<String, List<String>>, _libraryImports); }, ), + [strings.functions, strings.varArgFunctions]: + Specification<Map<String, List<RawVarArgFunction>>>( + requirement: Requirement.no, + validator: varArgFunctionConfigValidator, + extractor: varArgFunctionConfigExtractor, + defaultValue: () => <String, List<RawVarArgFunction>>{}, + extractedResult: (dynamic result) { + _varArgFunctions = makeVarArgFunctionsMapping( + result as Map<String, List<RawVarArgFunction>>, _libraryImports); + }, + ), [strings.excludeAllByDefault]: Specification<bool>( requirement: Requirement.no, validator: booleanValidator,
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart index 8521bdb..1eddfc9 100644 --- a/pkgs/ffigen/lib/src/config_provider/config_types.dart +++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -5,6 +5,7 @@ /// Contains all the neccesary classes required by config. import 'dart:io'; +import 'package:ffigen/src/code_generator.dart'; import 'package:quiver/pattern.dart' as quiver; import 'path_finder.dart'; @@ -423,3 +424,17 @@ OutputConfig(this.output, this.symbolFile); } + +class RawVarArgFunction { + String? postfix; + final List<String> rawTypeStrings; + + RawVarArgFunction(this.postfix, this.rawTypeStrings); +} + +class VarArgFunction { + final String postfix; + final List<Type> types; + + VarArgFunction(this.postfix, this.types); +}
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart index dfe6bbf..8bb3e2b 100644 --- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart +++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -6,6 +6,7 @@ import 'package:ffigen/src/code_generator.dart'; import 'package:ffigen/src/code_generator/utils.dart'; +import 'package:ffigen/src/header_parser/type_extractor/cxtypekindmap.dart'; import 'package:file/local.dart'; import 'package:glob/glob.dart'; import 'package:logging/logging.dart'; @@ -329,6 +330,99 @@ return typeMappings; } +Type makePointerToType(Type type, int pointerCount) { + for (var i = 0; i < pointerCount; i++) { + type = PointerType(type); + } + return type; +} + +String makePostfixFromRawVarArgType(List<String> rawVarArgType) { + return rawVarArgType + .map((e) => e + .replaceAll('*', 'Ptr') + .replaceAll(RegExp(r'_t$'), '') + .replaceAll(' ', '') + .replaceAll(RegExp('[^A-Za-z0-9_]'), '')) + .map((e) => e.length > 1 ? '${e[0].toUpperCase()}${e.substring(1)}' : e) + .join(''); +} + +Type makeTypeFromRawVarArgType( + String rawVarArgType, Map<String, LibraryImport> libraryImportsMap) { + Type baseType; + var rawBaseType = rawVarArgType.trim(); + // Split the raw type based on pointer usage. E.g - + // int => [int] + // char* => [char,*] + // ffi.Hello ** => [ffi.Hello,**] + final typeStringRegexp = RegExp(r'([a-zA-Z0-9_\s\.]+)(\**)$'); + if (!typeStringRegexp.hasMatch(rawBaseType)) { + throw Exception('Cannot parse variadic argument type - $rawVarArgType.'); + } + final regExpMatch = typeStringRegexp.firstMatch(rawBaseType)!; + final groups = regExpMatch.groups([1, 2]); + rawBaseType = groups[0]!; + // Handle basic supported types. + if (cxTypeKindToImportedTypes.containsKey(rawBaseType)) { + baseType = cxTypeKindToImportedTypes[rawBaseType]!; + } else if (supportedTypedefToImportedType.containsKey(rawBaseType)) { + baseType = supportedTypedefToImportedType[rawBaseType]!; + } else if (suportedTypedefToSuportedNativeType.containsKey(rawBaseType)) { + baseType = NativeType(suportedTypedefToSuportedNativeType[rawBaseType]!); + } else { + // Use library import if specified (E.g - ffi.UintPtr or custom.MyStruct) + final rawVarArgTypeSplit = rawBaseType.split('.'); + if (rawVarArgTypeSplit.length == 1) { + final typeName = rawVarArgTypeSplit[0].replaceAll(' ', ''); + baseType = SelfImportedType(typeName, typeName); + } else if (rawVarArgTypeSplit.length == 2) { + final lib = rawVarArgTypeSplit[0]; + final libraryImport = strings.predefinedLibraryImports[lib] ?? + libraryImportsMap[rawVarArgTypeSplit[0]]; + if (libraryImport == null) { + throw Exception('Please declare $lib in library-imports.'); + } + final typeName = rawVarArgTypeSplit[1].replaceAll(' ', ''); + baseType = ImportedType(libraryImport, typeName, typeName); + } else { + throw Exception( + 'Invalid type $rawVarArgType : Expected 0 or 1 .(dot) separators.'); + } + } + + // Handle pointers + final pointerCount = groups[1]!.length; + return makePointerToType(baseType, pointerCount); +} + +Map<String, List<VarArgFunction>> makeVarArgFunctionsMapping( + Map<String, List<RawVarArgFunction>> rawVarArgMappings, + Map<String, LibraryImport> libraryImportsMap) { + final mappings = <String, List<VarArgFunction>>{}; + for (final key in rawVarArgMappings.keys) { + final varArgList = <VarArgFunction>[]; + for (final rawVarArg in rawVarArgMappings[key]!) { + var postfix = rawVarArg.postfix ?? ''; + final types = <Type>[]; + for (final rva in rawVarArg.rawTypeStrings) { + types.add(makeTypeFromRawVarArgType(rva, libraryImportsMap)); + } + if (postfix.isEmpty) { + if (rawVarArgMappings[key]!.length == 1) { + postfix = ''; + } else { + postfix = makePostfixFromRawVarArgType(rawVarArg.rawTypeStrings); + } + } + // Extract postfix from config and/or deduce from var names. + varArgList.add(VarArgFunction(postfix, types)); + } + mappings[key] = varArgList; + } + return mappings; +} + final _quoteMatcher = RegExp(r'''^["'](.*)["']$''', dotAll: true); final _cmdlineArgMatcher = RegExp(r'''['"](\\"|[^"])*?['"]|[^ ]+'''); List<String> compilerOptsToList(String compilerOpts) { @@ -743,6 +837,85 @@ ); } +Map<String, List<RawVarArgFunction>> varArgFunctionConfigExtractor( + dynamic yamlMap) { + final result = <String, List<RawVarArgFunction>>{}; + final configMap = (yamlMap as YamlMap); + for (final key in configMap.keys) { + final List<RawVarArgFunction> vafuncs = []; + for (final rawVaFunc in (configMap[key] as YamlList)) { + if (rawVaFunc is YamlList) { + vafuncs.add(RawVarArgFunction(null, rawVaFunc.cast())); + } else if (rawVaFunc is YamlMap) { + vafuncs.add(RawVarArgFunction(rawVaFunc[strings.postfix] as String?, + (rawVaFunc[strings.types] as YamlList).cast())); + } else { + throw Exception("Unexpected type in variadic-argument config."); + } + } + result[key as String] = vafuncs; + } + + return result; +} + +bool varArgFunctionConfigValidator(List<String> name, dynamic value) { + if (!checkType<YamlMap>(name, value)) { + return false; + } + var result = true; + for (final key in (value as YamlMap).keys) { + final list = value[key as String]; + if (!checkType<YamlList>([...name, key], list)) { + result = false; + continue; + } + (list as YamlList).asMap().forEach((idx, subList) { + if (subList is YamlMap) { + if (!subList.containsKey(strings.types)) { + result = false; + _logger.severe('Missing required key - ${[ + ...name, + key, + idx.toString(), + strings.types + ].join(" -> ")}'); + } + subList.forEach((subkey, subvalue) { + subkey = subkey as String; + if (subkey == strings.postfix) { + if (!checkType<String>( + [...name, key, idx.toString(), subkey], subvalue)) { + result = false; + } + } else if (subkey == strings.types) { + if (!checkType<YamlList>( + [...name, key, idx.toString(), subkey], subvalue)) { + result = false; + } + } else { + result = false; + _logger.severe('Unknown key - ${[ + ...name, + key, + idx.toString(), + subkey + ].join(" -> ")}'); + } + }); + } else if (subList is! YamlList) { + result = false; + _logger.severe('Expected ${[ + ...name, + key, + idx + ].join(" -> ")} to be a List or a Map.'); + } + }); + } + return result; +} + Declaration declarationConfigExtractor(dynamic yamlMap) { final renamePatterns = <RegExpRenamer>[]; final renameFull = <String, String>{};
diff --git a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart index 4c4c472..c7d6e2f 100644 --- a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart +++ b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -792,6 +792,21 @@ _clang_Type_getObjCObjectBaseTypePtr .asFunction<CXType Function(CXType)>(); + /// Return 1 if the CXType is a variadic function type, and 0 otherwise. + int clang_isFunctionTypeVariadic( + CXType T, + ) { + return _clang_isFunctionTypeVariadic( + T, + ); + } + + late final _clang_isFunctionTypeVariadicPtr = + _lookup<ffi.NativeFunction<ffi.UnsignedInt Function(CXType)>>( + 'clang_isFunctionTypeVariadic'); + late final _clang_isFunctionTypeVariadic = + _clang_isFunctionTypeVariadicPtr.asFunction<int Function(CXType)>(); + /// Retrieve the return type associated with a given cursor. /// /// This only returns a valid type if the cursor refers to a function or method.
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart index d2d3412..94d5ed0 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/config_provider/config_types.dart'; import 'package:ffigen/src/header_parser/data.dart'; import 'package:logging/logging.dart'; @@ -14,7 +15,9 @@ /// Holds temporary information regarding [Func] while parsing. class _ParserFunc { - Func? func; + /// Multiple values are since there may be more than one instance of the + /// same base C function with different variadic arguments. + List<Func> funcs = []; bool incompleteStructParameter = false; bool unimplementedParameterType = false; _ParserFunc(); @@ -23,7 +26,7 @@ final _stack = Stack<_ParserFunc>(); /// Parses a function declaration. -Func? parseFunctionDeclaration(clang_types.CXCursor cursor) { +List<Func>? parseFunctionDeclaration(clang_types.CXCursor cursor) { _stack.push(_ParserFunc()); final funcUsr = cursor.usr(); @@ -39,8 +42,8 @@ '${cursor.completeStringRepr()}'); _logger.warning( "Skipped Function '$funcName', inline functions are not supported."); - // Returning null so that [addToBindings] function excludes this. - return _stack.pop().func; + // Returning empty so that [addToBindings] function excludes this. + return _stack.pop().funcs; } if (rt.isIncompleteCompound || _stack.top.incompleteStructParameter) { @@ -51,7 +54,7 @@ "Skipped Function '$funcName', Incomplete struct pass/return by " 'value not supported.'); // Returning null so that [addToBindings] function excludes this. - return _stack.pop().func; + return _stack.pop().funcs; } if (rt.baseType is UnimplementedType || @@ -62,32 +65,46 @@ "Skipped Function '$funcName', function has unsupported return type " 'or parameter type.'); // Returning null so that [addToBindings] function excludes this. - return _stack.pop().func; + return _stack.pop().funcs; } - _stack.top.func = Func( - dartDoc: getCursorDocComment( - cursor, - nesting.length + commentPrefix.length, - ), - usr: funcUsr, - name: config.functionDecl.renameUsingConfig(funcName), - originalName: funcName, - returnType: rt, - parameters: parameters, - exposeSymbolAddress: - config.functionDecl.shouldIncludeSymbolAddress(funcName), - exposeFunctionTypedefs: - config.exposeFunctionTypedefs.shouldInclude(funcName), - isLeaf: config.leafFunctions.shouldInclude(funcName), - ffiNativeConfig: config.ffiNativeConfig, - ); - bindingsIndex.addFuncToSeen(funcUsr, _stack.top.func!); + // Initialized with a single value with no prefix and empty var args. + var varArgFunctions = [VarArgFunction('', [])]; + if (clang.clang_isFunctionTypeVariadic(cursor.type()) == 1) { + if (config.varArgFunctions.containsKey(funcName)) { + varArgFunctions = config.varArgFunctions[funcName]!; + } else { + _logger.warning( + "Skipping variadic-argument config for function $funcName since its not variadic."); + } + } + for (final vaFunc in varArgFunctions) { + _stack.top.funcs.add(Func( + dartDoc: getCursorDocComment( + cursor, + nesting.length + commentPrefix.length, + ), + usr: funcUsr + vaFunc.postfix, + name: config.functionDecl.renameUsingConfig(funcName) + vaFunc.postfix, + originalName: funcName, + returnType: rt, + parameters: parameters, + varArgParameters: + vaFunc.types.map((ta) => Parameter(type: ta, name: 'va')).toList(), + exposeSymbolAddress: + config.functionDecl.shouldIncludeSymbolAddress(funcName), + exposeFunctionTypedefs: + config.exposeFunctionTypedefs.shouldInclude(funcName), + isLeaf: config.leafFunctions.shouldInclude(funcName), + ffiNativeConfig: config.ffiNativeConfig, + )); + } + bindingsIndex.addFuncToSeen(funcUsr, _stack.top.funcs.last); } else if (bindingsIndex.isSeenFunc(funcUsr)) { - _stack.top.func = bindingsIndex.getSeenFunc(funcUsr); + _stack.top.funcs.add(bindingsIndex.getSeenFunc(funcUsr)!); } - return _stack.pop().func; + return _stack.pop().funcs; } Type _getFunctionReturnType(clang_types.CXCursor cursor) {
diff --git a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart index ea9d594..31f50b8 100644 --- a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
@@ -56,7 +56,7 @@ _logger.finest('rootCursorVisitor: ${cursor.completeStringRepr()}'); switch (clang.clang_getCursorKind(cursor)) { case clang_types.CXCursorKind.CXCursor_FunctionDecl: - addToBindings(parseFunctionDeclaration(cursor)); + addAllToBindings(parseFunctionDeclaration(cursor) as List<Binding>); break; case clang_types.CXCursorKind.CXCursor_StructDecl: case clang_types.CXCursorKind.CXCursor_UnionDecl: @@ -96,6 +96,14 @@ } } +/// Adds all binding if not empty. +void addAllToBindings(List<Binding> b) { + if (b.isNotEmpty) { + // This is a set, and hence will not have duplicates. + _bindings.addAll(b); + } +} + BindingType? _getCodeGenTypeFromCursor(clang_types.CXCursor cursor) { final t = getCodeGenType(cursor.type(), ignoreFilter: false); return t is BindingType ? t : null;
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index e0f2761..245dd7c 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart
@@ -85,6 +85,11 @@ // Nested under `functions` const exposeFunctionTypedefs = 'expose-typedefs'; const leafFunctions = 'leaf'; +const varArgFunctions = 'variadic-arguments'; + +// Nested under varArg entries +const postfix = "postfix"; +const types = "types"; // Sub-fields of ObjC interfaces. const objcModule = 'module';
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index d4a7013..fa5c897 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: 8.0.0-dev.2 +version: 8.0.0-dev.3 description: Generator for FFI bindings, using LibClang to parse C header files. repository: https://github.com/dart-lang/ffigen
diff --git a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_varargs_bindings.dart b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_varargs_bindings.dart new file mode 100644 index 0000000..5b51bcd --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_varargs_bindings.dart
@@ -0,0 +1,196 @@ +// ignore_for_file: camel_case_types + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +/// VarArgs Test +class NativeLibrary { + /// Holds the symbol lookup function. + final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + NativeLibrary(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + /// The symbols are looked up with [lookup]. + NativeLibrary.fromLookup( + ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) + lookup) + : _lookup = lookup; + + int myfunc( + int a, + int va, + ffi.Pointer<ffi.Char> va1, + SA va2, + ) { + return _myfunc( + a, + va, + va1, + va2, + ); + } + + late final _myfuncPtr = _lookup< + ffi.NativeFunction< + ffi.Int Function( + ffi.Int, + ffi.VarArgs< + ( + ffi.Int, + ffi.Pointer<ffi.Char>, + SA, + )>)>>('myfunc'); + late final _myfunc = _myfuncPtr + .asFunction<int Function(int, int, ffi.Pointer<ffi.Char>, SA)>(); + + void myfunc2CharPtrLongPtrPtr( + int a, + int b, + ffi.Pointer<ffi.Char> va, + ffi.Pointer<ffi.Pointer<ffi.Long>> va1, + ) { + return _myfunc2CharPtrLongPtrPtr( + a, + b, + va, + va1, + ); + } + + late final _myfunc2CharPtrLongPtrPtrPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Char, + ffi.Char, + ffi.VarArgs< + ( + ffi.Pointer<ffi.Char>, + ffi.Pointer<ffi.Pointer<ffi.Long>>, + )>)>>('myfunc2'); + late final _myfunc2CharPtrLongPtrPtr = + _myfunc2CharPtrLongPtrPtrPtr.asFunction< + void Function(int, int, ffi.Pointer<ffi.Char>, + ffi.Pointer<ffi.Pointer<ffi.Long>>)>(); + + void myfunc2SAIntPtrUnsignedcharPtrPtr( + int a, + int b, + SA va, + ffi.Pointer<ffi.Int> va1, + ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>> va2, + ) { + return _myfunc2SAIntPtrUnsignedcharPtrPtr( + a, + b, + va, + va1, + va2, + ); + } + + late final _myfunc2SAIntPtrUnsignedcharPtrPtrPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Char, + ffi.Char, + ffi.VarArgs< + ( + SA, + ffi.Pointer<ffi.Int>, + ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>, + )>)>>('myfunc2'); + late final _myfunc2SAIntPtrUnsignedcharPtrPtr = + _myfunc2SAIntPtrUnsignedcharPtrPtrPtr.asFunction< + void Function(int, int, SA, ffi.Pointer<ffi.Int>, + ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>)>(); + + void myfunc2_custompostfix( + int a, + int b, + SA va, + ffi.Pointer<ffi.Int> va1, + ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>> va2, + ) { + return _myfunc2_custompostfix( + a, + b, + va, + va1, + va2, + ); + } + + late final _myfunc2_custompostfixPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Char, + ffi.Char, + ffi.VarArgs< + ( + SA, + ffi.Pointer<ffi.Int>, + ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>, + )>)>>('myfunc2'); + late final _myfunc2_custompostfix = _myfunc2_custompostfixPtr.asFunction< + void Function(int, int, SA, ffi.Pointer<ffi.Int>, + ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>)>(); + + void myfunc3Struct_WithLong_Name_testPtrFloatPtr( + int a, + ffi.Pointer<Struct_WithLong_Name_test> va, + ffi.Pointer<ffi.Float> va1, + ) { + return _myfunc3Struct_WithLong_Name_testPtrFloatPtr( + a, + va, + va1, + ); + } + + late final _myfunc3Struct_WithLong_Name_testPtrFloatPtrPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Long, + ffi.VarArgs< + ( + ffi.Pointer<Struct_WithLong_Name_test>, + ffi.Pointer<ffi.Float>, + )>)>>('myfunc3'); + late final _myfunc3Struct_WithLong_Name_testPtrFloatPtr = + _myfunc3Struct_WithLong_Name_testPtrFloatPtrPtr.asFunction< + void Function(int, ffi.Pointer<Struct_WithLong_Name_test>, + ffi.Pointer<ffi.Float>)>(); + + void myfunc3_custompostfix2( + int a, + Struct_WithLong_Name_test va, + ) { + return _myfunc3_custompostfix2( + a, + va, + ); + } + + late final _myfunc3_custompostfix2Ptr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Long, ffi.VarArgs<(Struct_WithLong_Name_test,)>)>>('myfunc3'); + late final _myfunc3_custompostfix2 = _myfunc3_custompostfix2Ptr + .asFunction<void Function(int, Struct_WithLong_Name_test)>(); +} + +final class SA extends ffi.Struct { + @ffi.Int() + external int a; +} + +final class Struct_WithLong_Name_test extends ffi.Struct { + @ffi.Int() + external int a; +}
diff --git a/pkgs/ffigen/test/header_parser_tests/varargs.h b/pkgs/ffigen/test/header_parser_tests/varargs.h new file mode 100644 index 0000000..e316b90 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/varargs.h
@@ -0,0 +1,15 @@ +// 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. + +int myfunc(int a, ...); +void myfunc2(char a, char b, ...); +void myfunc3(long a, ...); + +struct SA { + int a; +}; + +struct Struct_WithLong_Name_test { + int a; +};
diff --git a/pkgs/ffigen/test/header_parser_tests/varargs_test.dart b/pkgs/ffigen/test/header_parser_tests/varargs_test.dart new file mode 100644 index 0000000..deaa5af --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/varargs_test.dart
@@ -0,0 +1,59 @@ +// 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 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/config_provider.dart'; +import 'package:ffigen/src/header_parser.dart' as parser; +import 'package:ffigen/src/strings.dart' as strings; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart' as yaml; + +import '../test_utils.dart'; + +late Library actual, expected; + +void main() { + group('varargs_test', () { + setUpAll(() { + logWarnings(); + actual = parser.parse( + Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'NativeLibrary' +${strings.description}: 'VarArgs Test' +${strings.output}: 'unused' + +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/varargs.h' + +${strings.functions}: + ${strings.varArgFunctions}: + myfunc: + - [int, char*, SA] + myfunc2: + - [char*, long**] + - [SA, int*, unsigned char**] + - types: [SA, int*, unsigned char**] + postfix: _custompostfix + myfunc3: + - [Struct_WithLong_Name_test*, float*] + - types: [Struct_WithLong_Name_test] + postfix: _custompostfix2 + +${strings.preamble}: | + // ignore_for_file: camel_case_types + ''') as yaml.YamlMap), + ); + }); + test('Expected Bindings', () { + matchLibraryWithExpected( + actual, 'header_parser_varargs_test_output.dart', [ + 'test', + 'header_parser_tests', + 'expected_bindings', + '_expected_varargs_bindings.dart' + ]); + }); + }); +}
diff --git a/pkgs/ffigen/test/test_utils.dart b/pkgs/ffigen/test/test_utils.dart index 50cb30e..95434ff 100644 --- a/pkgs/ffigen/test/test_utils.dart +++ b/pkgs/ffigen/test/test_utils.dart
@@ -57,13 +57,13 @@ /// This will not delete the actual debug file incase [expect] throws an error. void matchLibraryWithExpected( Library library, String pathForActual, List<String> pathToExpected, - {String Function(String)? codeNormalizer}) { + {String Function(String)? codeNormalizer, bool format = true}) { _matchFileWithExpected( library: library, pathForActual: pathForActual, pathToExpected: pathToExpected, fileWriter: ({required Library library, required File file}) => - library.generateFile(file), + library.generateFile(file, format: format), codeNormalizer: codeNormalizer, ); }
diff --git a/pkgs/ffigen/tool/libclang_config.yaml b/pkgs/ffigen/tool/libclang_config.yaml index 339c52c..6ac924f 100644 --- a/pkgs/ffigen/tool/libclang_config.yaml +++ b/pkgs/ffigen/tool/libclang_config.yaml
@@ -91,6 +91,7 @@ - clang_Cursor_getArgument - clang_getNumArgTypes - clang_getArgType + - clang_isFunctionTypeVariadic - clang_getCursorResultType - clang_getEnumConstantDeclValue - clang_equalRanges