Automatically add std lib for macos, allow passing list to compiler-opts. (#193)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 319671a..40ae92a 100644
--- a/CHANGELOG.md
+++ b/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/README.md b/README.md
index 5eb0103..e3faf97 100644
--- a/README.md
+++ b/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/example/libclang-example/pubspec.yaml b/example/libclang-example/pubspec.yaml
index fe0f30b..a7cbf4a 100644
--- a/example/libclang-example/pubspec.yaml
+++ b/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/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index 434137e..673daea 100644
--- a/lib/src/config_provider/config.dart
+++ b/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/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index 35d24f0..0240c19 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/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/lib/src/config_provider/path_finder.dart b/lib/src/config_provider/path_finder.dart
new file mode 100644
index 0000000..3f6a597
--- /dev/null
+++ b/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/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index 692f921..ce6aae5 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/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/lib/src/header_parser/parser.dart b/lib/src/header_parser/parser.dart
index a8473af..ff28679 100644
--- a/lib/src/header_parser/parser.dart
+++ b/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/lib/src/strings.dart b/lib/src/strings.dart
index 885427d..a4f1d68 100644
--- a/lib/src/strings.dart
+++ b/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/pubspec.yaml b/pubspec.yaml
index 31048c4..4104cd3 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.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/test/config_tests/compiler_opts_test.dart b/test/config_tests/compiler_opts_test.dart
index a3aa117..1c5493b 100644
--- a/test/config_tests/compiler_opts_test.dart
+++ b/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/test/header_parser_tests/dart_handle_test.dart b/test/header_parser_tests/dart_handle_test.dart
index 16387ff..27ce68e 100644
--- a/test/header_parser_tests/dart_handle_test.dart
+++ b/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/test/header_parser_tests/globals_test.dart b/test/header_parser_tests/globals_test.dart
index db7bf8e..7da2414 100644
--- a/test/header_parser_tests/globals_test.dart
+++ b/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/test/large_integration_tests/large_test.dart b/test/large_integration_tests/large_test.dart
index 697ea92..2815a1f 100644
--- a/test/large_integration_tests/large_test.dart
+++ b/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/test/native_test/config.yaml b/test/native_test/config.yaml
index 7abdce4..6c7b239 100644
--- a/test/native_test/config.yaml
+++ b/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/tool/libclang_config.yaml b/tool/libclang_config.yaml
index ec49446..f61c097 100644
--- a/tool/libclang_config.yaml
+++ b/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'