| // 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. |
| |
| import 'dart:async'; |
| |
| import '../api.dart'; |
| import '../executor.dart'; |
| import '../executor/augmentation_library.dart'; |
| |
| /// A [MacroExecutor] implementation which delegates most work to other |
| /// executors which are spawned through a provided callback. |
| class MultiMacroExecutor extends MacroExecutor with AugmentationLibraryBuilder { |
| /// Executors by [MacroInstanceIdentifier]. |
| /// |
| /// Using an expando means we don't have to worry about cleaning up instances |
| /// for executors that were shut down. |
| final Expando<ExecutorFactoryToken> _instanceExecutors = Expando(); |
| |
| /// Registered factories for starting up a new macro executor for a library. |
| final Map<Uri, ExecutorFactoryToken> _libraryExecutorFactories = {}; |
| |
| /// All known registered executor factories. |
| final Set<ExecutorFactoryToken> _executorFactoryTokens = {}; |
| |
| /// Whether or not an executor factory for [library] is currently registered. |
| bool libraryIsRegistered(Uri library) => |
| _libraryExecutorFactories.containsKey(library); |
| |
| /// Registers a [factory] which can produce a [MacroExecutor] that can be |
| /// used to run any macro defined in [libraries]. |
| /// |
| /// Throws an [ArgumentError] if a library in [libraries] already has a |
| /// factory registered. |
| /// |
| /// Returns a token which can be used to shut down any executors spawned in |
| /// this way via [unregisterExecutorFactory]. |
| ExecutorFactoryToken registerExecutorFactory( |
| FutureOr<MacroExecutor> Function() factory, Set<Uri> libraries) { |
| ExecutorFactoryToken token = ExecutorFactoryToken._(factory, libraries); |
| _executorFactoryTokens.add(token); |
| for (Uri library in libraries) { |
| if (_libraryExecutorFactories.containsKey(library)) { |
| throw ArgumentError( |
| 'Attempted to register a macro executor factory for library ' |
| '$library which already has one assigned.'); |
| } |
| _libraryExecutorFactories[library] = token; |
| } |
| return token; |
| } |
| |
| /// Unregisters [token] for all [libraries]. |
| /// |
| /// If [libraries] is not passed (or `null`), then the token is unregistered |
| /// for all libraries. |
| /// |
| /// If no libraries are registered for [token] after this call, then the |
| /// executor mapped to [token] will be shut down and the token will be freed. |
| /// |
| /// This should be called whenever the executors might be stale, or as an |
| /// optimization to shut them down when they are known to be not used any |
| /// longer. |
| Future<void> unregisterExecutorFactory(ExecutorFactoryToken token, |
| {Set<Uri>? libraries}) async { |
| bool shouldClose; |
| if (libraries == null) { |
| libraries = token._libraries; |
| shouldClose = true; |
| } else { |
| token._libraries.removeAll(libraries); |
| shouldClose = token._libraries.isEmpty; |
| } |
| |
| for (Uri library in libraries) { |
| _libraryExecutorFactories.remove(library); |
| } |
| |
| if (shouldClose) { |
| _executorFactoryTokens.remove(token); |
| token._libraries.clear(); |
| await token._close(); |
| } |
| } |
| |
| /// Shuts down all executors and clears all configuration. |
| Future<void> closeAndReset() async { |
| await Future.wait(_executorFactoryTokens |
| .toList() |
| .map((token) => unregisterExecutorFactory(token))); |
| } |
| |
| /// Shuts down all executors, but does not clear [_libraryExecutorFactories] |
| /// or [_executorFactoryTokens]. |
| @override |
| Future<void> close() { |
| Future done = Future.wait([ |
| for (ExecutorFactoryToken token in _executorFactoryTokens) token._close(), |
| ]); |
| return done; |
| } |
| |
| @override |
| Future<MacroExecutionResult> executeDeclarationsPhase( |
| MacroInstanceIdentifier macro, |
| MacroTarget target, |
| DeclarationPhaseIntrospector introspector) => |
| _instanceExecutors[macro]!._withInstance((executor) => |
| executor.executeDeclarationsPhase(macro, target, introspector)); |
| |
| @override |
| Future<MacroExecutionResult> executeDefinitionsPhase( |
| MacroInstanceIdentifier macro, |
| MacroTarget target, |
| DefinitionPhaseIntrospector introspector) => |
| _instanceExecutors[macro]!._withInstance((executor) => |
| executor.executeDefinitionsPhase(macro, target, introspector)); |
| |
| @override |
| Future<MacroExecutionResult> executeTypesPhase(MacroInstanceIdentifier macro, |
| MacroTarget target, TypePhaseIntrospector introspector) => |
| _instanceExecutors[macro]!._withInstance((executor) => |
| executor.executeTypesPhase(macro, target, introspector)); |
| |
| @override |
| Future<MacroInstanceIdentifier> instantiateMacro( |
| Uri library, String name, String constructor, Arguments arguments) { |
| ExecutorFactoryToken? token = _libraryExecutorFactories[library]; |
| if (token == null) { |
| throw ArgumentError('No executor registered to run macros from $library'); |
| } |
| return token._withInstance((executor) async { |
| MacroInstanceIdentifier instance = await executor.instantiateMacro( |
| library, name, constructor, arguments); |
| _instanceExecutors[instance] = token; |
| return instance; |
| }); |
| } |
| |
| @override |
| void disposeMacro(MacroInstanceIdentifier instance) { |
| _instanceExecutors[instance]!._withInstance((executor) { |
| executor.disposeMacro(instance); |
| }); |
| } |
| } |
| |
| /// A token to track registered [MacroExecutor] factories. |
| /// |
| /// Used to unregister them later on, and also handles bookkeeping for the |
| /// factory and actual instances. |
| class ExecutorFactoryToken { |
| final FutureOr<MacroExecutor> Function() _factory; |
| FutureOr<MacroExecutor>? _instance; |
| final Set<Uri> _libraries; |
| |
| ExecutorFactoryToken._(this._factory, this._libraries); |
| |
| /// Runs [callback] with an actual instance once available. |
| /// |
| /// This will spin up an instance if one is not currently running. |
| Future<T> _withInstance<T>( |
| FutureOr<T> Function(MacroExecutor) callback) async => |
| callback(await (_instance ??= _factory())); |
| |
| /// Closes [_instance] if non-null, and sets it to `null`. |
| Future<void> _close() async { |
| FutureOr<MacroExecutor>? instance = _instance; |
| _instance = null; |
| if (instance != null) { |
| if (instance is Future<MacroExecutor>) { |
| await (await instance).close(); |
| } else { |
| await instance.close(); |
| } |
| } |
| } |
| } |