[ffigen] Added support for Global variables (#139) (#165)
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index a7b8617..1e49bdc 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,6 @@ +# 2.0.0-dev.4 +- Add support for parsing and generating globals. + # 2.0.0-dev.3 - Removed the usage of `--no-sound-null-safety` flag.
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md index df041e7..f2c194e 100644 --- a/pkgs/ffigen/README.md +++ b/pkgs/ffigen/README.md
@@ -144,7 +144,7 @@ </td> </tr> <tr> - <td>functions<br>structs<br>enums<br>unnamed-enums<br>macros</td> + <td>functions<br>structs<br>enums<br>unnamed-enums<br>macros<br>globals</td> <td>Filters for declarations.<br><b>Default: all are included</b></td> <td> @@ -170,6 +170,12 @@ 'CXTypeKind': # Full names have higher priority. # $1 keeps only the 1st group i.e '(.*)'. 'CXType(.*)': '$1' +globals: + exclude: + - aGlobal + rename: + # Removes '_' from beginning of a name. + - '_(.*)': '$1' ``` </td> </tr>
diff --git a/pkgs/ffigen/lib/src/code_generator/global.dart b/pkgs/ffigen/lib/src/code_generator/global.dart index b52fa70..d419e92 100644 --- a/pkgs/ffigen/lib/src/code_generator/global.dart +++ b/pkgs/ffigen/lib/src/code_generator/global.dart
@@ -2,6 +2,8 @@ // 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/typedef.dart'; + import 'binding.dart'; import 'binding_string.dart'; import 'type.dart'; @@ -34,6 +36,21 @@ dartDoc: dartDoc, ); + List<Typedef>? _typedefDependencies; + @override + List<Typedef> getTypedefDependencies(Writer w) { + if (_typedefDependencies == null) { + _typedefDependencies = <Typedef>[]; + + // Add typedef's required by the variable's type. + final valueType = type.getBaseType(); + if (valueType.broadType == BroadType.NativeFunction) { + _typedefDependencies!.addAll(valueType.nativeFunc!.getDependencies()); + } + } + return _typedefDependencies!; + } + @override BindingString toBindingString(Writer w) { final s = StringBuffer(); @@ -41,13 +58,18 @@ if (dartDoc != null) { s.write(makeDartDoc(dartDoc!)); } + final pointerName = w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName'); + final dartType = type.getDartType(w); + final cType = type.getCType(w); + final refOrValue = type.broadType == BroadType.Struct ? 'ref' : 'value'; - final holderVarName = - w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName'); s.write( - '${w.ffiLibraryPrefix}.Pointer<${type.getCType(w)}> $holderVarName;\n'); - s.write( - "${type.getDartType(w)} get $globalVarName => ($holderVarName ??= ${w.dylibIdentifier}.lookup<${type.getCType(w)}>('$originalName')).value;\n\n"); + "late final ${w.ffiLibraryPrefix}.Pointer<$dartType> $pointerName = ${w.dylibIdentifier}.lookup<$cType>('$originalName');\n\n"); + s.write('$dartType get $globalVarName => $pointerName.$refOrValue;\n\n'); + if (type.broadType != BroadType.Struct) { + s.write( + 'set $globalVarName($dartType value) => $pointerName.value = value;\n\n'); + } return BindingString(type: BindingStringType.global, string: s.toString()); }
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index 96ca643..6c11b34 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -51,6 +51,10 @@ Declaration get unnamedEnumConstants => _unnamedEnumConstants; late Declaration _unnamedEnumConstants; + /// Declaration config for Globals. + Declaration get globals => _globals; + late Declaration _globals; + /// Declaration config for Macro constants. Declaration get macroDecl => _macroDecl; late Declaration _macroDecl; @@ -222,6 +226,15 @@ extractedResult: (dynamic result) => _unnamedEnumConstants = result as Declaration, ), + strings.globals: Specification<Declaration>( + requirement: Requirement.no, + validator: declarationConfigValidator, + extractor: declarationConfigExtractor, + defaultValue: () => Declaration(), + extractedResult: (dynamic result) { + _globals = result as Declaration; + }, + ), strings.macros: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator,
diff --git a/pkgs/ffigen/lib/src/header_parser/includer.dart b/pkgs/ffigen/lib/src/header_parser/includer.dart index baa5b59..bd82476 100644 --- a/pkgs/ffigen/lib/src/header_parser/includer.dart +++ b/pkgs/ffigen/lib/src/header_parser/includer.dart
@@ -47,6 +47,16 @@ } } +bool shouldIncludeGlobalVar(String usr, String name) { + if (bindingsIndex.isSeenGlobalVar(usr) || name == '') { + return false; + } else if (config.globals.shouldInclude(name)) { + return true; + } else { + return false; + } +} + bool shouldIncludeMacro(String usr, String name) { if (bindingsIndex.isSeenMacro(usr) || name == '') { return false;
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 new file mode 100644 index 0000000..d0248ec --- /dev/null +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart
@@ -0,0 +1,47 @@ +// 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/data.dart'; +import 'package:ffigen/src/header_parser/includer.dart'; +import 'package:logging/logging.dart'; + +import '../clang_bindings/clang_bindings.dart' as clang_types; +import '../data.dart'; +import '../utils.dart'; + +final _logger = Logger('ffigen.header_parser.var_parser'); + +/// Parses a global variable +Global? parseVarDeclaration(clang_types.CXCursor cursor) { + final name = cursor.spelling(); + final usr = cursor.usr(); + if (bindingsIndex.isSeenGlobalVar(usr)) { + return bindingsIndex.getSeenGlobalVar(usr); + } + if (!shouldIncludeGlobalVar(usr, name)) { + return null; + } + + _logger.fine('++++ Adding Global: ${cursor.completeStringRepr()}'); + + final type = cursor.type().toCodeGenType(); + if (type.getBaseType().broadType == BroadType.Unimplemented || + type.getBaseType().isIncompleteStruct) { + _logger.fine( + '---- Removed Global, reason: unsupported type: ${cursor.completeStringRepr()}'); + _logger.warning("Skipped global variable '$name', type not supported."); + return null; + } + + final global = Global( + originalName: name, + name: config.globals.renameUsingConfig(name), + usr: usr, + type: type, + dartDoc: getCursorDocComment(cursor), + ); + bindingsIndex.addGlobalVarToSeen(usr, global); + return global; +}
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 7c08410..69f7f13 100644 --- a/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/translation_unit_parser.dart
@@ -6,6 +6,7 @@ import 'package:ffigen/src/code_generator.dart'; import 'package:ffigen/src/header_parser/sub_parsers/macro_parser.dart'; +import 'package:ffigen/src/header_parser/sub_parsers/var_parser.dart'; import 'package:logging/logging.dart'; import 'clang_bindings/clang_bindings.dart' as clang_types; @@ -58,6 +59,9 @@ case clang_types.CXCursorKind.CXCursor_MacroDefinition: saveMacroDefinition(cursor); break; + case clang_types.CXCursorKind.CXCursor_VarDecl: + addToBindings(parseVarDeclaration(cursor)); + break; default: _logger.finer('rootCursorVisitor: CursorKind not implemented'); }
diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart index f0a4127..28ca657 100644 --- a/pkgs/ffigen/lib/src/header_parser/utils.dart +++ b/pkgs/ffigen/lib/src/header_parser/utils.dart
@@ -316,6 +316,7 @@ final Map<String, EnumClass> _enumClass = {}; final Map<String, Constant> _unnamedEnumConstants = {}; final Map<String, String> _macros = {}; + final Map<String, Global> _globals = {}; // Stores only named typedefC used in NativeFunc. final Map<String, Typedef> _functionTypedefs = {}; @@ -367,6 +368,18 @@ return _unnamedEnumConstants[usr]; } + bool isSeenGlobalVar(String usr) { + return _globals.containsKey(usr); + } + + void addGlobalVarToSeen(String usr, Global global) { + _globals[usr] = global; + } + + Global? getSeenGlobalVar(String usr) { + return _globals[usr]; + } + bool isSeenMacro(String usr) { return _macros.containsKey(usr); }
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index 8f0ac93..99e0c68 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart
@@ -39,6 +39,7 @@ const structs = 'structs'; const enums = 'enums'; const unnamedEnums = 'unnamed-enums'; +const globals = 'globals'; const macros = 'macros'; // Sub-fields of Declarations.
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index adf2993..87663a2 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.0.0-dev.3 +version: 2.0.0-dev.4 homepage: https://github.com/dart-lang/ffigen description: Experimental generator for FFI bindings, using LibClang to parse C header files.
diff --git a/pkgs/ffigen/test/code_generator_test.dart b/pkgs/ffigen/test/code_generator_test.dart index ea6393b..aae8b0f 100644 --- a/pkgs/ffigen/test/code_generator_test.dart +++ b/pkgs/ffigen/test/code_generator_test.dart
@@ -436,14 +436,23 @@ /// The symbols are looked up in [dynamicLibrary]. Bindings(ffi.DynamicLibrary dynamicLibrary): _dylib = dynamicLibrary; -ffi.Pointer<ffi.Int32> _test1; -int get test1 => (_test1 ??= _dylib.lookup<ffi.Int32>('test1')).value; +late final ffi.Pointer<int> _test1 = _dylib.lookup<ffi.Int32>('test1'); -ffi.Pointer<ffi.Pointer<ffi.Float>> _test2; -ffi.Pointer<ffi.Float> get test2 => (_test2 ??= _dylib.lookup<ffi.Pointer<ffi.Float>>('test2')).value; +int get test1 => _test1.value; -ffi.Pointer<ffi.Pointer<Some>> _test5; -ffi.Pointer<Some> get test5 => (_test5 ??= _dylib.lookup<ffi.Pointer<Some>>('test5')).value; +set test1(int value) => _test1.value = value; + +late final ffi.Pointer<ffi.Pointer<ffi.Float>> _test2 = _dylib.lookup<ffi.Pointer<ffi.Float>>('test2'); + +ffi.Pointer<ffi.Float> get test2 => _test2.value; + +set test2(ffi.Pointer<ffi.Float> value) => _test2.value = value; + +late final ffi.Pointer<ffi.Pointer<Some>> _test5 = _dylib.lookup<ffi.Pointer<Some>>('test5'); + +ffi.Pointer<Some> get test5 => _test5.value; + +set test5(ffi.Pointer<Some> value) => _test5.value = value; }
diff --git a/pkgs/ffigen/test/header_parser_tests/globals.h b/pkgs/ffigen/test/header_parser_tests/globals.h new file mode 100644 index 0000000..f0f464f --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/globals.h
@@ -0,0 +1,15 @@ +// 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. + +#include <stdint.h> +#include <stdbool.h> + +bool coolGlobal; +int32_t myInt; +int32_t *aGlobalPointer; +long double longDouble; +long double *pointerToLongDouble; + +// This should be ignored +int GlobalIgnore;
diff --git a/pkgs/ffigen/test/header_parser_tests/globals_test.dart b/pkgs/ffigen/test/header_parser_tests/globals_test.dart new file mode 100644 index 0000000..7184792 --- /dev/null +++ b/pkgs/ffigen/test/header_parser_tests/globals_test.dart
@@ -0,0 +1,75 @@ +// 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: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, expected; + +void main() { + group('globals_test', () { + setUpAll(() { + logWarnings(); + expected = expectedLibrary(); + actual = parser.parse( + Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'NativeLibrary' +${strings.description}: 'Globals Test' +${strings.output}: 'unused' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/globals.h' + ${strings.includeDirectives}: + - '**globals.h' +${strings.globals}: + ${strings.exclude}: + - GlobalIgnore +# Needed for stdbool.h in MacOS +${strings.compilerOpts}: '-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Headers/ -Wno-nullability-completeness' + ''') as yaml.YamlMap), + ); + }); + + test('Total bindings count', () { + expect(actual.bindings.length, expected.bindings.length); + }); + + test('Parse global Values', () { + expect(actual.getBindingAsString('coolGlobal'), + expected.getBindingAsString('coolGlobal')); + expect(actual.getBindingAsString('myInt'), + expected.getBindingAsString('myInt')); + expect(actual.getBindingAsString('aGlobalPointer'), + expected.getBindingAsString('aGlobalPointer')); + }); + + test('Ignore global values', () { + expect(() => actual.getBindingAsString('GlobalIgnore'), + throwsA(TypeMatcher<NotFoundException>())); + expect(() => actual.getBindingAsString('longDouble'), + throwsA(TypeMatcher<NotFoundException>())); + expect(() => actual.getBindingAsString('pointerToLongDouble'), + throwsA(TypeMatcher<NotFoundException>())); + }); + }); +} + +Library expectedLibrary() { + return Library( + name: 'Bindings', + bindings: [ + Global(type: Type.boolean(), name: 'coolGlobal'), + Global(type: Type.nativeType(SupportedNativeType.Int32), name: 'myInt'), + Global( + type: Type.pointer(Type.nativeType(SupportedNativeType.Int32)), + name: 'aGlobalPointer'), + ], + ); +}
diff --git a/pkgs/ffigen/test/large_integration_tests/_expected_sqlite_bindings.dart b/pkgs/ffigen/test/large_integration_tests/_expected_sqlite_bindings.dart index 7ea5be8..6abdd48 100644 --- a/pkgs/ffigen/test/large_integration_tests/_expected_sqlite_bindings.dart +++ b/pkgs/ffigen/test/large_integration_tests/_expected_sqlite_bindings.dart
@@ -11,6 +11,44 @@ /// The symbols are looked up in [dynamicLibrary]. SQLite(ffi.DynamicLibrary dynamicLibrary) : _dylib = dynamicLibrary; + /// CAPI3REF: Run-Time Library Version Numbers + /// KEYWORDS: sqlite3_version sqlite3_sourceid + /// + /// These interfaces provide the same information as the [SQLITE_VERSION], + /// [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros + /// but are associated with the library instead of the header file. ^(Cautious + /// programmers might include assert() statements in their application to + /// verify that values returned by these interfaces match the macros in + /// the header, and thus ensure that the application is + /// compiled with matching library and header files. + /// + /// <blockquote><pre> + /// assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER ); + /// assert( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 ); + /// assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 ); + /// </pre></blockquote>)^ + /// + /// ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] + /// macro. ^The sqlite3_libversion() function returns a pointer to the + /// to the sqlite3_version[] string constant. The sqlite3_libversion() + /// function is provided for use in DLLs since DLL users usually do not have + /// direct access to string constants within the DLL. ^The + /// sqlite3_libversion_number() function returns an integer equal to + /// [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns + /// a pointer to a string constant whose value is the same as the + /// [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built + /// using an edited copy of [the amalgamation], then the last four characters + /// of the hash might be different from [SQLITE_SOURCE_ID].)^ + /// + /// See also: [sqlite_version()] and [sqlite_source_id()]. + late final ffi.Pointer<ffi.Pointer<ffi.Int8>> _sqlite3_version = + _dylib.lookup<ffi.Pointer<ffi.Int8>>('sqlite3_version'); + + ffi.Pointer<ffi.Int8> get sqlite3_version => _sqlite3_version.value; + + set sqlite3_version(ffi.Pointer<ffi.Int8> value) => + _sqlite3_version.value = value; + ffi.Pointer<ffi.Int8> sqlite3_libversion() { return (_sqlite3_libversion ??= _dylib.lookupFunction<_c_sqlite3_libversion, _dart_sqlite3_libversion>( @@ -5458,6 +5496,111 @@ _dart_sqlite3_sleep? _sqlite3_sleep; + /// CAPI3REF: Name Of The Folder Holding Temporary Files + /// + /// ^(If this global variable is made to point to a string which is + /// the name of a folder (a.k.a. directory), then all temporary files + /// created by SQLite when using a built-in [sqlite3_vfs | VFS] + /// will be placed in that directory.)^ ^If this variable + /// is a NULL pointer, then SQLite performs a search for an appropriate + /// temporary file directory. + /// + /// Applications are strongly discouraged from using this global variable. + /// It is required to set a temporary folder on Windows Runtime (WinRT). + /// But for all other platforms, it is highly recommended that applications + /// neither read nor write this variable. This global variable is a relic + /// that exists for backwards compatibility of legacy applications and should + /// be avoided in new projects. + /// + /// It is not safe to read or modify this variable in more than one + /// thread at a time. It is not safe to read or modify this variable + /// if a [database connection] is being used at the same time in a separate + /// thread. + /// It is intended that this variable be set once + /// as part of process initialization and before any SQLite interface + /// routines have been called and that this variable remain unchanged + /// thereafter. + /// + /// ^The [temp_store_directory pragma] may modify this variable and cause + /// it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, + /// the [temp_store_directory pragma] always assumes that any string + /// that this variable points to is held in memory obtained from + /// [sqlite3_malloc] and the pragma may attempt to free that memory + /// using [sqlite3_free]. + /// Hence, if this variable is modified directly, either it should be + /// made NULL or made to point to memory obtained from [sqlite3_malloc] + /// or else the use of the [temp_store_directory pragma] should be avoided. + /// Except when requested by the [temp_store_directory pragma], SQLite + /// does not free the memory that sqlite3_temp_directory points to. If + /// the application wants that memory to be freed, it must do + /// so itself, taking care to only do so after all [database connection] + /// objects have been destroyed. + /// + /// <b>Note to Windows Runtime users:</b> The temporary directory must be set + /// prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various + /// features that require the use of temporary files may fail. Here is an + /// example of how to do this using C++ with the Windows Runtime: + /// + /// <blockquote><pre> + /// LPCWSTR zPath = Windows::Storage::ApplicationData::Current-> + /// TemporaryFolder->Path->Data(); + /// char zPathBuf[MAX_PATH + 1]; + /// memset(zPathBuf, 0, sizeof(zPathBuf)); + /// WideCharToMultiByte(CP_UTF8, 0, zPath, -1, zPathBuf, sizeof(zPathBuf), + /// NULL, NULL); + /// sqlite3_temp_directory = sqlite3_mprintf("%s", zPathBuf); + /// </pre></blockquote> + late final ffi.Pointer<ffi.Pointer<ffi.Int8>> _sqlite3_temp_directory = + _dylib.lookup<ffi.Pointer<ffi.Int8>>('sqlite3_temp_directory'); + + ffi.Pointer<ffi.Int8> get sqlite3_temp_directory => + _sqlite3_temp_directory.value; + + set sqlite3_temp_directory(ffi.Pointer<ffi.Int8> value) => + _sqlite3_temp_directory.value = value; + + /// CAPI3REF: Name Of The Folder Holding Database Files + /// + /// ^(If this global variable is made to point to a string which is + /// the name of a folder (a.k.a. directory), then all database files + /// specified with a relative pathname and created or accessed by + /// SQLite when using a built-in windows [sqlite3_vfs | VFS] will be assumed + /// to be relative to that directory.)^ ^If this variable is a NULL + /// pointer, then SQLite assumes that all database files specified + /// with a relative pathname are relative to the current directory + /// for the process. Only the windows VFS makes use of this global + /// variable; it is ignored by the unix VFS. + /// + /// Changing the value of this variable while a database connection is + /// open can result in a corrupt database. + /// + /// It is not safe to read or modify this variable in more than one + /// thread at a time. It is not safe to read or modify this variable + /// if a [database connection] is being used at the same time in a separate + /// thread. + /// It is intended that this variable be set once + /// as part of process initialization and before any SQLite interface + /// routines have been called and that this variable remain unchanged + /// thereafter. + /// + /// ^The [data_store_directory pragma] may modify this variable and cause + /// it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, + /// the [data_store_directory pragma] always assumes that any string + /// that this variable points to is held in memory obtained from + /// [sqlite3_malloc] and the pragma may attempt to free that memory + /// using [sqlite3_free]. + /// Hence, if this variable is modified directly, either it should be + /// made NULL or made to point to memory obtained from [sqlite3_malloc] + /// or else the use of the [data_store_directory pragma] should be avoided. + late final ffi.Pointer<ffi.Pointer<ffi.Int8>> _sqlite3_data_directory = + _dylib.lookup<ffi.Pointer<ffi.Int8>>('sqlite3_data_directory'); + + ffi.Pointer<ffi.Int8> get sqlite3_data_directory => + _sqlite3_data_directory.value; + + set sqlite3_data_directory(ffi.Pointer<ffi.Int8> value) => + _sqlite3_data_directory.value = value; + /// CAPI3REF: Win32 Specific Interface /// /// These interfaces are available only on Windows. The