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 |