Chromium Code Reviews| 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 |