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 'lock_file.dart'; |
9 import 'log.dart' as log; | 9 import 'log.dart' as log; |
10 import 'package.dart'; | 10 import 'package.dart'; |
11 import 'root_source.dart'; | 11 import 'root_source.dart'; |
12 import 'system_cache.dart'; | 12 import 'system_cache.dart'; |
13 import 'utils.dart'; | 13 import 'utils.dart'; |
14 import 'version.dart'; | 14 import 'version.dart'; |
15 import 'version_solver.dart'; | 15 import 'version_solver.dart'; |
16 | 16 |
17 /** | 17 /// Pub operates over a directed graph of dependencies that starts at a root |
18 * Pub operates over a directed graph of dependencies that starts at a root | 18 /// "entrypoint" package. This is typically the package where the current |
19 * "entrypoint" package. This is typically the package where the current | 19 /// working directory is located. An entrypoint knows the [root] package it is |
20 * working directory is located. An entrypoint knows the [root] package it is | 20 /// associated with and is responsible for managing the "packages" directory |
21 * associated with and is responsible for managing the "packages" directory | 21 /// for it. |
22 * for it. | 22 /// |
23 * | 23 /// That directory contains symlinks to all packages used by an app. These links |
24 * That directory contains symlinks to all packages used by an app. These links | 24 /// point either to the [SystemCache] or to some other location on the local |
25 * point either to the [SystemCache] or to some other location on the local | 25 /// filesystem. |
26 * filesystem. | 26 /// |
27 * | 27 /// While entrypoints are typically applications, a pure library package may end |
28 * While entrypoints are typically applications, a pure library package may end | 28 /// up being used as an entrypoint. Also, a single package may be used as an |
29 * up being used as an entrypoint. Also, a single package may be used as an | 29 /// entrypoint in one context but not in another. For example, a package that |
30 * entrypoint in one context but not in another. For example, a package that | 30 /// contains a reusable library may not be the entrypoint when used by an app, |
31 * contains a reusable library may not be the entrypoint when used by an app, | 31 /// but may be the entrypoint when you're running its tests. |
32 * but may be the entrypoint when you're running its tests. | |
33 */ | |
34 class Entrypoint { | 32 class Entrypoint { |
35 /** | 33 /// The root package this entrypoint is associated with. |
36 * The root package this entrypoint is associated with. | |
37 */ | |
38 final Package root; | 34 final Package root; |
39 | 35 |
40 /** | 36 /// The system-wide cache which caches packages that need to be fetched over |
41 * The system-wide cache which caches packages that need to be fetched over | 37 /// the network. |
42 * the network. | |
43 */ | |
44 final SystemCache cache; | 38 final SystemCache cache; |
45 | 39 |
46 /** | 40 /// Packages which are either currently being asynchronously installed to the |
47 * Packages which are either currently being asynchronously installed to the | 41 /// directory, or have already been installed. |
48 * directory, or have already been installed. | |
49 */ | |
50 final Map<PackageId, Future<PackageId>> _installs; | 42 final Map<PackageId, Future<PackageId>> _installs; |
51 | 43 |
52 Entrypoint(this.root, this.cache) | 44 Entrypoint(this.root, this.cache) |
53 : _installs = new Map<PackageId, Future<PackageId>>(); | 45 : _installs = new Map<PackageId, Future<PackageId>>(); |
54 | 46 |
55 /// Loads the entrypoint from a package at [rootDir]. | 47 /// Loads the entrypoint from a package at [rootDir]. |
56 static Future<Entrypoint> load(String rootDir, SystemCache cache) { | 48 static Future<Entrypoint> load(String rootDir, SystemCache cache) { |
57 return Package.load(null, rootDir, cache.sources).transform((package) => | 49 return Package.load(null, rootDir, cache.sources).transform((package) => |
58 new Entrypoint(package, cache)); | 50 new Entrypoint(package, cache)); |
59 } | 51 } |
60 | 52 |
61 /** | |
62 * The path to this "packages" directory. | |
63 */ | |
64 // TODO(rnystrom): Make this path configurable. | 53 // TODO(rnystrom): Make this path configurable. |
| 54 /// The path to this "packages" directory. |
65 String get path => join(root.dir, 'packages'); | 55 String get path => join(root.dir, 'packages'); |
66 | 56 |
67 /** | 57 /// Ensures that the package identified by [id] is installed to the directory. |
68 * Ensures that the package identified by [id] is installed to the directory. | 58 /// Returns the resolved [PackageId]. |
69 * Returns the resolved [PackageId]. | 59 /// |
70 * | 60 /// If this completes successfully, the package is guaranteed to be importable |
71 * If this completes successfully, the package is guaranteed to be importable | 61 /// using the `package:` scheme. |
72 * using the `package:` scheme. | 62 /// |
73 * | 63 /// This will automatically install the package to the system-wide cache as |
74 * This will automatically install the package to the system-wide cache as | 64 /// well if it requires network access to retrieve (specifically, if |
75 * well if it requires network access to retrieve (specifically, if | 65 /// `id.source.shouldCache` is true). |
76 * `id.source.shouldCache` is true). | 66 /// |
77 * | 67 /// See also [installDependencies]. |
78 * See also [installDependencies]. | |
79 */ | |
80 Future<PackageId> install(PackageId id) { | 68 Future<PackageId> install(PackageId id) { |
81 var pendingOrCompleted = _installs[id]; | 69 var pendingOrCompleted = _installs[id]; |
82 if (pendingOrCompleted != null) return pendingOrCompleted; | 70 if (pendingOrCompleted != null) return pendingOrCompleted; |
83 | 71 |
84 var packageDir = join(path, id.name); | 72 var packageDir = join(path, id.name); |
85 var future = ensureDir(dirname(packageDir)).chain((_) { | 73 var future = ensureDir(dirname(packageDir)).chain((_) { |
86 return exists(packageDir); | 74 return exists(packageDir); |
87 }).chain((exists) { | 75 }).chain((exists) { |
88 if (!exists) return new Future.immediate(null); | 76 if (!exists) return new Future.immediate(null); |
89 // TODO(nweiz): figure out when to actually delete the directory, and when | 77 // TODO(nweiz): figure out when to actually delete the directory, and when |
(...skipping 11 matching lines...) Expand all Loading... |
101 throw 'Package ${id.name} not found in source "${id.source.name}".'; | 89 throw 'Package ${id.name} not found in source "${id.source.name}".'; |
102 }); | 90 }); |
103 } | 91 } |
104 }).chain((_) => id.resolved); | 92 }).chain((_) => id.resolved); |
105 | 93 |
106 _installs[id] = future; | 94 _installs[id] = future; |
107 | 95 |
108 return future; | 96 return future; |
109 } | 97 } |
110 | 98 |
111 /** | 99 /// Installs all dependencies of the [root] package to its "packages" |
112 * Installs all dependencies of the [root] package to its "packages" | 100 /// directory, respecting the [LockFile] if present. Returns a [Future] that |
113 * directory, respecting the [LockFile] if present. Returns a [Future] that | 101 /// completes when all dependencies are installed. |
114 * completes when all dependencies are installed. | |
115 */ | |
116 Future installDependencies() { | 102 Future installDependencies() { |
117 return _loadLockFile() | 103 return _loadLockFile() |
118 .chain((lockFile) => resolveVersions(cache.sources, root, lockFile)) | 104 .chain((lockFile) => resolveVersions(cache.sources, root, lockFile)) |
119 .chain(_installDependencies); | 105 .chain(_installDependencies); |
120 } | 106 } |
121 | 107 |
122 /** | 108 /// Installs the latest available versions of all dependencies of the [root] |
123 * Installs the latest available versions of all dependencies of the [root] | 109 /// package to its "package" directory, writing a new [LockFile]. Returns a |
124 * package to its "package" directory, writing a new [LockFile]. Returns a | 110 /// [Future] that completes when all dependencies are installed. |
125 * [Future] that completes when all dependencies are installed. | |
126 */ | |
127 Future updateAllDependencies() { | 111 Future updateAllDependencies() { |
128 return resolveVersions(cache.sources, root, new LockFile.empty()) | 112 return resolveVersions(cache.sources, root, new LockFile.empty()) |
129 .chain(_installDependencies); | 113 .chain(_installDependencies); |
130 } | 114 } |
131 | 115 |
132 /** | 116 /// Installs the latest available versions of [dependencies], while leaving |
133 * Installs the latest available versions of [dependencies], while leaving | 117 /// other dependencies as specified by the [LockFile] if possible. Returns a |
134 * other dependencies as specified by the [LockFile] if possible. Returns a | 118 /// [Future] that completes when all dependencies are installed. |
135 * [Future] that completes when all dependencies are installed. | |
136 */ | |
137 Future updateDependencies(List<String> dependencies) { | 119 Future updateDependencies(List<String> dependencies) { |
138 return _loadLockFile().chain((lockFile) { | 120 return _loadLockFile().chain((lockFile) { |
139 var versionSolver = new VersionSolver(cache.sources, root, lockFile); | 121 var versionSolver = new VersionSolver(cache.sources, root, lockFile); |
140 for (var dependency in dependencies) { | 122 for (var dependency in dependencies) { |
141 versionSolver.useLatestVersion(dependency); | 123 versionSolver.useLatestVersion(dependency); |
142 } | 124 } |
143 return versionSolver.solve(); | 125 return versionSolver.solve(); |
144 }).chain(_installDependencies); | 126 }).chain(_installDependencies); |
145 } | 127 } |
146 | 128 |
147 /** | 129 /// Removes the old packages directory, installs all dependencies listed in |
148 * Removes the old packages directory, installs all dependencies listed in | 130 /// [packageVersions], and writes a [LockFile]. |
149 * [packageVersions], and writes a [LockFile]. | |
150 */ | |
151 Future _installDependencies(List<PackageId> packageVersions) { | 131 Future _installDependencies(List<PackageId> packageVersions) { |
152 return cleanDir(path).chain((_) { | 132 return cleanDir(path).chain((_) { |
153 return Futures.wait(packageVersions.map((id) { | 133 return Futures.wait(packageVersions.map((id) { |
154 if (id.source is RootSource) return new Future.immediate(id); | 134 if (id.source is RootSource) return new Future.immediate(id); |
155 return install(id); | 135 return install(id); |
156 })); | 136 })); |
157 }).chain(_saveLockFile) | 137 }).chain(_saveLockFile) |
158 .chain(_installSelfReference) | 138 .chain(_installSelfReference) |
159 .chain(_linkSecondaryPackageDirs); | 139 .chain(_linkSecondaryPackageDirs); |
160 } | 140 } |
161 | 141 |
162 /** | 142 /// Loads the list of concrete package versions from the `pubspec.lock`, if it |
163 * Loads the list of concrete package versions from the `pubspec.lock`, if it | 143 /// exists. If it doesn't, this completes to an empty [LockFile]. |
164 * exists. If it doesn't, this completes to an empty [LockFile]. | 144 /// |
165 * | 145 /// If there's an error reading the `pubspec.lock` file, this will print a |
166 * If there's an error reading the `pubspec.lock` file, this will print a | 146 /// warning message and act as though the file doesn't exist. |
167 * warning message and act as though the file doesn't exist. | |
168 */ | |
169 Future<LockFile> _loadLockFile() { | 147 Future<LockFile> _loadLockFile() { |
170 var lockFilePath = join(root.dir, 'pubspec.lock'); | 148 var lockFilePath = join(root.dir, 'pubspec.lock'); |
171 | 149 |
172 log.fine("Loading lockfile."); | 150 log.fine("Loading lockfile."); |
173 return fileExists(lockFilePath).chain((exists) { | 151 return fileExists(lockFilePath).chain((exists) { |
174 if (!exists) { | 152 if (!exists) { |
175 log.fine("No lock file at $lockFilePath, creating empty one."); | 153 log.fine("No lock file at $lockFilePath, creating empty one."); |
176 return new Future<LockFile>.immediate(new LockFile.empty()); | 154 return new Future<LockFile>.immediate(new LockFile.empty()); |
177 } | 155 } |
178 | 156 |
179 return readTextFile(lockFilePath).transform((text) => | 157 return readTextFile(lockFilePath).transform((text) => |
180 new LockFile.parse(text, cache.sources)); | 158 new LockFile.parse(text, cache.sources)); |
181 }); | 159 }); |
182 } | 160 } |
183 | 161 |
184 /** | 162 /// Saves a list of concrete package versions to the `pubspec.lock` file. |
185 * Saves a list of concrete package versions to the `pubspec.lock` file. | |
186 */ | |
187 Future _saveLockFile(List<PackageId> packageIds) { | 163 Future _saveLockFile(List<PackageId> packageIds) { |
188 var lockFile = new LockFile.empty(); | 164 var lockFile = new LockFile.empty(); |
189 for (var id in packageIds) { | 165 for (var id in packageIds) { |
190 if (id.source is! RootSource) lockFile.packages[id.name] = id; | 166 if (id.source is! RootSource) lockFile.packages[id.name] = id; |
191 } | 167 } |
192 | 168 |
193 var lockFilePath = join(root.dir, 'pubspec.lock'); | 169 var lockFilePath = join(root.dir, 'pubspec.lock'); |
194 log.fine("Saving lockfile."); | 170 log.fine("Saving lockfile."); |
195 return writeTextFile(lockFilePath, lockFile.serialize()); | 171 return writeTextFile(lockFilePath, lockFile.serialize()); |
196 } | 172 } |
197 | 173 |
198 /** | 174 /// Installs a self-referential symlink in the `packages` directory that will |
199 * Installs a self-referential symlink in the `packages` directory that will | 175 /// allow a package to import its own files using `package:`. |
200 * allow a package to import its own files using `package:`. | |
201 */ | |
202 Future _installSelfReference(_) { | 176 Future _installSelfReference(_) { |
203 var linkPath = join(path, root.name); | 177 var linkPath = join(path, root.name); |
204 return exists(linkPath).chain((exists) { | 178 return exists(linkPath).chain((exists) { |
205 // Create the symlink if it doesn't exist. | 179 // Create the symlink if it doesn't exist. |
206 if (exists) return new Future.immediate(null); | 180 if (exists) return new Future.immediate(null); |
207 return ensureDir(path).chain( | 181 return ensureDir(path).chain( |
208 (_) => createPackageSymlink(root.name, root.dir, linkPath, | 182 (_) => createPackageSymlink(root.name, root.dir, linkPath, |
209 isSelfLink: true)); | 183 isSelfLink: true)); |
210 }); | 184 }); |
211 } | 185 } |
212 | 186 |
213 /** | 187 /// If `bin/`, `test/`, or `example/` directories exist, symlink `packages/` |
214 * If `bin/`, `test/`, or `example/` directories exist, symlink `packages/` | 188 /// into them so that their entrypoints can be run. Do the same for any |
215 * into them so that their entrypoints can be run. Do the same for any | 189 /// subdirectories of `test/` and `example/`. |
216 * subdirectories of `test/` and `example/`. | |
217 */ | |
218 Future _linkSecondaryPackageDirs(_) { | 190 Future _linkSecondaryPackageDirs(_) { |
219 var binDir = join(root.dir, 'bin'); | 191 var binDir = join(root.dir, 'bin'); |
220 var exampleDir = join(root.dir, 'example'); | 192 var exampleDir = join(root.dir, 'example'); |
221 var testDir = join(root.dir, 'test'); | 193 var testDir = join(root.dir, 'test'); |
222 var toolDir = join(root.dir, 'tool'); | 194 var toolDir = join(root.dir, 'tool'); |
223 var webDir = join(root.dir, 'web'); | 195 var webDir = join(root.dir, 'web'); |
224 return dirExists(binDir).chain((exists) { | 196 return dirExists(binDir).chain((exists) { |
225 if (!exists) return new Future.immediate(null); | 197 if (!exists) return new Future.immediate(null); |
226 return _linkSecondaryPackageDir(binDir); | 198 return _linkSecondaryPackageDir(binDir); |
227 }).chain((_) => _linkSecondaryPackageDirsRecursively(exampleDir)) | 199 }).chain((_) => _linkSecondaryPackageDirsRecursively(exampleDir)) |
228 .chain((_) => _linkSecondaryPackageDirsRecursively(testDir)) | 200 .chain((_) => _linkSecondaryPackageDirsRecursively(testDir)) |
229 .chain((_) => _linkSecondaryPackageDirsRecursively(toolDir)) | 201 .chain((_) => _linkSecondaryPackageDirsRecursively(toolDir)) |
230 .chain((_) => _linkSecondaryPackageDirsRecursively(webDir)); | 202 .chain((_) => _linkSecondaryPackageDirsRecursively(webDir)); |
231 } | 203 } |
232 | 204 |
233 /** | 205 /// Creates a symlink to the `packages` directory in [dir] and all its |
234 * Creates a symlink to the `packages` directory in [dir] and all its | 206 /// subdirectories. |
235 * subdirectories. | |
236 */ | |
237 Future _linkSecondaryPackageDirsRecursively(String dir) { | 207 Future _linkSecondaryPackageDirsRecursively(String dir) { |
238 return dirExists(dir).chain((exists) { | 208 return dirExists(dir).chain((exists) { |
239 if (!exists) return new Future.immediate(null); | 209 if (!exists) return new Future.immediate(null); |
240 return _linkSecondaryPackageDir(dir) | 210 return _linkSecondaryPackageDir(dir) |
241 .chain((_) => _listDirWithoutPackages(dir)) | 211 .chain((_) => _listDirWithoutPackages(dir)) |
242 .chain((files) { | 212 .chain((files) { |
243 return Futures.wait(files.map((file) { | 213 return Futures.wait(files.map((file) { |
244 return dirExists(file).chain((isDir) { | 214 return dirExists(file).chain((isDir) { |
245 if (!isDir) return new Future.immediate(null); | 215 if (!isDir) return new Future.immediate(null); |
246 return _linkSecondaryPackageDir(file); | 216 return _linkSecondaryPackageDir(file); |
247 }); | 217 }); |
248 })); | 218 })); |
249 }); | 219 }); |
250 }); | 220 }); |
251 } | 221 } |
252 | 222 |
253 // TODO(nweiz): roll this into [listDir] in io.dart once issue 4775 is fixed. | 223 // TODO(nweiz): roll this into [listDir] in io.dart once issue 4775 is fixed. |
254 /** | 224 /// Recursively lists the contents of [dir], excluding hidden `.DS_Store` |
255 * Recursively lists the contents of [dir], excluding hidden `.DS_Store` files | 225 /// files and `package` files. |
256 * and `package` files. | |
257 */ | |
258 Future<List<String>> _listDirWithoutPackages(dir) { | 226 Future<List<String>> _listDirWithoutPackages(dir) { |
259 return listDir(dir).chain((files) { | 227 return listDir(dir).chain((files) { |
260 return Futures.wait(files.map((file) { | 228 return Futures.wait(files.map((file) { |
261 if (basename(file) == 'packages') return new Future.immediate([]); | 229 if (basename(file) == 'packages') return new Future.immediate([]); |
262 return dirExists(file).chain((isDir) { | 230 return dirExists(file).chain((isDir) { |
263 if (!isDir) return new Future.immediate([]); | 231 if (!isDir) return new Future.immediate([]); |
264 return _listDirWithoutPackages(file); | 232 return _listDirWithoutPackages(file); |
265 }).transform((subfiles) { | 233 }).transform((subfiles) { |
266 var fileAndSubfiles = [file]; | 234 var fileAndSubfiles = [file]; |
267 fileAndSubfiles.addAll(subfiles); | 235 fileAndSubfiles.addAll(subfiles); |
268 return fileAndSubfiles; | 236 return fileAndSubfiles; |
269 }); | 237 }); |
270 })); | 238 })); |
271 }).transform(flatten); | 239 }).transform(flatten); |
272 } | 240 } |
273 | 241 |
274 /** | 242 /// Creates a symlink to the `packages` directory in [dir] if none exists. |
275 * Creates a symlink to the `packages` directory in [dir] if none exists. | |
276 */ | |
277 Future _linkSecondaryPackageDir(String dir) { | 243 Future _linkSecondaryPackageDir(String dir) { |
278 var to = join(dir, 'packages'); | 244 var to = join(dir, 'packages'); |
279 return exists(to).chain((exists) { | 245 return exists(to).chain((exists) { |
280 if (exists) return new Future.immediate(null); | 246 if (exists) return new Future.immediate(null); |
281 return createSymlink(path, to); | 247 return createSymlink(path, to); |
282 }); | 248 }); |
283 } | 249 } |
284 } | 250 } |
OLD | NEW |