Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(141)

Side by Side Diff: pkg/analysis_server/lib/src/plugin/plugin_manager.dart

Issue 2988673002: Add support for creating a .packages file when the plugin is in a Bazel workspace (Closed)
Patch Set: address comments Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | pkg/analysis_server/test/src/plugin/plugin_manager_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analysis_server/test/src/plugin/plugin_manager_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698