Chromium Code Reviews| Index: pkg/analysis_server/lib/src/plugin/plugin_watcher.dart |
| diff --git a/pkg/analysis_server/lib/src/plugin/plugin_watcher.dart b/pkg/analysis_server/lib/src/plugin/plugin_watcher.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1490253d2002993fd4ac8225b39c5d328c1d40b5 |
| --- /dev/null |
| +++ b/pkg/analysis_server/lib/src/plugin/plugin_watcher.dart |
| @@ -0,0 +1,150 @@ |
| +// 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 'package:analysis_server/src/plugin/plugin_locator.dart'; |
| +import 'package:analysis_server/src/plugin/plugin_manager.dart'; |
| +import 'package:analyzer/file_system/file_system.dart'; |
| +import 'package:analyzer/source/package_map_resolver.dart'; |
| +import 'package:analyzer/src/dart/analysis/driver.dart'; |
| +import 'package:analyzer/src/dart/analysis/file_state.dart'; |
| +import 'package:analyzer/src/util/absolute_path.dart'; |
| +import 'package:analyzer_plugin/protocol/protocol_generated.dart'; |
| + |
| +/** |
| + * 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 { |
| + /** |
| + * 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. |
| + */ |
| + Map<AnalysisDriver, _DriverInfo> _driverInfo = |
| + <AnalysisDriver, _DriverInfo>{}; |
| + |
| + /** |
| + * Initialize a newly created plugin watcher. |
| + */ |
| + PluginWatcher(this.resourceProvider, this.manager) |
| + : _locator = new PluginLocator(resourceProvider); |
| + |
| + /** |
| + * The context manager has just added the given analysis [driver]. This method |
| + * must be called before the driver has been allowed to perform any analysis. |
| + */ |
| + void addedDriver(AnalysisDriver driver, ContextRoot contextRoot) { |
| + _driverInfo[driver] = new _DriverInfo( |
| + contextRoot, <String>[contextRoot.root, _getSdkPath(driver)]); |
| + driver.results.listen((AnalysisResult result) { |
| + List<String> addedPluginPaths = _checkPluginsFor(driver); |
|
scheglov
2017/03/15 17:41:33
It seems very wasteful to me to perform this cycle
Brian Wilkerson
2017/03/15 18:25:45
I agree that this is wasteful.
I'm not convinced
|
| + for (String pluginPath in addedPluginPaths) { |
| + manager.addPluginToContextRoot(contextRoot, pluginPath); |
| + } |
| + }); |
| + } |
| + |
| + /** |
| + * The context manager has just removed the given analysis [driver]. |
| + */ |
| + void removedDriver(AnalysisDriver driver) { |
| + _DriverInfo info = _driverInfo[driver]; |
| + if (info == null) { |
| + throw new StateError('Cannot remove a driver that was not added'); |
| + } |
| + manager.removedContextRoot(info.contextRoot); |
| + _driverInfo.remove(driver); |
| + } |
| + |
| + /** |
| + * Check all of the files that have been analyzed so far by the given [driver] |
| + * to see whether any of them are in a package that had not previously been |
| + * seen that defines a plugin. Return a list of the roots of all such plugins |
| + * that are found. |
| + */ |
| + List<String> _checkPluginsFor(AnalysisDriver driver) { |
| + AbsolutePathContext context = resourceProvider.absolutePathContext; |
| + List<String> packageRoots = _driverInfo[driver].packageRoots; |
| + |
| + bool isInRoot(String path) { |
| + for (String root in packageRoots) { |
| + if (context.isWithin(root, path)) { |
| + return true; |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + String getPackageRoot(String path, Uri uri) { |
| + List<String> segments = uri.pathSegments.toList(); |
| + segments[0] = 'lib'; |
| + String suffix = resourceProvider.pathContext.joinAll(segments); |
| + return path.substring(0, path.length - suffix.length - 1); |
| + } |
| + |
| + List<String> addedPluginPaths = <String>[]; |
| + for (FileState state in driver.fsState.knownFiles) { |
|
scheglov
2017/03/15 17:41:34
If the set of files is the same as it was the last
Brian Wilkerson
2017/03/15 18:25:45
True, and I can cache the previous list in order t
|
| + String path = state.path; |
| + if (!isInRoot(path)) { |
| + // Found a file not in a previously known package. |
| + Uri uri = state.uri; |
| + if (PackageMapUriResolver.isPackageUri(uri)) { |
| + String packageRoot = getPackageRoot(path, uri); |
| + packageRoots.add(packageRoot); |
| + addedPluginPaths.add(_locator.findPlugin(packageRoot)); |
|
scheglov
2017/03/15 17:41:34
Can findPlugin() return null?
Also, could we cach
Brian Wilkerson
2017/03/15 18:25:45
Yes. Good catch! I'll fix it before committing.
|
| + } |
| + } |
| + } |
| + return addedPluginPaths; |
| + } |
| + |
| + /** |
| + * Return the path to the root of the SDK being used by the given analysis |
| + * [driver]. |
| + */ |
| + String _getSdkPath(AnalysisDriver driver) { |
| + AbsolutePathContext context = resourceProvider.absolutePathContext; |
| + String sdkRoot = driver.sourceFactory.forUri('dart:core').fullName; |
| + while (sdkRoot.isNotEmpty && context.basename(sdkRoot) != 'lib') { |
| + sdkRoot = context.dirname(sdkRoot); |
| + } |
| + return sdkRoot; |
| + } |
| +} |
| + |
| +/** |
| + * Information related to an analysis driver. |
| + */ |
| +class _DriverInfo { |
| + /** |
| + * The context root representing the context being analyzed by the driver. |
| + */ |
| + ContextRoot contextRoot; |
|
scheglov
2017/03/15 17:41:34
Do these fields change over the life of the instan
Brian Wilkerson
2017/03/15 18:25:45
The context root doesn't. The list of package root
|
| + |
| + /** |
| + * A list of the absolute paths of directories inside of which we have already |
| + * searched for a plugin. |
| + */ |
| + List<String> packageRoots; |
| + |
| + /** |
| + * Initialize a newly created information holder. |
| + */ |
| + _DriverInfo(this.contextRoot, this.packageRoots); |
| +} |