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