| 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 |