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

Side by Side Diff: pkg/analysis_server/lib/src/context_manager.dart

Issue 1258723002: Generalize the dependency tracking mechanism used by ContextManager. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 4 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 | no next file » | 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) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, 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 library context.directory.manager; 5 library context.directory.manager;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8 import 'dart:collection';
9 import 'dart:convert'; 9 import 'dart:convert';
10 import 'dart:core' hide Resource; 10 import 'dart:core' hide Resource;
(...skipping 16 matching lines...) Expand all
27 import 'package:package_config/src/packages_impl.dart' show MapPackages; 27 import 'package:package_config/src/packages_impl.dart' show MapPackages;
28 import 'package:path/path.dart' as pathos; 28 import 'package:path/path.dart' as pathos;
29 import 'package:watcher/watcher.dart'; 29 import 'package:watcher/watcher.dart';
30 import 'package:yaml/yaml.dart'; 30 import 'package:yaml/yaml.dart';
31 31
32 /** 32 /**
33 * Information tracked by the [ContextManager] for each context. 33 * Information tracked by the [ContextManager] for each context.
34 */ 34 */
35 class ContextInfo { 35 class ContextInfo {
36 /** 36 /**
37 * The [ContextManager] which is tracking this information.
38 */
39 final ContextManagerImpl contextManager;
40
41 /**
37 * The [Folder] for which this information object is created. 42 * The [Folder] for which this information object is created.
38 */ 43 */
39 final Folder folder; 44 final Folder folder;
40 45
41 /// The [PathFilter] used to filter sources from being analyzed. 46 /// The [PathFilter] used to filter sources from being analyzed.
42 final PathFilter pathFilter; 47 final PathFilter pathFilter;
43 48
44 /** 49 /**
45 * The enclosed pubspec-based contexts. 50 * The enclosed pubspec-based contexts.
46 */ 51 */
(...skipping 17 matching lines...) Expand all
64 String packageDescriptionPath; 69 String packageDescriptionPath;
65 70
66 /** 71 /**
67 * Stream subscription we are using to watch the context's directory for 72 * Stream subscription we are using to watch the context's directory for
68 * changes. 73 * changes.
69 */ 74 */
70 StreamSubscription<WatchEvent> changeSubscription; 75 StreamSubscription<WatchEvent> changeSubscription;
71 76
72 /** 77 /**
73 * Stream subscriptions we are using to watch the files 78 * Stream subscriptions we are using to watch the files
74 * used to determine the package map. 79 * used to determine the package map. Organized as a map from path to
80 * subscription.
81 *
82 * For paths that are inside [folder], the subscription is null, since the
83 * entire contents of the folder are automatically being watched.
75 */ 84 */
76 final List<StreamSubscription<WatchEvent>> dependencySubscriptions = 85 final Map<String, StreamSubscription<WatchEvent>> _dependencySubscriptions =
77 <StreamSubscription<WatchEvent>>[]; 86 <String, StreamSubscription<WatchEvent>>{};
78 87
79 /** 88 /**
80 * The analysis context that was created for the [folder]. 89 * The analysis context that was created for the [folder].
81 */ 90 */
82 AnalysisContext context; 91 AnalysisContext context;
83 92
84 /** 93 /**
85 * Map from full path to the [Source] object, for each source that has been 94 * Map from full path to the [Source] object, for each source that has been
86 * added to the context. 95 * added to the context.
87 */ 96 */
88 Map<String, Source> sources = new HashMap<String, Source>(); 97 Map<String, Source> sources = new HashMap<String, Source>();
89 98
90 /** 99 ContextInfo(this.contextManager, this.parent, Folder folder,
91 * Info returned by the last call to 100 File packagespecFile, this.packageRoot)
92 * [OptimizingPubPackageMapProvider.computePackageMap], or `null` if the
93 * package map hasn't been computed for this context yet.
94 */
95 PackageMapInfo packageMapInfo;
96
97 ContextInfo(
98 this.parent, Folder folder, File packagespecFile, this.packageRoot)
99 : folder = folder, 101 : folder = folder,
100 pathFilter = new PathFilter(folder.path, null) { 102 pathFilter = new PathFilter(folder.path, null) {
101 packageDescriptionPath = packagespecFile.path; 103 packageDescriptionPath = packagespecFile.path;
102 parent.children.add(this); 104 parent.children.add(this);
103 } 105 }
104 106
105 /** 107 /**
106 * Create the virtual [ContextInfo] which acts as an ancestor to all other 108 * Create the virtual [ContextInfo] which acts as an ancestor to all other
107 * [ContextInfo]s. 109 * [ContextInfo]s.
108 */ 110 */
109 ContextInfo._root() 111 ContextInfo._root()
110 : folder = null, 112 : contextManager = null,
113 folder = null,
111 pathFilter = null; 114 pathFilter = null;
112 115
113 /** 116 /**
114 * Iterate through all [children] and their children, recursively. 117 * Iterate through all [children] and their children, recursively.
115 */ 118 */
116 Iterable<ContextInfo> get descendants sync* { 119 Iterable<ContextInfo> get descendants sync* {
117 for (ContextInfo child in children) { 120 for (ContextInfo child in children) {
118 yield child; 121 yield child;
119 yield* child.descendants; 122 yield* child.descendants;
120 } 123 }
(...skipping 26 matching lines...) Expand all
147 */ 150 */
148 ContextInfo findChildInfoFor(String path) { 151 ContextInfo findChildInfoFor(String path) {
149 for (ContextInfo info in children) { 152 for (ContextInfo info in children) {
150 if (info.folder.isOrContains(path)) { 153 if (info.folder.isOrContains(path)) {
151 return info; 154 return info;
152 } 155 }
153 } 156 }
154 return null; 157 return null;
155 } 158 }
156 159
160 /**
161 * Determine if the given [path] is one of the dependencies most recently
162 * passed to [setDependencies].
163 */
164 bool hasDependency(String path) => _dependencySubscriptions.containsKey(path);
165
157 /// Returns `true` if [path] should be ignored. 166 /// Returns `true` if [path] should be ignored.
158 bool ignored(String path) => pathFilter.ignored(path); 167 bool ignored(String path) => pathFilter.ignored(path);
159 168
160 /** 169 /**
161 * Returns `true` if [path] is the package description file for this context 170 * Returns `true` if [path] is the package description file for this context
162 * (pubspec.yaml or .packages). 171 * (pubspec.yaml or .packages).
163 */ 172 */
164 bool isPathToPackageDescription(String path) => 173 bool isPathToPackageDescription(String path) =>
165 path == packageDescriptionPath; 174 path == packageDescriptionPath;
175
176 /**
177 * Update the set of dependencies for this context. Watchers are
178 * automatically set up for the dependencies (if necessary) to ensure that
179 * [ContextManagerImpl._handleWatchEvent] is called when they are modified.
180 */
181 void setDependencies(Iterable<String> newDependencies) {
182 for (String oldDependency in _dependencySubscriptions.keys.toList()) {
183 if (!newDependencies.contains(oldDependency)) {
184 StreamSubscription<WatchEvent> subscription =
185 _dependencySubscriptions[oldDependency];
186 if (subscription != null) {
187 subscription.cancel();
188 }
189 _dependencySubscriptions.remove(oldDependency);
190 }
191 }
192 for (String newDependency in newDependencies) {
193 if (!_dependencySubscriptions.containsKey(newDependency)) {
194 StreamSubscription<WatchEvent> subscription;
195 if (!folder.contains(newDependency)) {
196 Resource resource =
197 contextManager.resourceProvider.getResource(newDependency);
198 if (resource is File) {
199 subscription = resource.changes.listen((WatchEvent event) {
200 contextManager._handleWatchEvent(folder, this, event);
201 }, onError: (error, StackTrace stackTrace) {
202 // Gracefully degrade if file is or becomes unwatchable
203 contextManager._instrumentationService.logException(
204 error, stackTrace);
205 subscription.cancel();
206 _dependencySubscriptions[newDependency] = null;
207 });
208 }
209 }
210 _dependencySubscriptions[newDependency] = subscription;
211 }
212 }
213 }
166 } 214 }
167 215
168 /** 216 /**
169 * Class that maintains a mapping from included/excluded paths to a set of 217 * Class that maintains a mapping from included/excluded paths to a set of
170 * folders that should correspond to analysis contexts. 218 * folders that should correspond to analysis contexts.
171 */ 219 */
172 abstract class ContextManager { 220 abstract class ContextManager {
173 // TODO(brianwilkerson) Support: 221 // TODO(brianwilkerson) Support:
174 // setting the default analysis options 222 // setting the default analysis options
175 // setting the default content cache 223 // setting the default content cache
(...skipping 479 matching lines...) Expand 10 before | Expand all | Expand 10 after
655 } 703 }
656 _addSourceFiles(changeSet, child, info); 704 _addSourceFiles(changeSet, child, info);
657 } 705 }
658 } 706 }
659 } 707 }
660 708
661 /** 709 /**
662 * Cancel all dependency subscriptions for the given context. 710 * Cancel all dependency subscriptions for the given context.
663 */ 711 */
664 void _cancelDependencySubscriptions(ContextInfo info) { 712 void _cancelDependencySubscriptions(ContextInfo info) {
665 for (StreamSubscription<WatchEvent> s in info.dependencySubscriptions) { 713 info.setDependencies(const <String>[]);
666 s.cancel();
667 }
668 info.dependencySubscriptions.clear();
669 } 714 }
670 715
671 void _checkForPackagespecUpdate( 716 void _checkForPackagespecUpdate(
672 String path, ContextInfo info, Folder folder) { 717 String path, ContextInfo info, Folder folder) {
673 // Check to see if this is the .packages file for this context and if so, 718 // Check to see if this is the .packages file for this context and if so,
674 // update the context's source factory. 719 // update the context's source factory.
675 if (pathContext.basename(path) == PACKAGE_SPEC_NAME && 720 if (pathContext.basename(path) == PACKAGE_SPEC_NAME &&
676 info.isPathToPackageDescription(path)) { 721 info.isPathToPackageDescription(path)) {
677 File packagespec = resourceProvider.getFile(path); 722 File packagespec = resourceProvider.getFile(path);
678 if (packagespec.exists) { 723 if (packagespec.exists) {
(...skipping 23 matching lines...) Expand all
702 if (context != contextN) { 747 if (context != contextN) {
703 for (Source source in contextN.sources) { 748 for (Source source in contextN.sources) {
704 flushedFiles.remove(source.fullName); 749 flushedFiles.remove(source.fullName);
705 } 750 }
706 } 751 }
707 } 752 }
708 return flushedFiles.toList(growable: false); 753 return flushedFiles.toList(growable: false);
709 } 754 }
710 755
711 /** 756 /**
712 * Compute the appropriate [FolderDisposition] for [folder], and store 757 * Compute the appropriate [FolderDisposition] for [info]. Use
713 * dependency information in [info]. 758 * [addDependency] to indicate which files needed to be consulted in order to
759 * figure out the [FolderDisposition]; these dependencies will be watched in
760 * order to determine when it is necessary to call this function again.
761 *
762 * TODO(paulberry): use [addDependency] for tracking all folder disposition
763 * dependencies (currently we only use it to track "pub list" dependencies).
714 */ 764 */
715 FolderDisposition _computeFolderDisposition(Folder folder, ContextInfo info) { 765 FolderDisposition _computeFolderDisposition(
716 _cancelDependencySubscriptions(info); 766 Folder folder, ContextInfo info, void addDependency(String path)) {
717 if (info.packageRoot != null) { 767 if (info.packageRoot != null) {
718 info.packageMapInfo = null;
719 // TODO(paulberry): We shouldn't be using JavaFile here because it 768 // TODO(paulberry): We shouldn't be using JavaFile here because it
720 // makes the code untestable (see dartbug.com/23909). 769 // makes the code untestable (see dartbug.com/23909).
721 JavaFile packagesDir = new JavaFile(info.packageRoot); 770 JavaFile packagesDir = new JavaFile(info.packageRoot);
722 Map<String, List<Folder>> packageMap = new Map<String, List<Folder>>(); 771 Map<String, List<Folder>> packageMap = new Map<String, List<Folder>>();
723 if (packagesDir.isDirectory()) { 772 if (packagesDir.isDirectory()) {
724 for (JavaFile file in packagesDir.listFiles()) { 773 for (JavaFile file in packagesDir.listFiles()) {
725 // Ensure symlinks in packages directory are canonicalized 774 // Ensure symlinks in packages directory are canonicalized
726 // to prevent 'type X cannot be assigned to type X' warnings 775 // to prevent 'type X cannot be assigned to type X' warnings
727 String path; 776 String path;
728 try { 777 try {
(...skipping 23 matching lines...) Expand all
752 if (resolver != null) { 801 if (resolver != null) {
753 return new CustomPackageResolverDisposition(resolver); 802 return new CustomPackageResolverDisposition(resolver);
754 } 803 }
755 } 804 }
756 PackageMapInfo packageMapInfo; 805 PackageMapInfo packageMapInfo;
757 ServerPerformanceStatistics.pub.makeCurrentWhile(() { 806 ServerPerformanceStatistics.pub.makeCurrentWhile(() {
758 packageMapInfo = _packageMapProvider.computePackageMap(folder); 807 packageMapInfo = _packageMapProvider.computePackageMap(folder);
759 }); 808 });
760 callbacks.endComputePackageMap(); 809 callbacks.endComputePackageMap();
761 for (String dependencyPath in packageMapInfo.dependencies) { 810 for (String dependencyPath in packageMapInfo.dependencies) {
762 Resource resource = resourceProvider.getResource(dependencyPath); 811 addDependency(dependencyPath);
763 if (resource is File) {
764 StreamSubscription<WatchEvent> subscription;
765 subscription = resource.changes.listen((WatchEvent event) {
766 if (info.packageMapInfo != null &&
767 info.packageMapInfo.dependencies.contains(dependencyPath)) {
768 _recomputeFolderDisposition(info);
769 }
770 }, onError: (error, StackTrace stackTrace) {
771 // Gracefully degrade if file is or becomes unwatchable
772 _instrumentationService.logException(error, stackTrace);
773 subscription.cancel();
774 info.dependencySubscriptions.remove(subscription);
775 });
776 info.dependencySubscriptions.add(subscription);
777 }
778 } 812 }
779 info.packageMapInfo = packageMapInfo;
780 if (packageMapInfo.packageMap == null) { 813 if (packageMapInfo.packageMap == null) {
781 return new NoPackageFolderDisposition(); 814 return new NoPackageFolderDisposition();
782 } 815 }
783 return new PackageMapDisposition(packageMapInfo.packageMap); 816 return new PackageMapDisposition(packageMapInfo.packageMap);
784 } 817 }
785 } 818 }
786 819
787 /** 820 /**
788 * Create a new empty context associated with [folder], having parent 821 * Create a new empty context associated with [folder], having parent
789 * [parent] and using [packagespecFile] to resolve package URI's. 822 * [parent] and using [packagespecFile] to resolve package URI's.
790 */ 823 */
791 ContextInfo _createContext( 824 ContextInfo _createContext(
792 ContextInfo parent, Folder folder, File packagespecFile) { 825 ContextInfo parent, Folder folder, File packagespecFile) {
793 ContextInfo info = new ContextInfo( 826 ContextInfo info = new ContextInfo(this, parent, folder, packagespecFile,
794 parent, folder, packagespecFile, normalizedPackageRoots[folder.path]); 827 normalizedPackageRoots[folder.path]);
795 Map<String, YamlNode> options = analysisOptionsProvider.getOptions(folder); 828 Map<String, YamlNode> options = analysisOptionsProvider.getOptions(folder);
796 processOptionsForContext(info, options); 829 processOptionsForContext(info, options);
797 info.changeSubscription = folder.changes.listen((WatchEvent event) { 830 info.changeSubscription = folder.changes.listen((WatchEvent event) {
798 _handleWatchEvent(folder, info, event); 831 _handleWatchEvent(folder, info, event);
799 }); 832 });
800 try { 833 try {
801 FolderDisposition disposition; 834 FolderDisposition disposition;
835 List<String> dependencies = <String>[];
802 836
803 if (ENABLE_PACKAGESPEC_SUPPORT) { 837 if (ENABLE_PACKAGESPEC_SUPPORT) {
804 // Try .packages first. 838 // Try .packages first.
805 if (pathos.basename(packagespecFile.path) == PACKAGE_SPEC_NAME) { 839 if (pathos.basename(packagespecFile.path) == PACKAGE_SPEC_NAME) {
806 Packages packages = _readPackagespec(packagespecFile); 840 Packages packages = _readPackagespec(packagespecFile);
807 disposition = new PackagesFileDisposition(packages); 841 disposition = new PackagesFileDisposition(packages);
808 } 842 }
809 } 843 }
810 844
811 // Next resort to a package uri resolver. 845 // Next resort to a package uri resolver.
812 if (disposition == null) { 846 if (disposition == null) {
813 disposition = _computeFolderDisposition(folder, info); 847 disposition = _computeFolderDisposition(folder, info, dependencies.add);
814 } 848 }
815 849
850 info.setDependencies(dependencies);
816 info.context = callbacks.addContext(folder, disposition); 851 info.context = callbacks.addContext(folder, disposition);
817 info.context.name = folder.path; 852 info.context.name = folder.path;
818 } catch (_) { 853 } catch (_) {
819 info.changeSubscription.cancel(); 854 info.changeSubscription.cancel();
820 rethrow; 855 rethrow;
821 } 856 }
822 return info; 857 return info;
823 } 858 }
824 859
825 /** 860 /**
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after
948 } 983 }
949 984
950 void _handleWatchEvent(Folder folder, ContextInfo info, WatchEvent event) { 985 void _handleWatchEvent(Folder folder, ContextInfo info, WatchEvent event) {
951 // TODO(brianwilkerson) If a file is explicitly included in one context 986 // TODO(brianwilkerson) If a file is explicitly included in one context
952 // but implicitly referenced in another context, we will only send a 987 // but implicitly referenced in another context, we will only send a
953 // changeSet to the context that explicitly includes the file (because 988 // changeSet to the context that explicitly includes the file (because
954 // that's the only context that's watching the file). 989 // that's the only context that's watching the file).
955 _instrumentationService.logWatchEvent( 990 _instrumentationService.logWatchEvent(
956 folder.path, event.path, event.type.toString()); 991 folder.path, event.path, event.type.toString());
957 String path = event.path; 992 String path = event.path;
993 // First handle changes that affect folderDisposition (since these need to
994 // be processed regardless of whether they are part of an excluded/ignored
995 // path).
996 if (info.hasDependency(path)) {
997 _recomputeFolderDisposition(info);
998 }
958 // maybe excluded globally 999 // maybe excluded globally
959 if (_isExcluded(path)) { 1000 if (_isExcluded(path)) {
960 return; 1001 return;
961 } 1002 }
962 // maybe excluded from the context, so other context will handle it 1003 // maybe excluded from the context, so other context will handle it
963 if (info.excludes(path)) { 1004 if (info.excludes(path)) {
964 return; 1005 return;
965 } 1006 }
966 if (info.ignored(path)) { 1007 if (info.ignored(path)) {
967 return; 1008 return;
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
1079 sources.forEach((Source source) { 1120 sources.forEach((Source source) {
1080 changeSet.changedSource(source); 1121 changeSet.changedSource(source);
1081 }); 1122 });
1082 callbacks.applyChangesToContext(folder, changeSet); 1123 callbacks.applyChangesToContext(folder, changeSet);
1083 } 1124 }
1084 break; 1125 break;
1085 } 1126 }
1086 1127
1087 //TODO(pquitslund): find the right place for this 1128 //TODO(pquitslund): find the right place for this
1088 _checkForPackagespecUpdate(path, info, folder); 1129 _checkForPackagespecUpdate(path, info, folder);
1089
1090 if (info.packageMapInfo != null &&
1091 info.packageMapInfo.dependencies.contains(path)) {
1092 _recomputeFolderDisposition(info);
1093 }
1094 } 1130 }
1095 1131
1096 /** 1132 /**
1097 * Returns `true` if the given [path] is excluded by [excludedPaths]. 1133 * Returns `true` if the given [path] is excluded by [excludedPaths].
1098 */ 1134 */
1099 bool _isExcluded(String path) => _isExcludedBy(excludedPaths, path); 1135 bool _isExcluded(String path) => _isExcludedBy(excludedPaths, path);
1100 1136
1101 /** 1137 /**
1102 * Returns `true` if the given [path] is excluded by [excludedPaths]. 1138 * Returns `true` if the given [path] is excluded by [excludedPaths].
1103 */ 1139 */
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
1158 1194
1159 /** 1195 /**
1160 * Recompute the [FolderDisposition] for the context described by [info], 1196 * Recompute the [FolderDisposition] for the context described by [info],
1161 * and update the client appropriately. 1197 * and update the client appropriately.
1162 */ 1198 */
1163 void _recomputeFolderDisposition(ContextInfo info) { 1199 void _recomputeFolderDisposition(ContextInfo info) {
1164 // TODO(paulberry): when computePackageMap is changed into an 1200 // TODO(paulberry): when computePackageMap is changed into an
1165 // asynchronous API call, we'll want to suspend analysis for this context 1201 // asynchronous API call, we'll want to suspend analysis for this context
1166 // while we're rerunning "pub list", since any analysis we complete while 1202 // while we're rerunning "pub list", since any analysis we complete while
1167 // "pub list" is in progress is just going to get thrown away anyhow. 1203 // "pub list" is in progress is just going to get thrown away anyhow.
1204 List<String> dependencies = <String>[];
1168 FolderDisposition disposition = 1205 FolderDisposition disposition =
1169 _computeFolderDisposition(info.folder, info); 1206 _computeFolderDisposition(info.folder, info, dependencies.add);
1207 info.setDependencies(dependencies);
1170 callbacks.updateContextPackageUriResolver(info.folder, disposition); 1208 callbacks.updateContextPackageUriResolver(info.folder, disposition);
1171 } 1209 }
1172 1210
1173 /** 1211 /**
1174 * Create and return a source representing the given [file] within the given 1212 * Create and return a source representing the given [file] within the given
1175 * [context]. 1213 * [context].
1176 */ 1214 */
1177 static Source createSourceInContext(AnalysisContext context, File file) { 1215 static Source createSourceInContext(AnalysisContext context, File file) {
1178 // TODO(brianwilkerson) Optimize this, by allowing support for source 1216 // TODO(brianwilkerson) Optimize this, by allowing support for source
1179 // factories to restore URI's from a file path rather than a source. 1217 // factories to restore URI's from a file path rather than a source.
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after
1313 class PackagesFileDisposition extends FolderDisposition { 1351 class PackagesFileDisposition extends FolderDisposition {
1314 @override 1352 @override
1315 final Packages packages; 1353 final Packages packages;
1316 1354
1317 PackagesFileDisposition(this.packages) {} 1355 PackagesFileDisposition(this.packages) {}
1318 1356
1319 @override 1357 @override
1320 Iterable<UriResolver> createPackageUriResolvers( 1358 Iterable<UriResolver> createPackageUriResolvers(
1321 ResourceProvider resourceProvider) => const <UriResolver>[]; 1359 ResourceProvider resourceProvider) => const <UriResolver>[];
1322 } 1360 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698