[ffigen] Added option to generate dependency-only structs as opaque (#181)
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index df06dd5..4a849f3 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,9 @@ +# 2.2.3 +- Added new subkey `dependency-only` (options - `full (default) | opaque`) under `structs`. +When set to `opaque`, ffigen will generate empty `Opaque` structs if structs +were excluded in config (i.e added because they were a dependency) and +only passed by reference(pointer). + # 2.2.2 - Fixed generation of empty opaque structs due to forward declarations in header files.
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md index 02c9083..7c00f1a 100644 --- a/pkgs/ffigen/README.md +++ b/pkgs/ffigen/README.md
@@ -214,6 +214,21 @@ </td> </tr> <tr> + <td>structs -> dependency-only</td> + <td>If `opaque`, generates empty `Opaque` structs if structs +were not included in config (but were added since they are a dependency) and +only passed by reference(pointer).<br> + Options - full(default) | opaque </i><br> + </td> + <td> + +```yaml +structs: + dependency-only: opaque +``` + </td> + </tr> + <tr> <td>sort</td> <td>Sort the bindings according to name.<br> <b>Default: false</b>, i.e keep the order as in the source files. @@ -480,6 +495,13 @@ This happens when an excluded struct 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 +these struct dependencies as `Opaque` if they were only passed by reference(pointer). +```yaml +structs: + dependency-only: opaque +``` + ### How to expose the native pointers and typedefs? By default all native pointers and typedefs are hidden, but you can use the
diff --git a/pkgs/ffigen/lib/src/code_generator/dart_keywords.dart b/pkgs/ffigen/lib/src/code_generator/dart_keywords.dart index 2f3d4d6..8e0de3c 100644 --- a/pkgs/ffigen/lib/src/code_generator/dart_keywords.dart +++ b/pkgs/ffigen/lib/src/code_generator/dart_keywords.dart
@@ -66,5 +66,6 @@ 'show', 'dynamic', 'implements', - 'static' + 'static', + 'late', };
diff --git a/pkgs/ffigen/lib/src/code_generator/struc.dart b/pkgs/ffigen/lib/src/code_generator/struc.dart index d566821..2fc96d6 100644 --- a/pkgs/ffigen/lib/src/code_generator/struc.dart +++ b/pkgs/ffigen/lib/src/code_generator/struc.dart
@@ -44,6 +44,9 @@ bool get isOpaque => members.isEmpty; + /// Marker for checking if the dependencies are parsed. + bool parsedDependencies = false; + Struc({ String? usr, String? originalName,
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index a330e60..cf083df 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -76,6 +76,10 @@ CommentType get commentType => _commentType; late CommentType _commentType; + /// Whether structs that are dependencies should be included. + StructDependencies get structDependencies => _structDependencies; + late StructDependencies _structDependencies; + /// If tool should generate array workarounds. /// /// If false(default), structs with inline array members will have all its @@ -121,13 +125,41 @@ return configspecs; } + /// Checks if there are nested [key] in [map]. + bool _checkKeyInYaml(List<String> key, YamlMap map) { + dynamic last = map; + for (final k in key) { + if (last is YamlMap) { + if (!last.containsKey(k)) return false; + last = last[k]; + } else { + return false; + } + } + return last != null; + } + + /// Extracts value of nested [key] from [map]. + dynamic _getKeyValueFromYaml(List<String> key, YamlMap map) { + if (_checkKeyInYaml(key, map)) { + dynamic last = map; + for (final k in key) { + last = last[k]; + } + return last; + } + + return null; + } + /// Validates Yaml according to given specs. - bool _checkConfigs(YamlMap map, Map<String, Specification> specs) { + bool _checkConfigs(YamlMap map, Map<List<String>, Specification> specs) { var _result = true; for (final key in specs.keys) { final spec = specs[key]; - if (map.containsKey(key)) { - _result = _result && spec!.validator(key, map[key]); + if (_checkKeyInYaml(key, map)) { + _result = + _result && spec!.validator(key, _getKeyValueFromYaml(key, map)); } else if (spec!.requirement == Requirement.yes) { _logger.severe("Key '$key' is required."); _result = false; @@ -137,7 +169,8 @@ } // Warn about unknown keys. for (final key in map.keys) { - if (!specs.containsKey(key)) { + final specString = specs.keys.map((e) => e.join(':')).toSet(); + if (!specString.contains(key)) { _logger.warning("Unknown key '$key' found."); } } @@ -148,11 +181,11 @@ /// Extracts variables from Yaml according to given specs. /// /// Validation must be done beforehand, using [_checkConfigs]. - void _extract(YamlMap map, Map<String, Specification> specs) { + void _extract(YamlMap map, Map<List<String>, Specification> specs) { for (final key in specs.keys) { final spec = specs[key]; - if (map.containsKey(key)) { - spec!.extractedResult(spec.extractor(map[key])); + if (_checkKeyInYaml(key, map)) { + spec!.extractedResult(spec.extractor(_getKeyValueFromYaml(key, map))); } else { spec!.extractedResult(spec.defaultValue?.call()); } @@ -162,28 +195,28 @@ /// Returns map of various specifications avaialble for our tool. /// /// Key: Name, Value: [Specification] - Map<String, Specification> _getSpecs() { - return <String, Specification>{ - strings.llvmLib: Specification<String>( + Map<List<String>, Specification> _getSpecs() { + return <List<String>, Specification>{ + [strings.llvmLib]: Specification<String>( requirement: Requirement.no, validator: llvmLibValidator, extractor: llvmLibExtractor, defaultValue: () => findDylibAtDefaultLocations(), extractedResult: (dynamic result) => _libclangDylib = result as String, ), - strings.output: Specification<String>( + [strings.output]: Specification<String>( requirement: Requirement.yes, validator: outputValidator, extractor: outputExtractor, extractedResult: (dynamic result) => _output = result as String, ), - strings.headers: Specification<Headers>( + [strings.headers]: Specification<Headers>( requirement: Requirement.yes, validator: headersValidator, extractor: headersExtractor, extractedResult: (dynamic result) => _headers = result as Headers, ), - strings.compilerOpts: Specification<List<String>>( + [strings.compilerOpts]: Specification<List<String>>( requirement: Requirement.no, validator: compilerOptsValidator, extractor: compilerOptsExtractor, @@ -191,7 +224,7 @@ extractedResult: (dynamic result) => _compilerOpts = result as List<String>, ), - strings.functions: Specification<Declaration>( + [strings.functions]: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator, extractor: declarationConfigExtractor, @@ -200,7 +233,7 @@ _functionDecl = result as Declaration; }, ), - strings.structs: Specification<Declaration>( + [strings.structs]: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator, extractor: declarationConfigExtractor, @@ -209,7 +242,7 @@ _structDecl = result as Declaration; }, ), - strings.enums: Specification<Declaration>( + [strings.enums]: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator, extractor: declarationConfigExtractor, @@ -218,7 +251,7 @@ _enumClassDecl = result as Declaration; }, ), - strings.unnamedEnums: Specification<Declaration>( + [strings.unnamedEnums]: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator, extractor: declarationConfigExtractor, @@ -226,7 +259,7 @@ extractedResult: (dynamic result) => _unnamedEnumConstants = result as Declaration, ), - strings.globals: Specification<Declaration>( + [strings.globals]: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator, extractor: declarationConfigExtractor, @@ -235,7 +268,7 @@ _globals = result as Declaration; }, ), - strings.macros: Specification<Declaration>( + [strings.macros]: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator, extractor: declarationConfigExtractor, @@ -244,7 +277,7 @@ _macroDecl = result as Declaration; }, ), - strings.sizemap: Specification<Map<int, SupportedNativeType>>( + [strings.sizemap]: Specification<Map<int, SupportedNativeType>>( validator: sizemapValidator, extractor: sizemapExtractor, defaultValue: () => <int, SupportedNativeType>{}, @@ -257,21 +290,21 @@ } }, ), - strings.typedefmap: Specification<Map<String, SupportedNativeType>>( + [strings.typedefmap]: Specification<Map<String, SupportedNativeType>>( validator: typedefmapValidator, extractor: typedefmapExtractor, defaultValue: () => <String, SupportedNativeType>{}, extractedResult: (dynamic result) => _typedefNativeTypeMappings = result as Map<String, SupportedNativeType>, ), - strings.sort: Specification<bool>( + [strings.sort]: Specification<bool>( requirement: Requirement.no, validator: booleanValidator, extractor: booleanExtractor, defaultValue: () => false, extractedResult: (dynamic result) => _sort = result as bool, ), - strings.useSupportedTypedefs: Specification<bool>( + [strings.useSupportedTypedefs]: Specification<bool>( requirement: Requirement.no, validator: booleanValidator, extractor: booleanExtractor, @@ -279,7 +312,7 @@ extractedResult: (dynamic result) => _useSupportedTypedefs = result as bool, ), - strings.comments: Specification<CommentType>( + [strings.comments]: Specification<CommentType>( requirement: Requirement.no, validator: commentValidator, extractor: commentExtractor, @@ -287,28 +320,37 @@ extractedResult: (dynamic result) => _commentType = result as CommentType, ), - strings.arrayWorkaround: Specification<bool>( + [strings.structs, strings.structDependencies]: + Specification<StructDependencies>( + requirement: Requirement.no, + validator: structDependenciesValidator, + extractor: structDependenciesExtractor, + defaultValue: () => StructDependencies.full, + extractedResult: (dynamic result) => + _structDependencies = result as StructDependencies, + ), + [strings.arrayWorkaround]: Specification<bool>( requirement: Requirement.no, validator: booleanValidator, extractor: booleanExtractor, defaultValue: () => false, extractedResult: (dynamic result) => _arrayWorkaround = result as bool, ), - strings.dartBool: Specification<bool>( + [strings.dartBool]: Specification<bool>( requirement: Requirement.no, validator: booleanValidator, extractor: booleanExtractor, defaultValue: () => true, extractedResult: (dynamic result) => _dartBool = result as bool, ), - strings.name: Specification<String>( + [strings.name]: Specification<String>( requirement: Requirement.prefer, validator: dartClassNameValidator, extractor: stringExtractor, defaultValue: () => 'NativeLibrary', extractedResult: (dynamic result) => _wrapperName = result as String, ), - strings.description: Specification<String?>( + [strings.description]: Specification<String?>( requirement: Requirement.prefer, validator: nonEmptyStringValidator, extractor: stringExtractor, @@ -316,13 +358,13 @@ extractedResult: (dynamic result) => _wrapperDocComment = result as String?, ), - strings.preamble: Specification<String?>( + [strings.preamble]: Specification<String?>( requirement: Requirement.no, validator: nonEmptyStringValidator, extractor: stringExtractor, extractedResult: (dynamic result) => _preamble = result as String?, ), - strings.useDartHandle: Specification<bool>( + [strings.useDartHandle]: Specification<bool>( requirement: Requirement.no, validator: booleanValidator, extractor: booleanExtractor,
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart index e412d96..35d24f0 100644 --- a/pkgs/ffigen/lib/src/config_provider/config_types.dart +++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -26,11 +26,13 @@ enum CommentStyle { doxygen, any } enum CommentLength { none, brief, full } +enum StructDependencies { full, opaque } + /// Represents a single specification in configurations. /// /// [E] is the return type of the extractedResult. class Specification<E> { - final bool Function(String name, dynamic value) validator; + final bool Function(List<String> name, dynamic value) validator; final E Function(dynamic map) extractor; final E Function()? defaultValue;
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart index eeab2ae..0351d8c 100644 --- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart +++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -40,8 +40,8 @@ bool booleanExtractor(dynamic value) => value as bool; -bool booleanValidator(String name, dynamic value) => - checkType<bool>([name], value); +bool booleanValidator(List<String> name, dynamic value) => + checkType<bool>(name, value); Map<int, SupportedNativeType> sizemapExtractor(dynamic yamlConfig) { final resultMap = <int, SupportedNativeType>{}; @@ -59,8 +59,8 @@ return resultMap; } -bool sizemapValidator(String name, dynamic yamlConfig) { - if (!checkType<YamlMap>([name], yamlConfig)) { +bool sizemapValidator(List<String> name, dynamic yamlConfig) { + if (!checkType<YamlMap>(name, yamlConfig)) { return false; } for (final key in (yamlConfig as YamlMap).keys) { @@ -89,8 +89,8 @@ return resultMap; } -bool typedefmapValidator(String name, dynamic yamlConfig) { - if (!checkType<YamlMap>([name], yamlConfig)) { +bool typedefmapValidator(List<String> name, dynamic yamlConfig) { + if (!checkType<YamlMap>(name, yamlConfig)) { return false; } for (final value in (yamlConfig as YamlMap).values) { @@ -106,8 +106,8 @@ List<String> compilerOptsExtractor(dynamic value) => (value as String).split(' '); -bool compilerOptsValidator(String name, dynamic value) => - checkType<String>([name], value); +bool compilerOptsValidator(List<String> name, dynamic value) => + checkType<String>(name, value); Headers headersExtractor(dynamic yamlConfig) { final entryPoints = <String>[]; @@ -147,8 +147,8 @@ ); } -bool headersValidator(String name, dynamic value) { - if (!checkType<YamlMap>([name], value)) { +bool headersValidator(List<String> name, dynamic value) { + if (!checkType<YamlMap>(name, value)) { return false; } if (!(value as YamlMap).containsKey(strings.entryPoints)) { @@ -157,7 +157,7 @@ } else { for (final key in value.keys) { if (key == strings.entryPoints || key == strings.includeDirectives) { - if (!checkType<YamlList>([name, key as String], value[key])) { + if (!checkType<YamlList>([...name, key as String], value[key])) { return false; } } else { @@ -171,8 +171,8 @@ String libclangDylibExtractor(dynamic value) => getDylibPath(value as String); -bool libclangDylibValidator(String name, dynamic value) { - if (!checkType<String>([name], value)) { +bool libclangDylibValidator(List<String> name, dynamic value) { + if (!checkType<String>(name, value)) { return false; } else { final dylibPath = getDylibPath(value as String); @@ -248,8 +248,8 @@ } } -bool llvmLibValidator(String name, dynamic value) { - if (!checkType<String>([name], value) || +bool llvmLibValidator(List<String> name, dynamic value) { + if (!checkType<String>(name, value) || !Directory(value as String).existsSync()) { _logger.severe('Expected $name to be a valid folder Path.'); return false; @@ -259,8 +259,8 @@ String outputExtractor(dynamic value) => _replaceSeparators(value as String); -bool outputValidator(String name, dynamic value) => - checkType<String>([name], value); +bool outputValidator(List<String> name, dynamic value) => + checkType<String>(name, value); /// Returns true if [str] is not a full name. /// @@ -380,37 +380,37 @@ ); } -bool declarationConfigValidator(String name, dynamic value) { +bool declarationConfigValidator(List<String> name, dynamic value) { var _result = true; if (value is YamlMap) { for (final key in value.keys) { if (key == strings.include || key == strings.exclude) { - if (!checkType<YamlList>([name, key as String], value[key])) { + if (!checkType<YamlList>([...name, key as String], value[key])) { _result = false; } } else if (key == strings.rename) { - if (!checkType<YamlMap>([name, key as String], value[key])) { + if (!checkType<YamlMap>([...name, key as String], value[key])) { _result = false; } else { for (final subkey in value[key].keys) { if (!checkType<String>( - [name, key, subkey as String], value[key][subkey])) { + [...name, key, subkey as String], value[key][subkey])) { _result = false; } } } } else if (key == strings.memberRename) { - if (!checkType<YamlMap>([name, key as String], value[key])) { + if (!checkType<YamlMap>([...name, key as String], value[key])) { _result = false; } else { for (final declNameKey in value[key].keys) { - if (!checkType<YamlMap>( - [name, key, declNameKey as String], value[key][declNameKey])) { + if (!checkType<YamlMap>([...name, key, declNameKey as String], + value[key][declNameKey])) { _result = false; } else { for (final memberNameKey in value[key][declNameKey].keys) { if (!checkType<String>([ - name, + ...name, key, declNameKey, memberNameKey as String, @@ -422,13 +422,13 @@ } } } else if (key == strings.symbolAddress) { - if (!checkType<YamlMap>([name, key as String], value[key])) { + if (!checkType<YamlMap>([...name, key as String], value[key])) { _result = false; } else { for (final subkey in value[key].keys) { if (subkey == strings.include || subkey == strings.exclude) { if (!checkType<YamlList>( - [name, key, subkey as String], value[key][subkey])) { + [...name, key, subkey as String], value[key][subkey])) { _result = false; } } else { @@ -437,9 +437,6 @@ } } } - } else { - _logger.severe("Unknown key '$key' in '$name'."); - _result = false; } } } else { @@ -467,7 +464,7 @@ String stringExtractor(dynamic value) => value as String; -bool nonEmptyStringValidator(String name, dynamic value) { +bool nonEmptyStringValidator(List<String> name, dynamic value) { if (value is String && value.isNotEmpty) { return true; } else { @@ -476,7 +473,7 @@ } } -bool dartClassNameValidator(String name, dynamic value) { +bool dartClassNameValidator(List<String> name, dynamic value) { if (value is String && quiver.matchesFull(RegExp('[a-zA-Z]+[_a-zA-Z0-9]*'), value)) { return true; @@ -516,7 +513,7 @@ return ct; } -bool commentValidator(String name, dynamic value) { +bool commentValidator(List<String> name, dynamic value) { if (value is bool) { return true; } else if (value is YamlMap) { @@ -547,3 +544,23 @@ return false; } } + +StructDependencies structDependenciesExtractor(dynamic value) { + var result = StructDependencies.full; + if (value == strings.opaqueStructDependencies) { + result = StructDependencies.opaque; + } + return result; +} + +bool structDependenciesValidator(List<String> name, dynamic value) { + var result = true; + if (value is! String || + !(value == strings.fullStructDependencies || + value == strings.opaqueStructDependencies)) { + _logger.severe( + "'$name' must be one of the following - {${strings.fullStructDependencies}, ${strings.opaqueStructDependencies}}"); + result = false; + } + return result; +}
diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart index 4a6964b..d9c4023 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/structdecl_parser.dart
@@ -5,6 +5,7 @@ import 'dart:ffi'; import 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/config_provider/config_types.dart'; import 'package:logging/logging.dart'; import '../clang_bindings/clang_bindings.dart' as clang_types; @@ -47,6 +48,13 @@ /// Option to ignore struct filter (Useful in case of extracting structs /// when they are passed/returned by an included function.) bool ignoreFilter = false, + + /// To track if the struct was used by reference(i.e struct*). (Used to only + /// generate these as opaque if `struct-dependencies` was set to opaque). + bool pointerReference = false, + + /// If the struct name should be updated, if it was already seen. + bool updateName = true, }) { _stack.push(_ParsedStruc()); @@ -83,15 +91,30 @@ // Adding to seen here to stop recursion if a struct has itself as a // member, members are updated later. bindingsIndex.addStructToSeen(structUsr, _stack.top.struc!); - _setStructMembers(cursor); } if (bindingsIndex.isSeenStruct(structUsr)) { - _stack.top.struc = bindingsIndex.getSeenStruct(structUsr)!; + _stack.top.struc = bindingsIndex.getSeenStruct(structUsr); - // If struct is seen, update it's name. - _stack.top.struc!.name = config.structDecl.renameUsingConfig(structName); + final skipDependencies = _stack.top.struc!.parsedDependencies || + (config.structDependencies == StructDependencies.opaque && + pointerReference && + ignoreFilter); + + if (!skipDependencies) { + // Prevents infinite recursion if struct has a pointer to itself. + _stack.top.struc!.parsedDependencies = true; + _setStructMembers(cursor); + } else if (!_stack.top.struc!.parsedDependencies) { + _logger.fine('Skipped dependencies.'); + } + + if (updateName) { + // If struct is seen, update it's name. + _stack.top.struc!.name = config.structDecl.renameUsingConfig(structName); + } } + return _stack.pop().struc; }
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 46d6a41..cd0445e 100644 --- a/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart +++ b/pkgs/ffigen/lib/src/header_parser/type_extractor/extractor.dart
@@ -18,14 +18,21 @@ const _padding = ' '; /// Converts cxtype to a typestring code_generator can accept. -Type getCodeGenType(clang_types.CXType cxtype, {String? parentName}) { +Type getCodeGenType( + clang_types.CXType cxtype, { + String? parentName, + + /// Passed on if a value was marked as a pointer before this one. + bool pointerReference = false, +}) { _logger.fine('${_padding}getCodeGenType ${cxtype.completeStringRepr()}'); final kind = cxtype.kind; switch (kind) { case clang_types.CXTypeKind.CXType_Pointer: final pt = clang.clang_getPointeeType(cxtype); - final s = getCodeGenType(pt, parentName: parentName); + final s = + getCodeGenType(pt, parentName: parentName, pointerReference: true); // Replace Pointer<_Dart_Handle> with Handle. if (config.useDartHandle && @@ -53,14 +60,17 @@ final ct = clang.clang_getTypedefDeclUnderlyingType( clang.clang_getTypeDeclaration(cxtype)); - final s = getCodeGenType(ct, parentName: parentName ?? spelling); + final s = getCodeGenType(ct, + parentName: parentName ?? spelling, + pointerReference: pointerReference); return s; case clang_types.CXTypeKind.CXType_Elaborated: final et = clang.clang_Type_getNamedType(cxtype); - final s = getCodeGenType(et, parentName: parentName); + final s = getCodeGenType(et, + parentName: parentName, pointerReference: pointerReference); return s; case clang_types.CXTypeKind.CXType_Record: - return _extractfromRecord(cxtype, parentName); + return _extractfromRecord(cxtype, parentName, pointerReference); case clang_types.CXTypeKind.CXType_Enum: return Type.nativeType( enumNativeType, @@ -98,7 +108,8 @@ } } -Type _extractfromRecord(clang_types.CXType cxtype, String? parentName) { +Type _extractfromRecord( + clang_types.CXType cxtype, String? parentName, bool pointerReference) { Type type; final cursor = clang.clang_getTypeDeclaration(cxtype); @@ -115,12 +126,25 @@ // TODO(23): Check if we should auto add struct. if (bindingsIndex.isSeenStruct(structUsr)) { type = Type.struct(bindingsIndex.getSeenStruct(structUsr)!); + + // This will parse the dependencies if needed. + parseStructDeclaration( + cursor, + name: structName, + ignoreFilter: true, + pointerReference: pointerReference, + updateName: false, + ); } else { - final struc = parseStructDeclaration(cursor, - name: structName, ignoreFilter: true); + final struc = parseStructDeclaration( + cursor, + name: structName, + ignoreFilter: true, + pointerReference: pointerReference, + ); type = Type.struct(struc!); - // Add to bindings if it's not Dart_Handle. + // Add to bindings if it's not Dart_Handle and is unseen. if (!(config.useDartHandle && structUsr == strings.dartHandleUsr)) { addToBindings(struc); }
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index 1929051..885427d 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart
@@ -49,6 +49,11 @@ const memberRename = 'member-rename'; const symbolAddress = 'symbol-address'; +const structDependencies = 'dependency-only'; +// Values for `structDependencies`. +const fullStructDependencies = 'full'; +const opaqueStructDependencies = 'opaque'; + const sizemap = 'size-map'; const typedefmap = 'typedef-map';
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index 1b6b447..21c65c3 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: 2.2.2 +version: 2.2.3 homepage: https://github.com/dart-lang/ffigen description: Generator for FFI bindings, using LibClang to parse C header files.
diff --git a/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart new file mode 100644 index 0000000..0c3498f --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart
@@ -0,0 +1,57 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +import 'dart:ffi' as ffi; + +/// Opaque Dependencies 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; + + ffi.Pointer<B> func( + ffi.Pointer<A> a, + ) { + return _func( + a, + ); + } + + late final _func_ptr = _lookup<ffi.NativeFunction<_c_func>>('func'); + late final _dart_func _func = _func_ptr.asFunction<_dart_func>(); +} + +class B extends ffi.Opaque {} + +class A extends ffi.Opaque {} + +class C extends ffi.Opaque {} + +class D extends ffi.Struct { + @ffi.Int32() + external int a; +} + +class E extends ffi.Struct { + external ffi.Pointer<C> c; + + external D d; +} + +typedef _c_func = ffi.Pointer<B> Function( + ffi.Pointer<A> a, +); + +typedef _dart_func = ffi.Pointer<B> Function( + ffi.Pointer<A> a, +);
diff --git a/pkgs/ffigen/test/header_parser_tests/opaque_dependencies.h b/pkgs/ffigen/test/header_parser_tests/opaque_dependencies.h new file mode 100644 index 0000000..fa24993 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/opaque_dependencies.h
@@ -0,0 +1,36 @@ +// Copyright (c) 2021, 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. + +// Opaque. +struct A +{ + int a; +}; + +// Opaque. +struct B +{ + int a; +}; + +struct B *func(struct A *a); + +// Opaque. +struct C +{ + int a; +}; + +// Full (excluded, but used by value). +struct D +{ + int a; +}; + +// Full (included) +struct E +{ + struct C *c; + struct D d; +};
diff --git a/pkgs/ffigen/test/header_parser_tests/opaque_dependencies_test.dart b/pkgs/ffigen/test/header_parser_tests/opaque_dependencies_test.dart new file mode 100644 index 0000000..1107767 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/opaque_dependencies_test.dart
@@ -0,0 +1,49 @@ +// Copyright (c) 2021, 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/header_parser.dart' as parser; +import 'package:ffigen/src/config_provider.dart'; +import 'package:logging/logging.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart' as yaml; +import 'package:ffigen/src/strings.dart' as strings; + +import '../test_utils.dart'; + +late Library actual; +void main() { + group('opaque_dependencies_test', () { + setUpAll(() { + logWarnings(Level.SEVERE); + actual = parser.parse( + Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'NativeLibrary' +${strings.description}: 'Opaque Dependencies Test' +${strings.output}: 'unused' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/opaque_dependencies.h' +${strings.structs}: + ${strings.include}: + - 'E' + ${strings.structDependencies}: ${strings.opaqueStructDependencies} + ''') as yaml.YamlMap), + ); + }); + + test('Expected bindings', () { + matchLibraryWithExpected(actual, [ + 'test', + 'debug_generated', + 'opaque_dependencies_test_output.dart' + ], [ + 'test', + 'header_parser_tests', + 'expected_bindings', + '_expected_opaque_dependencies_bindings.dart' + ]); + }); + }); +}