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 |