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 /// Attempts to resolve a set of version constraints for a package dependency |
6 * Attempts to resolve a set of version constraints for a package dependency | 6 /// graph and select an appropriate set of best specific versions for all |
7 * graph and select an appropriate set of best specific versions for all | 7 /// dependent packages. It works iteratively and tries to reach a stable |
8 * dependent packages. It works iteratively and tries to reach a stable | 8 /// solution where the constraints of all dependencies are met. If it fails to |
9 * solution where the constraints of all dependencies are met. If it fails to | 9 /// reach a solution after a certain number of iterations, it assumes the |
10 * reach a solution after a certain number of iterations, it assumes the | 10 /// dependency graph is unstable and reports and error. |
11 * dependency graph is unstable and reports and error. | 11 /// |
12 * | 12 /// There are two fundamental operations in the process of iterating over the |
13 * There are two fundamental operations in the process of iterating over the | 13 /// graph: |
14 * graph: | 14 /// |
15 * | 15 /// 1. Changing the selected concrete version of some package. (This includes |
16 * 1. Changing the selected concrete version of some package. (This includes | 16 /// adding and removing a package too, which is considering changing the |
17 * adding and removing a package too, which is considering changing the | 17 /// version to or from "none".) In other words, a node has changed. |
18 * version to or from "none".) In other words, a node has changed. | 18 /// 2. Changing the version constraint that one package places on another. In |
19 * 2. Changing the version constraint that one package places on another. In | 19 /// other words, and edge has changed. |
20 * other words, and edge has changed. | 20 /// |
21 * | 21 /// Both of these events have a corresponding (potentional) async operation and |
22 * Both of these events have a corresponding (potentional) async operation and | 22 /// roughly cycle back and forth between each other. When we change the version |
23 * roughly cycle back and forth between each other. When we change the version | 23 /// of package changes, we asynchronously load the pubspec for the new version. |
24 * of package changes, we asynchronously load the pubspec for the new version. | 24 /// When that's done, we compare the dependencies of the new version versus the |
25 * When that's done, we compare the dependencies of the new version versus the | 25 /// old one. For everything that differs, we change those constraints between |
26 * old one. For everything that differs, we change those constraints between | 26 /// this package and that dependency. |
27 * this package and that dependency. | 27 /// |
28 * | 28 /// When a constraint on a package changes, we re-calculate the overall |
29 * When a constraint on a package changes, we re-calculate the overall | 29 /// constraint on that package. I.e. with a shared dependency, we intersect all |
30 * constraint on that package. I.e. with a shared dependency, we intersect all | 30 /// of the constraints that its depending packages place on it. If that overall |
31 * of the constraints that its depending packages place on it. If that overall | 31 /// constraint changes (say from "<3.0.0" to "<2.5.0"), then the currently |
32 * constraint changes (say from "<3.0.0" to "<2.5.0"), then the currently | 32 /// picked version for that package may fall outside of the new constraint. If |
33 * picked version for that package may fall outside of the new constraint. If | 33 /// that happens, we find the new best version that meets the updated constraint |
34 * that happens, we find the new best version that meets the updated constraint | 34 /// and then the change the package to use that version. That cycles back up to |
35 * and then the change the package to use that version. That cycles back up to | 35 /// the beginning again. |
36 * the beginning again. | |
37 */ | |
38 library version_solver; | 36 library version_solver; |
39 | 37 |
40 import 'dart:json'; | 38 import 'dart:json'; |
41 import 'dart:math'; | 39 import 'dart:math'; |
42 import 'lock_file.dart'; | 40 import 'lock_file.dart'; |
43 import 'log.dart' as log; | 41 import 'log.dart' as log; |
44 import 'package.dart'; | 42 import 'package.dart'; |
45 import 'pubspec.dart'; | 43 import 'pubspec.dart'; |
46 import 'root_source.dart'; | 44 import 'root_source.dart'; |
47 import 'source.dart'; | 45 import 'source.dart'; |
48 import 'source_registry.dart'; | 46 import 'source_registry.dart'; |
49 import 'utils.dart'; | 47 import 'utils.dart'; |
50 import 'version.dart'; | 48 import 'version.dart'; |
51 | 49 |
52 /** | 50 /// Attempts to select the best concrete versions for all of the transitive |
53 * Attempts to select the best concrete versions for all of the transitive | 51 /// dependencies of [root] taking into account all of the [VersionConstraint]s |
54 * dependencies of [root] taking into account all of the [VersionConstraint]s | 52 /// that those dependencies place on each other and the requirements imposed by |
55 * that those dependencies place on each other and the requirements imposed by | 53 /// [lockFile]. If successful, completes to a [Map] that maps package names to |
56 * [lockFile]. If successful, completes to a [Map] that maps package names to | 54 /// the selected version for that package. If it fails, the future will complete |
57 * the selected version for that package. If it fails, the future will complete | 55 /// with a [NoVersionException], [DisjointConstraintException], or |
58 * with a [NoVersionException], [DisjointConstraintException], or | 56 /// [CouldNotSolveException]. |
59 * [CouldNotSolveException]. | |
60 */ | |
61 Future<List<PackageId>> resolveVersions(SourceRegistry sources, Package root, | 57 Future<List<PackageId>> resolveVersions(SourceRegistry sources, Package root, |
62 LockFile lockFile) { | 58 LockFile lockFile) { |
63 log.message('Resolving dependencies...'); | 59 log.message('Resolving dependencies...'); |
64 return new VersionSolver(sources, root, lockFile).solve(); | 60 return new VersionSolver(sources, root, lockFile).solve(); |
65 } | 61 } |
66 | 62 |
67 class VersionSolver { | 63 class VersionSolver { |
68 final SourceRegistry _sources; | 64 final SourceRegistry _sources; |
69 final Package _root; | 65 final Package _root; |
70 final LockFile lockFile; | 66 final LockFile lockFile; |
71 final PubspecCache _pubspecs; | 67 final PubspecCache _pubspecs; |
72 final Map<String, Dependency> _packages; | 68 final Map<String, Dependency> _packages; |
73 final Queue<WorkItem> _work; | 69 final Queue<WorkItem> _work; |
74 int _numIterations = 0; | 70 int _numIterations = 0; |
75 | 71 |
76 VersionSolver(SourceRegistry sources, this._root, this.lockFile) | 72 VersionSolver(SourceRegistry sources, this._root, this.lockFile) |
77 : _sources = sources, | 73 : _sources = sources, |
78 _pubspecs = new PubspecCache(sources), | 74 _pubspecs = new PubspecCache(sources), |
79 _packages = <String, Dependency>{}, | 75 _packages = <String, Dependency>{}, |
80 _work = new Queue<WorkItem>(); | 76 _work = new Queue<WorkItem>(); |
81 | 77 |
82 /** | 78 /// Tell the version solver to use the most recent version of [package] that |
83 * Tell the version solver to use the most recent version of [package] that | 79 /// exists in whatever source it's installed from. If that version violates |
84 * exists in whatever source it's installed from. If that version violates | 80 /// constraints imposed by other dependencies, an error will be raised when |
85 * constraints imposed by other dependencies, an error will be raised when | 81 /// solving the versions, even if an earlier compatible version exists. |
86 * solving the versions, even if an earlier compatible version exists. | |
87 */ | |
88 void useLatestVersion(String package) { | 82 void useLatestVersion(String package) { |
89 // TODO(nweiz): How do we want to detect and handle unknown dependencies | 83 // TODO(nweiz): How do we want to detect and handle unknown dependencies |
90 // here? | 84 // here? |
91 getDependency(package).useLatestVersion = true; | 85 getDependency(package).useLatestVersion = true; |
92 lockFile.packages.remove(package); | 86 lockFile.packages.remove(package); |
93 } | 87 } |
94 | 88 |
95 Future<List<PackageId>> solve() { | 89 Future<List<PackageId>> solve() { |
96 // Kick off the work by adding the root package at its concrete version to | 90 // Kick off the work by adding the root package at its concrete version to |
97 // the dependency graph. | 91 // the dependency graph. |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
134 _work.add(work); | 128 _work.add(work); |
135 } | 129 } |
136 | 130 |
137 Dependency getDependency(String package) { | 131 Dependency getDependency(String package) { |
138 // There can be unused dependencies in the graph, so just create an empty | 132 // There can be unused dependencies in the graph, so just create an empty |
139 // one if needed. | 133 // one if needed. |
140 _packages.putIfAbsent(package, () => new Dependency(package)); | 134 _packages.putIfAbsent(package, () => new Dependency(package)); |
141 return _packages[package]; | 135 return _packages[package]; |
142 } | 136 } |
143 | 137 |
144 /** | 138 /// Sets the best selected version of [package] to [version]. |
145 * Sets the best selected version of [package] to [version]. | |
146 */ | |
147 void setVersion(String package, Version version) { | 139 void setVersion(String package, Version version) { |
148 _packages[package].version = version; | 140 _packages[package].version = version; |
149 } | 141 } |
150 | 142 |
151 /** | 143 /// Returns the most recent version of [dependency] that satisfies all of its |
152 * Returns the most recent version of [dependency] that satisfies all of its | 144 /// version constraints. |
153 * version constraints. | |
154 */ | |
155 Future<Version> getBestVersion(Dependency dependency) { | 145 Future<Version> getBestVersion(Dependency dependency) { |
156 return dependency.getVersions().transform((versions) { | 146 return dependency.getVersions().transform((versions) { |
157 var best = null; | 147 var best = null; |
158 for (var version in versions) { | 148 for (var version in versions) { |
159 if (dependency.useLatestVersion || | 149 if (dependency.useLatestVersion || |
160 dependency.constraint.allows(version)) { | 150 dependency.constraint.allows(version)) { |
161 if (best == null || version > best) best = version; | 151 if (best == null || version > best) best = version; |
162 } | 152 } |
163 } | 153 } |
164 | 154 |
165 // TODO(rnystrom): Better exception. | 155 // TODO(rnystrom): Better exception. |
166 if (best == null) { | 156 if (best == null) { |
167 if (tryUnlockDepender(dependency)) return null; | 157 if (tryUnlockDepender(dependency)) return null; |
168 throw new NoVersionException(dependency.name, dependency.constraint, | 158 throw new NoVersionException(dependency.name, dependency.constraint, |
169 dependency._refs); | 159 dependency._refs); |
170 } else if (!dependency.constraint.allows(best)) { | 160 } else if (!dependency.constraint.allows(best)) { |
171 if (tryUnlockDepender(dependency)) return null; | 161 if (tryUnlockDepender(dependency)) return null; |
172 throw new CouldNotUpdateException( | 162 throw new CouldNotUpdateException( |
173 dependency.name, dependency.constraint, best); | 163 dependency.name, dependency.constraint, best); |
174 } | 164 } |
175 | 165 |
176 return best; | 166 return best; |
177 }); | 167 }); |
178 } | 168 } |
179 | 169 |
180 /** | 170 /// Looks for a package that depends (transitively) on [dependency] and has |
181 * Looks for a package that depends (transitively) on [dependency] and has its | 171 /// its version locked in the lockfile. If one is found, enqueues an |
182 * version locked in the lockfile. If one is found, enqueues an | 172 /// [UnlockPackage] work item for it and returns true. Otherwise, returns |
183 * [UnlockPackage] work item for it and returns true. Otherwise, returns | 173 /// false. |
184 * false. | 174 /// |
185 * | 175 /// This does a breadth-first search; immediate dependers will be unlocked |
186 * This does a breadth-first search; immediate dependers will be unlocked | 176 /// first, followed by transitive dependers. |
187 * first, followed by transitive dependers. | |
188 */ | |
189 bool tryUnlockDepender(Dependency dependency, [Set<String> seen]) { | 177 bool tryUnlockDepender(Dependency dependency, [Set<String> seen]) { |
190 if (seen == null) seen = new Set(); | 178 if (seen == null) seen = new Set(); |
191 // Avoid an infinite loop if there are circular dependencies. | 179 // Avoid an infinite loop if there are circular dependencies. |
192 if (seen.contains(dependency.name)) return false; | 180 if (seen.contains(dependency.name)) return false; |
193 seen.add(dependency.name); | 181 seen.add(dependency.name); |
194 | 182 |
195 for (var dependerName in dependency.dependers) { | 183 for (var dependerName in dependency.dependers) { |
196 var depender = getDependency(dependerName); | 184 var depender = getDependency(dependerName); |
197 var locked = lockFile.packages[dependerName]; | 185 var locked = lockFile.packages[dependerName]; |
198 if (locked != null && depender.version == locked.version) { | 186 if (locked != null && depender.version == locked.version) { |
(...skipping 18 matching lines...) Expand all Loading... |
217 dep.source.descriptionsEqual( | 205 dep.source.descriptionsEqual( |
218 description, lockedPackage.description)) { | 206 description, lockedPackage.description)) { |
219 description = lockedPackage.description; | 207 description = lockedPackage.description; |
220 } | 208 } |
221 | 209 |
222 return new PackageId(dep.name, dep.source, dep.version, description); | 210 return new PackageId(dep.name, dep.source, dep.version, description); |
223 }); | 211 }); |
224 } | 212 } |
225 } | 213 } |
226 | 214 |
227 /** | 215 /// The constraint solver works by iteratively processing a queue of work items. |
228 * The constraint solver works by iteratively processing a queue of work items. | 216 /// Each item is a single atomic change to the dependency graph. Handling them |
229 * Each item is a single atomic change to the dependency graph. Handling them | 217 /// in a queue lets us handle asynchrony (resolving versions requires |
230 * in a queue lets us handle asynchrony (resolving versions requires information | 218 /// information from servers) as well as avoid deeply nested recursion. |
231 * from servers) as well as avoid deeply nested recursion. | |
232 */ | |
233 abstract class WorkItem { | 219 abstract class WorkItem { |
234 /** | 220 /// Processes this work item. Returns a future that completes when the work is |
235 * Processes this work item. Returns a future that completes when the work is | 221 /// done. If `null` is returned, that means the work has completed |
236 * done. If `null` is returned, that means the work has completed | 222 /// synchronously and the next item can be started immediately. |
237 * synchronously and the next item can be started immediately. | |
238 */ | |
239 Future process(VersionSolver solver); | 223 Future process(VersionSolver solver); |
240 } | 224 } |
241 | 225 |
242 /** | 226 /// The best selected version for a package has changed to [version]. If the |
243 * The best selected version for a package has changed to [version]. If the | 227 /// previous version of the package is `null`, that means the package is being |
244 * previous version of the package is `null`, that means the package is being | 228 /// added to the graph. If [version] is `null`, it is being removed. |
245 * added to the graph. If [version] is `null`, it is being removed. | |
246 */ | |
247 class ChangeVersion implements WorkItem { | 229 class ChangeVersion implements WorkItem { |
248 /// The name of the package whose version is being changed. | 230 /// The name of the package whose version is being changed. |
249 final String package; | 231 final String package; |
250 | 232 |
251 /** | 233 /// The source of the package whose version is changing. |
252 * The source of the package whose version is changing. | |
253 */ | |
254 final Source source; | 234 final Source source; |
255 | 235 |
256 /** | 236 /// The description identifying the package whose version is changing. |
257 * The description identifying the package whose version is changing. | |
258 */ | |
259 final description; | 237 final description; |
260 | 238 |
261 /** | 239 /// The new selected version. |
262 * The new selected version. | |
263 */ | |
264 final Version version; | 240 final Version version; |
265 | 241 |
266 ChangeVersion(this.package, this.source, this.description, this.version) { | 242 ChangeVersion(this.package, this.source, this.description, this.version) { |
267 if (source == null) throw "null source"; | 243 if (source == null) throw "null source"; |
268 } | 244 } |
269 | 245 |
270 Future process(VersionSolver solver) { | 246 Future process(VersionSolver solver) { |
271 log.fine("Changing $package to version $version."); | 247 log.fine("Changing $package to version $version."); |
272 | 248 |
273 var dependency = solver.getDependency(package); | 249 var dependency = solver.getDependency(package); |
(...skipping 22 matching lines...) Expand all Loading... |
296 } | 272 } |
297 | 273 |
298 // Everything that's left is a depdendency that's only in the new | 274 // Everything that's left is a depdendency that's only in the new |
299 // version of the package. | 275 // version of the package. |
300 for (var newRef in newDependencyRefs.values) { | 276 for (var newRef in newDependencyRefs.values) { |
301 solver.enqueue(new AddConstraint(package, newRef)); | 277 solver.enqueue(new AddConstraint(package, newRef)); |
302 } | 278 } |
303 }); | 279 }); |
304 } | 280 } |
305 | 281 |
306 /** | 282 /// Get the dependencies at [version] of the package being changed. |
307 * Get the dependencies at [version] of the package being changed. | |
308 */ | |
309 Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver, | 283 Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver, |
310 Version version) { | 284 Version version) { |
311 // If there is no version, it means no package, so no dependencies. | 285 // If there is no version, it means no package, so no dependencies. |
312 if (version == null) { | 286 if (version == null) { |
313 return | 287 return |
314 new Future<Map<String, PackageRef>>.immediate(<String, PackageRef>{}); | 288 new Future<Map<String, PackageRef>>.immediate(<String, PackageRef>{}); |
315 } | 289 } |
316 | 290 |
317 var id = new PackageId(package, source, version, description); | 291 var id = new PackageId(package, source, version, description); |
318 return solver._pubspecs.load(id).transform((pubspec) { | 292 return solver._pubspecs.load(id).transform((pubspec) { |
319 var dependencies = <String, PackageRef>{}; | 293 var dependencies = <String, PackageRef>{}; |
320 for (var dependency in pubspec.dependencies) { | 294 for (var dependency in pubspec.dependencies) { |
321 dependencies[dependency.name] = dependency; | 295 dependencies[dependency.name] = dependency; |
322 } | 296 } |
323 return dependencies; | 297 return dependencies; |
324 }); | 298 }); |
325 } | 299 } |
326 } | 300 } |
327 | 301 |
328 /** | 302 /// A constraint that a depending package places on a dependent package has |
329 * A constraint that a depending package places on a dependent package has | 303 /// changed. |
330 * changed. | 304 /// |
331 * | 305 /// This is an abstract class that contains logic for updating the dependency |
332 * This is an abstract class that contains logic for updating the dependency | 306 /// graph once a dependency has changed. Changing the dependency is the |
333 * graph once a dependency has changed. Changing the dependency is the | 307 /// responsibility of subclasses. |
334 * responsibility of subclasses. | |
335 */ | |
336 abstract class ChangeConstraint implements WorkItem { | 308 abstract class ChangeConstraint implements WorkItem { |
337 Future process(VersionSolver solver); | 309 Future process(VersionSolver solver); |
338 | 310 |
339 void undo(VersionSolver solver); | 311 void undo(VersionSolver solver); |
340 | 312 |
341 Future _processChange(VersionSolver solver, Dependency oldDependency, | 313 Future _processChange(VersionSolver solver, Dependency oldDependency, |
342 Dependency newDependency) { | 314 Dependency newDependency) { |
343 var name = newDependency.name; | 315 var name = newDependency.name; |
344 var source = oldDependency.source != null ? | 316 var source = oldDependency.source != null ? |
345 oldDependency.source : newDependency.source; | 317 oldDependency.source : newDependency.source; |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
395 return solver.getBestVersion(newDependency).transform((best) { | 367 return solver.getBestVersion(newDependency).transform((best) { |
396 if (best == null) { | 368 if (best == null) { |
397 undo(solver); | 369 undo(solver); |
398 } else if (newDependency.version != best) { | 370 } else if (newDependency.version != best) { |
399 solver.enqueue(new ChangeVersion(name, source, description, best)); | 371 solver.enqueue(new ChangeVersion(name, source, description, best)); |
400 } | 372 } |
401 }); | 373 }); |
402 } | 374 } |
403 } | 375 } |
404 | 376 |
405 /** | 377 /// The constraint given by [ref] is being placed by [depender]. |
406 * The constraint given by [ref] is being placed by [depender]. | |
407 */ | |
408 class AddConstraint extends ChangeConstraint { | 378 class AddConstraint extends ChangeConstraint { |
409 /** | 379 /// The package that has the dependency. |
410 * The package that has the dependency. | |
411 */ | |
412 final String depender; | 380 final String depender; |
413 | 381 |
414 /** | 382 /// The package being depended on and the constraints being placed on it. The |
415 * The package being depended on and the constraints being placed on it. The | 383 /// source, version, and description in this ref are all considered |
416 * source, version, and description in this ref are all considered constraints | 384 /// constraints on the dependent package. |
417 * on the dependent package. | |
418 */ | |
419 final PackageRef ref; | 385 final PackageRef ref; |
420 | 386 |
421 AddConstraint(this.depender, this.ref); | 387 AddConstraint(this.depender, this.ref); |
422 | 388 |
423 Future process(VersionSolver solver) { | 389 Future process(VersionSolver solver) { |
424 log.fine("Adding $depender's constraint $ref."); | 390 log.fine("Adding $depender's constraint $ref."); |
425 | 391 |
426 var dependency = solver.getDependency(ref.name); | 392 var dependency = solver.getDependency(ref.name); |
427 var oldDependency = dependency.clone(); | 393 var oldDependency = dependency.clone(); |
428 dependency.placeConstraint(depender, ref); | 394 dependency.placeConstraint(depender, ref); |
429 return _processChange(solver, oldDependency, dependency); | 395 return _processChange(solver, oldDependency, dependency); |
430 } | 396 } |
431 | 397 |
432 void undo(VersionSolver solver) { | 398 void undo(VersionSolver solver) { |
433 solver.getDependency(ref.name).removeConstraint(depender); | 399 solver.getDependency(ref.name).removeConstraint(depender); |
434 } | 400 } |
435 } | 401 } |
436 | 402 |
437 /** | 403 /// [depender] is no longer placing a constraint on [dependent]. |
438 * [depender] is no longer placing a constraint on [dependent]. | |
439 */ | |
440 class RemoveConstraint extends ChangeConstraint { | 404 class RemoveConstraint extends ChangeConstraint { |
441 /** | 405 /// The package that was placing a constraint on [dependent]. |
442 * The package that was placing a constraint on [dependent]. | |
443 */ | |
444 String depender; | 406 String depender; |
445 | 407 |
446 /** | 408 /// The package that was being depended on. |
447 * The package that was being depended on. | |
448 */ | |
449 String dependent; | 409 String dependent; |
450 | 410 |
451 /** The constraint that was removed. */ | 411 /// The constraint that was removed. |
452 PackageRef _removed; | 412 PackageRef _removed; |
453 | 413 |
454 RemoveConstraint(this.depender, this.dependent); | 414 RemoveConstraint(this.depender, this.dependent); |
455 | 415 |
456 Future process(VersionSolver solver) { | 416 Future process(VersionSolver solver) { |
457 log.fine("Removing $depender's constraint ($_removed) on $dependent."); | 417 log.fine("Removing $depender's constraint ($_removed) on $dependent."); |
458 | 418 |
459 var dependency = solver.getDependency(dependent); | 419 var dependency = solver.getDependency(dependent); |
460 var oldDependency = dependency.clone(); | 420 var oldDependency = dependency.clone(); |
461 _removed = dependency.removeConstraint(depender); | 421 _removed = dependency.removeConstraint(depender); |
462 return _processChange(solver, oldDependency, dependency); | 422 return _processChange(solver, oldDependency, dependency); |
463 } | 423 } |
464 | 424 |
465 void undo(VersionSolver solver) { | 425 void undo(VersionSolver solver) { |
466 solver.getDependency(dependent).placeConstraint(depender, _removed); | 426 solver.getDependency(dependent).placeConstraint(depender, _removed); |
467 } | 427 } |
468 } | 428 } |
469 | 429 |
470 /** [package]'s version is no longer constrained by the lockfile. */ | 430 /// [package]'s version is no longer constrained by the lockfile. |
471 class UnlockPackage implements WorkItem { | 431 class UnlockPackage implements WorkItem { |
472 /** The package being unlocked. */ | 432 /// The package being unlocked. |
473 Dependency package; | 433 Dependency package; |
474 | 434 |
475 UnlockPackage(this.package); | 435 UnlockPackage(this.package); |
476 | 436 |
477 Future process(VersionSolver solver) { | 437 Future process(VersionSolver solver) { |
478 log.fine("Unlocking ${package.name}."); | 438 log.fine("Unlocking ${package.name}."); |
479 | 439 |
480 solver.lockFile.packages.remove(package.name); | 440 solver.lockFile.packages.remove(package.name); |
481 return solver.getBestVersion(package).transform((best) { | 441 return solver.getBestVersion(package).transform((best) { |
482 if (best == null) return null; | 442 if (best == null) return null; |
483 solver.enqueue(new ChangeVersion( | 443 solver.enqueue(new ChangeVersion( |
484 package.name, package.source, package.description, best)); | 444 package.name, package.source, package.description, best)); |
485 }); | 445 }); |
486 } | 446 } |
487 } | 447 } |
488 | 448 |
489 // TODO(rnystrom): Instead of always pulling from the source (which will mean | 449 // TODO(rnystrom): Instead of always pulling from the source (which will mean |
490 // hitting a server), we should consider caching pubspecs of uninstalled | 450 // hitting a server), we should consider caching pubspecs of uninstalled |
491 // packages in the system cache. | 451 // packages in the system cache. |
492 /** | 452 /// Maintains a cache of previously-loaded pubspecs. Used to avoid requesting |
493 * Maintains a cache of previously-loaded pubspecs. Used to avoid requesting | 453 /// the same pubspec from the server repeatedly. |
494 * the same pubspec from the server repeatedly. | |
495 */ | |
496 class PubspecCache { | 454 class PubspecCache { |
497 final SourceRegistry _sources; | 455 final SourceRegistry _sources; |
498 final Map<PackageId, Pubspec> _pubspecs; | 456 final Map<PackageId, Pubspec> _pubspecs; |
499 | 457 |
500 PubspecCache(this._sources) | 458 PubspecCache(this._sources) |
501 : _pubspecs = new Map<PackageId, Pubspec>(); | 459 : _pubspecs = new Map<PackageId, Pubspec>(); |
502 | 460 |
503 /** | 461 /// Caches [pubspec] as the [Pubspec] for the package identified by [id]. |
504 * Caches [pubspec] as the [Pubspec] for the package identified by [id]. | |
505 */ | |
506 void cache(PackageId id, Pubspec pubspec) { | 462 void cache(PackageId id, Pubspec pubspec) { |
507 _pubspecs[id] = pubspec; | 463 _pubspecs[id] = pubspec; |
508 } | 464 } |
509 | 465 |
510 /** | 466 /// Loads the pubspec for the package identified by [id]. |
511 * Loads the pubspec for the package identified by [id]. | |
512 */ | |
513 Future<Pubspec> load(PackageId id) { | 467 Future<Pubspec> load(PackageId id) { |
514 // Complete immediately if it's already cached. | 468 // Complete immediately if it's already cached. |
515 if (_pubspecs.containsKey(id)) { | 469 if (_pubspecs.containsKey(id)) { |
516 return new Future<Pubspec>.immediate(_pubspecs[id]); | 470 return new Future<Pubspec>.immediate(_pubspecs[id]); |
517 } | 471 } |
518 | 472 |
519 return id.describe().transform((pubspec) { | 473 return id.describe().transform((pubspec) { |
520 // Cache it. | 474 // Cache it. |
521 _pubspecs[id] = pubspec; | 475 _pubspecs[id] = pubspec; |
522 return pubspec; | 476 return pubspec; |
523 }); | 477 }); |
524 } | 478 } |
525 } | 479 } |
526 | 480 |
527 /** | 481 /// Describes one [Package] in the [DependencyGraph] and keeps track of which |
528 * Describes one [Package] in the [DependencyGraph] and keeps track of which | 482 /// packages depend on it and what constraints they place on it. |
529 * packages depend on it and what constraints they place on it. | |
530 */ | |
531 class Dependency { | 483 class Dependency { |
532 /** | 484 /// The name of the this dependency's package. |
533 * The name of the this dependency's package. | |
534 */ | |
535 final String name; | 485 final String name; |
536 | 486 |
537 /** | 487 /// The [PackageRefs] that represent constraints that depending packages have |
538 * The [PackageRefs] that represent constraints that depending packages have | 488 /// placed on this one. |
539 * placed on this one. | |
540 */ | |
541 final Map<String, PackageRef> _refs; | 489 final Map<String, PackageRef> _refs; |
542 | 490 |
543 /** | 491 /// The currently-selected best version for this dependency. |
544 * The currently-selected best version for this dependency. | |
545 */ | |
546 Version version; | 492 Version version; |
547 | 493 |
548 /** | 494 /// Whether this dependency should always select the latest version. |
549 * Whether this dependency should always select the latest version. | |
550 */ | |
551 bool useLatestVersion = false; | 495 bool useLatestVersion = false; |
552 | 496 |
553 /** | 497 /// Gets whether or not any other packages are currently depending on this |
554 * Gets whether or not any other packages are currently depending on this | 498 /// one. If `false`, then it means this package is not part of the dependency |
555 * one. If `false`, then it means this package is not part of the dependency | 499 /// graph and should be omitted. |
556 * graph and should be omitted. | |
557 */ | |
558 bool get isDependedOn => !_refs.isEmpty; | 500 bool get isDependedOn => !_refs.isEmpty; |
559 | 501 |
560 /** The names of all the packages that depend on this dependency. */ | 502 /// The names of all the packages that depend on this dependency. |
561 Collection<String> get dependers => _refs.keys; | 503 Collection<String> get dependers => _refs.keys; |
562 | 504 |
563 /** | 505 /// Gets the overall constraint that all packages are placing on this one. |
564 * Gets the overall constraint that all packages are placing on this one. | 506 /// If no packages have a constraint on this one (which can happen when this |
565 * If no packages have a constraint on this one (which can happen when this | 507 /// package is in the process of being added to the graph), returns `null`. |
566 * package is in the process of being added to the graph), returns `null`. | |
567 */ | |
568 VersionConstraint get constraint { | 508 VersionConstraint get constraint { |
569 if (_refs.isEmpty) return null; | 509 if (_refs.isEmpty) return null; |
570 return new VersionConstraint.intersection( | 510 return new VersionConstraint.intersection( |
571 _refs.values.map((ref) => ref.constraint)); | 511 _refs.values.map((ref) => ref.constraint)); |
572 } | 512 } |
573 | 513 |
574 /// The source of this dependency's package. | 514 /// The source of this dependency's package. |
575 Source get source { | 515 Source get source { |
576 var canonical = _canonicalRef(); | 516 var canonical = _canonicalRef(); |
577 if (canonical == null) return null; | 517 if (canonical == null) return null; |
(...skipping 21 matching lines...) Expand all Loading... |
599 } | 539 } |
600 | 540 |
601 Dependency(this.name) | 541 Dependency(this.name) |
602 : _refs = <String, PackageRef>{}; | 542 : _refs = <String, PackageRef>{}; |
603 | 543 |
604 Dependency._clone(Dependency other) | 544 Dependency._clone(Dependency other) |
605 : name = other.name, | 545 : name = other.name, |
606 version = other.version, | 546 version = other.version, |
607 _refs = new Map<String, PackageRef>.from(other._refs); | 547 _refs = new Map<String, PackageRef>.from(other._refs); |
608 | 548 |
609 /** Creates a copy of this dependency. */ | 549 /// Creates a copy of this dependency. |
610 Dependency clone() => new Dependency._clone(this); | 550 Dependency clone() => new Dependency._clone(this); |
611 | 551 |
612 /// Return a list of available versions for this dependency. | 552 /// Return a list of available versions for this dependency. |
613 Future<List<Version>> getVersions() => source.getVersions(name, description); | 553 Future<List<Version>> getVersions() => source.getVersions(name, description); |
614 | 554 |
615 /** | 555 /// Places [ref] as a constraint from [package] onto this. |
616 * Places [ref] as a constraint from [package] onto this. | |
617 */ | |
618 void placeConstraint(String package, PackageRef ref) { | 556 void placeConstraint(String package, PackageRef ref) { |
619 var requiredDepender = _requiredDepender(); | 557 var requiredDepender = _requiredDepender(); |
620 if (requiredDepender != null) { | 558 if (requiredDepender != null) { |
621 var required = _refs[requiredDepender]; | 559 var required = _refs[requiredDepender]; |
622 if (required.source.name != ref.source.name) { | 560 if (required.source.name != ref.source.name) { |
623 throw new SourceMismatchException(name, | 561 throw new SourceMismatchException(name, |
624 requiredDepender, required.source, package, ref.source); | 562 requiredDepender, required.source, package, ref.source); |
625 } else if (!required.source.descriptionsEqual( | 563 } else if (!required.source.descriptionsEqual( |
626 required.description, ref.description)) { | 564 required.description, ref.description)) { |
627 throw new DescriptionMismatchException(name, | 565 throw new DescriptionMismatchException(name, |
(...skipping 13 matching lines...) Expand all Loading... |
641 var dependers = _refs.keys; | 579 var dependers = _refs.keys; |
642 if (dependers.length == 1) { | 580 if (dependers.length == 1) { |
643 var depender = dependers[0]; | 581 var depender = dependers[0]; |
644 if (_refs[depender].source is RootSource) return null; | 582 if (_refs[depender].source is RootSource) return null; |
645 return depender; | 583 return depender; |
646 } | 584 } |
647 | 585 |
648 return dependers[1]; | 586 return dependers[1]; |
649 } | 587 } |
650 | 588 |
651 /** | 589 /// Removes the constraint from [package] onto this. |
652 * Removes the constraint from [package] onto this. | |
653 */ | |
654 PackageRef removeConstraint(String package) => _refs.remove(package); | 590 PackageRef removeConstraint(String package) => _refs.remove(package); |
655 } | 591 } |
656 | 592 |
657 /** | 593 /// Exception thrown when the [VersionConstraint] used to match a package is |
658 * Exception thrown when the [VersionConstraint] used to match a package is | 594 /// valid (i.e. non-empty), but there are no released versions of the package |
659 * valid (i.e. non-empty), but there are no released versions of the package | 595 /// that fit that constraint. |
660 * that fit that constraint. | |
661 */ | |
662 class NoVersionException implements Exception { | 596 class NoVersionException implements Exception { |
663 final String package; | 597 final String package; |
664 final VersionConstraint constraint; | 598 final VersionConstraint constraint; |
665 final Map<String, PackageRef> _dependencies; | 599 final Map<String, PackageRef> _dependencies; |
666 | 600 |
667 NoVersionException(this.package, this.constraint, this._dependencies); | 601 NoVersionException(this.package, this.constraint, this._dependencies); |
668 | 602 |
669 String toString() { | 603 String toString() { |
670 var buffer = new StringBuffer(); | 604 var buffer = new StringBuffer(); |
671 buffer.add("Package '$package' has no versions that match $constraint " | 605 buffer.add("Package '$package' has no versions that match $constraint " |
672 "derived from:\n"); | 606 "derived from:\n"); |
673 | 607 |
674 var keys = new List.from(_dependencies.keys); | 608 var keys = new List.from(_dependencies.keys); |
675 keys.sort(); | 609 keys.sort(); |
676 | 610 |
677 for (var key in keys) { | 611 for (var key in keys) { |
678 buffer.add("- '$key' depends on version " | 612 buffer.add("- '$key' depends on version " |
679 "${_dependencies[key].constraint}\n"); | 613 "${_dependencies[key].constraint}\n"); |
680 } | 614 } |
681 | 615 |
682 return buffer.toString(); | 616 return buffer.toString(); |
683 } | 617 } |
684 } | 618 } |
685 | 619 |
686 // TODO(rnystrom): Report the list of depending packages and their constraints. | 620 // TODO(rnystrom): Report the list of depending packages and their constraints. |
687 /** | 621 /// Exception thrown when the most recent version of [package] must be selected, |
688 * Exception thrown when the most recent version of [package] must be selected, | 622 /// but doesn't match the [VersionConstraint] imposed on the package. |
689 * but doesn't match the [VersionConstraint] imposed on the package. | |
690 */ | |
691 class CouldNotUpdateException implements Exception { | 623 class CouldNotUpdateException implements Exception { |
692 final String package; | 624 final String package; |
693 final VersionConstraint constraint; | 625 final VersionConstraint constraint; |
694 final Version best; | 626 final Version best; |
695 | 627 |
696 CouldNotUpdateException(this.package, this.constraint, this.best); | 628 CouldNotUpdateException(this.package, this.constraint, this.best); |
697 | 629 |
698 String toString() => | 630 String toString() => |
699 "The latest version of '$package', $best, does not match $constraint."; | 631 "The latest version of '$package', $best, does not match $constraint."; |
700 } | 632 } |
701 | 633 |
702 /** | 634 /// Exception thrown when the [VersionConstraint] used to match a package is |
703 * Exception thrown when the [VersionConstraint] used to match a package is | 635 /// the empty set: in other words, multiple packages depend on it and have |
704 * the empty set: in other words, multiple packages depend on it and have | 636 /// conflicting constraints that have no overlap. |
705 * conflicting constraints that have no overlap. | |
706 */ | |
707 class DisjointConstraintException implements Exception { | 637 class DisjointConstraintException implements Exception { |
708 final String package; | 638 final String package; |
709 final Map<String, PackageRef> _dependencies; | 639 final Map<String, PackageRef> _dependencies; |
710 | 640 |
711 DisjointConstraintException(this.package, this._dependencies); | 641 DisjointConstraintException(this.package, this._dependencies); |
712 | 642 |
713 String toString() { | 643 String toString() { |
714 var buffer = new StringBuffer(); | 644 var buffer = new StringBuffer(); |
715 buffer.add("Incompatible version constraints on '$package':\n"); | 645 buffer.add("Incompatible version constraints on '$package':\n"); |
716 | 646 |
717 var keys = new List.from(_dependencies.keys); | 647 var keys = new List.from(_dependencies.keys); |
718 keys.sort(); | 648 keys.sort(); |
719 | 649 |
720 for (var key in keys) { | 650 for (var key in keys) { |
721 buffer.add("- '$key' depends on version " | 651 buffer.add("- '$key' depends on version " |
722 "${_dependencies[key].constraint}\n"); | 652 "${_dependencies[key].constraint}\n"); |
723 } | 653 } |
724 | 654 |
725 return buffer.toString(); | 655 return buffer.toString(); |
726 } | 656 } |
727 } | 657 } |
728 | 658 |
729 /** | 659 /// Exception thrown when the [VersionSolver] fails to find a solution after a |
730 * Exception thrown when the [VersionSolver] fails to find a solution after a | 660 /// certain number of iterations. |
731 * certain number of iterations. | |
732 */ | |
733 class CouldNotSolveException implements Exception { | 661 class CouldNotSolveException implements Exception { |
734 CouldNotSolveException(); | 662 CouldNotSolveException(); |
735 | 663 |
736 String toString() => | 664 String toString() => |
737 "Could not find a solution that met all version constraints."; | 665 "Could not find a solution that met all version constraints."; |
738 } | 666 } |
739 | 667 |
740 /** | 668 /// Exception thrown when two packages with the same name but different sources |
741 * Exception thrown when two packages with the same name but different sources | 669 /// are depended upon. |
742 * are depended upon. | |
743 */ | |
744 class SourceMismatchException implements Exception { | 670 class SourceMismatchException implements Exception { |
745 final String package; | 671 final String package; |
746 final String depender1; | 672 final String depender1; |
747 final Source source1; | 673 final Source source1; |
748 final String depender2; | 674 final String depender2; |
749 final Source source2; | 675 final Source source2; |
750 | 676 |
751 SourceMismatchException(this.package, this.depender1, this.source1, | 677 SourceMismatchException(this.package, this.depender1, this.source1, |
752 this.depender2, this.source2); | 678 this.depender2, this.source2); |
753 | 679 |
754 String toString() { | 680 String toString() { |
755 return "Incompatible dependencies on '$package':\n" | 681 return "Incompatible dependencies on '$package':\n" |
756 "- '$depender1' depends on it from source '$source1'\n" | 682 "- '$depender1' depends on it from source '$source1'\n" |
757 "- '$depender2' depends on it from source '$source2'"; | 683 "- '$depender2' depends on it from source '$source2'"; |
758 } | 684 } |
759 } | 685 } |
760 | 686 |
761 /** | 687 /// Exception thrown when two packages with the same name and source but |
762 * Exception thrown when two packages with the same name and source but | 688 /// different descriptions are depended upon. |
763 * different descriptions are depended upon. | |
764 */ | |
765 class DescriptionMismatchException implements Exception { | 689 class DescriptionMismatchException implements Exception { |
766 final String package; | 690 final String package; |
767 final String depender1; | 691 final String depender1; |
768 final description1; | 692 final description1; |
769 final String depender2; | 693 final String depender2; |
770 final description2; | 694 final description2; |
771 | 695 |
772 DescriptionMismatchException(this.package, this.depender1, this.description1, | 696 DescriptionMismatchException(this.package, this.depender1, this.description1, |
773 this.depender2, this.description2); | 697 this.depender2, this.description2); |
774 | 698 |
775 String toString() { | 699 String toString() { |
776 // TODO(nweiz): Dump descriptions to YAML when that's supported. | 700 // TODO(nweiz): Dump descriptions to YAML when that's supported. |
777 return "Incompatible dependencies on '$package':\n" | 701 return "Incompatible dependencies on '$package':\n" |
778 "- '$depender1' depends on it with description " | 702 "- '$depender1' depends on it with description " |
779 "${JSON.stringify(description1)}\n" | 703 "${JSON.stringify(description1)}\n" |
780 "- '$depender2' depends on it with description " | 704 "- '$depender2' depends on it with description " |
781 "${JSON.stringify(description2)}"; | 705 "${JSON.stringify(description2)}"; |
782 } | 706 } |
783 } | 707 } |
OLD | NEW |