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

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

Issue 450103002: Support for pubspec based contexts in subfolders. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: tweak for tests Created 6 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | pkg/analysis_server/test/context_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) 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 9
10 import 'package:analysis_server/src/package_map_provider.dart'; 10 import 'package:analysis_server/src/package_map_provider.dart';
11 import 'package:analyzer/file_system/file_system.dart'; 11 import 'package:analyzer/file_system/file_system.dart';
12 import 'package:analyzer/src/generated/engine.dart'; 12 import 'package:analyzer/src/generated/engine.dart';
13 import 'package:analyzer/src/generated/source.dart'; 13 import 'package:analyzer/src/generated/source.dart';
14 import 'package:path/path.dart' as pathos;
14 import 'package:watcher/watcher.dart'; 15 import 'package:watcher/watcher.dart';
15 16
17
18 /**
19 * File name of pubspec files.
20 */
21 const String PUBSPEC_NAME = 'pubspec.yaml';
22
23
16 /** 24 /**
17 * Class that maintains a mapping from included/excluded paths to a set of 25 * Class that maintains a mapping from included/excluded paths to a set of
18 * folders that should correspond to analysis contexts. 26 * folders that should correspond to analysis contexts.
19 */ 27 */
20 abstract class ContextManager { 28 abstract class ContextManager {
21 /** 29 /**
22 * File name of pubspec files.
23 */
24 static const String PUBSPEC_NAME = 'pubspec.yaml';
25
26 /**
27 * [_ContextInfo] object for each included directory in the most 30 * [_ContextInfo] object for each included directory in the most
28 * recent successful call to [setRoots]. 31 * recent successful call to [setRoots].
29 */ 32 */
30 Map<Folder, _ContextInfo> _currentDirectoryInfo = 33 Map<Folder, _ContextInfo> _contexts = new HashMap<Folder, _ContextInfo>();
31 new HashMap<Folder, _ContextInfo>();
32 34
33 /** 35 /**
34 * The [ResourceProvider] using which paths are converted into [Resource]s. 36 * The [ResourceProvider] using which paths are converted into [Resource]s.
35 */ 37 */
36 final ResourceProvider resourceProvider; 38 final ResourceProvider resourceProvider;
37 39
38 /** 40 /**
41 * The [path.Context] for this manager.
Brian Wilkerson 2014/08/08 14:00:02 I assume "path" was suppose to be "pathos". As wo
scheglov 2014/08/08 15:54:33 Done.
42 */
43 pathos.Context pathContext;
44
45
46 /**
39 * Provider which is used to determine the mapping from package name to 47 * Provider which is used to determine the mapping from package name to
40 * package folder. 48 * package folder.
41 */ 49 */
42 final PackageMapProvider packageMapProvider; 50 final PackageMapProvider packageMapProvider;
43 51
44 ContextManager(this.resourceProvider, this.packageMapProvider); 52 ContextManager(this.resourceProvider, this.packageMapProvider) {
53 pathContext = resourceProvider.pathContext;
54 }
45 55
46 /** 56 /**
47 * Called when a new context needs to be created. 57 * Called when a new context needs to be created.
48 */ 58 */
49 void addContext(Folder folder, Map<String, List<Folder>> packageMap); 59 void addContext(Folder folder, Map<String, List<Folder>> packageMap);
50 60
51 /** 61 /**
52 * Called when the set of files associated with a context have changed (or 62 * Called when the set of files associated with a context have changed (or
53 * some of those files have been modified). [changeSet] is the set of 63 * some of those files have been modified). [changeSet] is the set of
54 * changes that need to be applied to the context. 64 * changes that need to be applied to the context.
55 */ 65 */
56 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet); 66 void applyChangesToContext(Folder contextFolder, ChangeSet changeSet);
57 67
58 /** 68 /**
59 * Returns `true` if the given absolute [path] is in one of the current 69 * Returns `true` if the given absolute [path] is in one of the current
60 * root folders and is not excluded. 70 * root folders and is not excluded.
61 */ 71 */
62 bool isInAnalysisRoot(String path) { 72 bool isInAnalysisRoot(String path) {
63 // TODO(scheglov) check for excluded paths 73 // TODO(scheglov) check for excluded paths
64 for (Folder root in _currentDirectoryInfo.keys) { 74 for (Folder root in _contexts.keys) {
65 if (root.contains(path)) { 75 if (root.contains(path)) {
66 return true; 76 return true;
67 } 77 }
68 } 78 }
69 return false; 79 return false;
70 } 80 }
71 81
72 /** 82 /**
73 * Remove the context associated with the given [folder]. 83 * Remove the context associated with the given [folder].
74 */ 84 */
(...skipping 18 matching lines...) Expand all
93 'Only support for folder analysis is implemented currently.'); 103 'Only support for folder analysis is implemented currently.');
94 } 104 }
95 } 105 }
96 // excluded 106 // excluded
97 // TODO(scheglov) remove when implemented 107 // TODO(scheglov) remove when implemented
98 if (excludedPaths.isNotEmpty) { 108 if (excludedPaths.isNotEmpty) {
99 throw new UnimplementedError('Excluded paths are not supported yet'); 109 throw new UnimplementedError('Excluded paths are not supported yet');
100 } 110 }
101 Set<Folder> excludedFolders = new HashSet<Folder>(); 111 Set<Folder> excludedFolders = new HashSet<Folder>();
102 // diff 112 // diff
103 Set<Folder> currentFolders = _currentDirectoryInfo.keys.toSet(); 113 Set<Folder> currentFolders = _contexts.keys.toSet();
Brian Wilkerson 2014/08/08 14:00:02 Are we making a copy to avoid modifying the keys w
scheglov 2014/08/08 15:54:33 We use it twice in the code below.
Paul Berry 2014/08/08 16:07:10 Agreed, but if we go with my suggestions below, th
scheglov 2014/08/08 16:34:23 Done.
104 Set<Folder> newFolders = includedFolders.difference(currentFolders); 114 Set<Folder> newFolders = new HashSet<Folder>();
105 Set<Folder> oldFolders = currentFolders.difference(includedFolders); 115 Set<Folder> oldFolders = new HashSet<Folder>();
116 for (Folder currentFolder in currentFolders) {
117 bool isIncluded = includedFolders.any((folder) {
118 return folder.contains(currentFolder.path);
119 });
120 if (!isIncluded) {
121 oldFolders.add(currentFolder);
Paul Berry 2014/08/08 16:07:10 I think it would be clearer to just do: _destro
scheglov 2014/08/08 16:34:23 Done.
122 }
123 }
124 for (Folder includedFolder in includedFolders) {
125 bool wasIncluded = currentFolders.any((folder) {
126 return folder.contains(includedFolder.path);
127 });
128 if (!wasIncluded) {
129 newFolders.add(includedFolder);
Paul Berry 2014/08/08 16:07:10 Similarly, I think it would be clearer to just to:
scheglov 2014/08/08 16:34:23 Done.
130 }
131 }
106 // destroy old contexts 132 // destroy old contexts
107 for (Folder folder in oldFolders) { 133 for (Folder folder in oldFolders) {
108 _destroyContext(folder); 134 _destroyContext(folder);
109 } 135 }
110 // create new contexts 136 // create new contexts
111 for (Folder folder in newFolders) { 137 for (Folder folder in newFolders) {
112 _createContext(folder); 138 _createContexts(folder, false);
113 } 139 }
114 } 140 }
115 141
116 /** 142 /**
117 * Called when the package map for a context has changed. 143 * Called when the package map for a context has changed.
118 */ 144 */
119 void updateContextPackageMap(Folder contextFolder, Map<String, 145 void updateContextPackageMap(Folder contextFolder, Map<String,
120 List<Folder>> packageMap); 146 List<Folder>> packageMap);
121 147
122 /** 148 /**
123 * Create a new context associated with the given folder. 149 * Create a new empty context associated with [folder].
124 */ 150 */
125 void _createContext(Folder folder) { 151 _ContextInfo _createContext(Folder folder, List<_ContextInfo> children) {
126 _ContextInfo info = new _ContextInfo(); 152 _ContextInfo info = new _ContextInfo(folder, children);
127 _currentDirectoryInfo[folder] = info; 153 _contexts[folder] = info;
128 info.changeSubscription = folder.changes.listen((WatchEvent event) { 154 info.changeSubscription = folder.changes.listen((WatchEvent event) {
129 _handleWatchEvent(folder, info, event); 155 _handleWatchEvent(folder, info, event);
130 }); 156 });
131 File pubspecFile = folder.getChild(PUBSPEC_NAME);
132 PackageMapInfo packageMapInfo = 157 PackageMapInfo packageMapInfo =
133 packageMapProvider.computePackageMap(folder); 158 packageMapProvider.computePackageMap(folder);
134 info.packageMapDependencies = packageMapInfo.dependencies; 159 info.packageMapDependencies = packageMapInfo.dependencies;
135 // TODO(paulberry): if any of the dependencies is outside of [folder], 160 // TODO(paulberry): if any of the dependencies is outside of [folder],
136 // we'll need to watch their parent folders as well. 161 // we'll need to watch their parent folders as well.
137 addContext(folder, packageMapInfo.packageMap); 162 addContext(folder, packageMapInfo.packageMap);
163 return info;
164 }
165
166 /**
167 * Create a new context associated with [folder] and fills its with sources.
168 */
169 _ContextInfo _createContextWithSources(Folder folder,
170 List<_ContextInfo> children) {
171 _ContextInfo info = _createContext(folder, children);
138 ChangeSet changeSet = new ChangeSet(); 172 ChangeSet changeSet = new ChangeSet();
139 _addSourceFiles(changeSet, folder, info); 173 _addSourceFiles(changeSet, folder, info);
140 applyChangesToContext(folder, changeSet); 174 applyChangesToContext(folder, changeSet);
175 return info;
176 }
177
178 /**
179 * Creates a new context associated with [folder].
180 *
181 * If there are subfolders with 'pubspec.yaml' files, separate contexts
182 * are created for them, and excluded from the context associated with
183 * [folder].
184 *
185 * If [folder] itself contains a 'pubspec.yaml' file, subfolders are ignored.
186 *
187 * Returns create pubspec-based contexts.
188 */
Paul Berry 2014/08/08 16:07:10 Can you document the meaning of "withPubspecOnly"
scheglov 2014/08/08 16:34:23 Done.
189 List<_ContextInfo> _createContexts(Folder folder, bool withPubspecOnly) {
190 // check if there is a pubspec in the folder
191 {
192 File pubspecFile = folder.getChild(PUBSPEC_NAME);
193 if (pubspecFile.exists) {
194 _ContextInfo info = _createContextWithSources(folder, <_ContextInfo>[]);
195 return [info];
196 }
197 }
198 // try to find subfolders with pubspec files
199 List<_ContextInfo> children = <_ContextInfo>[];
200 for (Resource child in folder.getChildren()) {
201 if (child is Folder) {
202 List<_ContextInfo> childContexts = _createContexts(child, true);
203 children.addAll(childContexts);
204 }
205 }
206 // no pubspec, done
207 if (withPubspecOnly) {
208 return children;
209 }
210 // OK, create a context without a pubspec
211 _createContextWithSources(folder, children);
212 return children;
141 } 213 }
142 214
143 /** 215 /**
144 * Clean up and destroy the context associated with the given folder. 216 * Clean up and destroy the context associated with the given folder.
145 */ 217 */
146 void _destroyContext(Folder folder) { 218 void _destroyContext(Folder folder) {
147 _currentDirectoryInfo[folder].changeSubscription.cancel(); 219 _contexts[folder].changeSubscription.cancel();
148 _currentDirectoryInfo.remove(folder); 220 _contexts.remove(folder);
149 removeContext(folder); 221 removeContext(folder);
150 } 222 }
151 223
224 /**
225 * Extract a new [pubspecFile]-based context from [oldInfo].
226 */
227 void _extractContext(_ContextInfo oldInfo, File pubspecFile) {
228 Folder newFolder = pubspecFile.parent;
229 _ContextInfo newInfo = _createContext(newFolder, []);
230 newInfo.parent = oldInfo;
231 // prepare sources to extract
232 Map<String, Source> extractSources = new HashMap<String, Source>();
Paul Berry 2014/08/08 16:07:10 Rename to "extractedSources"
scheglov 2014/08/08 16:34:23 Done.
233 oldInfo.sources.forEach((path, source) {
234 if (newFolder.contains(path)) {
235 extractSources[path] = source;
236 }
237 });
238 // update new context
239 {
240 ChangeSet changeSet = new ChangeSet();
241 extractSources.forEach((path, source) {
242 newInfo.sources[path] = source;
243 changeSet.addedSource(source);
244 });
245 applyChangesToContext(newFolder, changeSet);
246 }
247 // update old context
248 {
249 ChangeSet changeSet = new ChangeSet();
250 extractSources.forEach((path, source) {
251 oldInfo.sources.remove(path);
252 changeSet.removedSource(source);
253 });
254 applyChangesToContext(oldInfo.folder, changeSet);
255 }
256 }
257
152 void _handleWatchEvent(Folder folder, _ContextInfo info, WatchEvent event) { 258 void _handleWatchEvent(Folder folder, _ContextInfo info, WatchEvent event) {
259 String path = event.path;
260 // maybe excluded, so other context will handle it
261 if (info.excludes(path)) {
262 return;
263 }
264 // handle the change
153 switch (event.type) { 265 switch (event.type) {
154 case ChangeType.ADD: 266 case ChangeType.ADD:
155 if (_isInPackagesDir(event.path, folder)) { 267 if (_isInPackagesDir(path, folder)) {
156 // TODO(paulberry): perhaps we should only skip packages dirs if 268 // TODO(paulberry): perhaps we should only skip packages dirs if
157 // there is a pubspec.yaml? 269 // there is a pubspec.yaml?
158 break; 270 break;
159 } 271 }
160 Resource resource = resourceProvider.getResource(event.path); 272 Resource resource = resourceProvider.getResource(path);
273 // pubspec was added, extract a new context
274 if (_isPubspec(path)) {
275 _extractContext(info, resource);
276 return;
277 }
161 // If the file went away and was replaced by a folder before we 278 // If the file went away and was replaced by a folder before we
162 // had a chance to process the event, resource might be a Folder. In 279 // had a chance to process the event, resource might be a Folder. In
163 // that case don't add it. 280 // that case don't add it.
164 if (resource is File) { 281 if (resource is File) {
165 File file = resource; 282 File file = resource;
166 if (_shouldFileBeAnalyzed(file)) { 283 if (_shouldFileBeAnalyzed(file)) {
167 ChangeSet changeSet = new ChangeSet(); 284 ChangeSet changeSet = new ChangeSet();
168 Source source = file.createSource(); 285 Source source = file.createSource();
169 changeSet.addedSource(source); 286 changeSet.addedSource(source);
170 applyChangesToContext(folder, changeSet); 287 applyChangesToContext(folder, changeSet);
171 info.sources[event.path] = source; 288 info.sources[path] = source;
172 } 289 }
173 } 290 }
174 break; 291 break;
175 case ChangeType.REMOVE: 292 case ChangeType.REMOVE:
176 Source source = info.sources[event.path]; 293 // pubspec was removed, merge the context into its parent
294 if (info.isPubspec(path)) {
295 _mergeContext(info);
296 return;
297 }
298 Source source = info.sources[path];
177 if (source != null) { 299 if (source != null) {
178 ChangeSet changeSet = new ChangeSet(); 300 ChangeSet changeSet = new ChangeSet();
179 changeSet.removedSource(source); 301 changeSet.removedSource(source);
180 applyChangesToContext(folder, changeSet); 302 applyChangesToContext(folder, changeSet);
181 info.sources.remove(event.path); 303 info.sources.remove(path);
182 } 304 }
183 break; 305 break;
184 case ChangeType.MODIFY: 306 case ChangeType.MODIFY:
185 Source source = info.sources[event.path]; 307 Source source = info.sources[path];
186 if (source != null) { 308 if (source != null) {
187 ChangeSet changeSet = new ChangeSet(); 309 ChangeSet changeSet = new ChangeSet();
188 changeSet.changedSource(source); 310 changeSet.changedSource(source);
189 applyChangesToContext(folder, changeSet); 311 applyChangesToContext(folder, changeSet);
190 } 312 }
191 break; 313 break;
192 } 314 }
193 315
194 if (info.packageMapDependencies.contains(event.path)) { 316 if (info.packageMapDependencies.contains(path)) {
195 // TODO(paulberry): when computePackageMap is changed into an 317 // TODO(paulberry): when computePackageMap is changed into an
196 // asynchronous API call, we'll want to suspend analysis for this context 318 // asynchronous API call, we'll want to suspend analysis for this context
197 // while we're rerunning "pub list", since any analysis we complete while 319 // while we're rerunning "pub list", since any analysis we complete while
198 // "pub list" is in progress is just going to get thrown away anyhow. 320 // "pub list" is in progress is just going to get thrown away anyhow.
199 PackageMapInfo packageMapInfo = 321 PackageMapInfo packageMapInfo =
200 packageMapProvider.computePackageMap(folder); 322 packageMapProvider.computePackageMap(folder);
201 info.packageMapDependencies = packageMapInfo.dependencies; 323 info.packageMapDependencies = packageMapInfo.dependencies;
202 updateContextPackageMap(folder, packageMapInfo.packageMap); 324 updateContextPackageMap(folder, packageMapInfo.packageMap);
203 } 325 }
204 } 326 }
205 327
206 /** 328 /**
207 * Determine if the path from [folder] to [path] contains a 'packages' 329 * Determine if the path from [folder] to [path] contains a 'packages'
208 * directory. 330 * directory.
209 */ 331 */
210 bool _isInPackagesDir(String path, Folder folder) { 332 bool _isInPackagesDir(String path, Folder folder) {
211 String relativePath = 333 String relativePath = pathContext.relative(path, from: folder.path);
212 resourceProvider.pathContext.relative(path, from: folder.path); 334 List<String> pathParts = pathContext.split(relativePath);
213 List<String> pathParts = resourceProvider.pathContext.split(relativePath);
214 for (int i = 0; i < pathParts.length - 1; i++) { 335 for (int i = 0; i < pathParts.length - 1; i++) {
215 if (pathParts[i] == 'packages') { 336 if (pathParts[i] == 'packages') {
216 return true; 337 return true;
217 } 338 }
218 } 339 }
219 return false; 340 return false;
220 } 341 }
221 342
222 /** 343 /**
344 * Returns `true` if the given absolute [path] is a pubspec file.
345 */
346 bool _isPubspec(String path) {
347 return pathContext.basename(path) == PUBSPEC_NAME;
348 }
349
350 /**
351 * Merges [info] context into its parent.
352 */
353 void _mergeContext(_ContextInfo info) {
354 // destroy the context
355 _destroyContext(info.folder);
356 // add files to the parent context
357 _ContextInfo parentInfo = info.parent;
358 if (parentInfo != null) {
359 parentInfo.children.remove(info);
360 ChangeSet changeSet = new ChangeSet();
361 _addSourceFiles(changeSet, info.folder, parentInfo);
Paul Berry 2014/08/08 16:07:10 It looks like this will re-read the directory cont
scheglov 2014/08/08 16:34:23 Done.
362 applyChangesToContext(parentInfo.folder, changeSet);
363 }
364 }
365
366 /**
223 * Resursively adds all Dart and HTML files to the [changeSet]. 367 * Resursively adds all Dart and HTML files to the [changeSet].
224 */ 368 */
225 static void _addSourceFiles(ChangeSet changeSet, Folder folder, 369 static void _addSourceFiles(ChangeSet changeSet, Folder folder,
226 _ContextInfo info) { 370 _ContextInfo info) {
371 if (info.excludesResource(folder)) {
372 return;
373 }
227 List<Resource> children = folder.getChildren(); 374 List<Resource> children = folder.getChildren();
228 for (Resource child in children) { 375 for (Resource child in children) {
229 if (child is File) { 376 if (child is File) {
230 if (_shouldFileBeAnalyzed(child)) { 377 if (_shouldFileBeAnalyzed(child)) {
231 Source source = child.createSource(); 378 Source source = child.createSource();
232 changeSet.addedSource(source); 379 changeSet.addedSource(source);
233 info.sources[child.path] = source; 380 info.sources[child.path] = source;
234 } 381 }
235 } else if (child is Folder) { 382 } else if (child is Folder) {
236 if (child.shortName == 'packages') { 383 if (child.shortName == 'packages') {
(...skipping 18 matching lines...) Expand all
255 // causing the analyzer to thrash, just ignore links to non-existent files. 402 // causing the analyzer to thrash, just ignore links to non-existent files.
256 return file.exists; 403 return file.exists;
257 } 404 }
258 } 405 }
259 406
260 /** 407 /**
261 * Information tracked by the [ContextManager] for each context. 408 * Information tracked by the [ContextManager] for each context.
262 */ 409 */
263 class _ContextInfo { 410 class _ContextInfo {
264 /** 411 /**
412 * The [Folder] for which this information object is created.
413 */
414 final Folder folder;
415
416 /**
417 * The enclosed pubspec-based contexts.
418 */
419 final List<_ContextInfo> children;
420
421 /**
422 * The [_ContextInfo] that encloses this one.
423 */
424 _ContextInfo parent;
425
426 /**
427 * The `pubspec.yaml` file path for this context.
428 */
429 String pubspecPath;
430
431 /**
265 * Stream subscription we are using to watch the context's directory for 432 * Stream subscription we are using to watch the context's directory for
266 * changes. 433 * changes.
267 */ 434 */
268 StreamSubscription<WatchEvent> changeSubscription; 435 StreamSubscription<WatchEvent> changeSubscription;
269 436
270 /** 437 /**
271 * Map from full path to the [Source] object, for each source that has been 438 * Map from full path to the [Source] object, for each source that has been
272 * added to the context. 439 * added to the context.
273 */ 440 */
274 Map<String, Source> sources = new HashMap<String, Source>(); 441 Map<String, Source> sources = new HashMap<String, Source>();
275 442
276 /** 443 /**
277 * Dependencies of the context's package map. 444 * Dependencies of the context's package map.
278 * If any of these files changes, the package map needs to be recomputed. 445 * If any of these files changes, the package map needs to be recomputed.
279 */ 446 */
280 Set<String> packageMapDependencies; 447 Set<String> packageMapDependencies;
448
449 _ContextInfo(this.folder, this.children) {
450 pubspecPath = folder.getChild(PUBSPEC_NAME).path;
451 for (_ContextInfo child in children) {
452 child.parent = this;
453 }
454 }
455
456 /**
457 * Returns `true` if [path] is excluded, as it is in one of the children.
458 */
459 bool excludes(String path) {
460 return children.any((child) {
461 return child.folder.contains(path);
462 });
463 }
464
465 /**
466 * Returns `true` if [resource] is excldued, as it is in one of the children.
467 */
468 bool excludesResource(Resource resource) {
469 return excludes(resource.path);
470 }
471
472 /**
473 * Returns `true` if [path] is the pubspec file of this context.
474 */
475 bool isPubspec(String path) {
476 return path == pubspecPath;
477 }
281 } 478 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analysis_server/test/context_manager_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698