[ffigen] Automatically add std lib for macos, allow passing list to compiler-opts. (#193)
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 319671a..40ae92a 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,8 @@ +# 2.3.0 +- Added config key `compiler-opts-automatic -> macos -> include-c-standard-library` +(default: true) to automatically find and add C standard library on macOS. +- Allow passing list of string to config key `compiler-opts`. + # 2.2.5 - Added new command line flag `--compiler-opts` to the command line tool.
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md index 5eb0103..e3faf97 100644 --- a/pkgs/ffigen/README.md +++ b/pkgs/ffigen/README.md
@@ -140,7 +140,8 @@ <td> ```yaml -compiler-opts: '-I/usr/lib/llvm-9/include/' +compiler-opts: + - '-I/usr/lib/llvm-9/include/' ``` and/or via the command line - ```bash @@ -149,6 +150,21 @@ ``` </td> </tr> + <tr> + <td>compiler-opts-automatic -> macos -> include-c-standard-library</td> + <td>Tries to automatically find and add C standard library path to + compiler-opts on macos. + <b>Default: true</b> + </td> + <td> + +```yaml +compiler-opts-automatic: + macos: + include-c-standard-library: false +``` + </td> + </tr> <tr> <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>
diff --git a/pkgs/ffigen/example/libclang-example/pubspec.yaml b/pkgs/ffigen/example/libclang-example/pubspec.yaml index fe0f30b..a7cbf4a 100644 --- a/pkgs/ffigen/example/libclang-example/pubspec.yaml +++ b/pkgs/ffigen/example/libclang-example/pubspec.yaml
@@ -29,7 +29,9 @@ - '**CXString.h' - '**Index.h' - compiler-opts: '-Ithird_party/libclang/include -IC:\Progra~1\LLVM\include -I/usr/local/opt/llvm/include/ -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ -Wno-nullability-completeness' + compiler-opts: + - '-Ithird_party/libclang/include' + - '-Wno-nullability-completeness' functions: include: - 'clang_.*' # Can be a regexp, '.' matches any character.
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart index 434137e..673daea 100644 --- a/pkgs/ffigen/lib/src/config_provider/config.dart +++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -136,41 +136,14 @@ } } - /// 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<List<String>, Specification> specs) { var _result = true; for (final key in specs.keys) { final spec = specs[key]; - if (_checkKeyInYaml(key, map)) { + if (checkKeyInYaml(key, map)) { _result = - _result && spec!.validator(key, _getKeyValueFromYaml(key, map)); + _result && spec!.validator(key, getKeyValueFromYaml(key, map)); } else if (spec!.requirement == Requirement.yes) { _logger.severe("Key '$key' is required."); _result = false; @@ -195,8 +168,8 @@ void _extract(YamlMap map, Map<List<String>, Specification> specs) { for (final key in specs.keys) { final spec = specs[key]; - if (_checkKeyInYaml(key, map)) { - spec!.extractedResult(spec.extractor(_getKeyValueFromYaml(key, map))); + if (checkKeyInYaml(key, map)) { + spec!.extractedResult(spec.extractor(getKeyValueFromYaml(key, map))); } else { spec!.extractedResult(spec.defaultValue?.call()); } @@ -235,6 +208,15 @@ extractedResult: (dynamic result) => _compilerOpts = result as List<String>, ), + [strings.compilerOptsAuto]: Specification<CompilerOptsAuto>( + requirement: Requirement.no, + validator: compilerOptsAutoValidator, + extractor: compilerOptsAutoExtractor, + defaultValue: () => CompilerOptsAuto(), + extractedResult: (dynamic result) { + _compilerOpts + .addAll((result as CompilerOptsAuto).extractCompilerOpts()); + }), [strings.functions]: Specification<Declaration>( requirement: Requirement.no, validator: declarationConfigValidator,
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart index 35d24f0..0240c19 100644 --- a/pkgs/ffigen/lib/src/config_provider/config_types.dart +++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -3,9 +3,12 @@ // BSD-style license that can be found in the LICENSE file. /// Contains all the neccesary classes required by config. +import 'dart:io'; import 'package:quiver/pattern.dart' as quiver; +import 'path_finder.dart'; + class CommentType { CommentStyle style; CommentLength length; @@ -319,3 +322,20 @@ return member; } } + +/// Handles config for automatically added compiler options. +class CompilerOptsAuto { + final bool macIncludeStdLib; + + CompilerOptsAuto({bool? macIncludeStdLib}) + : macIncludeStdLib = macIncludeStdLib ?? true; + + /// Extracts compiler options based on OS and config. + List<String> extractCompilerOpts() { + if (Platform.isMacOS && macIncludeStdLib) { + return getCStandardLibraryHeadersForMac(); + } + + return []; + } +}
diff --git a/pkgs/ffigen/lib/src/config_provider/path_finder.dart b/pkgs/ffigen/lib/src/config_provider/path_finder.dart new file mode 100644 index 0000000..3f6a597 --- /dev/null +++ b/pkgs/ffigen/lib/src/config_provider/path_finder.dart
@@ -0,0 +1,63 @@ +// 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. + +/// Utils for finding header paths on system. + +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as p; + +final _logger = Logger('ffigen.config_provider.path_finder'); + +/// This will return include path from either LLVM, XCode or CommandLineTools. +List<String> getCStandardLibraryHeadersForMac() { + final includePaths = <String>[]; + + /// Add system headers. + const systemHeaders = + '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include'; + if (Directory(systemHeaders).existsSync()) { + _logger.fine('Added $systemHeaders to compiler-opts.'); + includePaths.add('-I' + systemHeaders); + } + + /// Find headers from XCode or LLVM installed via brew. + const brewLlvmPath = '/usr/local/opt/llvm/lib/clang'; + const xcodeClangPath = + '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/'; + const searchPaths = [brewLlvmPath, xcodeClangPath]; + for (final searchPath in searchPaths) { + if (!Directory(searchPath).existsSync()) continue; + + final result = Process.runSync('ls', [searchPath]); + final stdout = result.stdout as String; + if (stdout != '') { + final versions = stdout.split('\n').where((s) => s != ''); + for (final version in versions) { + final path = p.join(searchPath, version, 'include'); + if (Directory(path).existsSync()) { + _logger.fine('Added stdlib path: $path to compiler-opts.'); + includePaths.add('-I' + path); + return includePaths; + } + } + } + } + + /// If CommandLineTools are installed use those headers. + const cmdLineToolHeaders = + '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Headers/'; + if (Directory(cmdLineToolHeaders).existsSync()) { + _logger.fine('Added stdlib path: $cmdLineToolHeaders to compiler-opts.'); + includePaths.add('-I' + cmdLineToolHeaders); + return includePaths; + } + + // Warnings for missing headers are printed by libclang while parsing. + _logger.fine('Couldn\'t find stdlib headers in default locations.'); + _logger.fine('Paths searched: ${[cmdLineToolHeaders, ...searchPaths]}'); + + return []; +}
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart index 692f921..ce6aae5 100644 --- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart +++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -38,6 +38,33 @@ return true; } +/// 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; +} + bool booleanExtractor(dynamic value) => value as bool; bool booleanValidator(List<String> name, dynamic value) => @@ -120,11 +147,69 @@ return list; } -List<String> compilerOptsExtractor(dynamic value) => - compilerOptsToList(value as String); +List<String> compilerOptsExtractor(dynamic value) { + if (value is String) { + return compilerOptsToList(value); + } -bool compilerOptsValidator(List<String> name, dynamic value) => - checkType<String>(name, value); + final list = <String>[]; + for (final el in (value as YamlList)) { + if (el is String) { + list.addAll(compilerOptsToList(el)); + } + } + return list; +} + +bool compilerOptsValidator(List<String> name, dynamic value) { + if (value is String || value is YamlList) { + return true; + } else { + _logger.severe('Expected $name to be a String or List of String.'); + return false; + } +} + +CompilerOptsAuto compilerOptsAutoExtractor(dynamic value) { + return CompilerOptsAuto( + macIncludeStdLib: getKeyValueFromYaml( + [strings.macos, strings.includeCStdLib], + value as YamlMap, + ) as bool?, + ); +} + +bool compilerOptsAutoValidator(List<String> name, dynamic value) { + var _result = true; + + if (!checkType<YamlMap>(name, value)) { + return false; + } + + for (final oskey in (value as YamlMap).keys) { + if (oskey == strings.macos) { + if (!checkType<YamlMap>([...name, oskey as String], value[oskey])) { + return false; + } + + for (final inckey in (value[oskey] as YamlMap).keys) { + if (inckey == strings.includeCStdLib) { + if (!checkType<bool>( + [...name, oskey, inckey as String], value[oskey][inckey])) { + _result = false; + } + } else { + _logger.severe("Unknown key '$inckey' in '$name -> $oskey."); + _result = false; + } + } + } else { + _logger.severe("Unknown key '$oskey' in '$name'."); + _result = false; + } + } + return _result; +} Headers headersExtractor(dynamic yamlConfig) { final entryPoints = <String>[]; @@ -169,7 +254,7 @@ return false; } if (!(value as YamlMap).containsKey(strings.entryPoints)) { - _logger.severe("Expected '$name -> ${strings.entryPoints}' to be a Map."); + _logger.severe("Required '$name -> ${strings.entryPoints}'."); return false; } else { for (final key in value.keys) {
diff --git a/pkgs/ffigen/lib/src/header_parser/parser.dart b/pkgs/ffigen/lib/src/header_parser/parser.dart index a8473af..ff28679 100644 --- a/pkgs/ffigen/lib/src/header_parser/parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/parser.dart
@@ -62,6 +62,7 @@ config.compilerOpts.add(strings.fparseAllComments); } + _logger.fine('CompilerOpts used: ${config.compilerOpts}'); clangCmdArgs = createDynamicStringArray(config.compilerOpts); cmdLen = config.compilerOpts.length;
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index 885427d..a4f1d68 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart
@@ -34,6 +34,12 @@ const compilerOpts = 'compiler-opts'; +const compilerOptsAuto = 'compiler-opts-automatic'; +// Sub-fields of compilerOptsAuto. +const macos = 'macos'; +// Sub-fields of macos. +const includeCStdLib = 'include-c-standard-library'; + // Declarations. const functions = 'functions'; const structs = 'structs';
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index 31048c4..4104cd3 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.5 +version: 2.3.0 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/config_tests/compiler_opts_test.dart b/pkgs/ffigen/test/config_tests/compiler_opts_test.dart index a3aa117..1c5493b 100644 --- a/pkgs/ffigen/test/config_tests/compiler_opts_test.dart +++ b/pkgs/ffigen/test/config_tests/compiler_opts_test.dart
@@ -2,8 +2,11 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:ffigen/ffigen.dart'; import 'package:ffigen/src/code_generator.dart'; import 'package:ffigen/src/config_provider/spec_utils.dart'; +import 'package:ffigen/src/strings.dart' as strings; +import 'package:yaml/yaml.dart' as yaml; import 'package:test/test.dart'; late Library actual, expected; @@ -25,5 +28,19 @@ ], ); }); + test('Compiler Opts Automatic', () { + final config = Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'NativeLibrary' +${strings.description}: 'Compiler Opts Test' +${strings.output}: 'unused' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/comment_markup.h' +${strings.compilerOptsAuto}: + ${strings.macos}: + ${strings.includeCStdLib}: false + ''') as yaml.YamlMap); + expect(config.compilerOpts.isEmpty, true); + }); }); }
diff --git a/pkgs/ffigen/test/header_parser_tests/dart_handle_test.dart b/pkgs/ffigen/test/header_parser_tests/dart_handle_test.dart index 16387ff..27ce68e 100644 --- a/pkgs/ffigen/test/header_parser_tests/dart_handle_test.dart +++ b/pkgs/ffigen/test/header_parser_tests/dart_handle_test.dart
@@ -25,7 +25,7 @@ ${strings.name}: 'NativeLibrary' ${strings.description}: 'Dart_Handle Test' ${strings.output}: 'unused' -${strings.compilerOpts}: '-I${path.join(getSdkPath(), "include")} -I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Headers/' +${strings.compilerOpts}: '-I${path.join(getSdkPath(), "include")}' ${strings.headers}: ${strings.entryPoints}:
diff --git a/pkgs/ffigen/test/header_parser_tests/globals_test.dart b/pkgs/ffigen/test/header_parser_tests/globals_test.dart index db7bf8e..7da2414 100644 --- a/pkgs/ffigen/test/header_parser_tests/globals_test.dart +++ b/pkgs/ffigen/test/header_parser_tests/globals_test.dart
@@ -36,8 +36,7 @@ - myInt - pointerToLongDouble - globalStruct -# Needed for stdbool.h in MacOS -${strings.compilerOpts}: '-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Headers/ -Wno-nullability-completeness' +${strings.compilerOpts}: '-Wno-nullability-completeness' ''') as yaml.YamlMap), ); });
diff --git a/pkgs/ffigen/test/large_integration_tests/large_test.dart b/pkgs/ffigen/test/large_integration_tests/large_test.dart index 697ea92..2815a1f 100644 --- a/pkgs/ffigen/test/large_integration_tests/large_test.dart +++ b/pkgs/ffigen/test/large_integration_tests/large_test.dart
@@ -79,8 +79,6 @@ ${strings.description}: Bindings to SQLite. ${strings.output}: unused ${strings.arrayWorkaround}: true -# Needed for stdarg.h in MacOS -${strings.compilerOpts}: '-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Headers/' ${strings.comments}: ${strings.style}: ${strings.any} ${strings.length}: ${strings.full}
diff --git a/pkgs/ffigen/test/native_test/config.yaml b/pkgs/ffigen/test/native_test/config.yaml index 7abdce4..6c7b239 100644 --- a/pkgs/ffigen/test/native_test/config.yaml +++ b/pkgs/ffigen/test/native_test/config.yaml
@@ -16,5 +16,4 @@ - '**native_test.c' array-workaround: true -# Needed for stdbool.h in MacOS -compiler-opts: '-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Headers/ -Wno-nullability-completeness' +compiler-opts: '-Wno-nullability-completeness'
diff --git a/pkgs/ffigen/tool/libclang_config.yaml b/pkgs/ffigen/tool/libclang_config.yaml index ec49446..f61c097 100644 --- a/pkgs/ffigen/tool/libclang_config.yaml +++ b/pkgs/ffigen/tool/libclang_config.yaml
@@ -12,8 +12,9 @@ name: Clang description: Holds bindings to LibClang. output: 'lib/src/header_parser/clang_bindings/clang_bindings.dart' -sort: true -compiler-opts: '-Ithird_party/libclang/include -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ -Wno-nullability-completeness' +compiler-opts: + - '-Ithird_party/libclang/include' + - '-Wno-nullability-completeness' headers: entry-points: - 'third_party/libclang/include/clang-c/Index.h'