OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 pub.global_packages; | 5 library pub.global_packages; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:io'; | 8 import 'dart:io'; |
9 | 9 |
10 import 'package:path/path.dart' as p; | 10 import 'package:path/path.dart' as p; |
11 | 11 |
12 import 'entrypoint.dart'; | 12 import 'entrypoint.dart'; |
13 import 'io.dart'; | 13 import 'io.dart'; |
14 import 'lock_file.dart'; | 14 import 'lock_file.dart'; |
15 import 'log.dart' as log; | 15 import 'log.dart' as log; |
16 import 'package.dart'; | 16 import 'package.dart'; |
17 import 'system_cache.dart'; | 17 import 'system_cache.dart'; |
18 import 'solver/version_solver.dart'; | 18 import 'solver/version_solver.dart'; |
| 19 import 'source.dart'; |
19 import 'source/cached.dart'; | 20 import 'source/cached.dart'; |
| 21 import 'source/path.dart'; |
20 import 'utils.dart'; | 22 import 'utils.dart'; |
21 import 'version.dart'; | 23 import 'version.dart'; |
22 | 24 |
23 /// Maintains the set of packages that have been globally activated. | 25 /// Maintains the set of packages that have been globally activated. |
24 /// | 26 /// |
25 /// These have been hand-chosen by the user to make their executables in bin/ | 27 /// These have been hand-chosen by the user to make their executables in bin/ |
26 /// available to the entire system. This lets them access them even when the | 28 /// available to the entire system. This lets them access them even when the |
27 /// current working directory is not inside another entrypoint package. | 29 /// current working directory is not inside another entrypoint package. |
28 /// | 30 /// |
29 /// Only one version of a given package name can be globally activated at a | 31 /// Only one version of a given package name can be globally activated at a |
30 /// time. Activating a different version of a package will deactivate the | 32 /// time. Activating a different version of a package will deactivate the |
31 /// previous one. | 33 /// previous one. |
| 34 /// |
| 35 /// This handles packages from uncached and cached sources a little differently. |
| 36 /// For a cached source, the package is physically in the user's pub cache and |
| 37 /// we don't want to mess with it by putting a lockfile in there. Instead, when |
| 38 /// we activate the package, we create a full lockfile and put it in the |
| 39 /// "global_packages" directory. It's named "<package>.lock". Unlike a normal |
| 40 /// lockfile, it also contains an entry for the root package itself, so that we |
| 41 /// know the version and description that was activated. |
| 42 /// |
| 43 /// Uncached packages (i.e. "path" packages) are somewhere else on the user's |
| 44 /// local file system and can have a lockfile directly in place. (And, in fact, |
| 45 /// we want to ensure we honor the user's lockfile there.) To activate it, we |
| 46 /// just need to know where that package directory is. For that, we create a |
| 47 /// lockfile that *only* contains the root package's [PackageId] -- basically |
| 48 /// just the path to the directory where the real lockfile lives. |
32 class GlobalPackages { | 49 class GlobalPackages { |
33 /// The [SystemCache] containing the global packages. | 50 /// The [SystemCache] containing the global packages. |
34 final SystemCache cache; | 51 final SystemCache cache; |
35 | 52 |
36 /// The directory where the lockfiles for activated packages are stored. | 53 /// The directory where the lockfiles for activated packages are stored. |
37 String get _directory => p.join(cache.rootDir, "global_packages"); | 54 String get _directory => p.join(cache.rootDir, "global_packages"); |
38 | 55 |
39 /// The source that global packages can be activated from. | |
40 // TODO(rnystrom): Allow activating packages from other sources. | |
41 CachedSource get _source => cache.sources["hosted"] as CachedSource; | |
42 | |
43 /// Creates a new global package registry backed by the given directory on | 56 /// Creates a new global package registry backed by the given directory on |
44 /// the user's file system. | 57 /// the user's file system. |
45 /// | 58 /// |
46 /// The directory may not physically exist yet. If not, this will create it | 59 /// The directory may not physically exist yet. If not, this will create it |
47 /// when needed. | 60 /// when needed. |
48 GlobalPackages(this.cache); | 61 GlobalPackages(this.cache); |
49 | 62 |
50 /// Finds the latest version of the hosted package with [name] that matches | 63 /// Finds the latest version of the hosted package with [name] that matches |
51 /// [constraint] and makes it the active global version. | 64 /// [constraint] and makes it the active global version. |
52 Future activate(String name, VersionConstraint constraint) { | 65 Future activateHosted(String name, VersionConstraint constraint) { |
53 // See if we already have it activated. | 66 // See if we already have it activated. |
54 var lockFile; | 67 var lockFile = _describeActive(name); |
55 var currentVersion; | 68 var currentVersion; |
56 try { | 69 if (lockFile != null) { |
57 lockFile = new LockFile.load(_getLockFilePath(name), cache.sources); | 70 var id = lockFile.packages[name]; |
58 currentVersion = lockFile.packages[name].version; | 71 |
| 72 // Try to preserve the current version if we've already activated the |
| 73 // hosted package. |
| 74 if (id.source == "hosted") currentVersion = id.version; |
59 | 75 |
60 // Pull the root package out of the lock file so the solver doesn't see | 76 // Pull the root package out of the lock file so the solver doesn't see |
61 // it. | 77 // it. |
62 lockFile.packages.remove(name); | 78 lockFile.packages.remove(name); |
63 | 79 } else { |
64 log.message("Package ${log.bold(name)} is already active at " | |
65 "version ${log.bold(currentVersion)}."); | |
66 } on IOException catch (error) { | |
67 // If we couldn't read the lock file, it's not activated. | |
68 lockFile = new LockFile.empty(); | 80 lockFile = new LockFile.empty(); |
69 } | 81 } |
70 | 82 |
71 var package; | |
72 var id; | |
73 return _selectVersion(name, currentVersion, constraint).then((version) { | 83 return _selectVersion(name, currentVersion, constraint).then((version) { |
74 // Make sure it's in the cache. | 84 // Make sure it's in the cache. |
75 id = new PackageId(name, _source.name, version, name); | 85 var id = new PackageId(name, "hosted", version, name); |
76 return _source.downloadToSystemCache(id); | 86 return _installInCache(id, lockFile); |
77 }).then((p) { | 87 }); |
78 package = p; | 88 } |
79 // Resolve it and download its dependencies. | 89 |
80 return resolveVersions(SolveType.GET, cache.sources, package, | 90 /// Makes the local package at [path] globally active. |
81 lockFile: lockFile); | 91 Future activatePath(String path) { |
| 92 return syncFuture(() { |
| 93 var entrypoint = new Entrypoint(path, cache); |
| 94 |
| 95 var name = entrypoint.root.name; |
| 96 |
| 97 // Call this just to log what the current active package is, if any. |
| 98 _describeActive(name); |
| 99 |
| 100 // Write a lockfile that points to the local package. |
| 101 var fullPath = canonicalize(entrypoint.root.dir); |
| 102 var id = new PackageId(name, "path", entrypoint.root.version, |
| 103 PathSource.describePath(fullPath)); |
| 104 _writeLockFile(id, new LockFile.empty()); |
| 105 }); |
| 106 } |
| 107 |
| 108 /// Installs the package [id] with [lockFile] into the system cache along |
| 109 /// with its dependencies. |
| 110 Future _installInCache(PackageId id, LockFile lockFile) { |
| 111 var source = cache.sources[id.source]; |
| 112 |
| 113 // Put the main package in the cache. |
| 114 return source.downloadToSystemCache(id).then((package) { |
| 115 // If we didn't know the version for the ID (which is true for Git |
| 116 // packages), look it up now that we have it. |
| 117 if (id.version == Version.none) { |
| 118 id = id.atVersion(package.version); |
| 119 } |
| 120 |
| 121 return source.resolveId(id).then((id_) { |
| 122 id = id_; |
| 123 |
| 124 // Resolve it and download its dependencies. |
| 125 return resolveVersions(SolveType.GET, cache.sources, package, |
| 126 lockFile: lockFile); |
| 127 }); |
82 }).then((result) { | 128 }).then((result) { |
83 if (!result.succeeded) throw result.error; | 129 if (!result.succeeded) throw result.error; |
84 result.showReport(SolveType.GET); | 130 result.showReport(SolveType.GET); |
85 | 131 |
86 // Make sure all of the dependencies are locally installed. | 132 // Make sure all of the dependencies are locally installed. |
87 return Future.wait(result.packages.map((id) { | 133 return Future.wait(result.packages.map(_cacheDependency)); |
88 var source = cache.sources[id.source]; | |
89 if (source is! CachedSource) return new Future.value(); | |
90 return source.downloadToSystemCache(id) | |
91 .then((_) => source.resolveId(id)); | |
92 })); | |
93 }).then((ids) { | 134 }).then((ids) { |
94 var lockFile = new LockFile(ids); | 135 _writeLockFile(id, new LockFile(ids)); |
95 | 136 }); |
96 // Add the root package itself to the lockfile. | 137 } |
97 lockFile.packages[name] = id; | 138 |
98 | 139 /// Downloads [id] into the system cache if it's a cached package. |
99 ensureDir(_directory); | 140 /// |
100 writeTextFile(_getLockFilePath(name), | 141 /// Returns the resolved [PackageId] for [id]. |
101 lockFile.serialize(cache.rootDir, cache.sources)); | 142 Future<PackageId> _cacheDependency(PackageId id) { |
102 | 143 var source = cache.sources[id.source]; |
103 log.message("Activated ${log.bold(package.name)} ${package.version}."); | 144 |
104 // TODO(rnystrom): Look in "bin" and display list of binaries that | 145 return syncFuture(() { |
105 // user can run. | 146 if (id.isRoot) return null; |
106 }); | 147 if (source is! CachedSource) return null; |
107 } | 148 |
108 | 149 return source.downloadToSystemCache(id); |
109 /// Deactivates a previously-activated package named [name] or fails with | 150 }).then((_) => source.resolveId(id)); |
110 /// an error if [name] is not an active package. | 151 } |
111 void deactivate(String name) { | 152 |
112 // See if we already have it activated. | 153 /// Finishes activating package [id] by saving [lockFile] in the cache. |
| 154 void _writeLockFile(PackageId id, LockFile lockFile) { |
| 155 // Add the root package to the lockfile. |
| 156 lockFile.packages[id.name] = id; |
| 157 |
| 158 ensureDir(_directory); |
| 159 writeTextFile(_getLockFilePath(id.name), |
| 160 lockFile.serialize(cache.rootDir, cache.sources)); |
| 161 |
| 162 if (id.source == "path") { |
| 163 var path = PathSource.pathFromDescription(id.description); |
| 164 log.message('Activated ${log.bold(id.name)} ${id.version} at path ' |
| 165 '"$path".'); |
| 166 } else { |
| 167 log.message("Activated ${log.bold(id.name)} ${id.version}."); |
| 168 } |
| 169 |
| 170 // TODO(rnystrom): Look in "bin" and display list of binaries that |
| 171 // user can run. |
| 172 } |
| 173 |
| 174 /// Gets the lock file for the currently active package with [name]. |
| 175 /// |
| 176 /// Displays a message to the user about the current package, if any. Returns |
| 177 /// the [LockFile] for the active package or `null` otherwise. |
| 178 LockFile _describeActive(String package) { |
113 try { | 179 try { |
114 var lockFilePath = p.join(_directory, "$name.lock"); | 180 var lockFile = new LockFile.load(_getLockFilePath(package), |
115 var lockFile = new LockFile.load(lockFilePath, cache.sources); | 181 cache.sources); |
116 var version = lockFile.packages[name].version; | 182 var id = lockFile.packages[package]; |
117 | 183 |
118 deleteEntry(lockFilePath); | 184 if (id.source == "path") { |
119 log.message("Deactivated package ${log.bold(name)} $version."); | 185 var path = PathSource.pathFromDescription(id.description); |
| 186 log.message('Package ${log.bold(package)} is currently active at ' |
| 187 'path "$path".'); |
| 188 } else { |
| 189 log.message("Package ${log.bold(package)} is currently active at " |
| 190 "version ${log.bold(id.version)}."); |
| 191 } |
| 192 |
| 193 return lockFile; |
120 } on IOException catch (error) { | 194 } on IOException catch (error) { |
121 dataError("No active package ${log.bold(name)}."); | 195 // If we couldn't read the lock file, it's not activated. |
122 } | 196 return null; |
123 } | 197 } |
124 | 198 } |
125 /// Finds the active packge with [name]. | 199 |
| 200 /// Deactivates a previously-activated package named [name]. |
| 201 /// |
| 202 /// If [logDeletion] is true, displays to the user when a package is |
| 203 /// deactivated. Otherwise, deactivates silently. |
| 204 /// |
| 205 /// Returns `false` if no package with [name] was currently active. |
| 206 bool deactivate(String name, {bool logDeactivate: false}) { |
| 207 var lockFilePath = _getLockFilePath(name); |
| 208 if (!fileExists(lockFilePath)) return false; |
| 209 |
| 210 var lockFile = new LockFile.load(lockFilePath, cache.sources); |
| 211 var id = lockFile.packages[name]; |
| 212 |
| 213 deleteEntry(lockFilePath); |
| 214 |
| 215 if (logDeactivate) { |
| 216 if (id.source == "path") { |
| 217 var path = PathSource.pathFromDescription(id.description); |
| 218 log.message('Deactivated package ${log.bold(name)} at path "$path".'); |
| 219 } else { |
| 220 log.message("Deactivated package ${log.bold(name)} ${id.version}."); |
| 221 } |
| 222 } |
| 223 |
| 224 return true; |
| 225 } |
| 226 |
| 227 /// Finds the active package with [name]. |
126 /// | 228 /// |
127 /// Returns an [Entrypoint] loaded with the active package if found. | 229 /// Returns an [Entrypoint] loaded with the active package if found. |
128 Future<Entrypoint> find(String name) { | 230 Future<Entrypoint> find(String name) { |
129 var lockFile; | |
130 var version; | |
131 return syncFuture(() { | 231 return syncFuture(() { |
| 232 var lockFile; |
132 try { | 233 try { |
133 lockFile = new LockFile.load(_getLockFilePath(name), cache.sources); | 234 lockFile = new LockFile.load(_getLockFilePath(name), cache.sources); |
134 version = lockFile.packages[name].version; | |
135 } on IOException catch (error) { | 235 } on IOException catch (error) { |
136 // If we couldn't read the lock file, it's not activated. | 236 // If we couldn't read the lock file, it's not activated. |
137 dataError("No active package ${log.bold(name)}."); | 237 dataError("No active package ${log.bold(name)}."); |
138 } | 238 } |
139 }).then((_) { | 239 |
140 // Load the package from the cache. | 240 // Load the package from the cache. |
141 var id = new PackageId(name, _source.name, version, name); | 241 var id = lockFile.packages[name]; |
142 return _source.getDirectory(id); | |
143 }).then((dir) { | |
144 return new Package.load(name, dir, cache.sources); | |
145 }).then((package) { | |
146 // Pull the root package out of the lock file so the solver doesn't see | |
147 // it. | |
148 lockFile.packages.remove(name); | 242 lockFile.packages.remove(name); |
149 | 243 |
150 return new Entrypoint.inMemory(package, lockFile, cache); | 244 var source = cache.sources[id.source]; |
151 }); | 245 if (source is CachedSource) { |
152 } | 246 // For cached sources, the package itself is in the cache and the |
153 | 247 // lockfile is the one we just loaded. |
154 /// Picks the best version of [package] to activate that meets [constraint]. | 248 return cache.sources[id.source].getDirectory(id) |
| 249 .then((dir) => new Package.load(name, dir, cache.sources)) |
| 250 .then((package) { |
| 251 return new Entrypoint.inMemory(package, lockFile, cache); |
| 252 }); |
| 253 } |
| 254 |
| 255 // For uncached sources (i.e. path), the ID just points to the real |
| 256 // directory for the package. |
| 257 assert(id.source == "path"); |
| 258 return new Entrypoint(PathSource.pathFromDescription(id.description), |
| 259 cache); |
| 260 }); |
| 261 } |
| 262 |
| 263 /// Picks the best hosted version of [package] to activate that meets |
| 264 /// [constraint]. |
155 /// | 265 /// |
156 /// If [version] is not `null`, this tries to maintain that version if | 266 /// If [version] is not `null`, this tries to maintain that version if |
157 /// possible. | 267 /// possible. |
158 Future<Version> _selectVersion(String package, Version version, | 268 Future<Version> _selectVersion(String package, Version version, |
159 VersionConstraint constraint) { | 269 VersionConstraint constraint) { |
160 // If we already have a valid active version, just use it. | 270 // If we already have a valid active version, just use it. |
161 if (version != null && constraint.allows(version)) { | 271 if (version != null && constraint.allows(version)) { |
162 return new Future.value(version); | 272 return new Future.value(version); |
163 } | 273 } |
164 | 274 |
165 // Otherwise, select the best version the matches the constraint. | 275 // Otherwise, select the best version the matches the constraint. |
166 return _source.getVersions(package, package).then((versions) { | 276 var source = cache.sources["hosted"]; |
| 277 return source.getVersions(package, package).then((versions) { |
167 versions = versions.where(constraint.allows).toList(); | 278 versions = versions.where(constraint.allows).toList(); |
168 | 279 |
169 if (versions.isEmpty) { | 280 if (versions.isEmpty) { |
170 // TODO(rnystrom): Show most recent unmatching version? | 281 // TODO(rnystrom): Show most recent unmatching version? |
171 dataError("Package ${log.bold(package)} has no versions that match " | 282 dataError("Package ${log.bold(package)} has no versions that match " |
172 "$constraint."); | 283 "$constraint."); |
173 } | 284 } |
174 | 285 |
175 // Pick the best matching version. | 286 // Pick the best matching version. |
176 versions.sort(Version.prioritize); | 287 versions.sort(Version.prioritize); |
177 return versions.last; | 288 return versions.last; |
178 }); | 289 }); |
179 } | 290 } |
180 | 291 |
181 /// Gets the path to the lock file for an activated package with [name]. | 292 /// Gets the path to the lock file for an activated cached package with |
| 293 /// [name]. |
182 String _getLockFilePath(name) => p.join(_directory, name + ".lock"); | 294 String _getLockFilePath(name) => p.join(_directory, name + ".lock"); |
183 } | 295 } |
OLD | NEW |