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:convert'; | 7 import 'dart:convert'; |
8 import 'dart:io' show Platform, Process, ProcessResult; | 8 import 'dart:io' show Platform, Process, ProcessResult; |
9 | 9 |
10 import 'package:analysis_server/src/plugin/notification_manager.dart'; | 10 import 'package:analysis_server/src/plugin/notification_manager.dart'; |
11 import 'package:analyzer/context/context_root.dart' as analyzer; | 11 import 'package:analyzer/context/context_root.dart' as analyzer; |
12 import 'package:analyzer/file_system/file_system.dart'; | 12 import 'package:analyzer/file_system/file_system.dart'; |
13 import 'package:analyzer/instrumentation/instrumentation.dart'; | 13 import 'package:analyzer/instrumentation/instrumentation.dart'; |
14 import 'package:analyzer/src/generated/bazel.dart'; | 14 import 'package:analyzer/src/generated/bazel.dart'; |
15 import 'package:analyzer/src/generated/gn.dart'; | 15 import 'package:analyzer/src/generated/gn.dart'; |
16 import 'package:analyzer/src/generated/source.dart'; | |
17 import 'package:analyzer/src/generated/workspace.dart'; | |
16 import 'package:analyzer/src/util/glob.dart'; | 18 import 'package:analyzer/src/util/glob.dart'; |
17 import 'package:analyzer_plugin/channel/channel.dart'; | 19 import 'package:analyzer_plugin/channel/channel.dart'; |
18 import 'package:analyzer_plugin/protocol/protocol.dart'; | 20 import 'package:analyzer_plugin/protocol/protocol.dart'; |
19 import 'package:analyzer_plugin/protocol/protocol_common.dart'; | 21 import 'package:analyzer_plugin/protocol/protocol_common.dart'; |
20 import 'package:analyzer_plugin/protocol/protocol_constants.dart'; | 22 import 'package:analyzer_plugin/protocol/protocol_constants.dart'; |
21 import 'package:analyzer_plugin/protocol/protocol_generated.dart'; | 23 import 'package:analyzer_plugin/protocol/protocol_generated.dart'; |
22 import 'package:analyzer_plugin/src/channel/isolate_channel.dart'; | 24 import 'package:analyzer_plugin/src/channel/isolate_channel.dart'; |
23 import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'; | 25 import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'; |
24 import 'package:convert/convert.dart'; | 26 import 'package:convert/convert.dart'; |
25 import 'package:crypto/crypto.dart'; | 27 import 'package:crypto/crypto.dart'; |
26 import 'package:meta/meta.dart'; | 28 import 'package:meta/meta.dart'; |
27 import 'package:path/path.dart' as path; | 29 import 'package:path/path.dart' as path; |
28 import 'package:watcher/watcher.dart' as watcher; | 30 import 'package:watcher/watcher.dart' as watcher; |
31 import 'package:yaml/yaml.dart'; | |
29 | 32 |
30 /** | 33 /** |
31 * Information about a plugin that is built-in. | 34 * Information about a plugin that is built-in. |
32 */ | 35 */ |
33 class BuiltInPluginInfo extends PluginInfo { | 36 class BuiltInPluginInfo extends PluginInfo { |
34 /** | 37 /** |
35 * The entry point function that will be executed in the plugin's isolate. | 38 * The entry point function that will be executed in the plugin's isolate. |
36 */ | 39 */ |
37 final EntryPoint entryPoint; | 40 final EntryPoint entryPoint; |
38 | 41 |
(...skipping 290 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
329 * yet been started, then it will be started by this method. | 332 * yet been started, then it will be started by this method. |
330 */ | 333 */ |
331 Future<Null> addPluginToContextRoot( | 334 Future<Null> addPluginToContextRoot( |
332 analyzer.ContextRoot contextRoot, String path) async { | 335 analyzer.ContextRoot contextRoot, String path) async { |
333 if (!_isWhitelisted(path)) { | 336 if (!_isWhitelisted(path)) { |
334 return; | 337 return; |
335 } | 338 } |
336 PluginInfo plugin = _pluginMap[path]; | 339 PluginInfo plugin = _pluginMap[path]; |
337 bool isNew = plugin == null; | 340 bool isNew = plugin == null; |
338 if (isNew) { | 341 if (isNew) { |
339 List<String> pluginPaths = _pathsFor(path); | 342 List<String> pluginPaths = pathsFor(path); |
340 if (pluginPaths == null || pluginPaths[1] == null) { | 343 if (pluginPaths == null) { |
341 return; | 344 return; |
342 } | 345 } |
343 plugin = new DiscoveredPluginInfo(path, pluginPaths[0], pluginPaths[1], | 346 plugin = new DiscoveredPluginInfo(path, pluginPaths[0], pluginPaths[1], |
344 notificationManager, instrumentationService); | 347 notificationManager, instrumentationService); |
345 _pluginMap[path] = plugin; | 348 _pluginMap[path] = plugin; |
346 if (pluginPaths[0] != null) { | 349 if (pluginPaths[0] != null) { |
347 PluginSession session = await plugin.start(byteStorePath, sdkPath); | 350 PluginSession session = await plugin.start(byteStorePath, sdkPath); |
348 session?.onDone?.then((_) { | 351 session?.onDone?.then((_) { |
349 _pluginMap.remove(path); | 352 _pluginMap.remove(path); |
350 }); | 353 }); |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
408 event ??= _convertWatchEvent(watchEvent); | 411 event ??= _convertWatchEvent(watchEvent); |
409 AnalysisHandleWatchEventsParams params = | 412 AnalysisHandleWatchEventsParams params = |
410 new AnalysisHandleWatchEventsParams([event]); | 413 new AnalysisHandleWatchEventsParams([event]); |
411 responses.add(session.sendRequest(params)); | 414 responses.add(session.sendRequest(params)); |
412 } | 415 } |
413 } | 416 } |
414 return responses; | 417 return responses; |
415 } | 418 } |
416 | 419 |
417 /** | 420 /** |
421 * Return the execution path and .packages path associated with the plugin at | |
422 * the given [path], or `null` if there is a problem that prevents us from | |
423 * executing the plugin. | |
424 */ | |
425 @visibleForTesting | |
426 List<String> pathsFor(String pluginPath) { | |
427 Folder pluginFolder = resourceProvider.getFolder(pluginPath); | |
428 File pubspecFile = pluginFolder.getChildAssumingFile('pubspec.yaml'); | |
429 if (!pubspecFile.exists) { | |
430 // If there's no pubspec file, then we don't need to copy the package | |
431 // because we won't be running pub. | |
mfairhurst
2017/07/24 18:00:07
This will prevent the bazel support from working w
| |
432 return _computePaths(pluginFolder); | |
433 } | |
434 Workspace workspace = | |
435 BazelWorkspace.find(resourceProvider, pluginFolder.path) ?? | |
436 GnWorkspace.find(resourceProvider, pluginFolder.path); | |
437 if (workspace != null) { | |
438 // Similarly, we won't be running pub if we're in a workspace because | |
439 // there is exactly one version of each package. | |
440 return _computePaths(pluginFolder, workspace: workspace); | |
441 } | |
442 // | |
443 // Copy the plugin directory to a unique subdirectory of the plugin | |
444 // manager's state location. The subdirectory's name is selected such that | |
445 // it will be invariant across sessions, reducing the number of times the | |
446 // plugin will need to be copied and pub will need to be run. | |
447 // | |
448 Folder stateFolder = resourceProvider.getStateLocation('.plugin_manager'); | |
449 String stateName = _uniqueDirectoryName(pluginPath); | |
450 Folder parentFolder = stateFolder.getChildAssumingFolder(stateName); | |
451 if (parentFolder.exists) { | |
452 Folder executionFolder = | |
453 parentFolder.getChildAssumingFolder(pluginFolder.shortName); | |
454 return _computePaths(executionFolder); | |
455 } | |
456 Folder executionFolder = pluginFolder.copyTo(parentFolder); | |
457 return _computePaths(executionFolder, runPub: true); | |
458 } | |
459 | |
460 /** | |
418 * Return a list of all of the plugins that are currently associated with the | 461 * Return a list of all of the plugins that are currently associated with the |
419 * given [contextRoot]. | 462 * given [contextRoot]. |
420 */ | 463 */ |
421 @visibleForTesting | 464 @visibleForTesting |
422 List<PluginInfo> pluginsForContextRoot(analyzer.ContextRoot contextRoot) { | 465 List<PluginInfo> pluginsForContextRoot(analyzer.ContextRoot contextRoot) { |
423 if (contextRoot == null) { | 466 if (contextRoot == null) { |
424 return _pluginMap.values.toList(); | 467 return _pluginMap.values.toList(); |
425 } | 468 } |
426 List<PluginInfo> plugins = <PluginInfo>[]; | 469 List<PluginInfo> plugins = <PluginInfo>[]; |
427 for (PluginInfo plugin in _pluginMap.values) { | 470 for (PluginInfo plugin in _pluginMap.values) { |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
511 /** | 554 /** |
512 * Whitelist all plugins. | 555 * Whitelist all plugins. |
513 */ | 556 */ |
514 @visibleForTesting | 557 @visibleForTesting |
515 void whitelistEverything() { | 558 void whitelistEverything() { |
516 _whitelistGlobs = <Glob>[ | 559 _whitelistGlobs = <Glob>[ |
517 new Glob(resourceProvider.pathContext.separator, '**/*') | 560 new Glob(resourceProvider.pathContext.separator, '**/*') |
518 ]; | 561 ]; |
519 } | 562 } |
520 | 563 |
564 /** | |
565 * Compute the paths to be returned by the enclosing method given that the | |
566 * plugin should exist in the given [pluginFolder]. | |
567 */ | |
568 List<String> _computePaths(Folder pluginFolder, | |
569 {bool runPub: false, Workspace workspace}) { | |
570 File pluginFile = pluginFolder | |
571 .getChildAssumingFolder('bin') | |
572 .getChildAssumingFile('plugin.dart'); | |
573 if (!pluginFile.exists) { | |
574 return null; | |
575 } | |
576 File packagesFile = pluginFolder.getChildAssumingFile('.packages'); | |
577 if (!packagesFile.exists) { | |
578 if (runPub) { | |
579 String vmPath = Platform.executable; | |
580 String pubPath = path.join(path.dirname(vmPath), 'pub'); | |
581 ProcessResult result = Process.runSync(pubPath, <String>['get'], | |
582 stderrEncoding: UTF8, | |
583 stdoutEncoding: UTF8, | |
584 workingDirectory: pluginFolder.path); | |
585 if (result.exitCode != 0) { | |
586 StringBuffer buffer = new StringBuffer(); | |
587 buffer.writeln('Failed to run pub get'); | |
588 buffer.writeln(' pluginFolder = ${pluginFolder.path}'); | |
589 buffer.writeln(' exitCode = ${result.exitCode}'); | |
590 buffer.writeln(' stdout = ${result.stdout}'); | |
591 buffer.writeln(' stderr = ${result.stderr}'); | |
592 instrumentationService.logError(buffer.toString()); | |
593 } | |
594 if (!packagesFile.exists) { | |
595 packagesFile = null; | |
596 } | |
597 } else if (workspace != null) { | |
598 packagesFile = | |
599 _createPackagesFile(pluginFolder, workspace.packageUriResolver); | |
600 } else { | |
601 packagesFile = null; | |
602 } | |
603 } | |
604 if (packagesFile == null) { | |
605 return null; | |
606 } | |
607 return <String>[pluginFile.path, packagesFile.path]; | |
608 } | |
609 | |
521 WatchEventType _convertChangeType(watcher.ChangeType type) { | 610 WatchEventType _convertChangeType(watcher.ChangeType type) { |
522 switch (type) { | 611 switch (type) { |
523 case watcher.ChangeType.ADD: | 612 case watcher.ChangeType.ADD: |
524 return WatchEventType.ADD; | 613 return WatchEventType.ADD; |
525 case watcher.ChangeType.MODIFY: | 614 case watcher.ChangeType.MODIFY: |
526 return WatchEventType.MODIFY; | 615 return WatchEventType.MODIFY; |
527 case watcher.ChangeType.REMOVE: | 616 case watcher.ChangeType.REMOVE: |
528 return WatchEventType.REMOVE; | 617 return WatchEventType.REMOVE; |
529 default: | 618 default: |
530 throw new StateError('Unknown change type: $type'); | 619 throw new StateError('Unknown change type: $type'); |
531 } | 620 } |
532 } | 621 } |
533 | 622 |
534 WatchEvent _convertWatchEvent(watcher.WatchEvent watchEvent) { | 623 WatchEvent _convertWatchEvent(watcher.WatchEvent watchEvent) { |
535 return new WatchEvent(_convertChangeType(watchEvent.type), watchEvent.path); | 624 return new WatchEvent(_convertChangeType(watchEvent.type), watchEvent.path); |
536 } | 625 } |
537 | 626 |
538 /** | 627 /** |
628 * Return a temporary `.packages` file that is appropriate for the plugin in | |
629 * the given [pluginFolder]. The [packageUriResolver] is used to determine the | |
630 * location of the packages that need to be included in the packages file. | |
631 */ | |
632 File _createPackagesFile( | |
633 Folder pluginFolder, UriResolver packageUriResolver) { | |
634 String pluginPath = pluginFolder.path; | |
635 Folder stateFolder = resourceProvider.getStateLocation('.plugin_manager'); | |
636 String stateName = _uniqueDirectoryName(pluginPath) + '.packages'; | |
637 File packagesFile = stateFolder.getChildAssumingFile(stateName); | |
638 if (!packagesFile.exists) { | |
639 File pluginPubspec = pluginFolder.getChildAssumingFile('pubspec.yaml'); | |
640 if (!pluginPubspec.exists) { | |
641 return null; | |
642 } | |
643 | |
644 try { | |
645 Map<String, String> visitedPackages = <String, String>{}; | |
646 path.Context context = resourceProvider.pathContext; | |
647 visitedPackages[context.basename(pluginPath)] = | |
648 context.join(pluginFolder.path, 'lib'); | |
649 List<File> pubspecFiles = <File>[]; | |
650 pubspecFiles.add(pluginPubspec); | |
651 while (pubspecFiles.isNotEmpty) { | |
652 File pubspecFile = pubspecFiles.removeLast(); | |
653 for (String packageName in _readDependecies(pubspecFile)) { | |
654 if (!visitedPackages.containsKey(packageName)) { | |
655 Uri uri = Uri.parse('package:$packageName/$packageName.dart'); | |
656 Source packageSource = packageUriResolver.resolveAbsolute(uri); | |
657 String libDirPath = context.dirname(packageSource.fullName); | |
658 visitedPackages[packageName] = libDirPath; | |
659 String pubspecPath = | |
660 context.join(context.dirname(libDirPath), 'pubspec.yaml'); | |
661 pubspecFiles.add(resourceProvider.getFile(pubspecPath)); | |
662 } | |
663 } | |
664 } | |
665 | |
666 StringBuffer buffer = new StringBuffer(); | |
667 visitedPackages.forEach((String name, String path) { | |
668 buffer.write(name); | |
669 buffer.write(':'); | |
670 buffer.writeln(new Uri.file(path)); | |
671 }); | |
672 packagesFile.writeAsStringSync(buffer.toString()); | |
673 } catch (exception) { | |
674 // If we are not able to produce a .packages file, return null so that | |
675 // callers will not try to load the plugin. | |
676 return null; | |
mfairhurst
2017/07/24 18:00:06
should log the exception too though
| |
677 } | |
678 } | |
679 return packagesFile; | |
680 } | |
681 | |
682 /** | |
539 * Return `true` if the plugin with the given [path] has been whitelisted. | 683 * Return `true` if the plugin with the given [path] has been whitelisted. |
540 */ | 684 */ |
541 bool _isWhitelisted(String path) { | 685 bool _isWhitelisted(String path) { |
542 for (Glob glob in _whitelistGlobs) { | 686 for (Glob glob in _whitelistGlobs) { |
543 if (glob.matches(path)) { | 687 if (glob.matches(path)) { |
544 return true; | 688 return true; |
545 } | 689 } |
546 } | 690 } |
547 return false; | 691 return false; |
548 } | 692 } |
549 | 693 |
550 /** | 694 /** |
551 * Return the execution path and .packages path associated with the plugin at | 695 * Return the names of packages that are listed as dependencies in the given |
552 * the given [path], or `null` if there is a problem that prevents us from | 696 * [pubspecFile]. |
553 * executing the plugin. | |
554 */ | 697 */ |
555 List<String> _pathsFor(String pluginPath) { | 698 Iterable<String> _readDependecies(File pubspecFile) { |
556 /** | 699 YamlDocument document = loadYamlDocument(pubspecFile.readAsStringSync(), |
557 * Return `true` if the plugin in the give [folder] needs to be copied to a | 700 sourceUrl: pubspecFile.toUri()); |
558 * temporary location so that 'pub' can be run to resolve dependencies. We | 701 YamlNode contents = document.contents; |
559 * need to run `pub` if the plugin contains a `pubspec.yaml` file and is not | 702 if (contents is YamlMap) { |
560 * in a workspace. | 703 YamlNode dependencies = contents['dependencies']; |
mfairhurst
2017/07/24 18:00:06
Is there any way we can get this info by running p
| |
561 */ | 704 if (dependencies is YamlMap) { |
562 bool needToCopy(Folder folder) { | 705 return dependencies.keys; |
563 File pubspecFile = folder.getChildAssumingFile('pubspec.yaml'); | |
564 if (!pubspecFile.exists) { | |
565 return false; | |
566 } | 706 } |
567 return BazelWorkspace.find(resourceProvider, folder.path) == null && | |
568 GnWorkspace.find(resourceProvider, folder.path) == null; | |
569 } | 707 } |
570 | 708 return const <String>[]; |
571 /** | |
572 * Compute the paths to be returned by the enclosing method given that the | |
573 * plugin should exist in the given [pluginFolder]. | |
574 */ | |
575 List<String> computePaths(Folder pluginFolder, {bool runPub: false}) { | |
576 File pluginFile = pluginFolder | |
577 .getChildAssumingFolder('bin') | |
578 .getChildAssumingFile('plugin.dart'); | |
579 if (!pluginFile.exists) { | |
580 return null; | |
581 } | |
582 File packagesFile = pluginFolder.getChildAssumingFile('.packages'); | |
583 if (!packagesFile.exists) { | |
584 if (runPub) { | |
585 String vmPath = Platform.executable; | |
586 String pubPath = path.join(path.dirname(vmPath), 'pub'); | |
587 ProcessResult result = Process.runSync(pubPath, <String>['get'], | |
588 stderrEncoding: UTF8, | |
589 stdoutEncoding: UTF8, | |
590 workingDirectory: pluginFolder.path); | |
591 if (result.exitCode != 0) { | |
592 StringBuffer buffer = new StringBuffer(); | |
593 buffer.writeln('Failed to run pub get'); | |
594 buffer.writeln(' pluginFolder = ${pluginFolder.path}'); | |
595 buffer.writeln(' exitCode = ${result.exitCode}'); | |
596 buffer.writeln(' stdout = ${result.stdout}'); | |
597 buffer.writeln(' stderr = ${result.stderr}'); | |
598 instrumentationService.logError(buffer.toString()); | |
599 } | |
600 if (!packagesFile.exists) { | |
601 packagesFile = null; | |
602 } | |
603 } else { | |
604 packagesFile = null; | |
605 } | |
606 } | |
607 return <String>[pluginFile.path, packagesFile?.path]; | |
608 } | |
609 | |
610 Folder pluginFolder = resourceProvider.getFolder(pluginPath); | |
611 if (!needToCopy(pluginFolder)) { | |
612 return computePaths(pluginFolder); | |
613 } | |
614 // | |
615 // Copy the plugin directory to a unique subdirectory of the plugin | |
616 // manager's state location. The subdirectory's name is selected such that | |
617 // it will be invariant across sessions, reducing the number of times the | |
618 // plugin will need to be copied and pub will need to be run. | |
619 // | |
620 Folder stateFolder = resourceProvider.getStateLocation('.plugin_manager'); | |
621 String stateName = _uniqueDirectoryName(pluginPath); | |
622 Folder parentFolder = stateFolder.getChildAssumingFolder(stateName); | |
623 if (parentFolder.exists) { | |
624 Folder executionFolder = | |
625 parentFolder.getChildAssumingFolder(pluginFolder.shortName); | |
626 return computePaths(executionFolder); | |
627 } | |
628 Folder executionFolder = pluginFolder.copyTo(parentFolder); | |
629 return computePaths(executionFolder, runPub: true); | |
630 } | 709 } |
631 | 710 |
632 /** | 711 /** |
633 * Return a hex-encoded MD5 signature of the given file [path]. | 712 * Return a hex-encoded MD5 signature of the given file [path]. |
634 */ | 713 */ |
635 String _uniqueDirectoryName(String path) { | 714 String _uniqueDirectoryName(String path) { |
636 List<int> bytes = md5.convert(path.codeUnits).bytes; | 715 List<int> bytes = md5.convert(path.codeUnits).bytes; |
637 return hex.encode(bytes); | 716 return hex.encode(bytes); |
638 } | 717 } |
639 | 718 |
(...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
898 * The completer that will be used to complete the future when the response is | 977 * The completer that will be used to complete the future when the response is |
899 * received from the plugin. | 978 * received from the plugin. |
900 */ | 979 */ |
901 final Completer<Response> completer; | 980 final Completer<Response> completer; |
902 | 981 |
903 /** | 982 /** |
904 * Initialize a pending request. | 983 * Initialize a pending request. |
905 */ | 984 */ |
906 _PendingRequest(this.method, this.requestTime, this.completer); | 985 _PendingRequest(this.method, this.requestTime, this.completer); |
907 } | 986 } |
OLD | NEW |