OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012, 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 library pub.entrypoint; |
| 6 |
| 7 import 'dart:async'; |
| 8 |
| 9 import 'package:path/path.dart' as path; |
| 10 import 'package:barback/barback.dart'; |
| 11 |
| 12 import 'barback/asset_environment.dart'; |
| 13 import 'io.dart'; |
| 14 import 'lock_file.dart'; |
| 15 import 'log.dart' as log; |
| 16 import 'package.dart'; |
| 17 import 'package_graph.dart'; |
| 18 import 'sdk.dart' as sdk; |
| 19 import 'solver/version_solver.dart'; |
| 20 import 'source/cached.dart'; |
| 21 import 'system_cache.dart'; |
| 22 import 'utils.dart'; |
| 23 |
| 24 /// The context surrounding the root package pub is operating on. |
| 25 /// |
| 26 /// Pub operates over a directed graph of dependencies that starts at a root |
| 27 /// "entrypoint" package. This is typically the package where the current |
| 28 /// working directory is located. An entrypoint knows the [root] package it is |
| 29 /// associated with and is responsible for managing the "packages" directory |
| 30 /// for it. |
| 31 /// |
| 32 /// That directory contains symlinks to all packages used by an app. These links |
| 33 /// point either to the [SystemCache] or to some other location on the local |
| 34 /// filesystem. |
| 35 /// |
| 36 /// While entrypoints are typically applications, a pure library package may end |
| 37 /// up being used as an entrypoint. Also, a single package may be used as an |
| 38 /// entrypoint in one context but not in another. For example, a package that |
| 39 /// contains a reusable library may not be the entrypoint when used by an app, |
| 40 /// but may be the entrypoint when you're running its tests. |
| 41 class Entrypoint { |
| 42 /// The root package this entrypoint is associated with. |
| 43 final Package root; |
| 44 |
| 45 /// The system-wide cache which caches packages that need to be fetched over |
| 46 /// the network. |
| 47 final SystemCache cache; |
| 48 |
| 49 /// Whether to create and symlink a "packages" directory containing links to |
| 50 /// the installed packages. |
| 51 final bool _packageSymlinks; |
| 52 |
| 53 /// The lockfile for the entrypoint. |
| 54 /// |
| 55 /// If not provided to the entrypoint, it will be laoded lazily from disc. |
| 56 LockFile _lockFile; |
| 57 |
| 58 /// The graph of all packages reachable from the entrypoint. |
| 59 PackageGraph _packageGraph; |
| 60 |
| 61 /// Loads the entrypoint from a package at [rootDir]. |
| 62 /// |
| 63 /// If [packageSymlinks] is `true`, this will create a "packages" directory |
| 64 /// with symlinks to the installed packages. This directory will be symlinked |
| 65 /// into any directory that might contain an entrypoint. |
| 66 Entrypoint(String rootDir, SystemCache cache, {bool packageSymlinks: true}) |
| 67 : root = new Package.load(null, rootDir, cache.sources), |
| 68 cache = cache, |
| 69 _packageSymlinks = packageSymlinks; |
| 70 |
| 71 /// Creates an entrypoint given package and lockfile objects. |
| 72 Entrypoint.inMemory(this.root, this._lockFile, this.cache) |
| 73 : _packageSymlinks = false; |
| 74 |
| 75 /// The path to the entrypoint's "packages" directory. |
| 76 String get packagesDir => root.path('packages'); |
| 77 |
| 78 /// `true` if the entrypoint package currently has a lock file. |
| 79 bool get lockFileExists => _lockFile != null || entryExists(lockFilePath); |
| 80 |
| 81 LockFile get lockFile { |
| 82 if (_lockFile != null) return _lockFile; |
| 83 |
| 84 if (!lockFileExists) { |
| 85 _lockFile = new LockFile.empty(); |
| 86 } else { |
| 87 _lockFile = new LockFile.load(lockFilePath, cache.sources); |
| 88 } |
| 89 |
| 90 return _lockFile; |
| 91 } |
| 92 |
| 93 /// The path to the entrypoint package's pubspec. |
| 94 String get pubspecPath => root.path('pubspec.yaml'); |
| 95 |
| 96 /// The path to the entrypoint package's lockfile. |
| 97 String get lockFilePath => root.path('pubspec.lock'); |
| 98 |
| 99 /// Gets all dependencies of the [root] package. |
| 100 /// |
| 101 /// Performs version resolution according to [SolveType]. |
| 102 /// |
| 103 /// [useLatest], if provided, defines a list of packages that will be |
| 104 /// unlocked and forced to their latest versions. If [upgradeAll] is |
| 105 /// true, the previous lockfile is ignored and all packages are re-resolved |
| 106 /// from scratch. Otherwise, it will attempt to preserve the versions of all |
| 107 /// previously locked packages. |
| 108 /// |
| 109 /// Shows a report of the changes made relative to the previous lockfile. If |
| 110 /// this is an upgrade or downgrade, all transitive dependencies are shown in |
| 111 /// the report. Otherwise, only dependencies that were changed are shown. If |
| 112 /// [dryRun] is `true`, no physical changes are made. |
| 113 Future acquireDependencies(SolveType type, {List<String> useLatest, |
| 114 bool dryRun: false}) { |
| 115 final completer0 = new Completer(); |
| 116 scheduleMicrotask(() { |
| 117 try { |
| 118 new Future.value( |
| 119 resolveVersions( |
| 120 type, |
| 121 cache.sources, |
| 122 root, |
| 123 lockFile: lockFile, |
| 124 useLatest: useLatest)).then((x0) { |
| 125 try { |
| 126 var result = x0; |
| 127 join0() { |
| 128 result.showReport(type); |
| 129 join1() { |
| 130 join2() { |
| 131 new Future.value( |
| 132 Future.wait(result.packages.map(_get))).then((x1) { |
| 133 try { |
| 134 var ids = x1; |
| 135 _saveLockFile(ids); |
| 136 join3() { |
| 137 _linkOrDeleteSecondaryPackageDirs(); |
| 138 result.summarizeChanges(type, dryRun: dryRun); |
| 139 new Future.value(loadPackageGraph(result)).then((x2) { |
| 140 try { |
| 141 var packageGraph = x2; |
| 142 packageGraph.loadTransformerCache().clearIfOutdated( |
| 143 result.changedPackages); |
| 144 join4() { |
| 145 completer0.complete(); |
| 146 } |
| 147 catch0(error, stackTrace) { |
| 148 try { |
| 149 log.exception(error, stackTrace); |
| 150 join4(); |
| 151 } catch (error, stackTrace) { |
| 152 completer0.completeError(error, stackTrace); |
| 153 } |
| 154 } |
| 155 try { |
| 156 new Future.value( |
| 157 precompileDependencies(changed: result.changed
Packages)).then((x3) { |
| 158 try { |
| 159 x3; |
| 160 new Future.value( |
| 161 precompileExecutables(changed: result.chan
gedPackages)).then((x4) { |
| 162 try { |
| 163 x4; |
| 164 join4(); |
| 165 } catch (e0, s0) { |
| 166 catch0(e0, s0); |
| 167 } |
| 168 }, onError: catch0); |
| 169 } catch (e1, s1) { |
| 170 catch0(e1, s1); |
| 171 } |
| 172 }, onError: catch0); |
| 173 } catch (e2, s2) { |
| 174 catch0(e2, s2); |
| 175 } |
| 176 } catch (e3, s3) { |
| 177 completer0.completeError(e3, s3); |
| 178 } |
| 179 }, onError: completer0.completeError); |
| 180 } |
| 181 if (_packageSymlinks) { |
| 182 _linkSelf(); |
| 183 join3(); |
| 184 } else { |
| 185 join3(); |
| 186 } |
| 187 } catch (e4, s4) { |
| 188 completer0.completeError(e4, s4); |
| 189 } |
| 190 }, onError: completer0.completeError); |
| 191 } |
| 192 if (_packageSymlinks) { |
| 193 cleanDir(packagesDir); |
| 194 join2(); |
| 195 } else { |
| 196 deleteEntry(packagesDir); |
| 197 join2(); |
| 198 } |
| 199 } |
| 200 if (dryRun) { |
| 201 result.summarizeChanges(type, dryRun: dryRun); |
| 202 completer0.complete(null); |
| 203 } else { |
| 204 join1(); |
| 205 } |
| 206 } |
| 207 if (!result.succeeded) { |
| 208 throw result.error; |
| 209 join0(); |
| 210 } else { |
| 211 join0(); |
| 212 } |
| 213 } catch (e5, s5) { |
| 214 completer0.completeError(e5, s5); |
| 215 } |
| 216 }, onError: completer0.completeError); |
| 217 } catch (e, s) { |
| 218 completer0.completeError(e, s); |
| 219 } |
| 220 }); |
| 221 return completer0.future; |
| 222 } |
| 223 |
| 224 /// Precompile any transformed dependencies of the entrypoint. |
| 225 /// |
| 226 /// If [changed] is passed, only dependencies whose contents might be changed |
| 227 /// if one of the given packages changes will be recompiled. |
| 228 Future precompileDependencies({Iterable<String> changed}) { |
| 229 final completer0 = new Completer(); |
| 230 scheduleMicrotask(() { |
| 231 try { |
| 232 join0() { |
| 233 new Future.value(loadPackageGraph()).then((x0) { |
| 234 try { |
| 235 var graph = x0; |
| 236 var depsDir = path.join('.pub', 'deps', 'debug'); |
| 237 var dependenciesToPrecompile = |
| 238 graph.packages.values.where(((package) { |
| 239 if (package.pubspec.transformers.isEmpty) return false; |
| 240 if (graph.isPackageMutable(package.name)) return false; |
| 241 if (!dirExists(path.join(depsDir, package.name))) return true; |
| 242 if (changed == null) return true; |
| 243 return overlaps( |
| 244 graph.transitiveDependencies( |
| 245 package.name).map((package) => package.name).toSet(), |
| 246 changed); |
| 247 })).map(((package) { |
| 248 return package.name; |
| 249 })).toSet(); |
| 250 join1() { |
| 251 join2() { |
| 252 join3() { |
| 253 completer0.complete(); |
| 254 } |
| 255 catch0(_, s0) { |
| 256 try { |
| 257 var it0 = dependenciesToPrecompile.iterator; |
| 258 break0() { |
| 259 completer0.completeError(_, s0); |
| 260 } |
| 261 var trampoline0; |
| 262 continue0() { |
| 263 trampoline0 = null; |
| 264 if (it0.moveNext()) { |
| 265 var package = it0.current; |
| 266 deleteEntry(path.join(depsDir, package)); |
| 267 trampoline0 = continue0; |
| 268 do trampoline0(); while (trampoline0 != null); |
| 269 } else { |
| 270 break0(); |
| 271 } |
| 272 } |
| 273 trampoline0 = continue0; |
| 274 do trampoline0(); while (trampoline0 != null); |
| 275 } catch (_, s0) { |
| 276 completer0.completeError(_, s0); |
| 277 } |
| 278 } |
| 279 try { |
| 280 new Future.value( |
| 281 log.progress("Precompiling dependencies", (() { |
| 282 final completer0 = new Completer(); |
| 283 scheduleMicrotask(() { |
| 284 try { |
| 285 var packagesToLoad = unionAll( |
| 286 dependenciesToPrecompile.map(graph.transitiveDepen
dencies)).map(((package) { |
| 287 return package.name; |
| 288 })).toSet(); |
| 289 new Future.value( |
| 290 AssetEnvironment.create( |
| 291 this, |
| 292 BarbackMode.DEBUG, |
| 293 packages: packagesToLoad, |
| 294 useDart2JS: false)).then((x0) { |
| 295 try { |
| 296 var environment = x0; |
| 297 environment.barback.errors.listen(((_) { |
| 298 })); |
| 299 new Future.value( |
| 300 environment.barback.getAllAssets()).then((x1)
{ |
| 301 try { |
| 302 var assets = x1; |
| 303 new Future.value( |
| 304 waitAndPrintErrors(assets.map(((asset) { |
| 305 final completer0 = new Completer(); |
| 306 scheduleMicrotask(() { |
| 307 try { |
| 308 join0() { |
| 309 var destPath = |
| 310 path.join(depsDir, asset.id.packag
e, path.fromUri(asset.id.path)); |
| 311 ensureDir(path.dirname(destPath)); |
| 312 new Future.value( |
| 313 createFileFromStream(asset.read(),
destPath)).then((x0) { |
| 314 try { |
| 315 x0; |
| 316 completer0.complete(); |
| 317 } catch (e0, s0) { |
| 318 completer0.completeError(e0, s0); |
| 319 } |
| 320 }, onError: completer0.completeError); |
| 321 } |
| 322 if (!dependenciesToPrecompile.contains( |
| 323 asset.id.package)) { |
| 324 completer0.complete(null); |
| 325 } else { |
| 326 join0(); |
| 327 } |
| 328 } catch (e, s) { |
| 329 completer0.completeError(e, s); |
| 330 } |
| 331 }); |
| 332 return completer0.future; |
| 333 })))).then((x2) { |
| 334 try { |
| 335 x2; |
| 336 log.message( |
| 337 "Precompiled " + |
| 338 toSentence(ordered(dependenciesToP
recompile).map(log.bold)) + |
| 339 "."); |
| 340 completer0.complete(); |
| 341 } catch (e0, s0) { |
| 342 completer0.completeError(e0, s0); |
| 343 } |
| 344 }, onError: completer0.completeError); |
| 345 } catch (e1, s1) { |
| 346 completer0.completeError(e1, s1); |
| 347 } |
| 348 }, onError: completer0.completeError); |
| 349 } catch (e2, s2) { |
| 350 completer0.completeError(e2, s2); |
| 351 } |
| 352 }, onError: completer0.completeError); |
| 353 } catch (e, s) { |
| 354 completer0.completeError(e, s); |
| 355 } |
| 356 }); |
| 357 return completer0.future; |
| 358 }))).then((x1) { |
| 359 try { |
| 360 x1; |
| 361 join3(); |
| 362 } catch (e0, s1) { |
| 363 catch0(e0, s1); |
| 364 } |
| 365 }, onError: catch0); |
| 366 } catch (e1, s2) { |
| 367 catch0(e1, s2); |
| 368 } |
| 369 } |
| 370 if (dependenciesToPrecompile.isEmpty) { |
| 371 completer0.complete(null); |
| 372 } else { |
| 373 join2(); |
| 374 } |
| 375 } |
| 376 if (dirExists(depsDir)) { |
| 377 var it1 = dependenciesToPrecompile.iterator; |
| 378 break1() { |
| 379 var it2 = listDir(depsDir).iterator; |
| 380 break2() { |
| 381 join1(); |
| 382 } |
| 383 var trampoline2; |
| 384 continue2() { |
| 385 trampoline2 = null; |
| 386 if (it2.moveNext()) { |
| 387 var subdir = it2.current; |
| 388 var package = graph.packages[path.basename(subdir)]; |
| 389 join4() { |
| 390 trampoline2 = continue2; |
| 391 do trampoline2(); while (trampoline2 != null); |
| 392 } |
| 393 if (package == null || |
| 394 package.pubspec.transformers.isEmpty || |
| 395 graph.isPackageMutable(package.name)) { |
| 396 deleteEntry(subdir); |
| 397 join4(); |
| 398 } else { |
| 399 join4(); |
| 400 } |
| 401 } else { |
| 402 break2(); |
| 403 } |
| 404 } |
| 405 trampoline2 = continue2; |
| 406 do trampoline2(); while (trampoline2 != null); |
| 407 } |
| 408 var trampoline1; |
| 409 continue1() { |
| 410 trampoline1 = null; |
| 411 if (it1.moveNext()) { |
| 412 var package = it1.current; |
| 413 deleteEntry(path.join(depsDir, package)); |
| 414 trampoline1 = continue1; |
| 415 do trampoline1(); while (trampoline1 != null); |
| 416 } else { |
| 417 break1(); |
| 418 } |
| 419 } |
| 420 trampoline1 = continue1; |
| 421 do trampoline1(); while (trampoline1 != null); |
| 422 } else { |
| 423 join1(); |
| 424 } |
| 425 } catch (e2, s3) { |
| 426 completer0.completeError(e2, s3); |
| 427 } |
| 428 }, onError: completer0.completeError); |
| 429 } |
| 430 if (changed != null) { |
| 431 changed = changed.toSet(); |
| 432 join0(); |
| 433 } else { |
| 434 join0(); |
| 435 } |
| 436 } catch (e, s) { |
| 437 completer0.completeError(e, s); |
| 438 } |
| 439 }); |
| 440 return completer0.future; |
| 441 } |
| 442 |
| 443 /// Precompiles all executables from dependencies that don't transitively |
| 444 /// depend on [this] or on a path dependency. |
| 445 Future precompileExecutables({Iterable<String> changed}) { |
| 446 final completer0 = new Completer(); |
| 447 scheduleMicrotask(() { |
| 448 try { |
| 449 join0() { |
| 450 var binDir = path.join('.pub', 'bin'); |
| 451 var sdkVersionPath = path.join(binDir, 'sdk-version'); |
| 452 var sdkMatches = |
| 453 fileExists(sdkVersionPath) && |
| 454 readTextFile(sdkVersionPath) == "${sdk.version}\n"; |
| 455 join1() { |
| 456 new Future.value(loadPackageGraph()).then((x0) { |
| 457 try { |
| 458 var graph = x0; |
| 459 var executables = |
| 460 new Map.fromIterable(root.immediateDependencies, key: ((dep)
{ |
| 461 return dep.name; |
| 462 }), value: ((dep) { |
| 463 return _executablesForPackage(graph, dep.name, changed); |
| 464 })); |
| 465 var it0 = executables.keys.toList().iterator; |
| 466 break0() { |
| 467 join2() { |
| 468 join3() { |
| 469 new Future.value( |
| 470 log.progress("Precompiling executables", (() { |
| 471 final completer0 = new Completer(); |
| 472 scheduleMicrotask(() { |
| 473 try { |
| 474 ensureDir(binDir); |
| 475 writeTextFile(sdkVersionPath, "${sdk.version}\n"); |
| 476 var packagesToLoad = |
| 477 unionAll(executables.keys.map(graph.transitiveDe
pendencies)).map(((package) { |
| 478 return package.name; |
| 479 })).toSet(); |
| 480 var executableIds = |
| 481 unionAll(executables.values.map(((ids) { |
| 482 return ids.toSet(); |
| 483 }))); |
| 484 new Future.value( |
| 485 AssetEnvironment.create( |
| 486 this, |
| 487 BarbackMode.RELEASE, |
| 488 packages: packagesToLoad, |
| 489 entrypoints: executableIds, |
| 490 useDart2JS: false)).then((x0) { |
| 491 try { |
| 492 var environment = x0; |
| 493 environment.barback.errors.listen(((error) { |
| 494 log.error(log.red("Build error:\n$error")); |
| 495 })); |
| 496 new Future.value( |
| 497 waitAndPrintErrors(executables.keys.map(((pa
ckage) { |
| 498 final completer0 = new Completer(); |
| 499 scheduleMicrotask(() { |
| 500 try { |
| 501 var dir = path.join(binDir, package); |
| 502 cleanDir(dir); |
| 503 new Future.value( |
| 504 environment.precompileExecutables( |
| 505 package, |
| 506 dir, |
| 507 executableIds: executables[package
])).then((x0) { |
| 508 try { |
| 509 x0; |
| 510 completer0.complete(); |
| 511 } catch (e0, s0) { |
| 512 completer0.completeError(e0, s0); |
| 513 } |
| 514 }, onError: completer0.completeError); |
| 515 } catch (e, s) { |
| 516 completer0.completeError(e, s); |
| 517 } |
| 518 }); |
| 519 return completer0.future; |
| 520 })))).then((x1) { |
| 521 try { |
| 522 x1; |
| 523 completer0.complete(); |
| 524 } catch (e0, s0) { |
| 525 completer0.completeError(e0, s0); |
| 526 } |
| 527 }, onError: completer0.completeError); |
| 528 } catch (e1, s1) { |
| 529 completer0.completeError(e1, s1); |
| 530 } |
| 531 }, onError: completer0.completeError); |
| 532 } catch (e, s) { |
| 533 completer0.completeError(e, s); |
| 534 } |
| 535 }); |
| 536 return completer0.future; |
| 537 }))).then((x1) { |
| 538 try { |
| 539 x1; |
| 540 completer0.complete(); |
| 541 } catch (e0, s0) { |
| 542 completer0.completeError(e0, s0); |
| 543 } |
| 544 }, onError: completer0.completeError); |
| 545 } |
| 546 if (executables.isEmpty) { |
| 547 completer0.complete(null); |
| 548 } else { |
| 549 join3(); |
| 550 } |
| 551 } |
| 552 if (!sdkMatches) { |
| 553 deleteEntry(binDir); |
| 554 join2(); |
| 555 } else { |
| 556 join2(); |
| 557 } |
| 558 } |
| 559 var trampoline0; |
| 560 continue0() { |
| 561 trampoline0 = null; |
| 562 if (it0.moveNext()) { |
| 563 var package = it0.current; |
| 564 join4() { |
| 565 trampoline0 = continue0; |
| 566 do trampoline0(); while (trampoline0 != null); |
| 567 } |
| 568 if (executables[package].isEmpty) { |
| 569 executables.remove(package); |
| 570 join4(); |
| 571 } else { |
| 572 join4(); |
| 573 } |
| 574 } else { |
| 575 break0(); |
| 576 } |
| 577 } |
| 578 trampoline0 = continue0; |
| 579 do trampoline0(); while (trampoline0 != null); |
| 580 } catch (e1, s1) { |
| 581 completer0.completeError(e1, s1); |
| 582 } |
| 583 }, onError: completer0.completeError); |
| 584 } |
| 585 if (!sdkMatches) { |
| 586 changed = null; |
| 587 join1(); |
| 588 } else { |
| 589 join1(); |
| 590 } |
| 591 } |
| 592 if (changed != null) { |
| 593 changed = changed.toSet(); |
| 594 join0(); |
| 595 } else { |
| 596 join0(); |
| 597 } |
| 598 } catch (e, s) { |
| 599 completer0.completeError(e, s); |
| 600 } |
| 601 }); |
| 602 return completer0.future; |
| 603 } |
| 604 |
| 605 /// Returns the list of all executable assets for [packageName] that should be |
| 606 /// precompiled. |
| 607 /// |
| 608 /// If [changed] isn't `null`, executables for [packageName] will only be |
| 609 /// compiled if they might depend on a package in [changed]. |
| 610 List<AssetId> _executablesForPackage(PackageGraph graph, String packageName, |
| 611 Set<String> changed) { |
| 612 var package = graph.packages[packageName]; |
| 613 var binDir = package.path('bin'); |
| 614 if (!dirExists(binDir)) return []; |
| 615 if (graph.isPackageMutable(packageName)) return []; |
| 616 |
| 617 var executables = package.executableIds; |
| 618 |
| 619 // If we don't know which packages were changed, always precompile the |
| 620 // executables. |
| 621 if (changed == null) return executables; |
| 622 |
| 623 // If any of the package's dependencies changed, recompile the executables. |
| 624 if (graph.transitiveDependencies( |
| 625 packageName).any((package) => changed.contains(package.name))) { |
| 626 return executables; |
| 627 } |
| 628 |
| 629 // If any executables doesn't exist, precompile them regardless of what |
| 630 // changed. Since we delete the bin directory before recompiling, we need to |
| 631 // recompile all executables. |
| 632 var executablesExist = executables.every( |
| 633 (executable) => |
| 634 fileExists( |
| 635 path.join( |
| 636 '.pub', |
| 637 'bin', |
| 638 packageName, |
| 639 "${path.url.basename(executable.path)}.snapshot"))); |
| 640 if (!executablesExist) return executables; |
| 641 |
| 642 // Otherwise, we don't need to recompile. |
| 643 return []; |
| 644 } |
| 645 |
| 646 /// Makes sure the package at [id] is locally available. |
| 647 /// |
| 648 /// This automatically downloads the package to the system-wide cache as well |
| 649 /// if it requires network access to retrieve (specifically, if the package's |
| 650 /// source is a [CachedSource]). |
| 651 Future<PackageId> _get(PackageId id) { |
| 652 if (id.isRoot) return new Future.value(id); |
| 653 |
| 654 var source = cache.sources[id.source]; |
| 655 return new Future.sync(() { |
| 656 if (!_packageSymlinks) { |
| 657 if (source is! CachedSource) return null; |
| 658 return source.downloadToSystemCache(id); |
| 659 } |
| 660 |
| 661 var packageDir = path.join(packagesDir, id.name); |
| 662 if (entryExists(packageDir)) deleteEntry(packageDir); |
| 663 return source.get(id, packageDir); |
| 664 }).then((_) => source.resolveId(id)); |
| 665 } |
| 666 |
| 667 /// Determines whether or not the lockfile is out of date with respect to the |
| 668 /// pubspec. |
| 669 /// |
| 670 /// This will be `false` if there is no lockfile at all, or if the pubspec |
| 671 /// contains dependencies that are not in the lockfile or that don't match |
| 672 /// what's in there. |
| 673 bool _isLockFileUpToDate(LockFile lockFile) { |
| 674 /// If this is an entrypoint for an in-memory package, trust the in-memory |
| 675 /// lockfile provided for it. |
| 676 if (root.dir == null) return true; |
| 677 |
| 678 return root.immediateDependencies.every((package) { |
| 679 var locked = lockFile.packages[package.name]; |
| 680 if (locked == null) return false; |
| 681 |
| 682 if (package.source != locked.source) return false; |
| 683 if (!package.constraint.allows(locked.version)) return false; |
| 684 |
| 685 var source = cache.sources[package.source]; |
| 686 if (source == null) return false; |
| 687 |
| 688 return source.descriptionsEqual(package.description, locked.description); |
| 689 }); |
| 690 } |
| 691 |
| 692 /// Determines whether all of the packages in the lockfile are already |
| 693 /// installed and available. |
| 694 /// |
| 695 /// Note: this assumes [isLockFileUpToDate] has already been called and |
| 696 /// returned `true`. |
| 697 Future<bool> _arePackagesAvailable(LockFile lockFile) { |
| 698 return Future.wait(lockFile.packages.values.map((package) { |
| 699 var source = cache.sources[package.source]; |
| 700 |
| 701 // This should only be called after [_isLockFileUpToDate] has returned |
| 702 // `true`, which ensures all of the sources in the lock file are valid. |
| 703 assert(source != null); |
| 704 |
| 705 // We only care about cached sources. Uncached sources aren't "installed". |
| 706 // If one of those is missing, we want to show the user the file not |
| 707 // found error later since installing won't accomplish anything. |
| 708 if (source is! CachedSource) return new Future.value(true); |
| 709 |
| 710 // Get the directory. |
| 711 return source.getDirectory(package).then((dir) { |
| 712 // See if the directory is there and looks like a package. |
| 713 return dirExists(dir) || fileExists(path.join(dir, "pubspec.yaml")); |
| 714 }); |
| 715 })).then((results) { |
| 716 // Make sure they are all true. |
| 717 return results.every((result) => result); |
| 718 }); |
| 719 } |
| 720 |
| 721 /// Gets dependencies if the lockfile is out of date with respect to the |
| 722 /// pubspec. |
| 723 Future ensureLockFileIsUpToDate() { |
| 724 return new Future.sync(() { |
| 725 // If we don't have a current lock file, we definitely need to install. |
| 726 if (!_isLockFileUpToDate(lockFile)) { |
| 727 if (lockFileExists) { |
| 728 log.message( |
| 729 "Your pubspec has changed, so we need to update your lockfile:"); |
| 730 } else { |
| 731 log.message( |
| 732 "You don't have a lockfile, so we need to generate that:"); |
| 733 } |
| 734 |
| 735 return false; |
| 736 } |
| 737 |
| 738 // If we do have a lock file, we still need to make sure the packages |
| 739 // are actually installed. The user may have just gotten a package that |
| 740 // includes a lockfile. |
| 741 return _arePackagesAvailable(lockFile).then((available) { |
| 742 if (!available) { |
| 743 log.message( |
| 744 "You are missing some dependencies, so we need to install them " "
first:"); |
| 745 } |
| 746 |
| 747 return available; |
| 748 }); |
| 749 }).then((upToDate) { |
| 750 if (upToDate) return null; |
| 751 return acquireDependencies(SolveType.GET); |
| 752 }); |
| 753 } |
| 754 |
| 755 /// Loads the package graph for the application and all of its transitive |
| 756 /// dependencies. |
| 757 /// |
| 758 /// If [result] is passed, this loads the graph from it without re-parsing the |
| 759 /// lockfile or any pubspecs. Otherwise, before loading, this makes sure the |
| 760 /// lockfile and dependencies are installed and up to date. |
| 761 Future<PackageGraph> loadPackageGraph([SolveResult result]) { |
| 762 final completer0 = new Completer(); |
| 763 scheduleMicrotask(() { |
| 764 try { |
| 765 join0() { |
| 766 new Future.value(log.progress("Loading package graph", (() { |
| 767 final completer0 = new Completer(); |
| 768 scheduleMicrotask(() { |
| 769 try { |
| 770 join0() { |
| 771 new Future.value(ensureLockFileIsUpToDate()).then((x0) { |
| 772 try { |
| 773 x0; |
| 774 new Future.value( |
| 775 Future.wait(lockFile.packages.values.map(((id) { |
| 776 final completer0 = new Completer(); |
| 777 scheduleMicrotask(() { |
| 778 try { |
| 779 var source = cache.sources[id.source]; |
| 780 new Future.value( |
| 781 source.getDirectory(id)).then((x0) { |
| 782 try { |
| 783 var dir = x0; |
| 784 completer0.complete( |
| 785 new Package.load(id.name, dir, cache.sources
)); |
| 786 } catch (e0, s0) { |
| 787 completer0.completeError(e0, s0); |
| 788 } |
| 789 }, onError: completer0.completeError); |
| 790 } catch (e, s) { |
| 791 completer0.completeError(e, s); |
| 792 } |
| 793 }); |
| 794 return completer0.future; |
| 795 })))).then((x1) { |
| 796 try { |
| 797 var packages = x1; |
| 798 var packageMap = |
| 799 new Map.fromIterable(packages, key: ((p) { |
| 800 return p.name; |
| 801 })); |
| 802 packageMap[root.name] = root; |
| 803 completer0.complete( |
| 804 new PackageGraph(this, lockFile, packageMap)); |
| 805 } catch (e0, s0) { |
| 806 completer0.completeError(e0, s0); |
| 807 } |
| 808 }, onError: completer0.completeError); |
| 809 } catch (e1, s1) { |
| 810 completer0.completeError(e1, s1); |
| 811 } |
| 812 }, onError: completer0.completeError); |
| 813 } |
| 814 if (result != null) { |
| 815 new Future.value(Future.wait(result.packages.map(((id) { |
| 816 final completer0 = new Completer(); |
| 817 scheduleMicrotask(() { |
| 818 try { |
| 819 new Future.value( |
| 820 cache.sources[id.source].getDirectory(id)).then((x0)
{ |
| 821 try { |
| 822 var dir = x0; |
| 823 completer0.complete( |
| 824 new Package(result.pubspecs[id.name], dir)); |
| 825 } catch (e0, s0) { |
| 826 completer0.completeError(e0, s0); |
| 827 } |
| 828 }, onError: completer0.completeError); |
| 829 } catch (e, s) { |
| 830 completer0.completeError(e, s); |
| 831 } |
| 832 }); |
| 833 return completer0.future; |
| 834 })))).then((x2) { |
| 835 try { |
| 836 var packages = x2; |
| 837 completer0.complete( |
| 838 new PackageGraph( |
| 839 this, |
| 840 new LockFile(result.packages), |
| 841 new Map.fromIterable(packages, key: ((package) { |
| 842 return package.name; |
| 843 })))); |
| 844 } catch (e2, s2) { |
| 845 completer0.completeError(e2, s2); |
| 846 } |
| 847 }, onError: completer0.completeError); |
| 848 } else { |
| 849 join0(); |
| 850 } |
| 851 } catch (e, s) { |
| 852 completer0.completeError(e, s); |
| 853 } |
| 854 }); |
| 855 return completer0.future; |
| 856 }), fine: true)).then((x0) { |
| 857 try { |
| 858 var graph = x0; |
| 859 _packageGraph = graph; |
| 860 completer0.complete(graph); |
| 861 } catch (e0, s0) { |
| 862 completer0.completeError(e0, s0); |
| 863 } |
| 864 }, onError: completer0.completeError); |
| 865 } |
| 866 if (_packageGraph != null) { |
| 867 completer0.complete(_packageGraph); |
| 868 } else { |
| 869 join0(); |
| 870 } |
| 871 } catch (e, s) { |
| 872 completer0.completeError(e, s); |
| 873 } |
| 874 }); |
| 875 return completer0.future; |
| 876 } |
| 877 |
| 878 /// Saves a list of concrete package versions to the `pubspec.lock` file. |
| 879 void _saveLockFile(List<PackageId> packageIds) { |
| 880 _lockFile = new LockFile(packageIds); |
| 881 var lockFilePath = root.path('pubspec.lock'); |
| 882 writeTextFile(lockFilePath, _lockFile.serialize(root.dir, cache.sources)); |
| 883 } |
| 884 |
| 885 /// Creates a self-referential symlink in the `packages` directory that allows |
| 886 /// a package to import its own files using `package:`. |
| 887 void _linkSelf() { |
| 888 var linkPath = path.join(packagesDir, root.name); |
| 889 // Create the symlink if it doesn't exist. |
| 890 if (entryExists(linkPath)) return; |
| 891 ensureDir(packagesDir); |
| 892 createPackageSymlink( |
| 893 root.name, |
| 894 root.dir, |
| 895 linkPath, |
| 896 isSelfLink: true, |
| 897 relative: true); |
| 898 } |
| 899 |
| 900 /// If [packageSymlinks] is true, add "packages" directories to the whitelist |
| 901 /// of directories that may contain Dart entrypoints. |
| 902 /// |
| 903 /// Otherwise, delete any "packages" directories in the whitelist of |
| 904 /// directories that may contain Dart entrypoints. |
| 905 void _linkOrDeleteSecondaryPackageDirs() { |
| 906 // Only the main "bin" directory gets a "packages" directory, not its |
| 907 // subdirectories. |
| 908 var binDir = root.path('bin'); |
| 909 if (dirExists(binDir)) _linkOrDeleteSecondaryPackageDir(binDir); |
| 910 |
| 911 // The others get "packages" directories in subdirectories too. |
| 912 for (var dir in ['benchmark', 'example', 'test', 'tool', 'web']) { |
| 913 _linkOrDeleteSecondaryPackageDirsRecursively(root.path(dir)); |
| 914 } |
| 915 } |
| 916 |
| 917 /// If [packageSymlinks] is true, creates a symlink to the "packages" |
| 918 /// directory in [dir] and all its subdirectories. |
| 919 /// |
| 920 /// Otherwise, deletes any "packages" directories in [dir] and all its |
| 921 /// subdirectories. |
| 922 void _linkOrDeleteSecondaryPackageDirsRecursively(String dir) { |
| 923 if (!dirExists(dir)) return; |
| 924 _linkOrDeleteSecondaryPackageDir(dir); |
| 925 _listDirWithoutPackages( |
| 926 dir).where(dirExists).forEach(_linkOrDeleteSecondaryPackageDir); |
| 927 } |
| 928 |
| 929 // TODO(nweiz): roll this into [listDir] in io.dart once issue 4775 is fixed. |
| 930 /// Recursively lists the contents of [dir], excluding hidden `.DS_Store` |
| 931 /// files and `package` files. |
| 932 List<String> _listDirWithoutPackages(dir) { |
| 933 return flatten(listDir(dir).map((file) { |
| 934 if (path.basename(file) == 'packages') return []; |
| 935 if (!dirExists(file)) return []; |
| 936 var fileAndSubfiles = [file]; |
| 937 fileAndSubfiles.addAll(_listDirWithoutPackages(file)); |
| 938 return fileAndSubfiles; |
| 939 })); |
| 940 } |
| 941 |
| 942 /// If [packageSymlinks] is true, creates a symlink to the "packages" |
| 943 /// directory in [dir]. |
| 944 /// |
| 945 /// Otherwise, deletes a "packages" directories in [dir] if one exists. |
| 946 void _linkOrDeleteSecondaryPackageDir(String dir) { |
| 947 var symlink = path.join(dir, 'packages'); |
| 948 if (entryExists(symlink)) deleteEntry(symlink); |
| 949 if (_packageSymlinks) createSymlink(packagesDir, symlink, relative: true); |
| 950 } |
| 951 } |
OLD | NEW |