| // Copyright (c) 2017, 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:convert'; |
| |
| import 'package:analysis_server/src/plugin/plugin_locator.dart'; |
| import 'package:analysis_server/src/plugin/plugin_manager.dart'; |
| import 'package:analysis_server/src/plugin2/generator.dart'; |
| import 'package:analyzer/dart/analysis/context_root.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/dart/sdk/sdk.dart'; |
| import 'package:analyzer/src/util/file_paths.dart' as file_paths; |
| |
| /// An object that watches the results produced by analysis drivers to identify |
| /// references to previously unseen packages and, if those packages have plugins |
| /// associated with them, causes the plugin to be associated with the driver's |
| /// context root (which in turn might cause the plugin to be started). |
| class PluginWatcher implements DriverWatcher { |
| /// The resource provider used to access the file system. |
| final ResourceProvider resourceProvider; |
| |
| /// The object managing the execution of plugins. |
| final PluginManager manager; |
| |
| /// The object used to locate plugins within packages. |
| final PluginLocator _locator; |
| |
| /// A table mapping analysis drivers to information related to the driver. |
| final Map<AnalysisDriver, _DriverInfo> _driverInfo = |
| <AnalysisDriver, _DriverInfo>{}; |
| |
| /// Initialize a newly created plugin watcher. |
| PluginWatcher(this.resourceProvider, this.manager) |
| : _locator = PluginLocator(resourceProvider); |
| |
| @override |
| void addedDriver(AnalysisDriver driver) { |
| var contextRoot = driver.analysisContext!.contextRoot; |
| _driverInfo[driver] = _DriverInfo(contextRoot, <String>[ |
| contextRoot.root.path, |
| _getSdkPath(driver), |
| ]); |
| |
| // We temporarily support both "legacy plugins" and (new) "plugins." We |
| // restrict the number of legacy plugins to 1, for performance reasons. |
| // At some point, we will stop adding legacy plugins to the context root. |
| _addLegacyPlugins(driver); |
| |
| var pluginsOptions = driver.pluginsOptions; |
| if (pluginsOptions == null || pluginsOptions.configurations.isEmpty) { |
| // Call the plugin manager "initialized." |
| if (!manager.initializedCompleter.isCompleted) { |
| manager.initializedCompleter.complete(); |
| } |
| return; |
| } |
| |
| // Now we add any specified (new) plugins to the context, as a single |
| // "legacy plugin" shared entrypoint. |
| // Add a shared entrypoint plugin to the context root, only if one or more |
| // plugins are specified in analysis options. |
| _addPlugins(driver); |
| } |
| |
| /// The context manager has just removed the given analysis [driver]. |
| @override |
| void removedDriver(AnalysisDriver driver) { |
| var info = _driverInfo[driver]; |
| if (info == null) { |
| throw StateError('Cannot remove a driver that was not added'); |
| } |
| manager.removedContextRoot(driver.analysisContext!.contextRoot); |
| _driverInfo.remove(driver); |
| } |
| |
| void _addLegacyPlugins(AnalysisDriver driver) { |
| for (var hostPackageName in driver.enabledLegacyPluginNames) { |
| // |
| // Determine whether the package exists and defines a plugin. |
| // |
| var uri = 'package:$hostPackageName/$hostPackageName.dart'; |
| var source = driver.sourceFactory.forUri(uri); |
| if (source == null) { |
| return; |
| } |
| var context = resourceProvider.pathContext; |
| var packageRoot = context.dirname(context.dirname(source.fullName)); |
| var pluginPath = _locator.findPlugin(packageRoot); |
| if (pluginPath == null) { |
| return; |
| } |
| // |
| // Add the plugin to the context root. |
| // |
| // TODO(brianwilkerson): Do we need to wait for the plugin to be added? |
| // If we don't, then tests don't have any way to know when to expect |
| // that the list of plugins has been updated. |
| manager.addPluginToContextRoot( |
| driver.analysisContext!.contextRoot, |
| pluginPath, |
| isLegacyPlugin: true, |
| ); |
| } |
| } |
| |
| void _addPlugins(AnalysisDriver driver) { |
| var pluginsOptions = driver.pluginsOptions; |
| var pluginConfigurations = pluginsOptions?.configurations ?? const []; |
| var pluginDependencyOverrides = pluginsOptions?.dependencyOverrides; |
| var contextRoot = driver.analysisContext!.contextRoot; |
| var packageGenerator = PluginPackageGenerator( |
| configurations: pluginConfigurations, |
| dependencyOverrides: pluginDependencyOverrides, |
| ); |
| // The path here just needs to be unique per context root. |
| |
| var sharedPluginFolder = manager.pluginStateFolder(contextRoot.root.path); |
| manager.instrumentationService.logInfo( |
| "Creating shared plugin folder at '${sharedPluginFolder.path}' for " |
| "context root: '${contextRoot.root.path}'", |
| ); |
| sharedPluginFolder.create(); |
| sharedPluginFolder |
| .getChildAssumingFile(file_paths.pubspecYaml) |
| .writeAsStringSync(packageGenerator.generatePubspec()); |
| var binFolder = sharedPluginFolder.getChildAssumingFolder('bin')..create(); |
| binFolder |
| .getChildAssumingFile('plugin.dart') |
| .writeAsStringSync(packageGenerator.generateEntrypoint()); |
| manager.instrumentationService.logInfo( |
| 'Adding ${pluginConfigurations.length} analyzer plugins for ' |
| "context root: '${contextRoot.root.path}'", |
| ); |
| manager.addPluginToContextRoot( |
| contextRoot, |
| sharedPluginFolder.path, |
| isLegacyPlugin: false, |
| ); |
| } |
| |
| /// Return the path to the root of the SDK being used by the given analysis |
| /// [driver]. |
| String _getSdkPath(AnalysisDriver driver) { |
| var coreSource = driver.sourceFactory.forUri('dart:core'); |
| |
| // TODO(scheglov): Debug for https://github.com/dart-lang/sdk/issues/35226 |
| if (coreSource == null) { |
| var sdk = driver.sourceFactory.dartSdk; |
| if (sdk is AbstractDartSdk) { |
| var sdkJson = JsonEncoder.withIndent(' ').convert(sdk.debugInfo()); |
| throw StateError('No dart:core, sdk: $sdkJson'); |
| } |
| } |
| |
| var sdkRoot = coreSource!.fullName; |
| while (resourceProvider.pathContext.basename(sdkRoot) != 'lib') { |
| var parent = resourceProvider.pathContext.dirname(sdkRoot); |
| if (parent == sdkRoot) { |
| break; |
| } |
| sdkRoot = parent; |
| } |
| return sdkRoot; |
| } |
| } |
| |
| /// Information related to an analysis driver. |
| class _DriverInfo { |
| /// The context root representing the context being analyzed by the driver. |
| final ContextRoot contextRoot; |
| |
| /// A list of the absolute paths of directories inside of which we have |
| /// already searched for a plugin. |
| final List<String> packageRoots; |
| |
| /// Initialize a newly created information holder. |
| _DriverInfo(this.contextRoot, this.packageRoots); |
| } |