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:collection'; | |
| 7 import 'dart:core' hide Resource; | |
| 8 | |
| 9 import 'package:analyzer/dart/ast/ast.dart'; | |
| 10 import 'package:analyzer/dart/ast/token.dart'; | |
| 11 import 'package:analyzer/file_system/file_system.dart'; | |
| 12 import 'package:analyzer/src/dart/scanner/reader.dart'; | |
| 13 import 'package:analyzer/src/dart/scanner/scanner.dart'; | |
| 14 import 'package:analyzer/src/generated/engine.dart'; | |
| 15 import 'package:analyzer/src/generated/error.dart'; | |
| 16 import 'package:analyzer/src/generated/parser.dart'; | |
| 17 import 'package:analyzer/src/generated/source.dart'; | |
| 18 import 'package:analyzer/src/summary/format.dart'; | |
| 19 import 'package:analyzer/src/summary/idl.dart'; | |
| 20 import 'package:analyzer/src/summary/package_bundle_reader.dart' | |
| 21 show ResynthesizerResultProvider; | |
| 22 import 'package:analyzer/src/summary/summarize_ast.dart' | |
| 23 show serializeAstUnlinked; | |
| 24 import 'package:analyzer/src/summary/summarize_elements.dart' | |
| 25 show PackageBundleAssembler; | |
| 26 import 'package:analyzer/src/util/fast_uri.dart'; | |
| 27 import 'package:path/path.dart' as pathos; | |
| 28 | |
| 29 /** | |
| 30 * A package in the pub cache. | |
| 31 */ | |
| 32 class PubPackage { | |
| 33 final String name; | |
| 34 final Folder libFolder; | |
| 35 | |
| 36 PubPackage(this.name, this.libFolder); | |
| 37 | |
| 38 Folder get folder => libFolder.parent; | |
| 39 | |
| 40 @override | |
| 41 int get hashCode => libFolder.hashCode; | |
| 42 | |
| 43 @override | |
| 44 bool operator ==(other) { | |
| 45 return other is PubPackage && other.libFolder == libFolder; | |
| 46 } | |
| 47 | |
| 48 @override | |
| 49 String toString() => '($name in $folder)'; | |
| 50 } | |
| 51 | |
| 52 /** | |
| 53 * Class that manages summaries for pub packages. | |
| 54 * | |
| 55 * The client should call [getLinkedBundles] after creating a new | |
| 56 * [AnalysisContext] and configuring its source factory, but before computing | |
| 57 * any analysis results. The returned linked bundles can be used to create and | |
| 58 * configure [ResynthesizerResultProvider] for the context. | |
| 59 */ | |
| 60 class PubSummaryManager { | |
| 61 static const UNLINKED_BUNDLE_FILE_NAME = 'unlinked.ds'; | |
| 62 | |
| 63 final ResourceProvider resourceProvider; | |
| 64 | |
| 65 /** | |
| 66 * The name of the temporary file that is used for atomic writes. | |
| 67 */ | |
| 68 final String tempFileName; | |
| 69 | |
| 70 /** | |
| 71 * The map from [PubPackage]s to their unlinked [PackageBundle]s in the pub | |
| 72 * cache. | |
| 73 */ | |
| 74 final Map<PubPackage, PackageBundle> unlinkedBundleMap = | |
| 75 new HashMap<PubPackage, PackageBundle>(); | |
| 76 | |
| 77 /** | |
| 78 * The set of packages to compute unlinked summaries for. | |
| 79 */ | |
| 80 final Set<PubPackage> packagesToComputeUnlinked = new Set<PubPackage>(); | |
| 81 | |
| 82 /** | |
| 83 * The set of already processed packages, which we have already checked | |
| 84 * for their unlinked bundle existence, or scheduled its computing. | |
| 85 */ | |
| 86 final Set<PubPackage> seenPackages = new Set<PubPackage>(); | |
| 87 | |
| 88 /** | |
| 89 * The [Completer] that completes when computing of all scheduled unlinked | |
| 90 * bundles is complete. | |
| 91 */ | |
| 92 Completer _onUnlinkedCompleteCompleter; | |
| 93 | |
| 94 PubSummaryManager(this.resourceProvider, this.tempFileName); | |
| 95 | |
| 96 /** | |
| 97 * The [Future] that completes when computing of all scheduled unlinked | |
| 98 * bundles is complete. | |
| 99 */ | |
| 100 Future get onUnlinkedComplete { | |
| 101 if (packagesToComputeUnlinked.isEmpty) { | |
| 102 return new Future.value(); | |
| 103 } | |
| 104 _onUnlinkedCompleteCompleter ??= new Completer(); | |
| 105 return _onUnlinkedCompleteCompleter.future; | |
| 106 } | |
| 107 | |
| 108 /** | |
| 109 * Return the [pathos.Context] corresponding to the [resourceProvider]. | |
| 110 */ | |
| 111 pathos.Context get pathContext => resourceProvider.pathContext; | |
| 112 | |
| 113 /** | |
| 114 * Return the list of linked [PackageBundle]s that can be provided at this | |
| 115 * time for a subset of the packages used by the given [context]. If | |
| 116 * information about some of the used packages is not available yet, schedule | |
| 117 * its computation, so that it might be available later for other contexts | |
| 118 * referencing the same packages. | |
| 119 */ | |
| 120 List<PackageBundle> getLinkedBundles(AnalysisContext context) { | |
| 121 Map<String, PackageBundle> unlinkedBundles = getUnlinkedBundles(context); | |
| 122 // TODO(scheglov) actually compute available linked bundles | |
| 123 return <PackageBundle>[]; | |
| 124 } | |
| 125 | |
| 126 /** | |
| 127 * Return all available unlinked [PackageBundle]s for the given [context], | |
| 128 * maybe an empty list, but not `null`. | |
| 129 */ | |
| 130 Map<String, PackageBundle> getUnlinkedBundles(AnalysisContext context) { | |
| 131 Map<String, PackageBundle> unlinkedBundles = | |
| 132 new HashMap<String, PackageBundle>(); | |
| 133 Map<String, List<Folder>> packageMap = context.sourceFactory.packageMap; | |
| 134 if (packageMap != null) { | |
| 135 packageMap.forEach((String packageName, List<Folder> libFolders) { | |
| 136 if (libFolders.length == 1) { | |
| 137 Folder libFolder = libFolders.first; | |
| 138 if (isPathInPubCache(pathContext, libFolder.path)) { | |
| 139 PubPackage package = new PubPackage(packageName, libFolder); | |
| 140 PackageBundle unlinkedBundle = _getUnlinkedOrSchedule(package); | |
| 141 if (unlinkedBundle != null) { | |
| 142 unlinkedBundles[packageName] = unlinkedBundle; | |
| 143 } | |
| 144 } | |
| 145 } | |
| 146 }); | |
| 147 } | |
| 148 return unlinkedBundles; | |
| 149 } | |
| 150 | |
| 151 /** | |
| 152 * Compute unlinked bundle for a package from [packagesToComputeUnlinked], | |
| 153 * and schedule delayed computation for the next package, if any. | |
| 154 */ | |
| 155 void _computeNextUnlinked() { | |
| 156 if (packagesToComputeUnlinked.isNotEmpty) { | |
| 157 PubPackage package = packagesToComputeUnlinked.first; | |
| 158 _computeUnlinked(package); | |
| 159 packagesToComputeUnlinked.remove(package); | |
| 160 _scheduleNextUnlinked(); | |
| 161 } else { | |
| 162 if (_onUnlinkedCompleteCompleter != null) { | |
| 163 _onUnlinkedCompleteCompleter.complete(true); | |
| 164 _onUnlinkedCompleteCompleter = null; | |
| 165 } | |
| 166 } | |
| 167 } | |
| 168 | |
| 169 /** | |
| 170 * Compute the unlinked bundle for the package with the given path, put | |
| 171 * it in the [unlinkedBundleMap] and store into the [resourceProvider]. | |
| 172 * | |
| 173 * TODO(scheglov) Consider moving into separate isolate(s). | |
| 174 */ | |
| 175 void _computeUnlinked(PubPackage package) { | |
| 176 Folder libFolder = package.libFolder; | |
| 177 String libPath = libFolder.path + pathContext.separator; | |
| 178 PackageBundleAssembler assembler = new PackageBundleAssembler(); | |
| 179 | |
| 180 /** | |
| 181 * Return the `package` [Uri] for the given [path] in the `lib` folder | |
| 182 * of the current package. | |
| 183 */ | |
| 184 Uri getUri(String path) { | |
| 185 String pathInLib = path.substring(libPath.length); | |
| 186 String uriPath = pathos.posix.joinAll(pathContext.split(pathInLib)); | |
| 187 String uriStr = 'package:${package.name}/$uriPath'; | |
| 188 return FastUri.parse(uriStr); | |
| 189 } | |
| 190 | |
| 191 /** | |
| 192 * If the given [file] is a Dart file, add its unlinked unit. | |
| 193 */ | |
| 194 void addDartFile(File file) { | |
| 195 String path = file.path; | |
| 196 if (AnalysisEngine.isDartFileName(path)) { | |
| 197 Uri uri = getUri(path); | |
| 198 Source source = file.createSource(uri); | |
| 199 CompilationUnit unit = _parse(source); | |
| 200 UnlinkedUnitBuilder unlinkedUnit = | |
| 201 serializeAstUnlinked(unit, unit.lineInfo.lineStarts); | |
| 202 assembler.addUnlinkedUnit(source, unlinkedUnit); | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 /** | |
| 207 * Visit the [folder] recursively. | |
| 208 */ | |
| 209 void addDartFiles(Folder folder) { | |
| 210 List<Resource> children = folder.getChildren(); | |
| 211 for (Resource child in children) { | |
| 212 if (child is File) { | |
| 213 addDartFile(child); | |
| 214 } | |
| 215 } | |
| 216 for (Resource child in children) { | |
| 217 if (child is Folder) { | |
| 218 addDartFiles(child); | |
| 219 } | |
| 220 } | |
| 221 } | |
| 222 | |
| 223 try { | |
| 224 addDartFiles(libFolder); | |
| 225 List<int> bytes = assembler.assemble().toBuffer(); | |
| 226 _writeAtomic(package.folder, UNLINKED_BUNDLE_FILE_NAME, bytes); | |
| 227 } on FileSystemException { | |
| 228 // Ignore file system exceptions. | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 /** | |
| 233 * Return the unlinked [PackageBundle] for the given [package]. If the bundle | |
| 234 * has not been compute yet, return `null` and schedule its computation. | |
| 235 */ | |
| 236 PackageBundle _getUnlinkedOrSchedule(PubPackage package) { | |
| 237 // Try to find in the cache. | |
| 238 PackageBundle bundle = unlinkedBundleMap[package]; | |
| 239 if (bundle != null) { | |
| 240 return bundle; | |
| 241 } | |
| 242 // Try to read from the file system. | |
| 243 File unlinkedFile = | |
| 244 package.folder.getChildAssumingFile(UNLINKED_BUNDLE_FILE_NAME); | |
| 245 if (unlinkedFile.exists) { | |
| 246 try { | |
| 247 List<int> bytes = unlinkedFile.readAsBytesSync(); | |
| 248 bundle = new PackageBundle.fromBuffer(bytes); | |
| 249 unlinkedBundleMap[package] = bundle; | |
| 250 return bundle; | |
| 251 } on FileSystemException { | |
| 252 // Ignore file system exceptions. | |
| 253 } | |
| 254 } | |
| 255 // Schedule computation in the background. | |
| 256 if (package != null && seenPackages.add(package)) { | |
| 257 if (packagesToComputeUnlinked.isEmpty) { | |
| 258 _scheduleNextUnlinked(); | |
| 259 } | |
| 260 packagesToComputeUnlinked.add(package); | |
| 261 } | |
| 262 // The bundle is for available. | |
| 263 return null; | |
| 264 } | |
| 265 | |
| 266 /** | |
| 267 * Parse the given [source] into AST. | |
| 268 */ | |
| 269 CompilationUnit _parse(Source source) { | |
| 270 String code = source.contents.data; | |
| 271 AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; | |
|
Brian Wilkerson
2016/08/09 14:09:00
Do we want to check that there are no parse errors
scheglov
2016/08/09 14:23:08
I don't think so.
The client would see the same AS
| |
| 272 CharSequenceReader reader = new CharSequenceReader(code); | |
| 273 Scanner scanner = new Scanner(source, reader, errorListener); | |
| 274 Token token = scanner.tokenize(); | |
| 275 LineInfo lineInfo = new LineInfo(scanner.lineStarts); | |
| 276 Parser parser = new Parser(source, errorListener); | |
| 277 CompilationUnit unit = parser.parseCompilationUnit(token); | |
| 278 unit.lineInfo = lineInfo; | |
| 279 return unit; | |
| 280 } | |
| 281 | |
| 282 /** | |
| 283 * Schedule delayed computation of the next package unlinked bundle from the | |
| 284 * set of [packagesToComputeUnlinked]. We delay each computation because we | |
| 285 * want operations in analysis server to proceed, and computing bundles of | |
| 286 * packages is a background task. | |
| 287 */ | |
| 288 void _scheduleNextUnlinked() { | |
| 289 new Future.delayed(new Duration(milliseconds: 10), _computeNextUnlinked); | |
| 290 } | |
| 291 | |
| 292 /** | |
| 293 * Atomically write the given [bytes] into the file in the [folder]. | |
| 294 */ | |
| 295 void _writeAtomic(Folder folder, String fileName, List<int> bytes) { | |
| 296 String filePath = folder.getChildAssumingFile(fileName).path; | |
| 297 File tempFile = folder.getChildAssumingFile(tempFileName); | |
| 298 tempFile.writeAsBytesSync(bytes); | |
| 299 tempFile.renameSync(filePath); | |
| 300 } | |
| 301 | |
| 302 /** | |
| 303 * Return `true` if the given absolute [path] is in the pub cache. | |
| 304 */ | |
| 305 static bool isPathInPubCache(pathos.Context pathContext, String path) { | |
| 306 List<String> parts = pathContext.split(path); | |
| 307 for (int i = 0; i < parts.length - 1; i++) { | |
| 308 if (parts[i] == '.pub-cache') { | |
| 309 return true; | |
| 310 } | |
| 311 if (parts[i] == 'Pub' && parts[i + 1] == 'Cache') { | |
| 312 return true; | |
| 313 } | |
| 314 } | |
| 315 return false; | |
| 316 } | |
| 317 } | |
| OLD | NEW |