| // 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:logging/logging.dart'; | |
| import 'package:package_config/package_config.dart'; | |
| import 'package:yaml/yaml.dart' as yaml; | |
| import '../../ffigen.dart'; | |
| import '../header_parser.dart'; | |
| final _ansi = Ansi(Ansi.terminalSupportsAnsi); | |
| final logger = () { | |
| final l = Logger('ffigen.ffigen'); | |
| l.onRecord.listen((record) { | |
| final levelStr = '[${record.level.name}]'.padRight(9); | |
| final log = '$levelStr: ${record.message}'; | |
| if (record.level < Level.SEVERE) { | |
| print(log); | |
| } else { | |
| print('${_ansi.red}$log${_ansi.none}'); | |
| } | |
| }); | |
| return l; | |
| }(); | |
| const compilerOpts = 'compiler-opts'; | |
| const ignoreSourceErrors = 'ignore-source-errors'; | |
| const format = 'format'; | |
| const conf = 'config'; | |
| const help = 'help'; | |
| const verbose = 'verbose'; | |
| const pubspecName = 'pubspec.yaml'; | |
| const configKey = 'ffigen'; | |
| const logAll = 'all'; | |
| const logFine = 'fine'; | |
| const logInfo = 'info'; | |
| const logWarning = 'warning'; | |
| const logSevere = 'severe'; | |
| Future<void> main(List<String> args) async { | |
| // Parses the cmd args. This will print usage and exit if --help was passed. | |
| final argResult = getArgResults(args); | |
| Logger.root.level = _parseLogLevel(argResult); | |
| // Create a config object. | |
| FfiGenerator generator; | |
| try { | |
| generator = getGenerator( | |
| argResult, | |
| await findPackageConfig(Directory.current), | |
| ); | |
| } on FormatException { | |
| logger.severe('Please fix configuration errors and re-run the tool.'); | |
| exit(1); | |
| } | |
| try { | |
| await generator.generate(logger: logger); | |
| } on HeaderParserException { | |
| exit(1); | |
| } | |
| } | |
| FfiGenerator getGenerator(ArgResults result, PackageConfig? packageConfig) { | |
| logger.info('Running in ${Directory.current}'); | |
| YamlConfig config; | |
| // Parse config from yaml. | |
| if (result.wasParsed(conf)) { | |
| config = getConfigFromCustomYaml(result[conf] as String, packageConfig); | |
| } else { | |
| config = getConfigFromPubspec(packageConfig); | |
| } | |
| // Add compiler options from command line. | |
| if (result.wasParsed(compilerOpts)) { | |
| logger.fine('Passed compiler opts - "${result[compilerOpts]}"'); | |
| config.addCompilerOpts(result[compilerOpts] as String, highPriority: true); | |
| } | |
| if (result.wasParsed(ignoreSourceErrors)) { | |
| config.ignoreSourceErrors = true; | |
| } | |
| config.formatOutput = result[format] as bool; | |
| return config.configAdapter(); | |
| } | |
| /// Extracts configuration from pubspec file. | |
| YamlConfig getConfigFromPubspec(PackageConfig? packageConfig) { | |
| 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 pubspecYaml = | |
| yaml.loadYaml(pubspecFile.readAsStringSync()) as yaml.YamlMap; | |
| final bindingsConfigMap = pubspecYaml[configKey] as yaml.YamlMap?; | |
| if (bindingsConfigMap == null) { | |
| logger.severe("Couldn't find an entry for '$configKey' in $pubspecName."); | |
| exit(1); | |
| } | |
| return YamlConfig.fromYaml( | |
| bindingsConfigMap, | |
| logger, | |
| filename: pubspecFile.path, | |
| packageConfig: packageConfig, | |
| ); | |
| } | |
| /// Extracts configuration from a custom yaml file. | |
| YamlConfig getConfigFromCustomYaml( | |
| String yamlPath, | |
| PackageConfig? packageConfig, | |
| ) { | |
| final yamlFile = File(yamlPath); | |
| if (!yamlFile.existsSync()) { | |
| logger.severe('Error: $yamlPath not found.'); | |
| exit(1); | |
| } | |
| return YamlConfig.fromFile(yamlFile, logger, packageConfig: packageConfig); | |
| } | |
| /// 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( | |
| conf, | |
| help: 'Path to Yaml file containing configurations if not in pubspec.yaml', | |
| ); | |
| parser.addOption( | |
| verbose, | |
| abbr: 'v', | |
| defaultsTo: logInfo, | |
| allowed: [logAll, logFine, logInfo, logWarning, logSevere], | |
| ); | |
| parser.addFlag(help, abbr: 'h', help: 'Prints this usage', negatable: false); | |
| parser.addOption( | |
| compilerOpts, | |
| help: 'Compiler options for clang. (E.g --$compilerOpts "-I/headers -W")', | |
| ); | |
| parser.addFlag( | |
| ignoreSourceErrors, | |
| help: 'Ignore any compiler warnings/errors in source header files', | |
| negatable: false, | |
| ); | |
| parser.addFlag( | |
| format, | |
| help: 'Format the generated code.', | |
| defaultsTo: true, | |
| negatable: true, | |
| ); | |
| 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; | |
| } | |
| Level _parseLogLevel(ArgResults result) { | |
| switch (result[verbose] as String?) { | |
| case logAll: | |
| // Logs everything, the entire AST touched by our parser. | |
| return Level.ALL; | |
| case logFine: | |
| // Logs AST parts relevant to user (i.e those included in filters). | |
| return Level.FINE; | |
| case logInfo: | |
| // Logs relevant info for general user (default). | |
| return Level.INFO; | |
| case logWarning: | |
| // Logs warnings for relevant stuff. | |
| return Level.WARNING; | |
| case logSevere: | |
| // Logs severe warnings and errors. | |
| return Level.SEVERE; | |
| default: | |
| return Level.INFO; | |
| } | |
| } |