blob: 73aa683c5d633878792080117ba5d967300aa16d [file] [log] [blame] [edit]
// 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:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
import '../code_generator.dart';
import '../code_generator/scope.dart';
import '../config_provider.dart';
import '../config_provider/utils.dart';
import '../context.dart';
import '../strings.dart' as strings;
import '../visitor/apply_config_filters.dart';
import '../visitor/ast.dart';
import '../visitor/copy_methods_from_super_type.dart';
import '../visitor/create_scopes.dart';
import '../visitor/fill_method_dependencies.dart';
import '../visitor/find_symbols.dart';
import '../visitor/find_transitive_deps.dart';
import '../visitor/fix_overridden_methods.dart';
import '../visitor/list_bindings.dart';
import '../visitor/mark_imports.dart';
import '../visitor/opaque_compounds.dart';
import '../visitor/sorter.dart';
import 'clang_bindings/clang_bindings.dart' as clang_types;
import 'sub_parsers/macro_parser.dart';
import 'translation_unit_parser.dart';
import 'utils.dart';
/// Main entrypoint for header_parser.
Library parse(Context context) => Library.fromContext(
bindings: transformBindings(parseToBindings(context), context),
context: context,
);
// =============================================================================
// BELOW FUNCTIONS ARE MEANT FOR INTERNAL USE AND TESTING
// =============================================================================
/// Parses source files and returns the bindings.
List<Binding> parseToBindings(Context context) {
final index = clang.clang_createIndex(0, 0);
final config = context.config;
Pointer<Pointer<Utf8>> clangCmdArgs = nullptr;
final compilerOpts = <String>[
// Add compiler opt for comment parsing for clang based on config.
if (config.commentType.length != CommentLength.none &&
config.commentType.style == CommentStyle.any)
strings.fparseAllComments,
// If the config targets Objective C, add a compiler opt for it.
if (config.language == Language.objc) ...[
...strings.clangLangObjC,
..._findObjectiveCSysroot(),
],
// Add the user options last so they can override any other options.
...context.compilerOpts,
];
context.logger.fine('CompilerOpts used: $compilerOpts');
clangCmdArgs = createDynamicStringArray(compilerOpts);
final cmdLen = compilerOpts.length;
// Contains all bindings. A set ensures we never have duplicates.
final bindings = <Binding>{};
// Log all headers for user.
context.logger.info('Input Headers: ${config.entryPoints}');
final tuList = <Pointer<clang_types.CXTranslationUnitImpl>>[];
// Parse all translation units from entry points.
for (final headerLocationUri in config.entryPoints) {
final headerLocation = headerLocationUri.toFilePath();
context.logger.fine('Creating TranslationUnit for header: $headerLocation');
final tu = clang.clang_parseTranslationUnit(
index,
headerLocation.toNativeUtf8().cast(),
clangCmdArgs.cast(),
cmdLen,
nullptr,
0,
clang_types.CXTranslationUnit_Flags.CXTranslationUnit_SkipFunctionBodies |
clang_types
.CXTranslationUnit_Flags
.CXTranslationUnit_DetailedPreprocessingRecord |
clang_types
.CXTranslationUnit_Flags
.CXTranslationUnit_IncludeAttributedTypes,
);
if (tu == nullptr) {
context.logger.severe(
"Skipped header/file: $headerLocation, couldn't parse source.",
);
// Skip parsing this header.
continue;
}
logTuDiagnostics(tu, context, headerLocation);
tuList.add(tu);
}
if (context.hasSourceErrors) {
context.logger.warning(
'The compiler found warnings/errors in source files.',
);
context.logger.warning('This will likely generate invalid bindings.');
if (config.ignoreSourceErrors) {
context.logger.warning(
'Ignored source errors. (User supplied --ignore-source-errors)',
);
} else if (config.language == Language.objc) {
context.logger.warning('Ignored source errors. (ObjC)');
} else {
context.logger.severe(
'Skipped generating bindings due to errors in source files. See https://github.com/dart-lang/native/blob/main/pkgs/ffigen/doc/errors.md.',
);
exit(1);
}
}
final tuCursors = tuList.map(
(tu) => clang.clang_getTranslationUnitCursor(tu),
);
// Build usr to CXCusror map from translation units.
for (final rootCursor in tuCursors) {
buildUsrCursorDefinitionMap(context, rootCursor);
}
// Parse definitions from translation units.
for (final rootCursor in tuCursors) {
bindings.addAll(parseTranslationUnit(context, rootCursor));
}
// Dispose translation units.
for (final tu in tuList) {
clang.clang_disposeTranslationUnit(tu);
}
// Add all saved unnamed enums.
bindings.addAll(context.unnamedEnumConstants);
// Parse all saved macros.
bindings.addAll(parseSavedMacros(context));
clangCmdArgs.dispose(cmdLen);
clang.clang_disposeIndex(index);
return bindings.toList();
}
List<String> _findObjectiveCSysroot() => [
'-isysroot',
firstLineOfStdout('xcrun', ['--show-sdk-path']),
];
@visibleForTesting
List<Binding> transformBindings(List<Binding> bindings, Context context) {
final config = context.config;
final allBindings = visit(
context,
FindTransitiveDepsVisitation(),
bindings,
).transitives;
visit(context, CopyMethodsFromSuperTypesVisitation(), allBindings);
visit(context, FixOverriddenMethodsVisitation(context), allBindings);
visit(context, FillMethodDependenciesVisitation(), allBindings);
final applyConfigFiltersVisitation = ApplyConfigFiltersVisitation(config);
visit(context, applyConfigFiltersVisitation, allBindings);
final directlyIncluded = applyConfigFiltersVisitation.directlyIncluded;
final included = directlyIncluded.union(
applyConfigFiltersVisitation.indirectlyIncluded,
);
final byValueCompounds = visit(
context,
FindByValueCompoundsVisitation(),
FindByValueCompoundsVisitation.rootNodes(included),
).byValueCompounds;
visit(
context,
ClearOpaqueCompoundMembersVisitation(config, byValueCompounds, included),
allBindings,
);
final transitives = visit(
context,
FindTransitiveDepsVisitation(),
included,
).transitives;
final directTransitives = visit(
context,
FindDirectTransitiveDepsVisitation(config, included, directlyIncluded),
included,
).directTransitives;
final finalBindings = visit(
context,
ListBindingsVisitation(config, included, transitives, directTransitives),
bindings,
).bindings;
visit(context, MarkBindingsVisitation(finalBindings), allBindings);
visit(context, MarkImportsVisitation(context), finalBindings);
_nameAllSymbols(context, finalBindings);
/// Sort bindings.
var finalBindingsList = finalBindings.toList();
if (config.sort) {
finalBindingsList = visit(
context,
SorterVisitation(finalBindings, SorterVisitation.nameSortKey),
finalBindings,
).sorted;
}
/// Handle any declaration-declaration name conflicts and emit warnings.
for (final b in finalBindingsList) {
_warnIfPrivateDeclaration(b, context.logger);
}
// Override pack values according to config. We do this after declaration
// conflicts have been handled so that users can target the generated names.
for (final b in finalBindingsList) {
if (b is Struct) {
final pack = config.structs.packingOverride(b);
if (pack != null) {
b.pack = pack.value;
}
}
}
return finalBindingsList;
}
/// Logs a warning if generated declaration will be private.
void _warnIfPrivateDeclaration(Binding b, Logger logger) {
if (b.name.startsWith('_') && !b.isInternal) {
logger.warning(
"Generated declaration '${b.name}' starts with '_' "
'and therefore will be private.',
);
}
}
void _nameAllSymbols(Context context, Set<Binding> bindings) {
final namingOrder = visit(
context,
SorterVisitation(bindings, SorterVisitation.originalNameSortKey),
bindings,
).sorted;
visit(
context,
CreateScopesVisitation(context, bindings, orderedPass: true),
namingOrder,
);
visit(
context,
CreateScopesVisitation(context, bindings, orderedPass: false),
namingOrder,
);
visit(context, FindSymbolsVisitation(context, bindings), namingOrder);
context.extraSymbols = _createExtraSymbols(context);
context.libs.createSymbols(context.rootScope);
context.rootScope.fillNames();
context.rootObjCScope.fillNames();
}
ExtraSymbols _createExtraSymbols(Context context) {
final bindingStyle = context.config.outputStyle;
Symbol? wrapperClassName;
Symbol? lookupFuncName;
if (bindingStyle is DynamicLibraryBindings) {
wrapperClassName = Symbol(bindingStyle.wrapperName);
lookupFuncName = Symbol('_lookup');
}
final extraSymbols = (
wrapperClassName: wrapperClassName,
lookupFuncName: lookupFuncName,
symbolAddressVariableName: Symbol('addresses'),
);
context.rootScope.add(extraSymbols.wrapperClassName);
context.rootScope.add(extraSymbols.lookupFuncName);
context.rootScope.add(extraSymbols.symbolAddressVariableName);
return extraSymbols;
}