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 #library('entrypoint'); | 5 #library('entrypoint'); |
6 | 6 |
7 #import('io.dart'); | 7 #import('io.dart'); |
| 8 #import('lock_file.dart'); |
8 #import('package.dart'); | 9 #import('package.dart'); |
9 #import('root_source.dart'); | 10 #import('root_source.dart'); |
10 #import('system_cache.dart'); | 11 #import('system_cache.dart'); |
11 #import('version.dart'); | 12 #import('version.dart'); |
12 #import('version_solver.dart'); | 13 #import('version_solver.dart'); |
13 #import('utils.dart'); | 14 #import('utils.dart'); |
14 | 15 |
15 /** | 16 /** |
16 * Pub operates over a directed graph of dependencies that starts at a root | 17 * Pub operates over a directed graph of dependencies that starts at a root |
17 * "entrypoint" package. This is typically the package where the current | 18 * "entrypoint" package. This is typically the package where the current |
(...skipping 17 matching lines...) Expand all Loading... |
35 */ | 36 */ |
36 final Package root; | 37 final Package root; |
37 | 38 |
38 /** | 39 /** |
39 * The system-wide cache which caches packages that need to be fetched over | 40 * The system-wide cache which caches packages that need to be fetched over |
40 * the network. | 41 * the network. |
41 */ | 42 */ |
42 final SystemCache cache; | 43 final SystemCache cache; |
43 | 44 |
44 /** | 45 /** |
45 * Packages which have already been loaded into memory. | 46 * Packages which are either currently being asynchronously installed to the |
| 47 * directory, or have already been installed. |
46 */ | 48 */ |
47 final Map<PackageId, Package> _loadedPackages; | 49 final Map<PackageId, Future<PackageId>> _installs; |
48 | |
49 /** | |
50 * Packages which are currently being asynchronously installed to the | |
51 * directory. | |
52 */ | |
53 final Map<PackageId, Future<Package>> _pendingInstalls; | |
54 | 50 |
55 Entrypoint(this.root, this.cache) | 51 Entrypoint(this.root, this.cache) |
56 : _loadedPackages = new Map<PackageId, Package>(), | 52 : _installs = new Map<PackageId, Future<PackageId>>(); |
57 _pendingInstalls = new Map<PackageId, Future<Package>>(); | |
58 | 53 |
59 /** | 54 /** |
60 * The path to this "packages" directory. | 55 * The path to this "packages" directory. |
61 */ | 56 */ |
62 // TODO(rnystrom): Make this path configurable. | 57 // TODO(rnystrom): Make this path configurable. |
63 String get path() => join(root.dir, 'packages'); | 58 String get path() => join(root.dir, 'packages'); |
64 | 59 |
65 /** | 60 /** |
66 * Ensures that the package identified by [id] is installed to the directory, | 61 * Ensures that the package identified by [id] is installed to the directory. |
67 * loads it, and returns it. | 62 * Returns the resolved [PackageId]. |
68 * | 63 * |
69 * If this completes successfully, the package is guaranteed to be importable | 64 * If this completes successfully, the package is guaranteed to be importable |
70 * using the `package:` scheme. | 65 * using the `package:` scheme. |
71 * | 66 * |
72 * This will automatically install the package to the system-wide cache as | 67 * This will automatically install the package to the system-wide cache as |
73 * well if it requires network access to retrieve (specifically, if | 68 * well if it requires network access to retrieve (specifically, if |
74 * `id.source.shouldCache` is true). | 69 * `id.source.shouldCache` is true). |
75 * | 70 * |
76 * See also [installTransitively]. | 71 * See also [installDependencies]. |
77 */ | 72 */ |
78 Future<Package> install(PackageId id) { | 73 Future<PackageId> install(PackageId id) { |
79 var package = _loadedPackages[id]; | 74 var pendingOrCompleted = _installs[id]; |
80 if (package != null) return new Future<Package>.immediate(package); | 75 if (pendingOrCompleted != null) return pendingOrCompleted; |
81 | |
82 var pending = _pendingInstalls[id]; | |
83 if (pending != null) return new Future<Package>.immediate(package); | |
84 | 76 |
85 var packageDir = join(path, id.name); | 77 var packageDir = join(path, id.name); |
86 var future = ensureDir(dirname(packageDir)).chain((_) { | 78 var future = ensureDir(dirname(packageDir)).chain((_) { |
87 return exists(packageDir); | 79 return exists(packageDir); |
88 }).chain((exists) { | 80 }).chain((exists) { |
89 // If the package already exists in the directory, no need to re-install. | 81 if (!exists) return new Future.immediate(null); |
90 if (exists) return new Future.immediate(null); | 82 // TODO(nweiz): figure out when to actually delete the directory, and when |
91 | 83 // we can just re-use the existing symlink. |
| 84 return deleteDir(packageDir); |
| 85 }).chain((_) { |
92 if (id.source.shouldCache) { | 86 if (id.source.shouldCache) { |
93 return cache.install(id).chain( | 87 return cache.install(id).chain( |
94 (pkg) => createSymlink(pkg.dir, packageDir)); | 88 (pkg) => createSymlink(pkg.dir, packageDir)); |
95 } else { | 89 } else { |
96 return id.source.install(id, packageDir).transform((found) { | 90 return id.source.install(id, packageDir).transform((found) { |
97 if (found) return null; | 91 if (found) return null; |
98 // TODO(nweiz): More robust error-handling. | 92 // TODO(nweiz): More robust error-handling. |
99 throw 'Package ${id.name} not found in source "${id.source.name}".'; | 93 throw 'Package ${id.name} not found in source "${id.source.name}".'; |
100 }); | 94 }); |
101 } | 95 } |
102 }).chain((_) => Package.load(packageDir, cache.sources)); | 96 }).chain((_) => id.resolved); |
103 | 97 |
104 future.then((pkg) => _loadedPackages[id] = pkg); | 98 _installs[id] = future; |
105 always(future, () => _pendingInstalls.remove(id)); | |
106 _pendingInstalls[id] = future; | |
107 | 99 |
108 return future; | 100 return future; |
109 } | 101 } |
110 | 102 |
111 /** | 103 /** |
112 * Installs all dependencies of the [root] package to its "packages" | 104 * Installs all dependencies of the [root] package to its "packages" |
113 * directory. Returns a [Future] that completes when all dependencies are | 105 * directory. Returns a [Future] that completes when all dependencies are |
114 * installed. | 106 * installed. |
115 */ | 107 */ |
116 Future installDependencies() { | 108 Future installDependencies() { |
117 return resolveVersions(cache.sources, root).chain((packageVersions) { | 109 return _getResolvedDependencies().chain((packageVersions) { |
118 // TODO(nweiz): persist packageVersions to a lockfile. | |
119 return Futures.wait(packageVersions.map((id) { | 110 return Futures.wait(packageVersions.map((id) { |
120 if (id.source is RootSource) return new Future.immediate(null); | 111 if (id.source is RootSource) return new Future.immediate(id); |
121 return install(id); | 112 return install(id); |
122 })); | 113 })); |
| 114 }).chain(_saveLockFile); |
| 115 } |
| 116 |
| 117 /** |
| 118 * Gets a list of all the [PackageId]s that this entrypoint transitively |
| 119 * depends on. The concrete versions of these ids are given by the |
| 120 * [VersionSolver] and the `pubspec.lock` file, if it exists. |
| 121 */ |
| 122 Future<List<PackageId>> _getResolvedDependencies() { |
| 123 return _loadLockFile().chain((lockFile) => |
| 124 resolveVersions(cache.sources, root, lockFile)); |
| 125 } |
| 126 |
| 127 /** |
| 128 * Loads the list of concrete package versions from the `pubspec.lock`, if it |
| 129 * exists. If it doesn't, this completes to an empty [LockFile]. |
| 130 * |
| 131 * If there's an error reading the `pubspec.lock` file, this will print a |
| 132 * warning message and act as though the file doesn't exist. |
| 133 */ |
| 134 Future<LockFile> _loadLockFile() { |
| 135 var completer = new Completer<LockFile>(); |
| 136 var lockFilePath = join(root.dir, 'pubspec.lock'); |
| 137 var future = readTextFile(lockFilePath); |
| 138 |
| 139 future.handleException((_) { |
| 140 completer.complete(new LockFile.empty()); |
| 141 |
| 142 // If we failed to load the lockfile but it does exist, something's |
| 143 // probably wrong and we should notify the user. |
| 144 fileExists(lockFilePath).then((exists) { |
| 145 if (!exists) return; |
| 146 printError("Error reading pubspec.lock: ${future.exception}"); |
| 147 }); |
| 148 |
| 149 return true; |
123 }); | 150 }); |
| 151 |
| 152 future.then((text) => |
| 153 completer.complete(new LockFile.parse(text, cache.sources))); |
| 154 return completer.future; |
| 155 } |
| 156 |
| 157 /** |
| 158 * Saves a list of concrete package versions to the `pubspec.lock` file. |
| 159 */ |
| 160 Future _saveLockFile(List<PackageId> packageIds) { |
| 161 var lockFile = new LockFile.empty(); |
| 162 for (var id in packageIds) { |
| 163 if (id.source is! RootSource) lockFile.packages[id.name] = id; |
| 164 } |
| 165 |
| 166 return writeTextFile(join(root.dir, 'pubspec.lock'), lockFile.serialize()); |
124 } | 167 } |
125 } | 168 } |
OLD | NEW |