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