| // Copyright (c) 2025, 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:logging/logging.dart'; | |
| import 'code_generator.dart'; | |
| import 'code_generator/scope.dart'; | |
| import 'config_provider/config.dart'; | |
| import 'config_provider/config_types.dart'; | |
| import 'config_provider/spec_utils.dart'; | |
| import 'header_parser/clang_bindings/clang_bindings.dart' show Clang; | |
| import 'header_parser/utils.dart'; | |
| /// Wrapper around various FFIgen-wide variables. | |
| class Context { | |
| final Logger logger; | |
| final FfiGenerator config; | |
| final CursorIndex cursorIndex; | |
| final bindingsIndex = BindingsIndex(); | |
| final savedMacros = <String, Macro>{}; | |
| final unnamedEnumConstants = <UnnamedEnumConstant>[]; | |
| late final ObjCBuiltInFunctions objCBuiltInFunctions; | |
| bool hasSourceErrors = false; | |
| final reportedCommentRanges = <((String, int), (String, int))>{}; | |
| final libs = LibraryImports(); | |
| late final compilerOpts = | |
| config.input.compilerOptions ?? defaultCompilerOpts(logger); | |
| final Scope rootScope = Scope.createRoot('root'); | |
| final Scope rootObjCScope = Scope.createRoot('objc_root'); | |
| late final ExtraSymbols extraSymbols; | |
| final String tmpDir; | |
| Context(this.logger, this.config, {Uri? libclangDylib, String? tmpDir}) | |
| : cursorIndex = CursorIndex(logger), | |
| tmpDir = | |
| tmpDir ?? | |
| Directory.systemTemp.createTempSync('ffigen temp dir ').path { | |
| objCBuiltInFunctions = ObjCBuiltInFunctions( | |
| this, | |
| // ignore: deprecated_member_use_from_same_package | |
| config.objectiveC?.generateForPackageObjectiveC ?? false, | |
| ); | |
| final libclangDylibPath = | |
| // ignore: deprecated_member_use_from_same_package | |
| config.libclangDylib?.toFilePath() ?? | |
| libclangDylib?.toFilePath() ?? | |
| findDylibAtDefaultLocations(logger); | |
| _clang ??= Clang(DynamicLibrary.open(libclangDylibPath)); | |
| } | |
| } | |
| /// The clang bindings. | |
| // | |
| // Ideally this would be in the Context, but the plumbing needed would be | |
| // excessive. The point of putting globals in the Context is to allow multiple | |
| // concurrent FFIgen runs without any risk of clobbering global state. The | |
| // clang bindings are loaded from a dylib specified by the config, so it's | |
| // possible that two different versions of clang could be loaded. But since we | |
| // interact with clang through a stable API, there's no real danger of version | |
| // skew. So the safest thing is to simply load clang the first time a Context | |
| // is created, and reuse it for all subsequent runs. | |
| Clang get clang => _clang!; | |
| Clang? _clang; | |
| class LibraryImports { | |
| String get ffiLibraryPrefix => prefix(ffiImport); | |
| String get ffiPkgLibraryPrefix => prefix(ffiPkgImport); | |
| String get objcPkgPrefix => prefix(objcPkgImport); | |
| String get selfImportPrefix => prefix(selfImport); | |
| // Dedupe [lib] by name. | |
| LibraryImport canonicalize(LibraryImport lib) => | |
| builtInLibraries[lib.name] ?? (_canonicalImports[lib.name] ??= lib); | |
| final _canonicalImports = <String, LibraryImport>{}; | |
| // Mark an import as being used so that it can be assigned a prefix. | |
| void markUsed(LibraryImport lib) { | |
| assert(lib == canonicalize(lib)); | |
| _used.add(lib); | |
| } | |
| Iterable<LibraryImport> get used => _used; | |
| final _used = <LibraryImport>{}; | |
| // Call after all used imports have been marked by [markUsed]. Creates Symbols | |
| // for all the library prefixes used for codegen. | |
| void createSymbols(Scope scope) { | |
| for (final lib in _used) { | |
| scope.add(_prefixes[lib] = Symbol(lib.name, SymbolKind.lib)); | |
| } | |
| _prefixesFilled = true; | |
| } | |
| bool _prefixesFilled = false; | |
| final _prefixes = <LibraryImport, Symbol>{}; | |
| String prefix(LibraryImport lib) { | |
| assert(lib == canonicalize(lib)); | |
| if (!_prefixesFilled) { | |
| // Before the prefixes have been filled, return a placeholder suitable for | |
| // debugging or hashing, but intentionally invalid as generated code. | |
| return '<${lib.name}>'; | |
| } | |
| // If this null assert fails, it means that a library was used during code | |
| // generation that wasn't visited by MarkImportsVisitation, which is a bug. | |
| return _prefixes[lib]!.name; | |
| } | |
| void forceFillForTesting() { | |
| _used.addAll(builtInLibraries.values); | |
| for (final lib in _used) { | |
| _prefixes[lib] = Symbol(lib.name, SymbolKind.lib)..forceFillForTesting(); | |
| } | |
| _prefixesFilled = true; | |
| } | |
| } | |
| typedef ExtraSymbols = ({ | |
| Symbol? wrapperClassName, | |
| Symbol? lookupFuncName, | |
| // TODO(https://github.com/dart-lang/native/issues/1259): Make this nullable. | |
| Symbol symbolAddressVariableName, | |
| }); |