Added option to generate dependency-only structs as opaque (#181)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df06dd5..4a849f3 100644
--- a/CHANGELOG.md
+++ b/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/README.md b/README.md
index 02c9083..7c00f1a 100644
--- a/README.md
+++ b/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/lib/src/code_generator/dart_keywords.dart b/lib/src/code_generator/dart_keywords.dart
index 2f3d4d6..8e0de3c 100644
--- a/lib/src/code_generator/dart_keywords.dart
+++ b/lib/src/code_generator/dart_keywords.dart
@@ -66,5 +66,6 @@
'show',
'dynamic',
'implements',
- 'static'
+ 'static',
+ 'late',
};
diff --git a/lib/src/code_generator/struc.dart b/lib/src/code_generator/struc.dart
index d566821..2fc96d6 100644
--- a/lib/src/code_generator/struc.dart
+++ b/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/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index a330e60..cf083df 100644
--- a/lib/src/config_provider/config.dart
+++ b/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/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index e412d96..35d24f0 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/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/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index eeab2ae..0351d8c 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/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/lib/src/header_parser/sub_parsers/structdecl_parser.dart b/lib/src/header_parser/sub_parsers/structdecl_parser.dart
index 4a6964b..d9c4023 100644
--- a/lib/src/header_parser/sub_parsers/structdecl_parser.dart
+++ b/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/lib/src/header_parser/type_extractor/extractor.dart b/lib/src/header_parser/type_extractor/extractor.dart
index 46d6a41..cd0445e 100644
--- a/lib/src/header_parser/type_extractor/extractor.dart
+++ b/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/lib/src/strings.dart b/lib/src/strings.dart
index 1929051..885427d 100644
--- a/lib/src/strings.dart
+++ b/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/pubspec.yaml b/pubspec.yaml
index 1b6b447..21c65c3 100644
--- a/pubspec.yaml
+++ b/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/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart b/test/header_parser_tests/expected_bindings/_expected_opaque_dependencies_bindings.dart
new file mode 100644
index 0000000..0c3498f
--- /dev/null
+++ b/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/test/header_parser_tests/opaque_dependencies.h b/test/header_parser_tests/opaque_dependencies.h
new file mode 100644
index 0000000..fa24993
--- /dev/null
+++ b/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/test/header_parser_tests/opaque_dependencies_test.dart b/test/header_parser_tests/opaque_dependencies_test.dart
new file mode 100644
index 0000000..1107767
--- /dev/null
+++ b/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'
+ ]);
+ });
+ });
+}