| 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'; | |
| 8 | |
| 9 import 'package:analyzer/dart/ast/ast.dart'; | |
| 10 import 'package:analyzer/dart/ast/token.dart'; | |
| 11 import 'package:analyzer/error/listener.dart'; | |
| 12 import 'package:analyzer/file_system/file_system.dart'; | |
| 13 import 'package:analyzer/src/dart/scanner/reader.dart'; | |
| 14 import 'package:analyzer/src/dart/scanner/scanner.dart'; | |
| 15 import 'package:analyzer/src/generated/engine.dart'; | |
| 16 import 'package:analyzer/src/generated/parser.dart'; | |
| 17 import 'package:analyzer/src/generated/source.dart'; | |
| 18 import 'package:analyzer/src/generated/utilities_collection.dart'; | |
| 19 import 'package:analyzer/src/summary/api_signature.dart'; | |
| 20 import 'package:analyzer/src/summary/format.dart'; | |
| 21 import 'package:analyzer/src/summary/idl.dart'; | |
| 22 import 'package:analyzer/src/summary/link.dart'; | |
| 23 import 'package:analyzer/src/summary/package_bundle_reader.dart' | |
| 24 show ResynthesizerResultProvider, SummaryDataStore; | |
| 25 import 'package:analyzer/src/summary/summarize_ast.dart' | |
| 26 show serializeAstUnlinked; | |
| 27 import 'package:analyzer/src/summary/summarize_elements.dart' | |
| 28 show PackageBundleAssembler; | |
| 29 import 'package:convert/convert.dart'; | |
| 30 import 'package:crypto/crypto.dart'; | |
| 31 import 'package:meta/meta.dart'; | |
| 32 import 'package:path/path.dart' as pathos; | |
| 33 | |
| 34 /** | |
| 35 * Unlinked and linked information about a [PubPackage]. | |
| 36 */ | |
| 37 class LinkedPubPackage { | |
| 38 final PubPackage package; | |
| 39 final PackageBundle unlinked; | |
| 40 final PackageBundle linked; | |
| 41 | |
| 42 final String linkedHash; | |
| 43 | |
| 44 LinkedPubPackage(this.package, this.unlinked, this.linked, this.linkedHash); | |
| 45 | |
| 46 @override | |
| 47 String toString() => package.toString(); | |
| 48 } | |
| 49 | |
| 50 /** | |
| 51 * A package in the pub cache. | |
| 52 */ | |
| 53 class PubPackage { | |
| 54 final String name; | |
| 55 final Folder libFolder; | |
| 56 | |
| 57 PubPackage(this.name, this.libFolder); | |
| 58 | |
| 59 Folder get folder => libFolder.parent; | |
| 60 | |
| 61 @override | |
| 62 int get hashCode => libFolder.hashCode; | |
| 63 | |
| 64 @override | |
| 65 bool operator ==(other) { | |
| 66 return other is PubPackage && other.libFolder == libFolder; | |
| 67 } | |
| 68 | |
| 69 @override | |
| 70 String toString() => '($name in $folder)'; | |
| 71 } | |
| 72 | |
| 73 /** | |
| 74 * Class that manages summaries for pub packages. | |
| 75 * | |
| 76 * The client should call [getLinkedBundles] after creating a new | |
| 77 * [AnalysisContext] and configuring its source factory, but before computing | |
| 78 * any analysis results. The returned linked bundles can be used to create and | |
| 79 * configure [ResynthesizerResultProvider] for the context. | |
| 80 */ | |
| 81 class PubSummaryManager { | |
| 82 static const UNLINKED_NAME = 'unlinked.ds'; | |
| 83 static const UNLINKED_SPEC_NAME = 'unlinked_spec.ds'; | |
| 84 | |
| 85 /** | |
| 86 * If `true` (by default), then linking new bundles is allowed. | |
| 87 * Otherwise only using existing cached bundles can be used. | |
| 88 */ | |
| 89 final bool allowLinking; | |
| 90 | |
| 91 /** | |
| 92 * See [PackageBundleAssembler.currentMajorVersion]. | |
| 93 */ | |
| 94 final int majorVersion; | |
| 95 | |
| 96 final ResourceProvider resourceProvider; | |
| 97 | |
| 98 /** | |
| 99 * The name of the temporary file that is used for atomic writes. | |
| 100 */ | |
| 101 final String tempFileName; | |
| 102 | |
| 103 /** | |
| 104 * The map from [PubPackage]s to their unlinked [PackageBundle]s in the pub | |
| 105 * cache. | |
| 106 */ | |
| 107 final Map<PubPackage, PackageBundle> unlinkedBundleMap = | |
| 108 new HashMap<PubPackage, PackageBundle>(); | |
| 109 | |
| 110 /** | |
| 111 * The map from linked file paths to the corresponding linked bundles. | |
| 112 */ | |
| 113 final Map<String, PackageBundle> linkedBundleMap = | |
| 114 new HashMap<String, PackageBundle>(); | |
| 115 | |
| 116 /** | |
| 117 * The set of packages to compute unlinked summaries for. | |
| 118 */ | |
| 119 final Set<PubPackage> packagesToComputeUnlinked = new Set<PubPackage>(); | |
| 120 | |
| 121 /** | |
| 122 * The set of already processed packages, which we have already checked | |
| 123 * for their unlinked bundle existence, or scheduled its computing. | |
| 124 */ | |
| 125 final Set<PubPackage> seenPackages = new Set<PubPackage>(); | |
| 126 | |
| 127 /** | |
| 128 * The [Completer] that completes when computing of all scheduled unlinked | |
| 129 * bundles is complete. | |
| 130 */ | |
| 131 Completer _onUnlinkedCompleteCompleter; | |
| 132 | |
| 133 PubSummaryManager(this.resourceProvider, this.tempFileName, | |
| 134 {@visibleForTesting this.allowLinking: true, | |
| 135 @visibleForTesting this.majorVersion: | |
| 136 PackageBundleAssembler.currentMajorVersion}); | |
| 137 | |
| 138 /** | |
| 139 * The [Future] that completes when computing of all scheduled unlinked | |
| 140 * bundles is complete. | |
| 141 */ | |
| 142 Future get onUnlinkedComplete { | |
| 143 if (packagesToComputeUnlinked.isEmpty) { | |
| 144 return new Future.value(); | |
| 145 } | |
| 146 _onUnlinkedCompleteCompleter ??= new Completer(); | |
| 147 return _onUnlinkedCompleteCompleter.future; | |
| 148 } | |
| 149 | |
| 150 /** | |
| 151 * Return the [pathos.Context] corresponding to the [resourceProvider]. | |
| 152 */ | |
| 153 pathos.Context get pathContext => resourceProvider.pathContext; | |
| 154 | |
| 155 /** | |
| 156 * Complete when the unlinked bundles for the package with the given [name] | |
| 157 * and the [libFolder] are computed and written to the files. | |
| 158 * | |
| 159 * This method is intended to be used for generating unlinked bundles for | |
| 160 * the `Flutter` packages. | |
| 161 */ | |
| 162 Future<Null> computeUnlinkedForFolder(String name, Folder libFolder) async { | |
| 163 PubPackage package = new PubPackage(name, libFolder); | |
| 164 _scheduleUnlinked(package); | |
| 165 await onUnlinkedComplete; | |
| 166 } | |
| 167 | |
| 168 /** | |
| 169 * Return the list of linked [LinkedPubPackage]s that can be provided at this | |
| 170 * time for a subset of the packages used by the given [context]. If | |
| 171 * information about some of the used packages is not available yet, schedule | |
| 172 * its computation, so that it might be available later for other contexts | |
| 173 * referencing the same packages. | |
| 174 */ | |
| 175 List<LinkedPubPackage> getLinkedBundles(AnalysisContext context) { | |
| 176 return new _ContextLinker(this, context).getLinkedBundles(); | |
| 177 } | |
| 178 | |
| 179 /** | |
| 180 * Return all available unlinked [PackageBundle]s for the given [context], | |
| 181 * maybe an empty map, but not `null`. | |
| 182 */ | |
| 183 @visibleForTesting | |
| 184 Map<PubPackage, PackageBundle> getUnlinkedBundles(AnalysisContext context) { | |
| 185 bool strong = context.analysisOptions.strongMode; | |
| 186 Map<PubPackage, PackageBundle> unlinkedBundles = | |
| 187 new HashMap<PubPackage, PackageBundle>(); | |
| 188 Map<String, List<Folder>> packageMap = context.sourceFactory.packageMap; | |
| 189 if (packageMap != null) { | |
| 190 packageMap.forEach((String packageName, List<Folder> libFolders) { | |
| 191 if (libFolders.length == 1) { | |
| 192 Folder libFolder = libFolders.first; | |
| 193 PubPackage package = new PubPackage(packageName, libFolder); | |
| 194 PackageBundle unlinkedBundle = | |
| 195 _getUnlinkedOrSchedule(package, strong); | |
| 196 if (unlinkedBundle != null) { | |
| 197 unlinkedBundles[package] = unlinkedBundle; | |
| 198 } | |
| 199 } | |
| 200 }); | |
| 201 } | |
| 202 return unlinkedBundles; | |
| 203 } | |
| 204 | |
| 205 /** | |
| 206 * Compute unlinked bundle for a package from [packagesToComputeUnlinked], | |
| 207 * and schedule delayed computation for the next package, if any. | |
| 208 */ | |
| 209 void _computeNextUnlinked() { | |
| 210 if (packagesToComputeUnlinked.isNotEmpty) { | |
| 211 PubPackage package = packagesToComputeUnlinked.first; | |
| 212 _computeUnlinked(package, false); | |
| 213 _computeUnlinked(package, true); | |
| 214 packagesToComputeUnlinked.remove(package); | |
| 215 _scheduleNextUnlinked(); | |
| 216 } else { | |
| 217 if (_onUnlinkedCompleteCompleter != null) { | |
| 218 _onUnlinkedCompleteCompleter.complete(true); | |
| 219 _onUnlinkedCompleteCompleter = null; | |
| 220 } | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 /** | |
| 225 * Compute the unlinked bundle for the package with the given path, put | |
| 226 * it in the [unlinkedBundleMap] and store into the [resourceProvider]. | |
| 227 * | |
| 228 * TODO(scheglov) Consider moving into separate isolate(s). | |
| 229 */ | |
| 230 void _computeUnlinked(PubPackage package, bool strong) { | |
| 231 Folder libFolder = package.libFolder; | |
| 232 String libPath = libFolder.path + pathContext.separator; | |
| 233 PackageBundleAssembler assembler = new PackageBundleAssembler(); | |
| 234 | |
| 235 /** | |
| 236 * Return the `package` [Uri] for the given [path] in the `lib` folder | |
| 237 * of the current package. | |
| 238 */ | |
| 239 Uri getUri(String path) { | |
| 240 String pathInLib = path.substring(libPath.length); | |
| 241 String uriPath = pathos.posix.joinAll(pathContext.split(pathInLib)); | |
| 242 String uriStr = 'package:${package.name}/$uriPath'; | |
| 243 return Uri.parse(uriStr); | |
| 244 } | |
| 245 | |
| 246 /** | |
| 247 * If the given [file] is a Dart file, add its unlinked unit. | |
| 248 */ | |
| 249 void addDartFile(File file) { | |
| 250 String path = file.path; | |
| 251 if (AnalysisEngine.isDartFileName(path)) { | |
| 252 Uri uri = getUri(path); | |
| 253 Source source = file.createSource(uri); | |
| 254 CompilationUnit unit = _parse(source, strong); | |
| 255 UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit); | |
| 256 assembler.addUnlinkedUnit(source, unlinkedUnit); | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 /** | |
| 261 * Visit the [folder] recursively. | |
| 262 */ | |
| 263 void addDartFiles(Folder folder) { | |
| 264 List<Resource> children = folder.getChildren(); | |
| 265 for (Resource child in children) { | |
| 266 if (child is File) { | |
| 267 addDartFile(child); | |
| 268 } | |
| 269 } | |
| 270 for (Resource child in children) { | |
| 271 if (child is Folder) { | |
| 272 addDartFiles(child); | |
| 273 } | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 try { | |
| 278 addDartFiles(libFolder); | |
| 279 PackageBundleBuilder bundleWriter = assembler.assemble(); | |
| 280 bundleWriter.majorVersion = majorVersion; | |
| 281 List<int> bytes = bundleWriter.toBuffer(); | |
| 282 String fileName = _getUnlinkedName(strong); | |
| 283 _writeAtomic(package.folder, fileName, bytes); | |
| 284 } on FileSystemException { | |
| 285 // Ignore file system exceptions. | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 /** | |
| 290 * Return the name of the file for an unlinked bundle, in strong or spec mode. | |
| 291 */ | |
| 292 String _getUnlinkedName(bool strong) { | |
| 293 if (strong) { | |
| 294 return UNLINKED_NAME; | |
| 295 } else { | |
| 296 return UNLINKED_SPEC_NAME; | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 /** | |
| 301 * Return the unlinked [PackageBundle] for the given [package]. If the bundle | |
| 302 * has not been compute yet, return `null` and schedule its computation. | |
| 303 */ | |
| 304 PackageBundle _getUnlinkedOrSchedule(PubPackage package, bool strong) { | |
| 305 // Try to find in the cache. | |
| 306 PackageBundle bundle = unlinkedBundleMap[package]; | |
| 307 if (bundle != null) { | |
| 308 return bundle; | |
| 309 } | |
| 310 | |
| 311 // Try to read from the file system. | |
| 312 String fileName = _getUnlinkedName(strong); | |
| 313 File file = package.folder.getChildAssumingFile(fileName); | |
| 314 if (file.exists) { | |
| 315 try { | |
| 316 List<int> bytes = file.readAsBytesSync(); | |
| 317 bundle = new PackageBundle.fromBuffer(bytes); | |
| 318 } on FileSystemException { | |
| 319 // Ignore file system exceptions. | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 // Verify compatibility and consistency. | |
| 324 bool isInPubCache = isPathInPubCache(pathContext, package.folder.path); | |
| 325 if (bundle != null && | |
| 326 bundle.majorVersion == majorVersion && | |
| 327 (isInPubCache || _isConsistent(package, bundle))) { | |
| 328 unlinkedBundleMap[package] = bundle; | |
| 329 return bundle; | |
| 330 } | |
| 331 | |
| 332 // Schedule computation in the background, if in the pub cache. | |
| 333 if (isInPubCache) { | |
| 334 if (seenPackages.add(package)) { | |
| 335 _scheduleUnlinked(package); | |
| 336 } | |
| 337 } | |
| 338 | |
| 339 // The bundle is not available. | |
| 340 return null; | |
| 341 } | |
| 342 | |
| 343 /** | |
| 344 * Return `true` if content hashes for the [package] library files are the | |
| 345 * same the hashes in the unlinked [bundle]. | |
| 346 */ | |
| 347 bool _isConsistent(PubPackage package, PackageBundle bundle) { | |
| 348 List<String> actualHashes = <String>[]; | |
| 349 | |
| 350 /** | |
| 351 * If the given [file] is a Dart file, add its content hash. | |
| 352 */ | |
| 353 void hashDartFile(File file) { | |
| 354 String path = file.path; | |
| 355 if (AnalysisEngine.isDartFileName(path)) { | |
| 356 List<int> fileBytes = file.readAsBytesSync(); | |
| 357 List<int> hashBytes = md5.convert(fileBytes).bytes; | |
| 358 String hashHex = hex.encode(hashBytes); | |
| 359 actualHashes.add(hashHex); | |
| 360 } | |
| 361 } | |
| 362 | |
| 363 /** | |
| 364 * Visit the [folder] recursively. | |
| 365 */ | |
| 366 void hashDartFiles(Folder folder) { | |
| 367 List<Resource> children = folder.getChildren(); | |
| 368 for (Resource child in children) { | |
| 369 if (child is File) { | |
| 370 hashDartFile(child); | |
| 371 } else if (child is Folder) { | |
| 372 hashDartFiles(child); | |
| 373 } | |
| 374 } | |
| 375 } | |
| 376 | |
| 377 // Recursively compute hashes of the `lib` folder Dart files. | |
| 378 try { | |
| 379 hashDartFiles(package.libFolder); | |
| 380 } on FileSystemException { | |
| 381 return false; | |
| 382 } | |
| 383 | |
| 384 // Compare sorted actual and bundle unit hashes. | |
| 385 List<String> bundleHashes = bundle.unlinkedUnitHashes.toList()..sort(); | |
| 386 actualHashes.sort(); | |
| 387 return listsEqual(actualHashes, bundleHashes); | |
| 388 } | |
| 389 | |
| 390 /** | |
| 391 * Parse the given [source] into AST. | |
| 392 */ | |
| 393 CompilationUnit _parse(Source source, bool strong) { | |
| 394 String code = source.contents.data; | |
| 395 AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER; | |
| 396 CharSequenceReader reader = new CharSequenceReader(code); | |
| 397 Scanner scanner = new Scanner(source, reader, errorListener); | |
| 398 scanner.scanGenericMethodComments = strong; | |
| 399 Token token = scanner.tokenize(); | |
| 400 LineInfo lineInfo = new LineInfo(scanner.lineStarts); | |
| 401 Parser parser = new Parser(source, errorListener); | |
| 402 parser.parseGenericMethodComments = strong; | |
| 403 CompilationUnit unit = parser.parseCompilationUnit(token); | |
| 404 unit.lineInfo = lineInfo; | |
| 405 return unit; | |
| 406 } | |
| 407 | |
| 408 /** | |
| 409 * Schedule delayed computation of the next package unlinked bundle from the | |
| 410 * set of [packagesToComputeUnlinked]. We delay each computation because we | |
| 411 * want operations in analysis server to proceed, and computing bundles of | |
| 412 * packages is a background task. | |
| 413 */ | |
| 414 void _scheduleNextUnlinked() { | |
| 415 new Future.delayed(new Duration(milliseconds: 10), _computeNextUnlinked); | |
| 416 } | |
| 417 | |
| 418 /** | |
| 419 * Schedule computing unlinked bundles for the given [package]. | |
| 420 */ | |
| 421 void _scheduleUnlinked(PubPackage package) { | |
| 422 if (packagesToComputeUnlinked.isEmpty) { | |
| 423 _scheduleNextUnlinked(); | |
| 424 } | |
| 425 packagesToComputeUnlinked.add(package); | |
| 426 } | |
| 427 | |
| 428 /** | |
| 429 * Atomically write the given [bytes] into the file in the [folder]. | |
| 430 */ | |
| 431 void _writeAtomic(Folder folder, String fileName, List<int> bytes) { | |
| 432 String filePath = folder.getChildAssumingFile(fileName).path; | |
| 433 File tempFile = folder.getChildAssumingFile(tempFileName); | |
| 434 tempFile.writeAsBytesSync(bytes); | |
| 435 tempFile.renameSync(filePath); | |
| 436 } | |
| 437 | |
| 438 /** | |
| 439 * If the given [uri] has the `package` scheme, return the name of the | |
| 440 * package that contains the referenced resource. Otherwise return `null`. | |
| 441 * | |
| 442 * For example `package:foo/bar.dart` => `foo`. | |
| 443 */ | |
| 444 static String getPackageName(String uri) { | |
| 445 const String PACKAGE_SCHEME = 'package:'; | |
| 446 if (uri.startsWith(PACKAGE_SCHEME)) { | |
| 447 int index = uri.indexOf('/'); | |
| 448 if (index != -1) { | |
| 449 return uri.substring(PACKAGE_SCHEME.length, index); | |
| 450 } | |
| 451 } | |
| 452 return null; | |
| 453 } | |
| 454 | |
| 455 /** | |
| 456 * Return `true` if the given absolute [path] is in the pub cache. | |
| 457 */ | |
| 458 static bool isPathInPubCache(pathos.Context pathContext, String path) { | |
| 459 List<String> parts = pathContext.split(path); | |
| 460 for (int i = 0; i < parts.length - 1; i++) { | |
| 461 if (parts[i] == '.pub-cache') { | |
| 462 return true; | |
| 463 } | |
| 464 if (parts[i] == 'Pub' && parts[i + 1] == 'Cache') { | |
| 465 return true; | |
| 466 } | |
| 467 } | |
| 468 return false; | |
| 469 } | |
| 470 } | |
| 471 | |
| 472 class _ContextLinker { | |
| 473 final PubSummaryManager manager; | |
| 474 final AnalysisContext context; | |
| 475 | |
| 476 final strong; | |
| 477 final _ListedPackages listedPackages; | |
| 478 final PackageBundle sdkBundle; | |
| 479 | |
| 480 final List<_LinkNode> nodes = <_LinkNode>[]; | |
| 481 final Map<String, _LinkNode> packageToNode = <String, _LinkNode>{}; | |
| 482 | |
| 483 _ContextLinker(this.manager, AnalysisContext context) | |
| 484 : context = context, | |
| 485 strong = context.analysisOptions.strongMode, | |
| 486 listedPackages = new _ListedPackages(context.sourceFactory), | |
| 487 sdkBundle = context.sourceFactory.dartSdk.getLinkedBundle(); | |
| 488 | |
| 489 /** | |
| 490 * Return the list of linked [LinkedPubPackage]s that can be provided at this | |
| 491 * time for a subset of the packages used by the [context]. | |
| 492 */ | |
| 493 List<LinkedPubPackage> getLinkedBundles() { | |
| 494 // Stopwatch timer = new Stopwatch()..start(); | |
| 495 | |
| 496 if (sdkBundle == null) { | |
| 497 return const <LinkedPubPackage>[]; | |
| 498 } | |
| 499 | |
| 500 Map<PubPackage, PackageBundle> unlinkedBundles = | |
| 501 manager.getUnlinkedBundles(context); | |
| 502 | |
| 503 // TODO(scheglov) remove debug output after optimizing | |
| 504 // print('LOADED ${unlinkedBundles.length} unlinked bundles' | |
| 505 // ' in ${timer.elapsedMilliseconds} ms'); | |
| 506 // timer..reset(); | |
| 507 | |
| 508 // If no unlinked bundles, there is nothing we can try to link. | |
| 509 if (unlinkedBundles.isEmpty) { | |
| 510 return const <LinkedPubPackage>[]; | |
| 511 } | |
| 512 | |
| 513 // Create nodes for packages. | |
| 514 unlinkedBundles.forEach((package, unlinked) { | |
| 515 _LinkNode node = new _LinkNode(this, package, unlinked); | |
| 516 nodes.add(node); | |
| 517 packageToNode[package.name] = node; | |
| 518 }); | |
| 519 | |
| 520 // Compute transitive dependencies, mark some nodes as failed. | |
| 521 for (_LinkNode node in nodes) { | |
| 522 node.computeTransitiveDependencies(); | |
| 523 } | |
| 524 | |
| 525 // Attempt to read existing linked bundles. | |
| 526 for (_LinkNode node in nodes) { | |
| 527 _readLinked(node); | |
| 528 } | |
| 529 | |
| 530 // Link new packages, if allowed. | |
| 531 if (manager.allowLinking) { | |
| 532 _link(); | |
| 533 } | |
| 534 | |
| 535 // Create successfully linked packages. | |
| 536 List<LinkedPubPackage> linkedPackages = <LinkedPubPackage>[]; | |
| 537 for (_LinkNode node in nodes) { | |
| 538 if (node.linked != null) { | |
| 539 linkedPackages.add(new LinkedPubPackage( | |
| 540 node.package, node.unlinked, node.linked, node.linkedHash)); | |
| 541 } | |
| 542 } | |
| 543 | |
| 544 // TODO(scheglov) remove debug output after optimizing | |
| 545 // print('LINKED ${linkedPackages.length} bundles' | |
| 546 // ' in ${timer.elapsedMilliseconds} ms'); | |
| 547 | |
| 548 // Done. | |
| 549 return linkedPackages; | |
| 550 } | |
| 551 | |
| 552 String _getDeclaredVariable(String name) { | |
| 553 return context.declaredVariables.get(name); | |
| 554 } | |
| 555 | |
| 556 /** | |
| 557 * Return the name of the file for a linked bundle, in strong or spec mode. | |
| 558 */ | |
| 559 String _getLinkedName(String hash) { | |
| 560 if (strong) { | |
| 561 return 'linked_$hash.ds'; | |
| 562 } else { | |
| 563 return 'linked_spec_$hash.ds'; | |
| 564 } | |
| 565 } | |
| 566 | |
| 567 void _link() { | |
| 568 // Fill the store with bundles. | |
| 569 // Append the linked SDK bundle. | |
| 570 // Append unlinked and (if read from a cache) linked package bundles. | |
| 571 SummaryDataStore store = new SummaryDataStore(const <String>[]); | |
| 572 store.addBundle(null, sdkBundle); | |
| 573 for (_LinkNode node in nodes) { | |
| 574 store.addBundle(null, node.unlinked); | |
| 575 if (node.linked != null) { | |
| 576 store.addBundle(null, node.linked); | |
| 577 } | |
| 578 } | |
| 579 | |
| 580 // Prepare URIs to link. | |
| 581 Map<String, _LinkNode> uriToNode = <String, _LinkNode>{}; | |
| 582 for (_LinkNode node in nodes) { | |
| 583 if (!node.isReady) { | |
| 584 for (String uri in node.unlinked.unlinkedUnitUris) { | |
| 585 uriToNode[uri] = node; | |
| 586 } | |
| 587 } | |
| 588 } | |
| 589 Set<String> libraryUris = uriToNode.keys.toSet(); | |
| 590 | |
| 591 // Perform linking. | |
| 592 Map<String, LinkedLibraryBuilder> linkedLibraries = | |
| 593 link(libraryUris, (String uri) { | |
| 594 return store.linkedMap[uri]; | |
| 595 }, (String uri) { | |
| 596 return store.unlinkedMap[uri]; | |
| 597 }, _getDeclaredVariable, strong); | |
| 598 | |
| 599 // Assemble newly linked bundles. | |
| 600 for (_LinkNode node in nodes) { | |
| 601 if (!node.isReady) { | |
| 602 PackageBundleAssembler assembler = new PackageBundleAssembler(); | |
| 603 linkedLibraries.forEach((uri, linkedLibrary) { | |
| 604 if (identical(uriToNode[uri], node)) { | |
| 605 assembler.addLinkedLibrary(uri, linkedLibrary); | |
| 606 } | |
| 607 }); | |
| 608 List<int> bytes = assembler.assemble().toBuffer(); | |
| 609 node.linkedNewBytes = bytes; | |
| 610 node.linked = new PackageBundle.fromBuffer(bytes); | |
| 611 } | |
| 612 } | |
| 613 | |
| 614 // Write newly linked bundles. | |
| 615 for (_LinkNode node in nodes) { | |
| 616 _writeLinked(node); | |
| 617 } | |
| 618 } | |
| 619 | |
| 620 /** | |
| 621 * Attempt to find the linked bundle that corresponds to the given [node] | |
| 622 * with all its transitive dependencies and put it into [_LinkNode.linked]. | |
| 623 */ | |
| 624 void _readLinked(_LinkNode node) { | |
| 625 String hash = node.linkedHash; | |
| 626 if (hash != null) { | |
| 627 String fileName = _getLinkedName(hash); | |
| 628 File file = node.package.folder.getChildAssumingFile(fileName); | |
| 629 // Try to find in the cache. | |
| 630 PackageBundle linked = manager.linkedBundleMap[file.path]; | |
| 631 if (linked != null) { | |
| 632 node.linked = linked; | |
| 633 return; | |
| 634 } | |
| 635 // Try to read from the file system. | |
| 636 if (file.exists) { | |
| 637 try { | |
| 638 List<int> bytes = file.readAsBytesSync(); | |
| 639 linked = new PackageBundle.fromBuffer(bytes); | |
| 640 manager.linkedBundleMap[file.path] = linked; | |
| 641 node.linked = linked; | |
| 642 } on FileSystemException { | |
| 643 // Ignore file system exceptions. | |
| 644 } | |
| 645 } | |
| 646 } | |
| 647 } | |
| 648 | |
| 649 /** | |
| 650 * If a new linked bundle was linked for the given [node], write the bundle | |
| 651 * into the memory cache and the file system. | |
| 652 */ | |
| 653 void _writeLinked(_LinkNode node) { | |
| 654 String hash = node.linkedHash; | |
| 655 if (hash != null && node.linkedNewBytes != null) { | |
| 656 String fileName = _getLinkedName(hash); | |
| 657 File file = node.package.folder.getChildAssumingFile(fileName); | |
| 658 manager.linkedBundleMap[file.path] = node.linked; | |
| 659 manager._writeAtomic(node.package.folder, fileName, node.linkedNewBytes); | |
| 660 } | |
| 661 } | |
| 662 } | |
| 663 | |
| 664 /** | |
| 665 * Information about a package to link. | |
| 666 */ | |
| 667 class _LinkNode { | |
| 668 final _ContextLinker linker; | |
| 669 final PubPackage package; | |
| 670 final PackageBundle unlinked; | |
| 671 | |
| 672 bool failed = false; | |
| 673 Set<_LinkNode> transitiveDependencies; | |
| 674 | |
| 675 List<_LinkNode> _dependencies; | |
| 676 String _linkedHash; | |
| 677 | |
| 678 List<int> linkedNewBytes; | |
| 679 PackageBundle linked; | |
| 680 | |
| 681 _LinkNode(this.linker, this.package, this.unlinked); | |
| 682 | |
| 683 /** | |
| 684 * Retrieve the dependencies of this node. | |
| 685 */ | |
| 686 List<_LinkNode> get dependencies { | |
| 687 if (_dependencies == null) { | |
| 688 Set<_LinkNode> dependencies = new Set<_LinkNode>(); | |
| 689 | |
| 690 void appendDependency(String uriStr) { | |
| 691 Uri uri = Uri.parse(uriStr); | |
| 692 if (!uri.hasScheme) { | |
| 693 // A relative path in this package, skip it. | |
| 694 } else if (uri.scheme == 'dart') { | |
| 695 // Dependency on the SDK is implicit and always added. | |
| 696 // The SDK linked bundle is precomputed before linking packages. | |
| 697 } else if (uriStr.startsWith('package:')) { | |
| 698 String package = PubSummaryManager.getPackageName(uriStr); | |
| 699 _LinkNode packageNode = linker.packageToNode[package]; | |
| 700 if (packageNode == null && linker.listedPackages.isListed(uriStr)) { | |
| 701 failed = true; | |
| 702 } | |
| 703 if (packageNode != null) { | |
| 704 dependencies.add(packageNode); | |
| 705 } | |
| 706 } else { | |
| 707 failed = true; | |
| 708 } | |
| 709 } | |
| 710 | |
| 711 for (UnlinkedUnit unit in unlinked.unlinkedUnits) { | |
| 712 for (UnlinkedImport import in unit.imports) { | |
| 713 if (!import.isImplicit) { | |
| 714 appendDependency(import.uri); | |
| 715 } | |
| 716 } | |
| 717 for (UnlinkedExportPublic export in unit.publicNamespace.exports) { | |
| 718 appendDependency(export.uri); | |
| 719 } | |
| 720 } | |
| 721 | |
| 722 _dependencies = dependencies.toList(); | |
| 723 } | |
| 724 return _dependencies; | |
| 725 } | |
| 726 | |
| 727 /** | |
| 728 * Return `true` is the node is ready - has the linked bundle or failed (does | |
| 729 * not have all required dependencies). | |
| 730 */ | |
| 731 bool get isReady => linked != null || failed; | |
| 732 | |
| 733 /** | |
| 734 * Return the hash string that corresponds to this linked bundle in the | |
| 735 * context of its SDK bundle and transitive dependencies. Return `null` if | |
| 736 * the hash computation fails, because for example the full transitive | |
| 737 * dependencies cannot computed. | |
| 738 */ | |
| 739 String get linkedHash { | |
| 740 if (_linkedHash == null && transitiveDependencies != null) { | |
| 741 ApiSignature signature = new ApiSignature(); | |
| 742 // Add all unlinked API signatures. | |
| 743 List<String> signatures = <String>[]; | |
| 744 signatures.add(linker.sdkBundle.apiSignature); | |
| 745 transitiveDependencies | |
| 746 .map((node) => node.unlinked.apiSignature) | |
| 747 .forEach(signatures.add); | |
| 748 signatures.sort(); | |
| 749 signatures.forEach(signature.addString); | |
| 750 // Combine into a single hash. | |
| 751 appendDeclaredVariables(signature); | |
| 752 _linkedHash = signature.toHex(); | |
| 753 } | |
| 754 return _linkedHash; | |
| 755 } | |
| 756 | |
| 757 /** | |
| 758 * Append names and values of all referenced declared variables (even the | |
| 759 * ones without actually declared values) to the given [signature]. | |
| 760 */ | |
| 761 void appendDeclaredVariables(ApiSignature signature) { | |
| 762 Set<String> nameSet = new Set<String>(); | |
| 763 for (_LinkNode node in transitiveDependencies) { | |
| 764 for (UnlinkedUnit unit in node.unlinked.unlinkedUnits) { | |
| 765 for (UnlinkedImport import in unit.imports) { | |
| 766 for (UnlinkedConfiguration configuration in import.configurations) { | |
| 767 nameSet.add(configuration.name); | |
| 768 } | |
| 769 } | |
| 770 for (UnlinkedExportPublic export in unit.publicNamespace.exports) { | |
| 771 for (UnlinkedConfiguration configuration in export.configurations) { | |
| 772 nameSet.add(configuration.name); | |
| 773 } | |
| 774 } | |
| 775 } | |
| 776 } | |
| 777 List<String> sortedNameList = nameSet.toList()..sort(); | |
| 778 signature.addInt(sortedNameList.length); | |
| 779 for (String name in sortedNameList) { | |
| 780 signature.addString(name); | |
| 781 signature.addString(linker._getDeclaredVariable(name) ?? ''); | |
| 782 } | |
| 783 } | |
| 784 | |
| 785 /** | |
| 786 * Compute the set of existing transitive dependencies for this node. | |
| 787 * If any `package` dependency cannot be resolved, but it is one of the | |
| 788 * [listedPackages] then set [failed] to `true`. | |
| 789 * Only [unlinked] is used, so this method can be called before linking. | |
| 790 */ | |
| 791 void computeTransitiveDependencies() { | |
| 792 if (transitiveDependencies == null) { | |
| 793 transitiveDependencies = new Set<_LinkNode>(); | |
| 794 | |
| 795 void appendDependencies(_LinkNode node) { | |
| 796 if (transitiveDependencies.add(node)) { | |
| 797 node.dependencies.forEach(appendDependencies); | |
| 798 } | |
| 799 } | |
| 800 | |
| 801 appendDependencies(this); | |
| 802 if (transitiveDependencies.any((node) => node.failed)) { | |
| 803 failed = true; | |
| 804 } | |
| 805 } | |
| 806 } | |
| 807 | |
| 808 @override | |
| 809 String toString() => package.toString(); | |
| 810 } | |
| 811 | |
| 812 /** | |
| 813 * The set of package names that are listed in the `.packages` file of a | |
| 814 * context. These are the only packages, references to which can | |
| 815 * be possibly resolved in the context. Nodes that reference a `package:` URI | |
| 816 * without the unlinked bundle, so without the node, cannot be linked. | |
| 817 */ | |
| 818 class _ListedPackages { | |
| 819 final Set<String> names = new Set<String>(); | |
| 820 | |
| 821 _ListedPackages(SourceFactory sourceFactory) { | |
| 822 Map<String, List<Folder>> map = sourceFactory.packageMap; | |
| 823 if (map != null) { | |
| 824 names.addAll(map.keys); | |
| 825 } | |
| 826 } | |
| 827 | |
| 828 /** | |
| 829 * Check whether the given `package:` [uri] is listed in the package map. | |
| 830 */ | |
| 831 bool isListed(String uri) { | |
| 832 String package = PubSummaryManager.getPackageName(uri); | |
| 833 return names.contains(package); | |
| 834 } | |
| 835 } | |
| OLD | NEW |