blob: 5acd29eaa81dcd3a0bee2848802d7b0606ec329d [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';
import 'package:ffigen/src/code_generator.dart';
import 'package:file/local.dart';
import 'package:glob/glob.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:quiver/pattern.dart' as quiver;
import 'package:yaml/yaml.dart';
import '../strings.dart' as strings;
import 'config_types.dart';
final _logger = Logger('ffigen.config_provider.spec_utils');
/// Replaces the path separators according to current platform.
String _replaceSeparators(String path) {
if (Platform.isWindows) {
return path.replaceAll(p.posix.separator, p.windows.separator);
} else {
return path.replaceAll(p.windows.separator, p.posix.separator);
}
}
/// Checks if type of value is [T], logs an error if it's not.
///
/// [key] is printed as `'item1 -> item2 => item3'` in log message.
bool checkType<T>(List<String> keys, dynamic value) {
if (value is! T) {
_logger.severe(
"Expected value of key '${keys.join(' -> ')}' to be of type '$T'.");
return false;
}
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) =>
checkType<bool>(name, value);
Map<int, SupportedNativeType> sizemapExtractor(dynamic yamlConfig) {
final resultMap = <int, SupportedNativeType>{};
final sizemap = yamlConfig as YamlMap?;
if (sizemap != null) {
for (final typeName in strings.sizemap_native_mapping.keys) {
if (sizemap.containsKey(typeName)) {
final cxTypeInt = strings.sizemap_native_mapping[typeName] as int;
final byteSize = sizemap[typeName] as int;
resultMap[cxTypeInt] = nativeSupportedType(byteSize,
signed: typeName.contains('unsigned') ? false : true);
}
}
}
return resultMap;
}
bool sizemapValidator(List<String> name, dynamic yamlConfig) {
if (!checkType<YamlMap>(name, yamlConfig)) {
return false;
}
for (final key in (yamlConfig as YamlMap).keys) {
if (!strings.sizemap_native_mapping.containsKey(key)) {
_logger.warning("Unknown subkey '$key' in '$name'.");
}
}
return true;
}
Map<String, SupportedNativeType> typedefmapExtractor(dynamic yamlConfig) {
final resultMap = <String, SupportedNativeType>{};
final typedefmap = yamlConfig as YamlMap?;
if (typedefmap != null) {
for (final typeName in typedefmap.keys) {
if (typedefmap[typeName] is String &&
strings.supportedNativeType_mappings
.containsKey(typedefmap[typeName])) {
// Map this typename to specified supportedNativeType.
resultMap[typeName as String] =
strings.supportedNativeType_mappings[typedefmap[typeName]]!;
}
}
}
return resultMap;
}
bool typedefmapValidator(List<String> name, dynamic yamlConfig) {
if (!checkType<YamlMap>(name, yamlConfig)) {
return false;
}
for (final value in (yamlConfig as YamlMap).values) {
if (value is! String ||
!strings.supportedNativeType_mappings.containsKey(value)) {
_logger.severe("Unknown value of subkey '$value' in '$name'.");
}
}
return true;
}
final _quoteMatcher = RegExp(r'''^["'](.*)["']$''', dotAll: true);
final _cmdlineArgMatcher = RegExp(r'''['"](\\"|[^"])*?['"]|[^ ]+''');
List<String> compilerOptsToList(String compilerOpts) {
final list = <String>[];
_cmdlineArgMatcher.allMatches(compilerOpts).forEach((element) {
var match = element.group(0);
if (match != null) {
if (quiver.matchesFull(_quoteMatcher, match)) {
match = _quoteMatcher.allMatches(match).first.group(1)!;
}
list.add(match);
}
});
return list;
}
List<String> compilerOptsExtractor(dynamic value) {
if (value is String) {
return compilerOptsToList(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>[];
final includeGlobs = <quiver.Glob>[];
for (final key in (yamlConfig as YamlMap).keys) {
if (key == strings.entryPoints) {
for (final h in (yamlConfig[key] as YamlList)) {
final headerGlob = h as String;
// Add file directly to header if it's not a Glob but a File.
if (File(headerGlob).existsSync()) {
final osSpecificPath = _replaceSeparators(headerGlob);
entryPoints.add(osSpecificPath);
_logger.fine('Adding header/file: $headerGlob');
} else {
final glob = Glob(headerGlob);
for (final file in glob.listFileSystemSync(const LocalFileSystem(),
followLinks: true)) {
final fixedPath = _replaceSeparators(file.path);
entryPoints.add(fixedPath);
_logger.fine('Adding header/file: $fixedPath');
}
}
}
}
if (key == strings.includeDirectives) {
for (final h in (yamlConfig[key] as YamlList)) {
final headerGlob = h as String;
includeGlobs.add(quiver.Glob(headerGlob));
}
}
}
return Headers(
entryPoints: entryPoints,
includeFilter: GlobHeaderFilter(
includeGlobs: includeGlobs,
),
);
}
bool headersValidator(List<String> name, dynamic value) {
if (!checkType<YamlMap>(name, value)) {
return false;
}
if (!(value as YamlMap).containsKey(strings.entryPoints)) {
_logger.severe("Required '$name -> ${strings.entryPoints}'.");
return false;
} else {
for (final key in value.keys) {
if (key == strings.entryPoints || key == strings.includeDirectives) {
if (!checkType<YamlList>([...name, key as String], value[key])) {
return false;
}
} else {
_logger.severe("Unknown key '$key' in '$name'.");
return false;
}
}
return true;
}
}
String libclangDylibExtractor(dynamic value) => getDylibPath(value as String);
bool libclangDylibValidator(List<String> name, dynamic value) {
if (!checkType<String>(name, value)) {
return false;
} else {
final dylibPath = getDylibPath(value as String);
if (!File(dylibPath).existsSync()) {
_logger.severe(
'Dynamic library: $dylibPath does not exist or is corrupt, input folder: $value.');
return false;
} else {
return true;
}
}
}
String getDylibPath(String dylibParentFoler) {
dylibParentFoler = _replaceSeparators(dylibParentFoler);
String dylibPath;
if (Platform.isMacOS) {
dylibPath = p.join(dylibParentFoler, strings.libclang_dylib_macos);
} else if (Platform.isWindows) {
dylibPath = p.join(dylibParentFoler, strings.libclang_dylib_windows);
} else {
dylibPath = p.join(dylibParentFoler, strings.libclang_dylib_linux);
}
return dylibPath;
}
/// Returns location of dynamic library by searching default locations. Logs
/// error and throws an Exception if not found.
String findDylibAtDefaultLocations() {
String? k;
if (Platform.isLinux) {
for (final l in strings.linuxDylibLocations) {
k = findLibclangDylib(l);
if (k != null) return k;
}
} else if (Platform.isWindows) {
for (final l in strings.windowsDylibLocations) {
k = findLibclangDylib(l);
if (k != null) return k;
}
} else if (Platform.isMacOS) {
for (final l in strings.macOsDylibLocations) {
k = findLibclangDylib(l);
if (k != null) return k;
}
} else {
throw Exception('Unsupported Platform.');
}
_logger.severe("Couldn't find dynamic library in default locations.");
_logger.severe(
"Please supply one or more path/to/llvm in ffigen's config under the key '${strings.llvmPath}'.");
throw Exception("Couldn't find dynamic library in default locations.");
}
String? findLibclangDylib(String parentFolder) {
final location = p.join(parentFolder, strings.dylibFileName);
if (File(location).existsSync()) {
return location;
} else {
return null;
}
}
String llvmPathExtractor(dynamic value) {
// Extract libclang's dylib from user specified paths.
for (final path in (value as YamlList)) {
if (path is! String) continue;
final dylibPath =
findLibclangDylib(p.join(path, strings.dynamicLibParentName));
if (dylibPath != null) {
_logger.fine('Found dynamic library at: $dylibPath');
return dylibPath;
}
// Check if user has specified complete path to dylib.
final completeDylibPath = path;
if (p.extension(completeDylibPath).isNotEmpty &&
File(completeDylibPath).existsSync()) {
_logger.info(
'Using complete dylib path: $completeDylibPath from llvm-path.');
return completeDylibPath;
}
}
_logger.fine(
"Couldn't find dynamic library under paths specified by ${strings.llvmPath}.");
// Extract path from default locations.
try {
final res = findDylibAtDefaultLocations();
return res;
} catch (e) {
_logger.severe(
"Couldn't find ${p.join(strings.dynamicLibParentName, strings.dylibFileName)} in specified locations.");
exit(1);
}
}
bool llvmPathValidator(List<String> name, dynamic value) {
if (!checkType<YamlList>(name, value)) {
return false;
}
return true;
}
String outputExtractor(dynamic value) => _replaceSeparators(value as String);
bool outputValidator(List<String> name, dynamic value) =>
checkType<String>(name, value);
/// Returns true if [str] is not a full name.
///
/// E.g `abc` is a full name, `abc.*` is not.
bool isFullDeclarationName(String str) =>
quiver.matchesFull(RegExp('[a-zA-Z_0-9]*'), str);
Includer _extractIncluderFromYaml(dynamic yamlMap) {
final includeMatchers = <RegExp>[],
includeFull = <String>{},
excludeMatchers = <RegExp>[],
excludeFull = <String>{};
final include = (yamlMap[strings.include] as YamlList?)?.cast<String>();
if (include != null) {
for (final str in include) {
if (isFullDeclarationName(str)) {
includeFull.add(str);
} else {
includeMatchers.add(RegExp(str, dotAll: true));
}
}
}
final exclude = (yamlMap[strings.exclude] as YamlList?)?.cast<String>();
if (exclude != null) {
for (final str in exclude) {
if (isFullDeclarationName(str)) {
excludeFull.add(str);
} else {
excludeMatchers.add(RegExp(str, dotAll: true));
}
}
}
return Includer(
includeMatchers: includeMatchers,
includeFull: includeFull,
excludeMatchers: excludeMatchers,
excludeFull: excludeFull,
);
}
Declaration declarationConfigExtractor(dynamic yamlMap) {
final renamePatterns = <RegExpRenamer>[];
final renameFull = <String, String>{};
final memberRenamePatterns = <RegExpMemberRenamer>[];
final memberRenamerFull = <String, Renamer>{};
final includer = _extractIncluderFromYaml(yamlMap);
Includer? symbolIncluder;
if (yamlMap[strings.symbolAddress] != null) {
symbolIncluder = _extractIncluderFromYaml(yamlMap[strings.symbolAddress]);
}
final rename = (yamlMap[strings.rename] as YamlMap?)?.cast<String, String>();
if (rename != null) {
for (final str in rename.keys) {
if (isFullDeclarationName(str)) {
renameFull[str] = rename[str]!;
} else {
renamePatterns
.add(RegExpRenamer(RegExp(str, dotAll: true), rename[str]!));
}
}
}
final memberRename =
(yamlMap[strings.memberRename] as YamlMap?)?.cast<String, YamlMap>();
if (memberRename != null) {
for (final decl in memberRename.keys) {
final renamePatterns = <RegExpRenamer>[];
final renameFull = <String, String>{};
final memberRenameMap = memberRename[decl]!.cast<String, String>();
for (final member in memberRenameMap.keys) {
if (isFullDeclarationName(member)) {
renameFull[member] = memberRenameMap[member]!;
} else {
renamePatterns.add(RegExpRenamer(
RegExp(member, dotAll: true), memberRenameMap[member]!));
}
}
if (isFullDeclarationName(decl)) {
memberRenamerFull[decl] = Renamer(
renameFull: renameFull,
renamePatterns: renamePatterns,
);
} else {
memberRenamePatterns.add(
RegExpMemberRenamer(
RegExp(decl, dotAll: true),
Renamer(
renameFull: renameFull,
renamePatterns: renamePatterns,
),
),
);
}
}
}
return Declaration(
includer: includer,
renamer: Renamer(
renameFull: renameFull,
renamePatterns: renamePatterns,
),
memberRenamer: MemberRenamer(
memberRenameFull: memberRenamerFull,
memberRenamePattern: memberRenamePatterns,
),
symbolAddressIncluder: symbolIncluder,
);
}
bool declarationConfigValidator(List<String> name, dynamic value) {
var _result = true;
if (value is YamlMap) {
for (final key in value.keys) {
if (key == strings.include || key == strings.exclude) {
if (!checkType<YamlList>([...name, key as String], value[key])) {
_result = false;
}
} else if (key == strings.rename) {
if (!checkType<YamlMap>([...name, key as String], value[key])) {
_result = false;
} else {
for (final subkey in value[key].keys) {
if (!checkType<String>(
[...name, key, subkey as String], value[key][subkey])) {
_result = false;
}
}
}
} else if (key == strings.memberRename) {
if (!checkType<YamlMap>([...name, key as String], value[key])) {
_result = false;
} else {
for (final declNameKey in value[key].keys) {
if (!checkType<YamlMap>([...name, key, declNameKey as String],
value[key][declNameKey])) {
_result = false;
} else {
for (final memberNameKey in value[key][declNameKey].keys) {
if (!checkType<String>([
...name,
key,
declNameKey,
memberNameKey as String,
], value[key][declNameKey][memberNameKey])) {
_result = false;
}
}
}
}
}
} else if (key == strings.symbolAddress) {
if (!checkType<YamlMap>([...name, key as String], value[key])) {
_result = false;
} else {
for (final subkey in value[key].keys) {
if (subkey == strings.include || subkey == strings.exclude) {
if (!checkType<YamlList>(
[...name, key, subkey as String], value[key][subkey])) {
_result = false;
}
} else {
_logger.severe("Unknown key '$subkey' in '$name -> $key'.");
_result = false;
}
}
}
}
}
} else {
_logger.severe("Expected value '$name' to be a Map.");
_result = false;
}
return _result;
}
Includer exposeFunctionTypeExtractor(dynamic value) =>
_extractIncluderFromYaml(value);
bool exposeFunctionTypeValidator(List<String> name, dynamic value) {
var _result = true;
if (!checkType<YamlMap>(name, value)) {
_result = false;
} else {
final mp = value as YamlMap;
for (final key in mp.keys) {
if (key == strings.include || key == strings.exclude) {
if (!checkType<YamlList>([...name, key as String], value[key])) {
_result = false;
}
} else {
_logger.severe("Unknown subkey '$key' in '$name'.");
_result = false;
}
}
}
return _result;
}
SupportedNativeType nativeSupportedType(int value, {bool signed = true}) {
switch (value) {
case 1:
return signed ? SupportedNativeType.Int8 : SupportedNativeType.Uint8;
case 2:
return signed ? SupportedNativeType.Int16 : SupportedNativeType.Uint16;
case 4:
return signed ? SupportedNativeType.Int32 : SupportedNativeType.Uint32;
case 8:
return signed ? SupportedNativeType.Int64 : SupportedNativeType.Uint64;
default:
throw Exception(
'Unsupported value given to sizemap, Allowed values for sizes are: 1, 2, 4, 8');
}
}
String stringExtractor(dynamic value) => value as String;
bool nonEmptyStringValidator(List<String> name, dynamic value) {
if (value is String && value.isNotEmpty) {
return true;
} else {
_logger.severe("Expected value of key '$name' to be a non-empty String.");
return false;
}
}
bool dartClassNameValidator(List<String> name, dynamic value) {
if (value is String &&
quiver.matchesFull(RegExp('[a-zA-Z]+[_a-zA-Z0-9]*'), value)) {
return true;
} else {
_logger.severe(
"Expected value of key '$name' to be a valid public class name.");
return false;
}
}
CommentType commentExtractor(dynamic value) {
if (value is bool) {
if (value) {
return CommentType.def();
} else {
return CommentType.none();
}
}
final ct = CommentType.def();
if (value is YamlMap) {
for (final key in value.keys) {
if (key == strings.style) {
if (value[key] == strings.any) {
ct.style = CommentStyle.any;
} else if (value[key] == strings.doxygen) {
ct.style = CommentStyle.doxygen;
}
} else if (key == strings.length) {
if (value[key] == strings.full) {
ct.length = CommentLength.full;
} else if (value[key] == strings.brief) {
ct.length = CommentLength.brief;
}
}
}
}
return ct;
}
bool commentValidator(List<String> name, dynamic value) {
if (value is bool) {
return true;
} else if (value is YamlMap) {
var result = true;
for (final key in value.keys) {
if (key == strings.style) {
if (value[key] is! String ||
!(value[key] == strings.doxygen || value[key] == strings.any)) {
_logger.severe(
"'$name'>'${strings.style}' must be one of the following - {${strings.doxygen}, ${strings.any}}");
result = false;
}
} else if (key == strings.length) {
if (value[key] is! String ||
!(value[key] == strings.brief || value[key] == strings.full)) {
_logger.severe(
"'$name'>'${strings.length}' must be one of the following - {${strings.brief}, ${strings.full}}");
result = false;
}
} else {
_logger.severe("Unknown key '$key' in '$name'.");
result = false;
}
}
return result;
} else {
_logger.severe("Expected value of key '$name' to be a bool or a Map.");
return false;
}
}
CompoundDependencies dependencyOnlyExtractor(dynamic value) {
var result = CompoundDependencies.full;
if (value == strings.opaqueCompoundDependencies) {
result = CompoundDependencies.opaque;
}
return result;
}
bool dependencyOnlyValidator(List<String> name, dynamic value) {
var result = true;
if (value is! String ||
!(value == strings.fullCompoundDependencies ||
value == strings.opaqueCompoundDependencies)) {
_logger.severe(
"'$name' must be one of the following - {${strings.fullCompoundDependencies}, ${strings.opaqueCompoundDependencies}}");
result = false;
}
return result;
}
StructPackingOverride structPackingOverrideExtractor(dynamic value) {
final matcherMap = <RegExp, int?>{};
for (final key in value.keys) {
matcherMap[RegExp(key as String, dotAll: true)] =
strings.packingValuesMap[value[key]];
}
return StructPackingOverride(matcherMap: matcherMap);
}
bool structPackingOverrideValidator(List<String> name, dynamic value) {
var _result = true;
if (!checkType<YamlMap>([...name], value)) {
_result = false;
} else {
for (final key in value.keys) {
if (!(strings.packingValuesMap.keys.contains(value[key]))) {
_logger.severe(
"'$name -> $key' must be one of the following - ${strings.packingValuesMap.keys.toList()}");
_result = false;
}
}
}
return _result;
}