| 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 /// Attempts to resolve a set of version constraints for a package dependency | 5 /// 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 | 6 /// graph and select an appropriate set of best specific versions for all |
| 7 /// dependent packages. It works iteratively and tries to reach a stable | 7 /// 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 | 8 /// 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 | 9 /// reach a solution after a certain number of iterations, it assumes the |
| 10 /// dependency graph is unstable and reports and error. | 10 /// dependency graph is unstable and reports and error. |
| (...skipping 17 matching lines...) Expand all Loading... |
| 28 /// When a constraint on a package changes, we re-calculate the overall | 28 /// 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 | 29 /// 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 | 30 /// 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 | 31 /// 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 | 32 /// 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 | 33 /// 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 | 34 /// and then the change the package to use that version. That cycles back up to |
| 35 /// the beginning again. | 35 /// the beginning again. |
| 36 library version_solver; | 36 library version_solver; |
| 37 | 37 |
| 38 import 'dart:json'; | 38 import 'dart:async'; |
| 39 import 'dart:json' as json; |
| 39 import 'dart:math'; | 40 import 'dart:math'; |
| 40 import 'lock_file.dart'; | 41 import 'lock_file.dart'; |
| 41 import 'log.dart' as log; | 42 import 'log.dart' as log; |
| 42 import 'package.dart'; | 43 import 'package.dart'; |
| 43 import 'pubspec.dart'; | 44 import 'pubspec.dart'; |
| 44 import 'root_source.dart'; | 45 import 'root_source.dart'; |
| 45 import 'source.dart'; | 46 import 'source.dart'; |
| 46 import 'source_registry.dart'; | 47 import 'source_registry.dart'; |
| 47 import 'utils.dart'; | 48 import 'utils.dart'; |
| 48 import 'version.dart'; | 49 import 'version.dart'; |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 109 if (_numIterations > max(50, _packages.length * 5)) { | 110 if (_numIterations > max(50, _packages.length * 5)) { |
| 110 throw new CouldNotSolveException(); | 111 throw new CouldNotSolveException(); |
| 111 } | 112 } |
| 112 | 113 |
| 113 // Run the first work item. | 114 // Run the first work item. |
| 114 var future = _work.removeFirst().process(this); | 115 var future = _work.removeFirst().process(this); |
| 115 | 116 |
| 116 // If we have an async operation to perform, chain the loop to resume | 117 // If we have an async operation to perform, chain the loop to resume |
| 117 // when it's done. Otherwise, just loop synchronously. | 118 // when it's done. Otherwise, just loop synchronously. |
| 118 if (future != null) { | 119 if (future != null) { |
| 119 return future.chain(processNextWorkItem); | 120 return future.then(processNextWorkItem); |
| 120 } | 121 } |
| 121 } | 122 } |
| 122 } | 123 } |
| 123 | 124 |
| 124 return processNextWorkItem(null); | 125 return processNextWorkItem(null); |
| 125 } | 126 } |
| 126 | 127 |
| 127 void enqueue(WorkItem work) { | 128 void enqueue(WorkItem work) { |
| 128 _work.add(work); | 129 _work.add(work); |
| 129 } | 130 } |
| 130 | 131 |
| 131 Dependency getDependency(String package) { | 132 Dependency getDependency(String package) { |
| 132 // There can be unused dependencies in the graph, so just create an empty | 133 // There can be unused dependencies in the graph, so just create an empty |
| 133 // one if needed. | 134 // one if needed. |
| 134 _packages.putIfAbsent(package, () => new Dependency(package)); | 135 _packages.putIfAbsent(package, () => new Dependency(package)); |
| 135 return _packages[package]; | 136 return _packages[package]; |
| 136 } | 137 } |
| 137 | 138 |
| 138 /// Sets the best selected version of [package] to [version]. | 139 /// Sets the best selected version of [package] to [version]. |
| 139 void setVersion(String package, Version version) { | 140 void setVersion(String package, Version version) { |
| 140 _packages[package].version = version; | 141 _packages[package].version = version; |
| 141 } | 142 } |
| 142 | 143 |
| 143 /// Returns the most recent version of [dependency] that satisfies all of its | 144 /// Returns the most recent version of [dependency] that satisfies all of its |
| 144 /// version constraints. | 145 /// version constraints. |
| 145 Future<Version> getBestVersion(Dependency dependency) { | 146 Future<Version> getBestVersion(Dependency dependency) { |
| 146 return dependency.getVersions().transform((versions) { | 147 return dependency.getVersions().then((versions) { |
| 147 var best = null; | 148 var best = null; |
| 148 for (var version in versions) { | 149 for (var version in versions) { |
| 149 if (dependency.useLatestVersion || | 150 if (dependency.useLatestVersion || |
| 150 dependency.constraint.allows(version)) { | 151 dependency.constraint.allows(version)) { |
| 151 if (best == null || version > best) best = version; | 152 if (best == null || version > best) best = version; |
| 152 } | 153 } |
| 153 } | 154 } |
| 154 | 155 |
| 155 // TODO(rnystrom): Better exception. | 156 // TODO(rnystrom): Better exception. |
| 156 if (best == null) { | 157 if (best == null) { |
| (...skipping 25 matching lines...) Expand all Loading... |
| 182 | 183 |
| 183 for (var dependerName in dependency.dependers) { | 184 for (var dependerName in dependency.dependers) { |
| 184 var depender = getDependency(dependerName); | 185 var depender = getDependency(dependerName); |
| 185 var locked = lockFile.packages[dependerName]; | 186 var locked = lockFile.packages[dependerName]; |
| 186 if (locked != null && depender.version == locked.version) { | 187 if (locked != null && depender.version == locked.version) { |
| 187 enqueue(new UnlockPackage(depender)); | 188 enqueue(new UnlockPackage(depender)); |
| 188 return true; | 189 return true; |
| 189 } | 190 } |
| 190 } | 191 } |
| 191 | 192 |
| 192 return dependency.dependers.map(getDependency).some((subdependency) => | 193 return dependency.dependers.mappedBy(getDependency).any((subdependency) => |
| 193 tryUnlockDepender(subdependency, seen)); | 194 tryUnlockDepender(subdependency, seen)); |
| 194 } | 195 } |
| 195 | 196 |
| 196 List<PackageId> buildResults() { | 197 List<PackageId> buildResults() { |
| 197 return _packages.values.filter((dep) => dep.isDependedOn).map((dep) { | 198 return _packages.values.where((dep) => dep.isDependedOn).mappedBy((dep) { |
| 198 var description = dep.description; | 199 var description = dep.description; |
| 199 | 200 |
| 200 // If the lockfile contains a fully-resolved description for the package, | 201 // If the lockfile contains a fully-resolved description for the package, |
| 201 // use that. This allows e.g. Git to ensure that the same commit is used. | 202 // use that. This allows e.g. Git to ensure that the same commit is used. |
| 202 var lockedPackage = lockFile.packages[dep.name]; | 203 var lockedPackage = lockFile.packages[dep.name]; |
| 203 if (lockedPackage != null && lockedPackage.version == dep.version && | 204 if (lockedPackage != null && lockedPackage.version == dep.version && |
| 204 lockedPackage.source.name == dep.source.name && | 205 lockedPackage.source.name == dep.source.name && |
| 205 dep.source.descriptionsEqual( | 206 dep.source.descriptionsEqual( |
| 206 description, lockedPackage.description)) { | 207 description, lockedPackage.description)) { |
| 207 description = lockedPackage.description; | 208 description = lockedPackage.description; |
| 208 } | 209 } |
| 209 | 210 |
| 210 return new PackageId(dep.name, dep.source, dep.version, description); | 211 return new PackageId(dep.name, dep.source, dep.version, description); |
| 211 }); | 212 }) |
| 213 .toList(); |
| 212 } | 214 } |
| 213 } | 215 } |
| 214 | 216 |
| 215 /// The constraint solver works by iteratively processing a queue of work items. | 217 /// 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 | 218 /// Each item is a single atomic change to the dependency graph. Handling them |
| 217 /// in a queue lets us handle asynchrony (resolving versions requires | 219 /// in a queue lets us handle asynchrony (resolving versions requires |
| 218 /// information from servers) as well as avoid deeply nested recursion. | 220 /// information from servers) as well as avoid deeply nested recursion. |
| 219 abstract class WorkItem { | 221 abstract class WorkItem { |
| 220 /// Processes this work item. Returns a future that completes when the work is | 222 /// 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 | 223 /// done. If `null` is returned, that means the work has completed |
| (...skipping 25 matching lines...) Expand all Loading... |
| 247 log.fine("Changing $package to version $version."); | 249 log.fine("Changing $package to version $version."); |
| 248 | 250 |
| 249 var dependency = solver.getDependency(package); | 251 var dependency = solver.getDependency(package); |
| 250 var oldVersion = dependency.version; | 252 var oldVersion = dependency.version; |
| 251 solver.setVersion(package, version); | 253 solver.setVersion(package, version); |
| 252 | 254 |
| 253 // The dependencies between the old and new version may be different. Walk | 255 // The dependencies between the old and new version may be different. Walk |
| 254 // them both and update any constraints that differ between the two. | 256 // them both and update any constraints that differ between the two. |
| 255 return Futures.wait([ | 257 return Futures.wait([ |
| 256 getDependencyRefs(solver, oldVersion), | 258 getDependencyRefs(solver, oldVersion), |
| 257 getDependencyRefs(solver, version)]).transform((list) { | 259 getDependencyRefs(solver, version)]).then((list) { |
| 258 var oldDependencyRefs = list[0]; | 260 var oldDependencyRefs = list[0]; |
| 259 var newDependencyRefs = list[1]; | 261 var newDependencyRefs = list[1]; |
| 260 | 262 |
| 261 for (var oldRef in oldDependencyRefs.values) { | 263 for (var oldRef in oldDependencyRefs.values) { |
| 262 if (newDependencyRefs.containsKey(oldRef.name)) { | 264 if (newDependencyRefs.containsKey(oldRef.name)) { |
| 263 // The dependency is in both versions of this package, but its | 265 // The dependency is in both versions of this package, but its |
| 264 // constraint may have changed. | 266 // constraint may have changed. |
| 265 var newRef = newDependencyRefs.remove(oldRef.name); | 267 var newRef = newDependencyRefs.remove(oldRef.name); |
| 266 solver.enqueue(new AddConstraint(package, newRef)); | 268 solver.enqueue(new AddConstraint(package, newRef)); |
| 267 } else { | 269 } else { |
| (...skipping 14 matching lines...) Expand all Loading... |
| 282 /// Get the dependencies at [version] of the package being changed. | 284 /// Get the dependencies at [version] of the package being changed. |
| 283 Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver, | 285 Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver, |
| 284 Version version) { | 286 Version version) { |
| 285 // If there is no version, it means no package, so no dependencies. | 287 // If there is no version, it means no package, so no dependencies. |
| 286 if (version == null) { | 288 if (version == null) { |
| 287 return | 289 return |
| 288 new Future<Map<String, PackageRef>>.immediate(<String, PackageRef>{}); | 290 new Future<Map<String, PackageRef>>.immediate(<String, PackageRef>{}); |
| 289 } | 291 } |
| 290 | 292 |
| 291 var id = new PackageId(package, source, version, description); | 293 var id = new PackageId(package, source, version, description); |
| 292 return solver._pubspecs.load(id).transform((pubspec) { | 294 return solver._pubspecs.load(id).then((pubspec) { |
| 293 var dependencies = <String, PackageRef>{}; | 295 var dependencies = <String, PackageRef>{}; |
| 294 for (var dependency in pubspec.dependencies) { | 296 for (var dependency in pubspec.dependencies) { |
| 295 dependencies[dependency.name] = dependency; | 297 dependencies[dependency.name] = dependency; |
| 296 } | 298 } |
| 297 return dependencies; | 299 return dependencies; |
| 298 }); | 300 }); |
| 299 } | 301 } |
| 300 } | 302 } |
| 301 | 303 |
| 302 /// A constraint that a depending package places on a dependent package has | 304 /// A constraint that a depending package places on a dependent package has |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 357 var lockedVersion = lockedPackage.version; | 359 var lockedVersion = lockedPackage.version; |
| 358 if (newConstraint.allows(lockedVersion)) { | 360 if (newConstraint.allows(lockedVersion)) { |
| 359 solver.enqueue( | 361 solver.enqueue( |
| 360 new ChangeVersion(name, source, description, lockedVersion)); | 362 new ChangeVersion(name, source, description, lockedVersion)); |
| 361 return null; | 363 return null; |
| 362 } | 364 } |
| 363 } | 365 } |
| 364 | 366 |
| 365 // The constraint has changed, so see what the best version of the package | 367 // The constraint has changed, so see what the best version of the package |
| 366 // that meets the new constraint is. | 368 // that meets the new constraint is. |
| 367 return solver.getBestVersion(newDependency).transform((best) { | 369 return solver.getBestVersion(newDependency).then((best) { |
| 368 if (best == null) { | 370 if (best == null) { |
| 369 undo(solver); | 371 undo(solver); |
| 370 } else if (newDependency.version != best) { | 372 } else if (newDependency.version != best) { |
| 371 solver.enqueue(new ChangeVersion(name, source, description, best)); | 373 solver.enqueue(new ChangeVersion(name, source, description, best)); |
| 372 } | 374 } |
| 373 }); | 375 }); |
| 374 } | 376 } |
| 375 } | 377 } |
| 376 | 378 |
| 377 /// The constraint given by [ref] is being placed by [depender]. | 379 /// The constraint given by [ref] is being placed by [depender]. |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 431 class UnlockPackage implements WorkItem { | 433 class UnlockPackage implements WorkItem { |
| 432 /// The package being unlocked. | 434 /// The package being unlocked. |
| 433 Dependency package; | 435 Dependency package; |
| 434 | 436 |
| 435 UnlockPackage(this.package); | 437 UnlockPackage(this.package); |
| 436 | 438 |
| 437 Future process(VersionSolver solver) { | 439 Future process(VersionSolver solver) { |
| 438 log.fine("Unlocking ${package.name}."); | 440 log.fine("Unlocking ${package.name}."); |
| 439 | 441 |
| 440 solver.lockFile.packages.remove(package.name); | 442 solver.lockFile.packages.remove(package.name); |
| 441 return solver.getBestVersion(package).transform((best) { | 443 return solver.getBestVersion(package).then((best) { |
| 442 if (best == null) return null; | 444 if (best == null) return null; |
| 443 solver.enqueue(new ChangeVersion( | 445 solver.enqueue(new ChangeVersion( |
| 444 package.name, package.source, package.description, best)); | 446 package.name, package.source, package.description, best)); |
| 445 }); | 447 }); |
| 446 } | 448 } |
| 447 } | 449 } |
| 448 | 450 |
| 449 // TODO(rnystrom): Instead of always pulling from the source (which will mean | 451 // TODO(rnystrom): Instead of always pulling from the source (which will mean |
| 450 // hitting a server), we should consider caching pubspecs of uninstalled | 452 // hitting a server), we should consider caching pubspecs of uninstalled |
| 451 // packages in the system cache. | 453 // packages in the system cache. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 463 _pubspecs[id] = pubspec; | 465 _pubspecs[id] = pubspec; |
| 464 } | 466 } |
| 465 | 467 |
| 466 /// Loads the pubspec for the package identified by [id]. | 468 /// Loads the pubspec for the package identified by [id]. |
| 467 Future<Pubspec> load(PackageId id) { | 469 Future<Pubspec> load(PackageId id) { |
| 468 // Complete immediately if it's already cached. | 470 // Complete immediately if it's already cached. |
| 469 if (_pubspecs.containsKey(id)) { | 471 if (_pubspecs.containsKey(id)) { |
| 470 return new Future<Pubspec>.immediate(_pubspecs[id]); | 472 return new Future<Pubspec>.immediate(_pubspecs[id]); |
| 471 } | 473 } |
| 472 | 474 |
| 473 return id.describe().transform((pubspec) { | 475 return id.describe().then((pubspec) { |
| 474 // Cache it. | 476 // Cache it. |
| 475 _pubspecs[id] = pubspec; | 477 _pubspecs[id] = pubspec; |
| 476 return pubspec; | 478 return pubspec; |
| 477 }); | 479 }); |
| 478 } | 480 } |
| 479 } | 481 } |
| 480 | 482 |
| 481 /// Describes one [Package] in the [DependencyGraph] and keeps track of which | 483 /// Describes one [Package] in the [DependencyGraph] and keeps track of which |
| 482 /// packages depend on it and what constraints they place on it. | 484 /// packages depend on it and what constraints they place on it. |
| 483 class Dependency { | 485 class Dependency { |
| 484 /// The name of the this dependency's package. | 486 /// The name of the this dependency's package. |
| 485 final String name; | 487 final String name; |
| 486 | 488 |
| 487 /// The [PackageRefs] that represent constraints that depending packages have | 489 /// The [PackageRefs] that represent constraints that depending packages have |
| 488 /// placed on this one. | 490 /// placed on this one. |
| 489 final Map<String, PackageRef> _refs; | 491 final Map<String, PackageRef> _refs; |
| 490 | 492 |
| 491 /// The currently-selected best version for this dependency. | 493 /// The currently-selected best version for this dependency. |
| 492 Version version; | 494 Version version; |
| 493 | 495 |
| 494 /// Whether this dependency should always select the latest version. | 496 /// Whether this dependency should always select the latest version. |
| 495 bool useLatestVersion = false; | 497 bool useLatestVersion = false; |
| 496 | 498 |
| 497 /// Gets whether or not any other packages are currently depending on this | 499 /// 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 | 500 /// one. If `false`, then it means this package is not part of the dependency |
| 499 /// graph and should be omitted. | 501 /// graph and should be omitted. |
| 500 bool get isDependedOn => !_refs.isEmpty; | 502 bool get isDependedOn => !_refs.isEmpty; |
| 501 | 503 |
| 502 /// The names of all the packages that depend on this dependency. | 504 /// The names of all the packages that depend on this dependency. |
| 503 Collection<String> get dependers => _refs.keys; | 505 Iterable<String> get dependers => _refs.keys; |
| 504 | 506 |
| 505 /// Gets the overall constraint that all packages are placing on this one. | 507 /// 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 | 508 /// 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`. | 509 /// package is in the process of being added to the graph), returns `null`. |
| 508 VersionConstraint get constraint { | 510 VersionConstraint get constraint { |
| 509 if (_refs.isEmpty) return null; | 511 if (_refs.isEmpty) return null; |
| 510 return new VersionConstraint.intersection( | 512 return new VersionConstraint.intersection( |
| 511 _refs.values.map((ref) => ref.constraint)); | 513 _refs.values.mappedBy((ref) => ref.constraint)); |
| 512 } | 514 } |
| 513 | 515 |
| 514 /// The source of this dependency's package. | 516 /// The source of this dependency's package. |
| 515 Source get source { | 517 Source get source { |
| 516 var canonical = _canonicalRef(); | 518 var canonical = _canonicalRef(); |
| 517 if (canonical == null) return null; | 519 if (canonical == null) return null; |
| 518 return canonical.source; | 520 return canonical.source; |
| 519 } | 521 } |
| 520 | 522 |
| 521 /// The description of this dependency's package. | 523 /// The description of this dependency's package. |
| 522 get description { | 524 get description { |
| 523 var canonical = _canonicalRef(); | 525 var canonical = _canonicalRef(); |
| 524 if (canonical == null) return null; | 526 if (canonical == null) return null; |
| 525 return canonical.description; | 527 return canonical.description; |
| 526 } | 528 } |
| 527 | 529 |
| 528 /// Return the PackageRef that has the canonical source and description for | 530 /// Return the PackageRef that has the canonical source and description for |
| 529 /// this package. If any dependency requires that this package come from a | 531 /// this package. If any dependency requires that this package come from a |
| 530 /// [RootSource], that will be used; otherwise, it will be the source and | 532 /// [RootSource], that will be used; otherwise, it will be the source and |
| 531 /// description that all dependencies agree upon. | 533 /// description that all dependencies agree upon. |
| 532 PackageRef _canonicalRef() { | 534 PackageRef _canonicalRef() { |
| 533 if (_refs.isEmpty) return null; | 535 if (_refs.isEmpty) return null; |
| 534 var refs = _refs.values; | 536 var refs = _refs.values; |
| 535 for (var ref in refs) { | 537 for (var ref in refs) { |
| 536 if (ref is RootSource) return ref; | 538 if (ref is RootSource) return ref; |
| 537 } | 539 } |
| 538 return refs[0]; | 540 return refs.first; |
| 539 } | 541 } |
| 540 | 542 |
| 541 Dependency(this.name) | 543 Dependency(this.name) |
| 542 : _refs = <String, PackageRef>{}; | 544 : _refs = <String, PackageRef>{}; |
| 543 | 545 |
| 544 Dependency._clone(Dependency other) | 546 Dependency._clone(Dependency other) |
| 545 : name = other.name, | 547 : name = other.name, |
| 546 version = other.version, | 548 version = other.version, |
| 547 _refs = new Map<String, PackageRef>.from(other._refs); | 549 _refs = new Map<String, PackageRef>.from(other._refs); |
| 548 | 550 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 569 | 571 |
| 570 _refs[package] = ref; | 572 _refs[package] = ref; |
| 571 } | 573 } |
| 572 | 574 |
| 573 /// Returns the name of a package whose constraint source and description | 575 /// Returns the name of a package whose constraint source and description |
| 574 /// all other constraints must match. Returns null if there are no | 576 /// all other constraints must match. Returns null if there are no |
| 575 /// requirements on new constraints. | 577 /// requirements on new constraints. |
| 576 String _requiredDepender() { | 578 String _requiredDepender() { |
| 577 if (_refs.isEmpty) return null; | 579 if (_refs.isEmpty) return null; |
| 578 | 580 |
| 579 var dependers = _refs.keys; | 581 var dependers = _refs.keys.toList(); |
| 580 if (dependers.length == 1) { | 582 if (dependers.length == 1) { |
| 581 var depender = dependers[0]; | 583 var depender = dependers[0]; |
| 582 if (_refs[depender].source is RootSource) return null; | 584 if (_refs[depender].source is RootSource) return null; |
| 583 return depender; | 585 return depender; |
| 584 } | 586 } |
| 585 | 587 |
| 586 return dependers[1]; | 588 return dependers[1]; |
| 587 } | 589 } |
| 588 | 590 |
| 589 /// Removes the constraint from [package] onto this. | 591 /// Removes the constraint from [package] onto this. |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 693 final String depender2; | 695 final String depender2; |
| 694 final description2; | 696 final description2; |
| 695 | 697 |
| 696 DescriptionMismatchException(this.package, this.depender1, this.description1, | 698 DescriptionMismatchException(this.package, this.depender1, this.description1, |
| 697 this.depender2, this.description2); | 699 this.depender2, this.description2); |
| 698 | 700 |
| 699 String toString() { | 701 String toString() { |
| 700 // TODO(nweiz): Dump descriptions to YAML when that's supported. | 702 // TODO(nweiz): Dump descriptions to YAML when that's supported. |
| 701 return "Incompatible dependencies on '$package':\n" | 703 return "Incompatible dependencies on '$package':\n" |
| 702 "- '$depender1' depends on it with description " | 704 "- '$depender1' depends on it with description " |
| 703 "${JSON.stringify(description1)}\n" | 705 "${json.stringify(description1)}\n" |
| 704 "- '$depender2' depends on it with description " | 706 "- '$depender2' depends on it with description " |
| 705 "${JSON.stringify(description2)}"; | 707 "${json.stringify(description2)}"; |
| 706 } | 708 } |
| 707 } | 709 } |
| OLD | NEW |