| OLD | NEW |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import 'dart:async'; | 5 import 'dart:async'; |
| 6 import 'dart:collection'; | 6 import 'dart:collection'; |
| 7 import 'dart:io' show Platform; | 7 import 'dart:io' show Platform; |
| 8 | 8 |
| 9 import 'package:analysis_server/src/plugin/notification_manager.dart'; | 9 import 'package:analysis_server/src/plugin/notification_manager.dart'; |
| 10 import 'package:analyzer/context/context_root.dart' as analyzer; | 10 import 'package:analyzer/context/context_root.dart' as analyzer; |
| 11 import 'package:analyzer/file_system/file_system.dart'; | 11 import 'package:analyzer/file_system/file_system.dart'; |
| 12 import 'package:analyzer/instrumentation/instrumentation.dart'; | 12 import 'package:analyzer/instrumentation/instrumentation.dart'; |
| 13 import 'package:analyzer/src/generated/bazel.dart'; | 13 import 'package:analyzer/src/generated/bazel.dart'; |
| 14 import 'package:analyzer/src/generated/gn.dart'; | 14 import 'package:analyzer/src/generated/gn.dart'; |
| 15 import 'package:analyzer/src/util/glob.dart'; | 15 import 'package:analyzer/src/util/glob.dart'; |
| 16 import 'package:analyzer_plugin/channel/channel.dart'; | 16 import 'package:analyzer_plugin/channel/channel.dart'; |
| 17 import 'package:analyzer_plugin/protocol/protocol.dart'; | 17 import 'package:analyzer_plugin/protocol/protocol.dart'; |
| 18 import 'package:analyzer_plugin/protocol/protocol_constants.dart'; | 18 import 'package:analyzer_plugin/protocol/protocol_constants.dart'; |
| 19 import 'package:analyzer_plugin/protocol/protocol_generated.dart'; | 19 import 'package:analyzer_plugin/protocol/protocol_generated.dart'; |
| 20 import 'package:analyzer_plugin/src/channel/isolate_channel.dart'; | 20 import 'package:analyzer_plugin/src/channel/isolate_channel.dart'; |
| 21 import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'; | 21 import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'; |
| 22 import 'package:convert/convert.dart'; | 22 import 'package:convert/convert.dart'; |
| 23 import 'package:crypto/crypto.dart'; | 23 import 'package:crypto/crypto.dart'; |
| 24 import 'package:meta/meta.dart'; | 24 import 'package:meta/meta.dart'; |
| 25 import 'package:path/path.dart' as path; | |
| 26 import 'package:watcher/watcher.dart' as watcher; | 25 import 'package:watcher/watcher.dart' as watcher; |
| 27 | 26 |
| 28 /** | 27 /** |
| 29 * Information about a single plugin. | 28 * Information about a plugin that is built-in. |
| 30 */ | 29 */ |
| 31 class PluginInfo { | 30 class BuiltInPluginInfo extends PluginInfo { |
| 31 /** |
| 32 * The entry point function that will be executed in the plugin's isolate. |
| 33 */ |
| 34 final EntryPoint entryPoint; |
| 35 |
| 36 @override |
| 37 final String pluginId; |
| 38 |
| 39 /** |
| 40 * Initialize a newly created built-in plugin. |
| 41 */ |
| 42 BuiltInPluginInfo( |
| 43 this.entryPoint, |
| 44 this.pluginId, |
| 45 NotificationManager notificationManager, |
| 46 InstrumentationService instrumentationService) |
| 47 : super(notificationManager, instrumentationService); |
| 48 |
| 49 @override |
| 50 ServerCommunicationChannel _createChannel() { |
| 51 return new ServerIsolateChannel.builtIn( |
| 52 entryPoint, pluginId, instrumentationService); |
| 53 } |
| 54 } |
| 55 |
| 56 /** |
| 57 * Information about a plugin that was discovered. |
| 58 */ |
| 59 class DiscoveredPluginInfo extends PluginInfo { |
| 32 /** | 60 /** |
| 33 * The path to the root directory of the definition of the plugin on disk (the | 61 * The path to the root directory of the definition of the plugin on disk (the |
| 34 * directory containing the 'pubspec.yaml' file and the 'bin' directory). | 62 * directory containing the 'pubspec.yaml' file and the 'bin' directory). |
| 35 */ | 63 */ |
| 36 final String path; | 64 final String path; |
| 37 | 65 |
| 38 /** | 66 /** |
| 39 * The path to the 'plugin.dart' file that will be executed in an isolate. | 67 * The path to the 'plugin.dart' file that will be executed in an isolate. |
| 40 */ | 68 */ |
| 41 final String executionPath; | 69 final String executionPath; |
| 42 | 70 |
| 43 /** | 71 /** |
| 44 * The path to the '.packages' file used to control the resolution of | 72 * The path to the '.packages' file used to control the resolution of |
| 45 * 'package:' URIs. | 73 * 'package:' URIs. |
| 46 */ | 74 */ |
| 47 final String packagesPath; | 75 final String packagesPath; |
| 48 | 76 |
| 49 /** | 77 /** |
| 78 * Initialize the newly created information about a plugin. |
| 79 */ |
| 80 DiscoveredPluginInfo( |
| 81 this.path, |
| 82 this.executionPath, |
| 83 this.packagesPath, |
| 84 NotificationManager notificationManager, |
| 85 InstrumentationService instrumentationService) |
| 86 : super(notificationManager, instrumentationService); |
| 87 |
| 88 @override |
| 89 String get pluginId => path; |
| 90 |
| 91 @override |
| 92 ServerCommunicationChannel _createChannel() { |
| 93 return new ServerIsolateChannel.discovered( |
| 94 new Uri.file(executionPath, windows: Platform.isWindows), |
| 95 new Uri.file(packagesPath, windows: Platform.isWindows), |
| 96 instrumentationService); |
| 97 } |
| 98 } |
| 99 |
| 100 /** |
| 101 * Information about a single plugin. |
| 102 */ |
| 103 abstract class PluginInfo { |
| 104 /** |
| 50 * The object used to manage the receiving and sending of notifications. | 105 * The object used to manage the receiving and sending of notifications. |
| 51 */ | 106 */ |
| 52 final NotificationManager notificationManager; | 107 final NotificationManager notificationManager; |
| 53 | 108 |
| 54 /** | 109 /** |
| 55 * The instrumentation service that is being used by the analysis server. | 110 * The instrumentation service that is being used by the analysis server. |
| 56 */ | 111 */ |
| 57 final InstrumentationService instrumentationService; | 112 final InstrumentationService instrumentationService; |
| 58 | 113 |
| 59 /** | 114 /** |
| 60 * The context roots that are currently using the results produced by the | 115 * The context roots that are currently using the results produced by the |
| 61 * plugin. | 116 * plugin. |
| 62 */ | 117 */ |
| 63 Set<analyzer.ContextRoot> contextRoots = new HashSet<analyzer.ContextRoot>(); | 118 Set<analyzer.ContextRoot> contextRoots = new HashSet<analyzer.ContextRoot>(); |
| 64 | 119 |
| 65 /** | 120 /** |
| 66 * The current execution of the plugin, or `null` if the plugin is not | 121 * The current execution of the plugin, or `null` if the plugin is not |
| 67 * currently being executed. | 122 * currently being executed. |
| 68 */ | 123 */ |
| 69 PluginSession currentSession; | 124 PluginSession currentSession; |
| 70 | 125 |
| 71 /** | 126 /** |
| 72 * Initialize the newly created information about a plugin. | 127 * Initialize the newly created information about a plugin. |
| 73 */ | 128 */ |
| 74 PluginInfo(this.path, this.executionPath, this.packagesPath, | 129 PluginInfo(this.notificationManager, this.instrumentationService); |
| 75 this.notificationManager, this.instrumentationService); | |
| 76 | 130 |
| 77 /** | 131 /** |
| 78 * Return the data known about this plugin. | 132 * Return the data known about this plugin. |
| 79 */ | 133 */ |
| 80 PluginData get data => | 134 PluginData get data => |
| 81 new PluginData(path, currentSession?.name, currentSession?.version); | 135 new PluginData(pluginId, currentSession?.name, currentSession?.version); |
| 136 |
| 137 /** |
| 138 * Return the id of this plugin, used to identify the plugin to users. |
| 139 */ |
| 140 String get pluginId; |
| 82 | 141 |
| 83 /** | 142 /** |
| 84 * Add the given [contextRoot] to the set of context roots being analyzed by | 143 * Add the given [contextRoot] to the set of context roots being analyzed by |
| 85 * this plugin. | 144 * this plugin. |
| 86 */ | 145 */ |
| 87 void addContextRoot(analyzer.ContextRoot contextRoot) { | 146 void addContextRoot(analyzer.ContextRoot contextRoot) { |
| 88 if (contextRoots.add(contextRoot)) { | 147 if (contextRoots.add(contextRoot)) { |
| 89 _updatePluginRoots(); | 148 _updatePluginRoots(); |
| 90 } | 149 } |
| 91 } | 150 } |
| 92 | 151 |
| 93 /** | 152 /** |
| 153 * Return `true` if at least one of the context roots being analyzed contains |
| 154 * the file with the given [filePath]. |
| 155 */ |
| 156 bool isAnalyzing(String filePath) { |
| 157 for (var contextRoot in contextRoots) { |
| 158 if (contextRoot.containsFile(filePath)) { |
| 159 return true; |
| 160 } |
| 161 } |
| 162 return false; |
| 163 } |
| 164 |
| 165 /** |
| 94 * Remove the given [contextRoot] from the set of context roots being analyzed | 166 * Remove the given [contextRoot] from the set of context roots being analyzed |
| 95 * by this plugin. | 167 * by this plugin. |
| 96 */ | 168 */ |
| 97 void removeContextRoot(analyzer.ContextRoot contextRoot) { | 169 void removeContextRoot(analyzer.ContextRoot contextRoot) { |
| 98 if (contextRoots.remove(contextRoot)) { | 170 if (contextRoots.remove(contextRoot)) { |
| 99 _updatePluginRoots(); | 171 _updatePluginRoots(); |
| 100 } | 172 } |
| 101 } | 173 } |
| 102 | 174 |
| 103 /** | 175 /** |
| (...skipping 27 matching lines...) Expand all Loading... |
| 131 Future<Null> stop() { | 203 Future<Null> stop() { |
| 132 if (currentSession == null) { | 204 if (currentSession == null) { |
| 133 throw new StateError('Cannot stop a plugin that is not running.'); | 205 throw new StateError('Cannot stop a plugin that is not running.'); |
| 134 } | 206 } |
| 135 Future<Null> doneFuture = currentSession.stop(); | 207 Future<Null> doneFuture = currentSession.stop(); |
| 136 currentSession = null; | 208 currentSession = null; |
| 137 return doneFuture; | 209 return doneFuture; |
| 138 } | 210 } |
| 139 | 211 |
| 140 /** | 212 /** |
| 213 * Create the channel used to communicate with the server. |
| 214 */ |
| 215 ServerCommunicationChannel _createChannel(); |
| 216 |
| 217 /** |
| 141 * Update the context roots that the plugin should be analyzing. | 218 * Update the context roots that the plugin should be analyzing. |
| 142 */ | 219 */ |
| 143 void _updatePluginRoots() { | 220 void _updatePluginRoots() { |
| 144 if (currentSession != null) { | 221 if (currentSession != null) { |
| 145 AnalysisSetContextRootsParams params = new AnalysisSetContextRootsParams( | 222 AnalysisSetContextRootsParams params = new AnalysisSetContextRootsParams( |
| 146 contextRoots | 223 contextRoots |
| 147 .map((analyzer.ContextRoot contextRoot) => | 224 .map((analyzer.ContextRoot contextRoot) => |
| 148 new ContextRoot(contextRoot.root, contextRoot.exclude)) | 225 new ContextRoot(contextRoot.root, contextRoot.exclude)) |
| 149 .toList()); | 226 .toList()); |
| 150 currentSession.sendRequest(params); | 227 currentSession.sendRequest(params); |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 */ | 294 */ |
| 218 Future<Null> addPluginToContextRoot( | 295 Future<Null> addPluginToContextRoot( |
| 219 analyzer.ContextRoot contextRoot, String path) async { | 296 analyzer.ContextRoot contextRoot, String path) async { |
| 220 PluginInfo plugin = _pluginMap[path]; | 297 PluginInfo plugin = _pluginMap[path]; |
| 221 bool isNew = plugin == null; | 298 bool isNew = plugin == null; |
| 222 if (isNew) { | 299 if (isNew) { |
| 223 List<String> pluginPaths = _pathsFor(path); | 300 List<String> pluginPaths = _pathsFor(path); |
| 224 if (pluginPaths == null) { | 301 if (pluginPaths == null) { |
| 225 return; | 302 return; |
| 226 } | 303 } |
| 227 plugin = new PluginInfo(path, pluginPaths[0], pluginPaths[1], | 304 plugin = new DiscoveredPluginInfo(path, pluginPaths[0], pluginPaths[1], |
| 228 notificationManager, instrumentationService); | 305 notificationManager, instrumentationService); |
| 229 _pluginMap[path] = plugin; | 306 _pluginMap[path] = plugin; |
| 230 if (pluginPaths[0] != null) { | 307 if (pluginPaths[0] != null) { |
| 231 PluginSession session = await plugin.start(byteStorePath); | 308 PluginSession session = await plugin.start(byteStorePath); |
| 232 session?.onDone?.then((_) { | 309 session?.onDone?.then((_) { |
| 233 _pluginMap.remove(path); | 310 _pluginMap.remove(path); |
| 234 }); | 311 }); |
| 235 } | 312 } |
| 236 } | 313 } |
| 237 plugin.addContextRoot(contextRoot); | 314 plugin.addContextRoot(contextRoot); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 272 * response. | 349 * response. |
| 273 */ | 350 */ |
| 274 Future<List<Future<Response>>> broadcastWatchEvent( | 351 Future<List<Future<Response>>> broadcastWatchEvent( |
| 275 watcher.WatchEvent watchEvent) async { | 352 watcher.WatchEvent watchEvent) async { |
| 276 String filePath = watchEvent.path; | 353 String filePath = watchEvent.path; |
| 277 | 354 |
| 278 /** | 355 /** |
| 279 * Return `true` if the given glob [pattern] matches the file being watched. | 356 * Return `true` if the given glob [pattern] matches the file being watched. |
| 280 */ | 357 */ |
| 281 bool matches(String pattern) => | 358 bool matches(String pattern) => |
| 282 new Glob(path.separator, pattern).matches(filePath); | 359 new Glob(resourceProvider.pathContext.separator, pattern) |
| 360 .matches(filePath); |
| 283 | 361 |
| 284 WatchEvent event = null; | 362 WatchEvent event = null; |
| 285 List<Future<Response>> responses = <Future<Response>>[]; | 363 List<Future<Response>> responses = <Future<Response>>[]; |
| 286 for (PluginInfo plugin in _pluginMap.values) { | 364 for (PluginInfo plugin in _pluginMap.values) { |
| 287 PluginSession session = plugin.currentSession; | 365 PluginSession session = plugin.currentSession; |
| 288 if (session != null && | 366 if (session != null && |
| 289 path.isWithin(plugin.path, filePath) && | 367 plugin.isAnalyzing(filePath) && |
| 290 session.interestingFiles.any(matches)) { | 368 session.interestingFiles.any(matches)) { |
| 291 event ??= _convertWatchEvent(watchEvent); | 369 event ??= _convertWatchEvent(watchEvent); |
| 292 AnalysisHandleWatchEventsParams params = | 370 AnalysisHandleWatchEventsParams params = |
| 293 new AnalysisHandleWatchEventsParams([event]); | 371 new AnalysisHandleWatchEventsParams([event]); |
| 294 responses.add(session.sendRequest(params)); | 372 responses.add(session.sendRequest(params)); |
| 295 } | 373 } |
| 296 } | 374 } |
| 297 return responses; | 375 return responses; |
| 298 } | 376 } |
| 299 | 377 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 315 return plugins; | 393 return plugins; |
| 316 } | 394 } |
| 317 | 395 |
| 318 /** | 396 /** |
| 319 * The given [contextRoot] is no longer being analyzed. | 397 * The given [contextRoot] is no longer being analyzed. |
| 320 */ | 398 */ |
| 321 void removedContextRoot(analyzer.ContextRoot contextRoot) { | 399 void removedContextRoot(analyzer.ContextRoot contextRoot) { |
| 322 List<PluginInfo> plugins = _pluginMap.values.toList(); | 400 List<PluginInfo> plugins = _pluginMap.values.toList(); |
| 323 for (PluginInfo plugin in plugins) { | 401 for (PluginInfo plugin in plugins) { |
| 324 plugin.removeContextRoot(contextRoot); | 402 plugin.removeContextRoot(contextRoot); |
| 325 if (plugin.contextRoots.isEmpty) { | 403 if (plugin is DiscoveredPluginInfo && plugin.contextRoots.isEmpty) { |
| 326 _pluginMap.remove(plugin.path); | 404 _pluginMap.remove(plugin.path); |
| 327 plugin.stop(); | 405 plugin.stop(); |
| 328 } | 406 } |
| 329 } | 407 } |
| 330 } | 408 } |
| 331 | 409 |
| 332 /** | 410 /** |
| 333 * Send a request based on the given [params] to existing plugins to set the | 411 * Send a request based on the given [params] to existing plugins to set the |
| 334 * priority files to those specified by the [params]. As a side-effect, record | 412 * priority files to those specified by the [params]. As a side-effect, record |
| 335 * the parameters so that they can be sent to any newly started plugins. | 413 * the parameters so that they can be sent to any newly started plugins. |
| (...skipping 230 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 566 */ | 644 */ |
| 567 void handleNotification(Notification notification) { | 645 void handleNotification(Notification notification) { |
| 568 if (notification.event == PLUGIN_NOTIFICATION_ERROR) { | 646 if (notification.event == PLUGIN_NOTIFICATION_ERROR) { |
| 569 PluginErrorParams params = | 647 PluginErrorParams params = |
| 570 new PluginErrorParams.fromNotification(notification); | 648 new PluginErrorParams.fromNotification(notification); |
| 571 if (params.isFatal) { | 649 if (params.isFatal) { |
| 572 info.stop(); | 650 info.stop(); |
| 573 stop(); | 651 stop(); |
| 574 } | 652 } |
| 575 } | 653 } |
| 576 info.notificationManager.handlePluginNotification(info.path, notification); | 654 info.notificationManager |
| 655 .handlePluginNotification(info.pluginId, notification); |
| 577 } | 656 } |
| 578 | 657 |
| 579 /** | 658 /** |
| 580 * Handle the fact that the plugin has stopped. | 659 * Handle the fact that the plugin has stopped. |
| 581 */ | 660 */ |
| 582 void handleOnDone() { | 661 void handleOnDone() { |
| 583 channel.close(); | 662 channel.close(); |
| 584 channel = null; | 663 channel = null; |
| 585 pluginStoppedCompleter.complete(null); | 664 pluginStoppedCompleter.complete(null); |
| 586 } | 665 } |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 634 Future<bool> start(String byteStorePath) async { | 713 Future<bool> start(String byteStorePath) async { |
| 635 if (channel != null) { | 714 if (channel != null) { |
| 636 throw new StateError('Cannot start a plugin that is already running.'); | 715 throw new StateError('Cannot start a plugin that is already running.'); |
| 637 } | 716 } |
| 638 if (byteStorePath == null || byteStorePath.isEmpty) { | 717 if (byteStorePath == null || byteStorePath.isEmpty) { |
| 639 throw new StateError('Missing byte store path'); | 718 throw new StateError('Missing byte store path'); |
| 640 } | 719 } |
| 641 if (!isCompatible) { | 720 if (!isCompatible) { |
| 642 return false; | 721 return false; |
| 643 } | 722 } |
| 644 channel = new ServerIsolateChannel( | 723 channel = info._createChannel(); |
| 645 new Uri.file(info.executionPath, windows: Platform.isWindows), | |
| 646 new Uri.file(info.packagesPath, windows: Platform.isWindows), | |
| 647 info.instrumentationService); | |
| 648 await channel.listen(handleResponse, handleNotification, | 724 await channel.listen(handleResponse, handleNotification, |
| 649 onDone: handleOnDone, onError: handleOnError); | 725 onDone: handleOnDone, onError: handleOnError); |
| 650 if (channel == null) { | 726 if (channel == null) { |
| 651 // If there is an error when starting the isolate, the channel will invoke | 727 // If there is an error when starting the isolate, the channel will invoke |
| 652 // handleOnDone, which will cause `channel` to be set to `null`. | 728 // handleOnDone, which will cause `channel` to be set to `null`. |
| 653 return false; | 729 return false; |
| 654 } | 730 } |
| 655 Response response = await sendRequest( | 731 Response response = await sendRequest( |
| 656 new PluginVersionCheckParams(byteStorePath ?? '', '1.0.0-alpha.0')); | 732 new PluginVersionCheckParams(byteStorePath ?? '', '1.0.0-alpha.0')); |
| 657 PluginVersionCheckResult result = | 733 PluginVersionCheckResult result = |
| (...skipping 16 matching lines...) Expand all Loading... |
| 674 Future<Null> stop() { | 750 Future<Null> stop() { |
| 675 if (channel == null) { | 751 if (channel == null) { |
| 676 throw new StateError('Cannot stop a plugin that is not running.'); | 752 throw new StateError('Cannot stop a plugin that is not running.'); |
| 677 } | 753 } |
| 678 // TODO(brianwilkerson) Ensure that the isolate is killed if it does not | 754 // TODO(brianwilkerson) Ensure that the isolate is killed if it does not |
| 679 // terminate normally. | 755 // terminate normally. |
| 680 sendRequest(new PluginShutdownParams()); | 756 sendRequest(new PluginShutdownParams()); |
| 681 return pluginStoppedCompleter.future; | 757 return pluginStoppedCompleter.future; |
| 682 } | 758 } |
| 683 } | 759 } |
| OLD | NEW |