Removed usage of --no-sound-null-safety flag. (#136)
* Removed usage of --no-sound-null-safety flag
* Update version, changelog
* Use flag when running tests on travis
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index 1026834..83fe4d6 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -50,7 +50,7 @@
- name: Install libclang-10-dev
run: sudo apt-get install libclang-10-dev
- name: Setup ffigen
- run: dart --no-sound-null-safety run ffigen:setup
+ run: dart run ffigen:setup
- name: Build test dylib
run: cd test/native_test && dart build_test_dylib.dart && cd ../..
- name: Run VM tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2697d97..38fa2af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 2.0.0-nullsafety.1
+- Removed the need for `--no-sound-null-safety` flag.
+
# 2.0.0-nullsafety.0
- Migrated to (unsound) null safety.
diff --git a/README.md b/README.md
index 3b56033..e6359eb 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,6 @@
Experimental binding generator for [FFI](https://dart.dev/guides/libraries/c-interop)
bindings.
-<!-- TODO: Remove this when package can run with sound null safety -->
-> Due to a few unmigrated dependencies, ffigen currently runs in unsound null safety, run using `dart --no-sound-null-safety run ffigen`.
-
## Example
For some header file _example.h_:
diff --git a/bin/ffigen.dart b/bin/ffigen.dart
index 82abac5..cfb482c 100644
--- a/bin/ffigen.dart
+++ b/bin/ffigen.dart
@@ -1,211 +1,8 @@
// Copyright (c) 2020, 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.
+//
+// TODO(128): Remove this when package can run with sound null safety.
+// @dart=2.7
-// Executable script to generate bindings for some C library.
-import 'dart:io';
-
-import 'package:args/args.dart';
-import 'package:cli_util/cli_logging.dart' show Ansi;
-import 'package:ffigen/ffigen.dart';
-import 'package:logging/logging.dart';
-import 'package:yaml/yaml.dart' as yaml;
-
-import 'setup.dart';
-
-final _logger = Logger('ffigen.ffigen');
-final _ansi = Ansi(Ansi.terminalSupportsAnsi);
-
-String successPen(String str) {
- return '${_ansi.green}$str${_ansi.none}';
-}
-
-String errorPen(String str) {
- return '${_ansi.red}$str${_ansi.none}';
-}
-
-void main(List<String> args) {
- // Parses the cmd args. This will print usage and exit if --help was passed.
- final argResult = getArgResults(args);
-
- // Setup logging level and printing.
- setupLogger(argResult);
-
- /// Prompt user if dylib doesn't exist and cannot be auto created to run
- /// `pub run ffigen:setup -Ipath/to/llvm/include -Lpath/to/llvm/lib`.
- if (!checkDylibExist() && !autoCreateDylib()) {
- _logger.severe('Unable to create dynamic library automatically.');
- _logger.severe('If LLVM (9+) is installed, try running:');
- _logger.severe(
- ' pub run ffigen:setup -Ipath/to/llvm/include -Lpath/to/llvm/lib');
- exit(1);
- }
-
- // Create a config object.
- Config config;
- try {
- config = getConfig(argResult);
- } on FormatException {
- _logger.severe('Please fix configuration errors and re-run the tool.');
- exit(1);
- }
-
- // Parse the bindings according to config object provided.
- final library = parse(config);
-
- // Generate file for the parsed bindings.
- final gen = File(config.output);
- library.generateFile(gen);
- _logger
- .info(successPen('Finished, Bindings generated in ${gen.absolute.path}'));
-}
-
-Config getConfig(ArgResults result) {
- _logger.info('Running in ${Directory.current}');
-
- if (result.wasParsed('config')) {
- return getConfigFromCustomYaml(result['config'] as String);
- } else {
- return getConfigFromPubspec();
- }
-}
-
-/// Extracts configuration from pubspec file.
-Config getConfigFromPubspec() {
- final pubspecName = 'pubspec.yaml';
- final configKey = 'ffigen';
-
- final pubspecFile = File(pubspecName);
-
- if (!pubspecFile.existsSync()) {
- _logger.severe(
- 'Error: $pubspecName not found, please run this tool from the root of your package.');
- exit(1);
- }
-
- // Casting this because pubspec is expected to be a YamlMap.
-
- // Throws a [YamlException] if it's unable to parse the Yaml.
- final bindingsConfigMap =
- yaml.loadYaml(pubspecFile.readAsStringSync())[configKey] as yaml.YamlMap?;
-
- if (bindingsConfigMap == null) {
- _logger.severe("Couldn't find an entry for '$configKey' in $pubspecName.");
- exit(1);
- }
- return Config.fromYaml(bindingsConfigMap);
-}
-
-/// Extracts configuration from a custom yaml file.
-Config getConfigFromCustomYaml(String yamlPath) {
- final yamlFile = File(yamlPath);
-
- if (!yamlFile.existsSync()) {
- _logger.severe('Error: $yamlPath not found.');
- exit(1);
- }
-
- // Throws a [YamlException] if it's unable to parse the Yaml.
- final bindingsConfigMap =
- yaml.loadYaml(yamlFile.readAsStringSync()) as yaml.YamlMap;
-
- return Config.fromYaml(bindingsConfigMap);
-}
-
-/// Parses the cmd line arguments.
-ArgResults getArgResults(List<String> args) {
- final parser = ArgParser(allowTrailingOptions: true);
-
- parser.addSeparator(
- 'FFIGEN: Generate dart bindings from C header files\nUsage:');
- parser.addOption(
- 'config',
- help: 'path to Yaml file containing configurations if not in pubspec.yaml',
- );
- parser.addOption(
- 'verbose',
- abbr: 'v',
- defaultsTo: 'info',
- allowed: [
- 'all',
- 'fine',
- 'info',
- 'warning',
- 'severe',
- ],
- );
- parser.addFlag(
- 'help',
- abbr: 'h',
- help: 'prints this usage',
- negatable: false,
- );
-
- ArgResults results;
- try {
- results = parser.parse(args);
-
- if (results.wasParsed('help')) {
- print(parser.usage);
- exit(0);
- }
- } catch (e) {
- print(e);
- print(parser.usage);
- exit(1);
- }
-
- return results;
-}
-
-/// Sets up the logging level and printing.
-void setupLogger(ArgResults result) {
- if (result.wasParsed('verbose')) {
- switch (result['verbose'] as String?) {
- case 'all':
- // Logs everything, the entire AST touched by our parser.
- Logger.root.level = Level.ALL;
- break;
- case 'fine':
- // Logs AST parts relevant to user (i.e those included in filters).
- Logger.root.level = Level.FINE;
- break;
- case 'info':
- // Logs relevant info for general user (default).
- Logger.root.level = Level.INFO;
- break;
- case 'warning':
- // Logs warnings for relevant stuff.
- Logger.root.level = Level.WARNING;
- break;
- case 'severe':
- // Logs severe warnings and errors.
- Logger.root.level = Level.SEVERE;
- break;
- }
- // Setup logger for printing (if verbosity was set by user).
- Logger.root.onRecord.listen((record) {
- final level = '[${record.level.name}]'.padRight(9);
- printLog('${level}: ${record.message}', record.level);
- });
- } else {
- // Setup logger for printing (if verbosity was not set by user).
- Logger.root.onRecord.listen((record) {
- if (record.level.value > Level.INFO.value) {
- final level = '[${record.level.name}]'.padRight(9);
- printLog('${level}: ${record.message}', record.level);
- } else {
- printLog(record.message, record.level);
- }
- });
- }
-}
-
-void printLog(String log, Level level) {
- // Prints text in red for Severe logs only.
- if (level < Level.SEVERE) {
- print(log);
- } else {
- print(errorPen(log));
- }
-}
+export 'package:ffigen/src/executables/ffigen.dart';
diff --git a/bin/setup.dart b/bin/setup.dart
index 9949ebc..5b07636 100644
--- a/bin/setup.dart
+++ b/bin/setup.dart
@@ -1,276 +1,8 @@
// Copyright (c) 2020, 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.
+//
+// TODO(128): Remove this when package can run with sound null safety.
+// @dart=2.7
-/// =======================================================================
-/// =============== Build script to generate dyamic library ===============
-/// =======================================================================
-/// This Script effectively calls the following (but user can provide
-/// command line args which will replace the defaults shown below)-
-///
-/// Linux:
-/// ```
-/// clang -I/usr/lib/llvm-9/include/ -I/usr/lib/llvm-10/include/ -I/usr/lib/llvm-11/include/ -lclang -shared -fpic path/to/wrapper.c -o path/to/libwrapped_clang.so
-/// ```
-/// MacOS:
-/// ```
-/// clang -I/usr/local/opt/llvm/include/ -L/usr/local/opt/llvm/lib/ -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ -v -lclang -shared -fpic path/to/wrapper.c -o path/to/libwrapped_clang.dylib
-/// ```
-/// Windows:
-/// ```
-/// clang -IC:\Progra~1\LLVM\include -LC:\Progra~1\LLVM\lib -llibclang -shared path/to/wrapper.c -o path/to/wrapped_clang.dll -Wl,/DEF:path/to/wrapper.def
-/// ```
-/// =======================================================================
-/// =======================================================================
-/// =======================================================================
-
-import 'dart:io';
-import 'package:args/args.dart';
-import 'package:ffigen/src/find_resource.dart';
-import 'package:ffigen/src/strings.dart' as strings;
-import 'package:path/path.dart' as path;
-
-/// Default platform options.
-final _linuxOpts = _Options(
- sharedFlag: '-shared',
- inputHeader: _getWrapperPath('wrapper.c'),
- fPIC: '-fpic',
- ldLibFlag: '-lclang',
- headerIncludes: [
- '-I/usr/lib/llvm-9/include/',
- '-I/usr/lib/llvm-10/include/',
- '-I/usr/lib/llvm-11/include/',
- ],
-);
-final _windowsOpts = _Options(
- sharedFlag: '-shared',
- inputHeader: _getWrapperPath('wrapper.c'),
- moduleDefPath: '-Wl,/DEF:${_getWrapperPath("wrapper.def")}',
- ldLibFlag: '-llibclang',
- headerIncludes: [
- r'-IC:\Progra~1\LLVM\include',
- ],
- libIncludes: [
- r'-LC:\Progra~1\LLVM\lib',
- ],
-);
-final _macOSOpts = _Options(
- sharedFlag: '-shared',
- inputHeader: _getWrapperPath('wrapper.c'),
- fPIC: '-fpic',
- ldLibFlag: '-lclang',
- headerIncludes: [
- '-I/usr/local/opt/llvm/include/',
- '-I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/',
- ],
- libIncludes: [
- '-L/usr/local/opt/llvm/lib/',
- ],
-);
-
-/// If main is called directly we always re-create the dynamic library.
-void main(List<String> arguments) {
- // Parses the cmd args. This will print usage and exit if --help was passed.
- final argResults = _getArgResults(arguments);
-
- print('Building Dynamic Library for libclang wrapper...');
- final options = _getPlatformOptions();
- _deleteOldDylib();
-
- // Updates header/lib includes in platform options.
- _changeIncludesUsingCmdArgs(argResults, options);
-
- // Run clang compiler to generate the dynamic library.
- final processResult = _runClangProcess(options);
- _printDetails(processResult, options);
-}
-
-/// Returns true if auto creating dylib was successful.
-///
-/// This will fail if llvm is not in default directories or if .dart_tool
-/// doesn't exist.
-bool autoCreateDylib() {
- _deleteOldDylib();
- final options = _getPlatformOptions();
- final processResult = _runClangProcess(options);
- if ((processResult.stderr as String).isNotEmpty) {
- print(processResult.stderr);
- }
- return checkDylibExist();
-}
-
-bool checkDylibExist() {
- return File(path.join(
- _getDotDartToolPath(),
- strings.ffigenFolderName,
- strings.dylibFileName,
- )).existsSync();
-}
-
-/// Removes old dynamic libraries(if any) by deleting .dart_tool/ffigen.
-///
-/// Throws error if '.dart_tool' is not found.
-void _deleteOldDylib() {
- // Find .dart_tool.
- final dtpath = _getDotDartToolPath();
- // Find .dart_tool/ffigen and delete recursively if it exists.
- final ffigenDir = Directory(path.join(dtpath, strings.ffigenFolderName));
- if (ffigenDir.existsSync()) ffigenDir.deleteSync(recursive: true);
-}
-
-/// Creates necesarry parent folders and return full path to dylib.
-String _dylibPath() {
- // Find .dart_tool.
- final dtpath = _getDotDartToolPath();
- // Create .dart_tool/ffigen if it doesn't exists.
- final ffigenDir = Directory(path.join(dtpath, strings.ffigenFolderName));
- if (!ffigenDir.existsSync()) ffigenDir.createSync();
-
- // Return dylib path
- return path.join(ffigenDir.absolute.path, strings.dylibFileName);
-}
-
-/// Returns full path of the wrapper files.
-///
-/// Throws error if not found.
-String _getWrapperPath(String wrapperName) {
- final file = File.fromUri(findWrapper(wrapperName)!);
- if (file.existsSync()) {
- return file.absolute.path;
- } else {
- throw Exception('Unable to find $wrapperName file.');
- }
-}
-
-/// Gets full path to .dart_tool.
-///
-/// Throws Exception if not found.
-String _getDotDartToolPath() {
- final dtpath = findDotDartTool()?.toFilePath();
- if (dtpath == null) {
- throw Exception('.dart_tool not found.');
- }
- return dtpath;
-}
-
-/// Calls the clang compiler.
-ProcessResult _runClangProcess(_Options options) {
- final result = Process.runSync(
- 'clang',
- [
- ...options.headerIncludes,
- ...options.libIncludes,
- options.ldLibFlag,
- options.sharedFlag,
- options.fPIC,
- options.inputHeader,
- '-o',
- _dylibPath(),
- options.moduleDefPath,
- '-Wno-nullability-completeness',
- ],
- );
- return result;
-}
-
-/// Prints success message (or process error if any).
-void _printDetails(ProcessResult result, _Options options) {
- print(result.stdout);
- if ((result.stderr as String).isNotEmpty) {
- print(result.stderr);
- } else {
- print('Created dynamic library.');
- }
-}
-
-ArgResults _getArgResults(List<String> args) {
- final parser = ArgParser(allowTrailingOptions: true);
- parser.addSeparator('Generates LLVM Wrapper used by this package:');
- parser.addMultiOption('include-header',
- abbr: 'I', help: 'Path to header include directories');
- parser.addMultiOption('include-lib',
- abbr: 'L', help: 'Path to library include directories');
- parser.addFlag(
- 'help',
- abbr: 'h',
- help: 'prints this usage',
- negatable: false,
- );
-
- ArgResults results;
- try {
- results = parser.parse(args);
-
- if (results.wasParsed('help')) {
- print(parser.usage);
- exit(0);
- }
- } catch (e) {
- print(e);
- print(parser.usage);
- exit(1);
- }
-
- return results;
-}
-
-/// Use cmd args(if any) to change header/lib include paths.
-void _changeIncludesUsingCmdArgs(ArgResults argResult, _Options options) {
- if (argResult.wasParsed('include-header')) {
- options.headerIncludes = (argResult['include-header'] as List<String>)
- .map((header) => '-I$header')
- .toList();
- }
- if (argResult.wasParsed('include-lib')) {
- options.libIncludes = (argResult['include-lib'] as List<String>)
- .map((lib) => '-L$lib')
- .toList();
- }
-}
-
-/// Get options based on current platform.
-_Options _getPlatformOptions() {
- if (Platform.isMacOS) {
- return _macOSOpts;
- } else if (Platform.isWindows) {
- return _windowsOpts;
- } else if (Platform.isLinux) {
- return _linuxOpts;
- } else {
- throw Exception('Unknown Platform.');
- }
-}
-
-/// Hold options which would be passed to clang.
-class _Options {
- /// Tells compiler to generate a shared library.
- final String sharedFlag;
-
- /// Flag for generating Position Independant Code (Not used on windows).
- final String fPIC;
-
- /// Input file.
- final String inputHeader;
-
- /// Path to `.def` file containing symbols to export, windows use only.
- final String moduleDefPath;
-
- /// Path to header files.
- List<String> headerIncludes;
-
- /// Path to dynamic/static libraries
- List<String> libIncludes;
-
- /// Linker flag for linking to libclang.
- final String ldLibFlag;
-
- _Options({
- required this.sharedFlag,
- required this.inputHeader,
- required this.ldLibFlag,
- this.headerIncludes = const [],
- this.libIncludes = const [],
- this.fPIC = '',
- this.moduleDefPath = '',
- });
-}
+export 'package:ffigen/src/executables/setup.dart';
diff --git a/lib/src/executables/ffigen.dart b/lib/src/executables/ffigen.dart
new file mode 100644
index 0000000..22b3744
--- /dev/null
+++ b/lib/src/executables/ffigen.dart
@@ -0,0 +1,211 @@
+// 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.
+
+// Executable script to generate bindings for some C library.
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:cli_util/cli_logging.dart' show Ansi;
+import 'package:ffigen/ffigen.dart';
+import 'package:logging/logging.dart';
+import 'package:yaml/yaml.dart' as yaml;
+
+import 'package:ffigen/src/executables/setup.dart';
+
+final _logger = Logger('ffigen.ffigen');
+final _ansi = Ansi(Ansi.terminalSupportsAnsi);
+
+String successPen(String str) {
+ return '${_ansi.green}$str${_ansi.none}';
+}
+
+String errorPen(String str) {
+ return '${_ansi.red}$str${_ansi.none}';
+}
+
+void main(List<String> args) {
+ // Parses the cmd args. This will print usage and exit if --help was passed.
+ final argResult = getArgResults(args);
+
+ // Setup logging level and printing.
+ setupLogger(argResult);
+
+ /// Prompt user if dylib doesn't exist and cannot be auto created to run
+ /// `pub run ffigen:setup -Ipath/to/llvm/include -Lpath/to/llvm/lib`.
+ if (!checkDylibExist() && !autoCreateDylib()) {
+ _logger.severe('Unable to create dynamic library automatically.');
+ _logger.severe('If LLVM (9+) is installed, try running:');
+ _logger.severe(
+ ' pub run ffigen:setup -Ipath/to/llvm/include -Lpath/to/llvm/lib');
+ exit(1);
+ }
+
+ // Create a config object.
+ Config config;
+ try {
+ config = getConfig(argResult);
+ } on FormatException {
+ _logger.severe('Please fix configuration errors and re-run the tool.');
+ exit(1);
+ }
+
+ // Parse the bindings according to config object provided.
+ final library = parse(config);
+
+ // Generate file for the parsed bindings.
+ final gen = File(config.output);
+ library.generateFile(gen);
+ _logger
+ .info(successPen('Finished, Bindings generated in ${gen.absolute.path}'));
+}
+
+Config getConfig(ArgResults result) {
+ _logger.info('Running in ${Directory.current}');
+
+ if (result.wasParsed('config')) {
+ return getConfigFromCustomYaml(result['config'] as String);
+ } else {
+ return getConfigFromPubspec();
+ }
+}
+
+/// Extracts configuration from pubspec file.
+Config getConfigFromPubspec() {
+ final pubspecName = 'pubspec.yaml';
+ final configKey = 'ffigen';
+
+ final pubspecFile = File(pubspecName);
+
+ if (!pubspecFile.existsSync()) {
+ _logger.severe(
+ 'Error: $pubspecName not found, please run this tool from the root of your package.');
+ exit(1);
+ }
+
+ // Casting this because pubspec is expected to be a YamlMap.
+
+ // Throws a [YamlException] if it's unable to parse the Yaml.
+ final bindingsConfigMap =
+ yaml.loadYaml(pubspecFile.readAsStringSync())[configKey] as yaml.YamlMap?;
+
+ if (bindingsConfigMap == null) {
+ _logger.severe("Couldn't find an entry for '$configKey' in $pubspecName.");
+ exit(1);
+ }
+ return Config.fromYaml(bindingsConfigMap);
+}
+
+/// Extracts configuration from a custom yaml file.
+Config getConfigFromCustomYaml(String yamlPath) {
+ final yamlFile = File(yamlPath);
+
+ if (!yamlFile.existsSync()) {
+ _logger.severe('Error: $yamlPath not found.');
+ exit(1);
+ }
+
+ // Throws a [YamlException] if it's unable to parse the Yaml.
+ final bindingsConfigMap =
+ yaml.loadYaml(yamlFile.readAsStringSync()) as yaml.YamlMap;
+
+ return Config.fromYaml(bindingsConfigMap);
+}
+
+/// Parses the cmd line arguments.
+ArgResults getArgResults(List<String> args) {
+ final parser = ArgParser(allowTrailingOptions: true);
+
+ parser.addSeparator(
+ 'FFIGEN: Generate dart bindings from C header files\nUsage:');
+ parser.addOption(
+ 'config',
+ help: 'path to Yaml file containing configurations if not in pubspec.yaml',
+ );
+ parser.addOption(
+ 'verbose',
+ abbr: 'v',
+ defaultsTo: 'info',
+ allowed: [
+ 'all',
+ 'fine',
+ 'info',
+ 'warning',
+ 'severe',
+ ],
+ );
+ parser.addFlag(
+ 'help',
+ abbr: 'h',
+ help: 'prints this usage',
+ negatable: false,
+ );
+
+ ArgResults results;
+ try {
+ results = parser.parse(args);
+
+ if (results.wasParsed('help')) {
+ print(parser.usage);
+ exit(0);
+ }
+ } catch (e) {
+ print(e);
+ print(parser.usage);
+ exit(1);
+ }
+
+ return results;
+}
+
+/// Sets up the logging level and printing.
+void setupLogger(ArgResults result) {
+ if (result.wasParsed('verbose')) {
+ switch (result['verbose'] as String?) {
+ case 'all':
+ // Logs everything, the entire AST touched by our parser.
+ Logger.root.level = Level.ALL;
+ break;
+ case 'fine':
+ // Logs AST parts relevant to user (i.e those included in filters).
+ Logger.root.level = Level.FINE;
+ break;
+ case 'info':
+ // Logs relevant info for general user (default).
+ Logger.root.level = Level.INFO;
+ break;
+ case 'warning':
+ // Logs warnings for relevant stuff.
+ Logger.root.level = Level.WARNING;
+ break;
+ case 'severe':
+ // Logs severe warnings and errors.
+ Logger.root.level = Level.SEVERE;
+ break;
+ }
+ // Setup logger for printing (if verbosity was set by user).
+ Logger.root.onRecord.listen((record) {
+ final level = '[${record.level.name}]'.padRight(9);
+ printLog('${level}: ${record.message}', record.level);
+ });
+ } else {
+ // Setup logger for printing (if verbosity was not set by user).
+ Logger.root.onRecord.listen((record) {
+ if (record.level.value > Level.INFO.value) {
+ final level = '[${record.level.name}]'.padRight(9);
+ printLog('${level}: ${record.message}', record.level);
+ } else {
+ printLog(record.message, record.level);
+ }
+ });
+ }
+}
+
+void printLog(String log, Level level) {
+ // Prints text in red for Severe logs only.
+ if (level < Level.SEVERE) {
+ print(log);
+ } else {
+ print(errorPen(log));
+ }
+}
diff --git a/lib/src/executables/setup.dart b/lib/src/executables/setup.dart
new file mode 100644
index 0000000..84a069b
--- /dev/null
+++ b/lib/src/executables/setup.dart
@@ -0,0 +1,276 @@
+// 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.
+
+/// =======================================================================
+/// =============== Build script to generate dyamic library ===============
+/// =======================================================================
+/// This Script effectively calls the following (but user can provide
+/// command line args which will replace the defaults shown below)-
+///
+/// Linux:
+/// ```
+/// clang -I/usr/lib/llvm-9/include/ -I/usr/lib/llvm-10/include/ -I/usr/lib/llvm-11/include/ -lclang -shared -fpic path/to/wrapper.c -o path/to/libwrapped_clang.so
+/// ```
+/// MacOS:
+/// ```
+/// clang -I/usr/local/opt/llvm/include/ -L/usr/local/opt/llvm/lib/ -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ -v -lclang -shared -fpic path/to/wrapper.c -o path/to/libwrapped_clang.dylib
+/// ```
+/// Windows:
+/// ```
+/// clang -IC:\Progra~1\LLVM\include -LC:\Progra~1\LLVM\lib -llibclang -shared path/to/wrapper.c -o path/to/wrapped_clang.dll -Wl,/DEF:path/to/wrapper.def
+/// ```
+/// =======================================================================
+/// =======================================================================
+/// =======================================================================
+
+import 'dart:io';
+import 'package:args/args.dart';
+import 'package:ffigen/src/find_resource.dart';
+import 'package:ffigen/src/strings.dart' as strings;
+import 'package:path/path.dart' as path;
+
+/// Default platform options.
+final _linuxOpts = _Options(
+ sharedFlag: '-shared',
+ inputHeader: _getWrapperPath('wrapper.c'),
+ fPIC: '-fpic',
+ ldLibFlag: '-lclang',
+ headerIncludes: [
+ '-I/usr/lib/llvm-9/include/',
+ '-I/usr/lib/llvm-10/include/',
+ '-I/usr/lib/llvm-11/include/',
+ ],
+);
+final _windowsOpts = _Options(
+ sharedFlag: '-shared',
+ inputHeader: _getWrapperPath('wrapper.c'),
+ moduleDefPath: '-Wl,/DEF:${_getWrapperPath("wrapper.def")}',
+ ldLibFlag: '-llibclang',
+ headerIncludes: [
+ r'-IC:\Progra~1\LLVM\include',
+ ],
+ libIncludes: [
+ r'-LC:\Progra~1\LLVM\lib',
+ ],
+);
+final _macOSOpts = _Options(
+ sharedFlag: '-shared',
+ inputHeader: _getWrapperPath('wrapper.c'),
+ fPIC: '-fpic',
+ ldLibFlag: '-lclang',
+ headerIncludes: [
+ '-I/usr/local/opt/llvm/include/',
+ '-I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/',
+ ],
+ libIncludes: [
+ '-L/usr/local/opt/llvm/lib/',
+ ],
+);
+
+/// If main is called directly we always re-create the dynamic library.
+void main(List<String> arguments) {
+ // Parses the cmd args. This will print usage and exit if --help was passed.
+ final argResults = _getArgResults(arguments);
+
+ print('Building Dynamic Library for libclang wrapper...');
+ final options = _getPlatformOptions();
+ _deleteOldDylib();
+
+ // Updates header/lib includes in platform options.
+ _changeIncludesUsingCmdArgs(argResults, options);
+
+ // Run clang compiler to generate the dynamic library.
+ final processResult = _runClangProcess(options);
+ _printDetails(processResult, options);
+}
+
+/// Returns true if auto creating dylib was successful.
+///
+/// This will fail if llvm is not in default directories or if .dart_tool
+/// doesn't exist.
+bool autoCreateDylib() {
+ _deleteOldDylib();
+ final options = _getPlatformOptions();
+ final processResult = _runClangProcess(options);
+ if ((processResult.stderr as String).isNotEmpty) {
+ print(processResult.stderr);
+ }
+ return checkDylibExist();
+}
+
+bool checkDylibExist() {
+ return File(path.join(
+ _getDotDartToolPath(),
+ strings.ffigenFolderName,
+ strings.dylibFileName,
+ )).existsSync();
+}
+
+/// Removes old dynamic libraries(if any) by deleting .dart_tool/ffigen.
+///
+/// Throws error if '.dart_tool' is not found.
+void _deleteOldDylib() {
+ // Find .dart_tool.
+ final dtpath = _getDotDartToolPath();
+ // Find .dart_tool/ffigen and delete recursively if it exists.
+ final ffigenDir = Directory(path.join(dtpath, strings.ffigenFolderName));
+ if (ffigenDir.existsSync()) ffigenDir.deleteSync(recursive: true);
+}
+
+/// Creates necesarry parent folders and return full path to dylib.
+String _dylibPath() {
+ // Find .dart_tool.
+ final dtpath = _getDotDartToolPath();
+ // Create .dart_tool/ffigen if it doesn't exists.
+ final ffigenDir = Directory(path.join(dtpath, strings.ffigenFolderName));
+ if (!ffigenDir.existsSync()) ffigenDir.createSync();
+
+ // Return dylib path
+ return path.join(ffigenDir.absolute.path, strings.dylibFileName);
+}
+
+/// Returns full path of the wrapper files.
+///
+/// Throws error if not found.
+String _getWrapperPath(String wrapperName) {
+ final file = File.fromUri(findWrapper(wrapperName)!);
+ if (file.existsSync()) {
+ return file.absolute.path;
+ } else {
+ throw Exception('Unable to find $wrapperName file.');
+ }
+}
+
+/// Gets full path to .dart_tool.
+///
+/// Throws Exception if not found.
+String _getDotDartToolPath() {
+ final dtpath = findDotDartTool()?.toFilePath();
+ if (dtpath == null) {
+ throw Exception('.dart_tool not found.');
+ }
+ return dtpath;
+}
+
+/// Calls the clang compiler.
+ProcessResult _runClangProcess(_Options options) {
+ final result = Process.runSync(
+ 'clang',
+ [
+ ...options.headerIncludes,
+ ...options.libIncludes,
+ options.ldLibFlag,
+ options.sharedFlag,
+ options.fPIC,
+ options.inputHeader,
+ '-o',
+ _dylibPath(),
+ options.moduleDefPath,
+ '-Wno-nullability-completeness',
+ ],
+ );
+ return result;
+}
+
+/// Prints success message (or process error if any).
+void _printDetails(ProcessResult result, _Options options) {
+ print(result.stdout);
+ if ((result.stderr as String).isNotEmpty) {
+ print(result.stderr);
+ } else {
+ print('Created dynamic library.');
+ }
+}
+
+ArgResults _getArgResults(List<String> args) {
+ final parser = ArgParser(allowTrailingOptions: true);
+ parser.addSeparator('Generates LLVM Wrapper used by this package:');
+ parser.addMultiOption('include-header',
+ abbr: 'I', help: 'Path to header include directories');
+ parser.addMultiOption('include-lib',
+ abbr: 'L', help: 'Path to library include directories');
+ parser.addFlag(
+ 'help',
+ abbr: 'h',
+ help: 'prints this usage',
+ negatable: false,
+ );
+
+ ArgResults results;
+ try {
+ results = parser.parse(args);
+
+ if (results.wasParsed('help')) {
+ print(parser.usage);
+ exit(0);
+ }
+ } catch (e) {
+ print(e);
+ print(parser.usage);
+ exit(1);
+ }
+
+ return results;
+}
+
+/// Use cmd args(if any) to change header/lib include paths.
+void _changeIncludesUsingCmdArgs(ArgResults argResult, _Options options) {
+ if (argResult.wasParsed('include-header')) {
+ options.headerIncludes = (argResult['include-header'] as List<String>)
+ .map((header) => '-I$header')
+ .toList();
+ }
+ if (argResult.wasParsed('include-lib')) {
+ options.libIncludes = (argResult['include-lib'] as List<String>)
+ .map((lib) => '-L$lib')
+ .toList();
+ }
+}
+
+/// Get options based on current platform.
+_Options _getPlatformOptions() {
+ if (Platform.isMacOS) {
+ return _macOSOpts;
+ } else if (Platform.isWindows) {
+ return _windowsOpts;
+ } else if (Platform.isLinux) {
+ return _linuxOpts;
+ } else {
+ throw Exception('Unknown Platform.');
+ }
+}
+
+/// Hold options which would be passed to clang.
+class _Options {
+ /// Tells compiler to generate a shared library.
+ final String sharedFlag;
+
+ /// Flag for generating Position Independant Code (Not used on windows).
+ final String fPIC;
+
+ /// Input file.
+ final String inputHeader;
+
+ /// Path to `.def` file containing symbols to export, windows use only.
+ final String moduleDefPath;
+
+ /// Path to header files.
+ List<String> headerIncludes;
+
+ /// Path to dynamic/static libraries
+ List<String> libIncludes;
+
+ /// Linker flag for linking to libclang.
+ final String ldLibFlag;
+
+ _Options({
+ required this.sharedFlag,
+ required this.inputHeader,
+ required this.ldLibFlag,
+ this.headerIncludes = const [],
+ this.libIncludes = const [],
+ this.fPIC = '',
+ this.moduleDefPath = '',
+ });
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 2a07001..aaddf64 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.0.0-nullsafety.0
+version: 2.0.0-nullsafety.1
homepage: https://github.com/dart-lang/ffigen
description: Experimental generator for FFI bindings, using LibClang to parse C header files.