Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 import 'dart:async'; | |
| 6 import 'dart:core' hide Resource; | |
| 7 | |
| 8 import 'package:analysis_server/src/analysis_server.dart'; | |
| 9 import 'package:analysis_server/src/context_manager.dart'; | |
| 10 import 'package:analyzer/dart/ast/ast.dart'; | |
| 11 import 'package:analyzer/file_system/file_system.dart'; | |
| 12 import 'package:analyzer/src/generated/engine.dart'; | |
| 13 import 'package:analyzer/src/generated/source.dart'; | |
| 14 import 'package:analyzer/src/summary/format.dart'; | |
| 15 import 'package:analyzer/src/summary/summarize_ast.dart' | |
| 16 show serializeAstUnlinked; | |
| 17 import 'package:analyzer/src/summary/summarize_elements.dart' | |
| 18 show PackageBundleAssembler; | |
| 19 import 'package:analyzer/src/task/dart.dart'; | |
| 20 import 'package:analyzer/src/util/fast_uri.dart'; | |
| 21 import 'package:analyzer/task/dart.dart'; | |
| 22 import 'package:analyzer/task/model.dart'; | |
| 23 import 'package:path/src/context.dart' as pathos; | |
| 24 | |
| 25 const visibleForTesting = const Object(); | |
| 26 | |
| 27 /** | |
| 28 * A package in the pub cache. | |
| 29 */ | |
| 30 @visibleForTesting | |
|
Brian Wilkerson
2016/08/05 22:09:14
nit: There have been requests for this annotation;
| |
| 31 class PubPackage { | |
| 32 final String name; | |
| 33 final Folder libFolder; | |
| 34 | |
| 35 PubPackage(this.name, this.libFolder); | |
| 36 | |
| 37 Folder get folder => libFolder.parent; | |
| 38 | |
| 39 @override | |
| 40 int get hashCode => libFolder.hashCode; | |
| 41 | |
| 42 @override | |
| 43 bool operator ==(other) { | |
| 44 return other is PubPackage && other.libFolder == libFolder; | |
| 45 } | |
| 46 | |
| 47 @override | |
| 48 String toString() => '($name in $folder)'; | |
| 49 } | |
| 50 | |
| 51 /** | |
| 52 * Class the manages summaries for pub packages. | |
|
Paul Berry
2016/08/05 21:43:02
s/the/that/
scheglov
2016/08/05 21:47:41
Done.
| |
| 53 */ | |
| 54 class PubSummaryManager { | |
|
Brian Wilkerson
2016/08/05 22:09:14
I'd love to see an example of how clients are expe
| |
| 55 final ResourceProvider resourceProvider; | |
| 56 final AnalysisServer server; | |
| 57 | |
| 58 // /** | |
| 59 // * The map from absolute paths of pub packages in the pub cache to their | |
| 60 // * unlinked summary bundles. | |
| 61 // */ | |
| 62 // final Map<String, PackageBundle> unlinkedBundleMap = | |
| 63 // new HashMap<String, PackageBundle>(); | |
| 64 | |
| 65 /** | |
| 66 * The set of packages to compute summaries for. | |
| 67 */ | |
| 68 final Set<PubPackage> packagesToSummarize = new Set<PubPackage>(); | |
| 69 | |
| 70 /** | |
| 71 * The set of already processed packages, which we have already checked | |
| 72 * for their unlinked summary existence, or scheduled its computing. | |
| 73 */ | |
| 74 final Set<PubPackage> seenPackages = new Set<PubPackage>(); | |
| 75 | |
| 76 /** | |
| 77 * The [Completer] that completes when analysis is complete. | |
| 78 */ | |
| 79 Completer _onCompleteCompleter; | |
| 80 | |
| 81 /** | |
| 82 * Create a new instance and start listening for [AnalysisServer] and | |
| 83 * [AnalysisContext] events, and schedule creating pub summaries. | |
| 84 */ | |
| 85 PubSummaryManager(this.resourceProvider, this.server) { | |
| 86 server.onContextsChanged.listen((ContextsChangedEvent event) { | |
| 87 for (AnalysisContext context in event.added) { | |
| 88 context | |
| 89 .onResultChanged(LIBRARY_ELEMENT1) | |
| 90 .listen(handleNewLibraryElementEvent); | |
| 91 } | |
| 92 }); | |
| 93 } | |
| 94 | |
| 95 /** | |
| 96 * The [Future] that completes when computing of all package summaries is | |
| 97 * complete. | |
| 98 */ | |
| 99 Future get onComplete { | |
| 100 if (packagesToSummarize.isEmpty) { | |
| 101 return new Future.value(); | |
| 102 } | |
| 103 _onCompleteCompleter ??= new Completer(); | |
| 104 return _onCompleteCompleter.future; | |
| 105 } | |
| 106 | |
| 107 /** | |
| 108 * Return the [pathos.Context] corresponding to the [resourceProvider]. | |
| 109 */ | |
| 110 pathos.Context get pathContext => resourceProvider.pathContext; | |
| 111 | |
| 112 /** | |
| 113 * If the given [source] has the 'package' scheme, and its path is in the | |
| 114 * pub cache, return information about the package that contains the [source]. | |
| 115 * Otherwise return `null`. | |
| 116 */ | |
| 117 @visibleForTesting | |
| 118 PubPackage getPackageInPubCache(AnalysisContext context, Source source) { | |
| 119 if (source.uri.scheme == 'package') { | |
| 120 String path = source.fullName; | |
| 121 if (isPathInPubCache(pathContext, path)) { | |
| 122 String packageName = getPackageName(source.uri); | |
| 123 if (packageName != null) { | |
| 124 List<Folder> libFolders = | |
| 125 context.sourceFactory.packageMap[packageName]; | |
| 126 if (libFolders != null && libFolders.length == 1) { | |
| 127 return new PubPackage(packageName, libFolders.first); | |
| 128 } | |
| 129 } | |
| 130 } | |
| 131 } | |
| 132 return null; | |
| 133 } | |
| 134 | |
| 135 /** | |
| 136 * Handle [ResultChangedEvent] for [LIBRARY_ELEMENT1] and schedule computing | |
| 137 * summary for the library, if it is not ready yet. | |
| 138 */ | |
| 139 @visibleForTesting | |
| 140 void handleNewLibraryElementEvent(ResultChangedEvent event) { | |
| 141 AnalysisTarget source = event.target; | |
| 142 if (event.wasComputed && source is Source) { | |
| 143 PubPackage package = getPackageInPubCache(event.context, source); | |
| 144 if (package != null && seenPackages.add(package)) { | |
| 145 packagesToSummarize.add(package); | |
| 146 if (packagesToSummarize.length == 1) { | |
| 147 _scheduleNextPackageSummary(); | |
|
Brian Wilkerson
2016/08/05 22:09:13
If a package 'a' depends on a package 'b', do we n
Paul Berry
2016/08/05 22:29:28
That is not a constraint when building unlinked su
| |
| 148 } | |
| 149 } | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 /** | |
| 154 * Compute summary for a package from [packagesToSummarize], and schedule | |
| 155 * delayed computation of the next package summary, if any. | |
| 156 */ | |
| 157 void _computeNextPackageSummary() { | |
| 158 if (packagesToSummarize.isNotEmpty) { | |
| 159 PubPackage package = packagesToSummarize.first; | |
| 160 _computeUnlinkedPackageSummary(package); | |
| 161 packagesToSummarize.remove(package); | |
| 162 _scheduleNextPackageSummary(); | |
| 163 } else { | |
| 164 if (_onCompleteCompleter != null) { | |
| 165 _onCompleteCompleter.complete(true); | |
| 166 _onCompleteCompleter = null; | |
| 167 } | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 /** | |
| 172 * Compute the unlinked summary for the package with the given path, put | |
| 173 * it in the [unlinkedBundleMap] and store into the [resourceProvider]. | |
| 174 * | |
| 175 * TODO(scheglov) Consider moving into separate isolate(s). | |
| 176 */ | |
| 177 void _computeUnlinkedPackageSummary(PubPackage package) { | |
| 178 AnalysisContext context = AnalysisEngine.instance.createAnalysisContext(); | |
| 179 context.sourceFactory = new SourceFactory( | |
| 180 <UriResolver>[new DartUriResolver(server.sdkManager.anySdk)]); | |
|
Brian Wilkerson
2016/08/05 22:09:14
This won't let us distinguish between packages bui
Paul Berry
2016/08/05 22:29:28
I'm not aware of any constraints Flutter imposes t
| |
| 181 | |
| 182 Folder libFolder = package.libFolder; | |
| 183 String libPath = libFolder.path + pathContext.separator; | |
| 184 PackageBundleAssembler assembler = new PackageBundleAssembler(); | |
| 185 | |
| 186 /** | |
| 187 * If the given [file] is a Dart file, the unlinked summary of it. | |
| 188 */ | |
| 189 void addDartFile(File file) { | |
| 190 String path = file.path; | |
| 191 if (AnalysisEngine.isDartFileName(path)) { | |
| 192 String pathInLib = path.substring(libPath.length); | |
| 193 String uriStr = 'package:${package.name}/$pathInLib'; | |
|
Paul Berry
2016/08/05 21:43:02
This looks incorrect for Windows, since on Windows
scheglov
2016/08/05 21:47:41
Fixed.
| |
| 194 Uri uri = FastUri.parse(uriStr); | |
| 195 Source source = file.createSource(uri); | |
| 196 CompilationUnit unit = context.computeResult(source, PARSED_UNIT); | |
| 197 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit); | |
| 198 assembler.addUnlinkedUnit(source, unlinkedUnit); | |
| 199 } | |
| 200 } | |
| 201 | |
| 202 /** | |
| 203 * Visit the [folder] recursively. | |
| 204 */ | |
| 205 void addDartFiles(Folder folder) { | |
| 206 List<Resource> children = folder.getChildren(); | |
| 207 for (Resource child in children) { | |
| 208 if (child is File) { | |
| 209 addDartFile(child); | |
| 210 } | |
| 211 } | |
| 212 for (Resource child in children) { | |
|
Brian Wilkerson
2016/08/05 22:09:14
Does the order in which we add the children matter
| |
| 213 if (child is Folder) { | |
| 214 addDartFiles(child); | |
| 215 } | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 try { | |
| 220 addDartFiles(libFolder); | |
| 221 List<int> bytes = assembler.assemble().toBuffer(); | |
| 222 package.folder | |
| 223 .getChildAssumingFile('summary_spec.full.ds') | |
| 224 .writeAsBytesSync(bytes); | |
| 225 } on FileSystemException { | |
| 226 // Ignore file system exceptions. | |
| 227 } | |
| 228 } | |
| 229 | |
| 230 /** | |
| 231 * Schedule delayed computation of the next package summary from the set of | |
| 232 * [packagesToSummarize]. We delay each computation because we want | |
| 233 * operations in analysis server to proceed, and computing summaries of | |
| 234 * packages is a background task. | |
| 235 */ | |
| 236 void _scheduleNextPackageSummary() { | |
| 237 new Future.delayed( | |
| 238 new Duration(milliseconds: 10), _computeNextPackageSummary); | |
| 239 } | |
| 240 | |
| 241 /** | |
| 242 * If the given [uri] has the `package` scheme, return the names of the | |
|
Paul Berry
2016/08/05 21:43:02
s/names/name/
scheglov
2016/08/05 21:47:41
Done.
| |
| 243 * package that contains the referenced resource. Otherwise return `null`. | |
| 244 * | |
| 245 * For example `package:foo/bar.dart` => `foo`. | |
| 246 */ | |
| 247 static String getPackageName(Uri uri) { | |
| 248 const String PACKAGE_SCHEME = 'package:'; | |
| 249 String text = uri.toString(); | |
| 250 if (text.startsWith(PACKAGE_SCHEME)) { | |
| 251 int index = text.indexOf('/'); | |
| 252 if (index != -1) { | |
| 253 return text.substring(PACKAGE_SCHEME.length, index); | |
| 254 } | |
| 255 } | |
| 256 return null; | |
| 257 } | |
| 258 | |
| 259 /** | |
| 260 * Return `true` if the given absolute [path] is in the pub cache. | |
| 261 */ | |
| 262 static bool isPathInPubCache(pathos.Context pathContext, String path) { | |
| 263 List<String> parts = pathContext.split(path); | |
| 264 for (int i = 0; i < parts.length - 1; i++) { | |
| 265 if (parts[i] == '.pub-cache') { | |
| 266 return true; | |
| 267 } | |
| 268 if (parts[i] == 'Pub' && parts[i + 1] == 'Cache') { | |
| 269 return true; | |
| 270 } | |
| 271 } | |
| 272 return false; | |
| 273 } | |
| 274 } | |
| OLD | NEW |