blob: 0471ece741a6e10c3dc1052551aa4748cdf3e6ec [file] [log] [blame] [edit]
// 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';
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);
}
generator.generate(logger: logger);
}
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;
}
}