| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * The "packages" directory for an application or library. | 6 * Pub operates over a directed graph of dependencies that starts at a root |
| 7 * "entrypoint" package. This is typically the package where the current |
| 8 * working directory is located. An entrypoint knows the [root] package it is |
| 9 * associated with and is responsible for managing the "packages" directory |
| 10 * for it. |
| 7 * | 11 * |
| 8 * This directory contains symlinks to all packages used by an app. These links | 12 * That directory contains symlinks to all packages used by an app. These links |
| 9 * point either to the [SystemCache] or to some other location on the local | 13 * point either to the [SystemCache] or to some other location on the local |
| 10 * filesystem. | 14 * filesystem. |
| 15 * |
| 16 * While entrypoints are typically applications, a pure library package may end |
| 17 * up being used as an entrypoint. Also, a single package may be used as an |
| 18 * entrypoint in one context but not in another. For example, a package that |
| 19 * contains a reusable library may not be the entrypoint when used by an app, |
| 20 * but may be the entrypoint when you're running its tests. |
| 11 */ | 21 */ |
| 12 class PackagesDir { | 22 class Entrypoint { |
| 13 /** | 23 /** |
| 14 * The package containing this directory. | 24 * The root package this entrypoint is associated with. |
| 15 */ | 25 */ |
| 16 final Package owner; | 26 final Package root; |
| 17 | 27 |
| 18 /** | 28 /** |
| 19 * The system-wide cache which caches packages that need to be fetched over | 29 * The system-wide cache which caches packages that need to be fetched over |
| 20 * the network. | 30 * the network. |
| 21 */ | 31 */ |
| 22 final SystemCache cache; | 32 final SystemCache cache; |
| 23 | 33 |
| 24 /** | 34 /** |
| 25 * Packages which have already been loaded into memory. | 35 * Packages which have already been loaded into memory. |
| 26 */ | 36 */ |
| 27 final Map<PackageId, Package> _loadedPackages; | 37 final Map<PackageId, Package> _loadedPackages; |
| 28 | 38 |
| 29 /** | 39 /** |
| 30 * Packages which are currently being asynchronously installed to the | 40 * Packages which are currently being asynchronously installed to the |
| 31 * directory. | 41 * directory. |
| 32 */ | 42 */ |
| 33 final Map<PackageId, Future<Package>> _pendingInstalls; | 43 final Map<PackageId, Future<Package>> _pendingInstalls; |
| 34 | 44 |
| 35 PackagesDir(this.owner, this.cache) | 45 Entrypoint(this.root, this.cache) |
| 36 : _loadedPackages = new Map<PackageId, Package>(), | 46 : _loadedPackages = new Map<PackageId, Package>(), |
| 37 _pendingInstalls = new Map<PackageId, Future<Package>>(); | 47 _pendingInstalls = new Map<PackageId, Future<Package>>(); |
| 38 | 48 |
| 39 /** | 49 /** |
| 40 * Returns the path to the "packages" directory. | 50 * The path to this "packages" directory. |
| 41 */ | 51 */ |
| 42 // TODO(rnystrom): Make this path configurable. | 52 // TODO(rnystrom): Make this path configurable. |
| 43 String get path() => join(owner.dir, 'packages'); | 53 String get path() => join(root.dir, 'packages'); |
| 44 | 54 |
| 45 /** | 55 /** |
| 46 * Ensures that the package identified by [id] is installed to the directory, | 56 * Ensures that the package identified by [id] is installed to the directory, |
| 47 * loads it, and returns it. | 57 * loads it, and returns it. |
| 48 * | 58 * |
| 49 * If this completes successfully, the package is guaranteed to be importable | 59 * If this completes successfully, the package is guaranteed to be importable |
| 50 * using the `package:` scheme. | 60 * using the `package:` scheme. |
| 51 * | 61 * |
| 52 * This will automatically install the package to the system-wide cache as | 62 * This will automatically install the package to the system-wide cache as |
| 53 * well if it requires network access to retrieve (specifically, if | 63 * well if it requires network access to retrieve (specifically, if |
| (...skipping 15 matching lines...) Expand all Loading... |
| 69 // If the package already exists in the directory, no need to re-install. | 79 // If the package already exists in the directory, no need to re-install. |
| 70 if (exists) return new Future.immediate(null); | 80 if (exists) return new Future.immediate(null); |
| 71 | 81 |
| 72 if (id.source.shouldCache) { | 82 if (id.source.shouldCache) { |
| 73 return cache.install(id).chain( | 83 return cache.install(id).chain( |
| 74 (pkg) => createSymlink(pkg.dir, packageDir)); | 84 (pkg) => createSymlink(pkg.dir, packageDir)); |
| 75 } else { | 85 } else { |
| 76 return id.source.install(id, packageDir).transform((found) { | 86 return id.source.install(id, packageDir).transform((found) { |
| 77 if (found) return null; | 87 if (found) return null; |
| 78 // TODO(nweiz): More robust error-handling. | 88 // TODO(nweiz): More robust error-handling. |
| 79 throw 'Package ${id.fullName} not found in source ' | 89 throw 'Package ${id.name} not found in source "${id.source.name}".'; |
| 80 '"${id.source.name}".'; | |
| 81 }); | 90 }); |
| 82 } | 91 } |
| 83 }).chain((_) => Package.load(packageDir, cache.sources)); | 92 }).chain((_) => Package.load(packageDir, cache.sources)); |
| 84 | 93 |
| 85 future.then((pkg) => _loadedPackages[id] = pkg); | 94 future.then((pkg) => _loadedPackages[id] = pkg); |
| 86 always(future, () => _pendingInstalls.remove(id)); | 95 always(future, () => _pendingInstalls.remove(id)); |
| 87 _pendingInstalls[id] = future; | 96 _pendingInstalls[id] = future; |
| 88 | 97 |
| 89 return future; | 98 return future; |
| 90 } | 99 } |
| 91 | 100 |
| 92 /** | 101 /** |
| 93 * Installs the package identified by [id] and all its transitive | 102 * Installs all dependencies of the [root] package to its "packages" |
| 94 * dependencies. | 103 * directory. Returns a [Future] that completes when all dependencies are |
| 104 * installed. |
| 95 */ | 105 */ |
| 96 Future<Package> installTransitively(PackageId id) { | 106 Future installDependencies() { |
| 97 var seen = new Set<PackageId>(); | 107 var seen = new Set<PackageId>(); |
| 98 Future<Package> helper(id) { | |
| 99 if (seen.contains(id)) return new Future.immediate(null); | |
| 100 seen.add(id); | |
| 101 | 108 |
| 102 return install(id).chain((package) { | 109 Future helper(List<PackageRef> packages) { |
| 103 return Futures.wait(package.dependencies.map(helper)). | 110 return Futures.wait(packages.map((ref) { |
| 104 transform((_) => package); | 111 return resolve(ref).chain((id) { |
| 105 }); | 112 if (seen.contains(id)) return new Future.immediate(null); |
| 113 seen.add(id); |
| 114 |
| 115 return install(id).chain((package) { |
| 116 return helper(package.dependencies); |
| 117 }); |
| 118 }); |
| 119 })); |
| 106 } | 120 } |
| 107 | 121 |
| 108 return helper(id); | 122 return helper(root.dependencies); |
| 109 } | 123 } |
| 110 | 124 |
| 111 /** | 125 /** |
| 112 * Installs all dependencies of [owner] to the "packages" directory. Returns a | 126 * Given [ref], which ambiguously identifies a dependent package, selects an |
| 113 * [Future] that completes when all dependencies are installed. | 127 * appropriate precise package to use when this is the entrypoint. In other |
| 128 * words, given a loose refence like "foo >= 2.0", figures out what concrete |
| 129 * package we should use starting from this entrypoint. |
| 114 */ | 130 */ |
| 115 Future installDependencies() { | 131 Future<PackageId> resolve(PackageRef ref) { |
| 116 return Futures.wait(owner.dependencies.map(installTransitively)). | 132 // TODO(rnystrom): This should use the lockfile to select the right version |
| 117 transform((_) => null); | 133 // once that's implemented. If the lockfile doesn't exist, it should |
| 134 // generate it. In the meantime, here's a dumb implementation: |
| 135 return new Future.immediate( |
| 136 new PackageId(ref.source, Version.none, ref.description)); |
| 118 } | 137 } |
| 119 } | 138 } |
| OLD | NEW |