blob: 89f55c204a0497dd8728939f85d5f99b2bda6eb4 [file] [log] [blame]
// 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.
import 'dart:io';
/// Validates the yaml input by the user, prints useful info for the user
import 'package:ffigen/src/code_generator.dart';
import 'package:ffigen/src/header_parser/type_extractor/cxtypekindmap.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
import '../strings.dart' as strings;
import 'filter.dart';
import 'spec_utils.dart';
var _logger = Logger('config_provider/config');
/// Provides configurations to other modules.
///
/// Handles validation, extraction of confiurations from yaml file.
class Config {
/// output file name.
String output;
/// libclang path (in accordance with the platform).
///
/// File may have the following extensions - `.so` / `.dll` / `.dylib`
/// as extracted by configspec.
String libclang_dylib_path;
/// Path to headers.
///
/// This contains all the headers, after extraction from Globs.
List<String> headers;
/// Filter for headers.
HeaderFilter headerFilter;
/// CommandLine Arguments to pass to clang_compiler.
List<String> compilerOpts;
/// Filter for functions.
Filter functionFilters;
/// Filter for structs.
Filter structFilters;
/// Filter for enumClass.
Filter enumClassFilters;
/// If generated bindings should be sorted alphabetically.
bool sort;
/// If typedef of supported types(int8_t) should be directly used.
bool useSupportedTypedefs;
/// If tool should extract doc comment from bindings.
bool extractComments;
/// Manually creating configurations.
///
/// Use [Config.fromYaml] if extracting info from a yaml file.
/// Ensure that log printing is setup before using this.
Config.raw({
this.output,
@required this.libclang_dylib_path,
@required this.headers,
this.headerFilter,
this.compilerOpts,
this.functionFilters,
this.structFilters,
this.enumClassFilters,
this.sort = false,
this.useSupportedTypedefs = true,
this.extractComments = true,
});
Config._();
/// Create config from Yaml map.
///
/// Ensure that log printing is setup before using this.
factory Config.fromYaml(YamlMap map) {
final configspecs = Config._();
_logger.finest('Config Map: ' + map.toString());
final specs = configspecs._getSpecs();
final result = configspecs._checkConfigs(map, specs);
if (!result) {
_logger.info('Please fix errors in Configurations and re-run the tool');
exit(1);
}
configspecs._extract(map, specs);
return configspecs;
}
/// Validates Yaml according to given specs.
bool _checkConfigs(YamlMap map, Map<String, Specification> specs) {
var _result = true;
for (final key in specs.keys) {
final spec = specs[key];
if (spec.isRequired && !map.containsKey(key)) {
_logger.severe("Key '${key}' is required.");
_result = false;
} else if (map.containsKey(key)) {
_result = _result && spec.validator(key, map[key]);
}
}
// Warn about unknown keys.
for (final key in map.keys) {
if (!specs.containsKey(key)) {
_logger.warning("Unknown key '$key' found.");
}
}
return _result;
}
/// Extracts variables from Yaml according to given specs.
///
/// Validation must be done beforehand, using [_checkConfigs].
void _extract(YamlMap map, Map<String, Specification> specs) {
for (final key in specs.keys) {
final spec = specs[key];
if (map.containsKey(key)) {
spec.extractedResult(spec.extractor(map[key]));
} else {
spec.extractedResult(spec.defaultValue);
}
}
}
/// Returns map of various specifications avaialble for our tool.
///
/// Key: Name, Value: [Specification]
Map<String, Specification> _getSpecs() {
return <String, Specification>{
strings.output: Specification<String>(
description: 'Output file name',
isRequired: true,
validator: outputValidator,
extractor: outputExtractor,
defaultValue: null,
extractedResult: (dynamic result) => output = result as String,
),
strings.libclang_dylib_folder: Specification<String>(
description:
'Path to folder containing libclang dynamic library, used to parse C headers',
isRequired: false,
defaultValue: getDylibPath(Platform.script
.resolve(path.join('..', 'tool', 'wrapped_libclang'))
.toFilePath()),
validator: libclangDylibValidator,
extractor: libclangDylibExtractor,
extractedResult: (dynamic result) =>
libclang_dylib_path = result as String,
),
strings.headers: Specification<List<String>>(
description: 'List of C headers to generate bindings of',
isRequired: true,
validator: headersValidator,
extractor: headersExtractor,
extractedResult: (dynamic result) => headers = result as List<String>,
),
strings.headerFilter: Specification<HeaderFilter>(
description: 'Include/Exclude inclusion headers',
validator: headerFilterValidator,
extractor: headerFilterExtractor,
defaultValue: HeaderFilter(),
extractedResult: (dynamic result) {
return headerFilter = result as HeaderFilter;
},
),
strings.compilerOpts: Specification<List<String>>(
description: 'Raw compiler options to pass to clang compiler',
isRequired: false,
validator: compilerOptsValidator,
extractor: compilerOptsExtractor,
defaultValue: null,
extractedResult: (dynamic result) =>
compilerOpts = result as List<String>,
),
strings.functions: Specification<Filter>(
description: 'Filter for functions',
isRequired: false,
validator: filterValidator,
extractor: filterExtractor,
defaultValue: null,
extractedResult: (dynamic result) => functionFilters = result as Filter,
),
strings.structs: Specification<Filter>(
description: 'Filter for Structs',
isRequired: false,
validator: filterValidator,
extractor: filterExtractor,
defaultValue: null,
extractedResult: (dynamic result) => structFilters = result as Filter,
),
strings.enums: Specification<Filter>(
description: 'Filter for enums',
isRequired: false,
validator: filterValidator,
extractor: filterExtractor,
defaultValue: null,
extractedResult: (dynamic result) =>
enumClassFilters = result as Filter,
),
strings.sizemap: Specification<Map<int, SupportedNativeType>>(
description: 'map of types: byte size in int',
validator: sizemapValidator,
extractor: sizemapExtractor,
defaultValue: <int, SupportedNativeType>{},
extractedResult: (dynamic result) {
final map = result as Map<int, SupportedNativeType>;
for (final key in map.keys) {
if (cxTypeKindToSupportedNativeTypes.containsKey(key)) {
cxTypeKindToSupportedNativeTypes[key] = map[key];
}
}
},
),
strings.sort: Specification<bool>(
description: 'whether or not to sort the bindings alphabetically',
isRequired: false,
validator: booleanValidator,
extractor: booleanExtractor,
defaultValue: false,
extractedResult: (dynamic result) => sort = result as bool,
),
strings.useSupportedTypedefs: Specification<bool>(
description: 'whether or not to directly map supported typedef by name',
isRequired: false,
validator: booleanValidator,
extractor: booleanExtractor,
defaultValue: true,
extractedResult: (dynamic result) =>
useSupportedTypedefs = result as bool,
),
strings.extractComments: Specification<bool>(
description: 'whether or not to extract comments from bindings',
isRequired: false,
validator: booleanValidator,
extractor: booleanExtractor,
defaultValue: true,
extractedResult: (dynamic result) => extractComments = result as bool,
),
};
}
}
/// Represents a single specification in configurations.
///
/// [E] is the return type of the extractedResult.
class Specification<E> {
final String description;
final bool Function(String name, dynamic value) validator;
final E Function(dynamic map) extractor;
final E defaultValue;
final bool isRequired;
final void Function(dynamic result) extractedResult;
Specification({
@required this.extractedResult,
@required this.description,
@required this.validator,
@required this.extractor,
this.defaultValue,
this.isRequired = false,
});
}
class HeaderFilter {
Set<String> includedInclusionHeaders;
Set<String> excludedInclusionHeaders;
HeaderFilter({
this.includedInclusionHeaders = const {},
this.excludedInclusionHeaders = const {},
});
}