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 /** |
6 * 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 |
7 * 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 |
8 * dependent packages. It works iteratively and tries to reach a stable | 8 * dependent packages. It works iteratively and tries to reach a stable |
9 * 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 |
10 * 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 |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
157 for (var version in versions) { | 157 for (var version in versions) { |
158 if (dependency.useLatestVersion || | 158 if (dependency.useLatestVersion || |
159 dependency.constraint.allows(version)) { | 159 dependency.constraint.allows(version)) { |
160 if (best == null || version > best) best = version; | 160 if (best == null || version > best) best = version; |
161 } | 161 } |
162 } | 162 } |
163 | 163 |
164 // TODO(rnystrom): Better exception. | 164 // TODO(rnystrom): Better exception. |
165 if (best == null) { | 165 if (best == null) { |
166 if (tryUnlockDepender(dependency)) return null; | 166 if (tryUnlockDepender(dependency)) return null; |
167 throw new NoVersionException(dependency.name, dependency.constraint); | 167 throw new NoVersionException(dependency.name, dependency.constraint, |
168 dependency._refs); | |
168 } else if (!dependency.constraint.allows(best)) { | 169 } else if (!dependency.constraint.allows(best)) { |
169 if (tryUnlockDepender(dependency)) return null; | 170 if (tryUnlockDepender(dependency)) return null; |
170 throw new CouldNotUpdateException( | 171 throw new CouldNotUpdateException( |
171 dependency.name, dependency.constraint, best); | 172 dependency.name, dependency.constraint, best); |
172 } | 173 } |
173 | 174 |
174 return best; | 175 return best; |
175 }); | 176 }); |
176 } | 177 } |
177 | 178 |
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
346 | 347 |
347 // If the package is over-constrained, i.e. the packages depending have | 348 // If the package is over-constrained, i.e. the packages depending have |
348 // disjoint constraints, then try unlocking a depender that's locked by the | 349 // disjoint constraints, then try unlocking a depender that's locked by the |
349 // lockfile. If there are no remaining locked dependencies, throw an error. | 350 // lockfile. If there are no remaining locked dependencies, throw an error. |
350 if (newConstraint != null && newConstraint.isEmpty) { | 351 if (newConstraint != null && newConstraint.isEmpty) { |
351 if (solver.tryUnlockDepender(newDependency)) { | 352 if (solver.tryUnlockDepender(newDependency)) { |
352 undo(solver); | 353 undo(solver); |
353 return null; | 354 return null; |
354 } | 355 } |
355 | 356 |
356 throw new DisjointConstraintException(name); | 357 throw new DisjointConstraintException(name, newDependency._refs); |
357 } | 358 } |
358 | 359 |
359 // If this constraint change didn't cause the overall constraint on the | 360 // If this constraint change didn't cause the overall constraint on the |
360 // package to change, then we don't need to do any further work. | 361 // package to change, then we don't need to do any further work. |
361 if (oldConstraint == newConstraint) return null; | 362 if (oldConstraint == newConstraint) return null; |
362 | 363 |
363 // If the dependency has been cut free from the graph, just remove it. | 364 // If the dependency has been cut free from the graph, just remove it. |
364 if (!newDependency.isDependedOn) { | 365 if (!newDependency.isDependedOn) { |
365 solver.enqueue(new ChangeVersion(name, source, description, null)); | 366 solver.enqueue(new ChangeVersion(name, source, description, null)); |
366 return null; | 367 return null; |
(...skipping 232 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
599 /** Creates a copy of this dependency. */ | 600 /** Creates a copy of this dependency. */ |
600 Dependency clone() => new Dependency._clone(this); | 601 Dependency clone() => new Dependency._clone(this); |
601 | 602 |
602 /// Return a list of available versions for this dependency. | 603 /// Return a list of available versions for this dependency. |
603 Future<List<Version>> getVersions() => source.getVersions(name, description); | 604 Future<List<Version>> getVersions() => source.getVersions(name, description); |
604 | 605 |
605 /** | 606 /** |
606 * Places [ref] as a constraint from [package] onto this. | 607 * Places [ref] as a constraint from [package] onto this. |
607 */ | 608 */ |
608 void placeConstraint(String package, PackageRef ref) { | 609 void placeConstraint(String package, PackageRef ref) { |
609 var required = _requiredRef(); | 610 var requiredDepender = _requiredDepender(); |
610 if (required != null) { | 611 if (requiredDepender != null) { |
612 var required = _refs[requiredDepender]; | |
611 if (required.source.name != ref.source.name) { | 613 if (required.source.name != ref.source.name) { |
612 throw new SourceMismatchException(name, required.source, ref.source); | 614 throw new SourceMismatchException(name, |
615 requiredDepender, required.source, package, ref.source); | |
613 } else if (!required.source.descriptionsEqual( | 616 } else if (!required.source.descriptionsEqual( |
614 required.description, ref.description)) { | 617 required.description, ref.description)) { |
615 throw new DescriptionMismatchException( | 618 throw new DescriptionMismatchException(name, |
616 name, required.description, ref.description); | 619 requiredDepender, required.description, package, ref.description); |
617 } | 620 } |
618 } | 621 } |
619 | 622 |
620 _refs[package] = ref; | 623 _refs[package] = ref; |
621 } | 624 } |
622 | 625 |
623 /// Returns a PackageRef whose source and description any new constraints are | 626 /// Returns the name of a package whose constraint source and description |
nweiz
2012/12/06 00:58:55
"constraint" -> ""?
Bob Nystrom
2012/12/06 17:44:41
It returns the name of the depender, and it's not
nweiz
2012/12/06 19:43:10
Then maybe "constraint's"?
| |
624 /// required to match. Returns null if there are no requirements on new | 627 /// all other constraints must match. Returns null if there are no |
625 /// constraints. | 628 /// requirements on new constraints. |
626 PackageRef _requiredRef() { | 629 String _requiredDepender() { |
627 if (_refs.isEmpty) return null; | 630 if (_refs.isEmpty) return null; |
628 var refs = _refs.values; | 631 |
629 var first = refs[0]; | 632 var dependers = _refs.keys; |
630 if (refs.length == 1) { | 633 if (dependers.length == 1) { |
631 if (first.source is RootSource) return null; | 634 var depender = dependers[0]; |
632 return first; | 635 if (_refs[depender].source is RootSource) return null; |
636 return depender; | |
633 } | 637 } |
634 return refs[1]; | 638 |
639 return dependers[1]; | |
635 } | 640 } |
636 | 641 |
637 /** | 642 /** |
638 * Removes the constraint from [package] onto this. | 643 * Removes the constraint from [package] onto this. |
639 */ | 644 */ |
640 PackageRef removeConstraint(String package) => _refs.remove(package); | 645 PackageRef removeConstraint(String package) => _refs.remove(package); |
641 } | 646 } |
642 | 647 |
643 // TODO(rnystrom): Report the last of depending packages and their constraints. | |
644 /** | 648 /** |
645 * Exception thrown when the [VersionConstraint] used to match a package is | 649 * Exception thrown when the [VersionConstraint] used to match a package is |
646 * valid (i.e. non-empty), but there are no released versions of the package | 650 * valid (i.e. non-empty), but there are no released versions of the package |
647 * that fit that constraint. | 651 * that fit that constraint. |
648 */ | 652 */ |
649 class NoVersionException implements Exception { | 653 class NoVersionException implements Exception { |
650 final String package; | 654 final String package; |
651 final VersionConstraint constraint; | 655 final VersionConstraint constraint; |
656 final Map<String, PackageRef> _dependencies; | |
652 | 657 |
653 NoVersionException(this.package, this.constraint); | 658 NoVersionException(this.package, this.constraint, this._dependencies); |
654 | 659 |
655 String toString() => | 660 String toString() { |
656 "Package '$package' has no versions that match $constraint."; | 661 var buffer = new StringBuffer(); |
662 buffer.add("Package '$package' has no versions that match $constraint derive d from:\n"); | |
nweiz
2012/12/06 00:58:55
Long line
Bob Nystrom
2012/12/06 17:44:41
Done.
| |
663 | |
664 var keys = new List.from(_dependencies.keys); | |
665 keys.sort(); | |
666 | |
667 for (var key in keys) { | |
668 buffer.add("- '$key' depends on version " | |
669 "${_dependencies[key].constraint}\n"); | |
670 } | |
671 | |
672 return buffer.toString(); | |
673 } | |
657 } | 674 } |
658 | 675 |
659 // TODO(rnystrom): Report the list of depending packages and their constraints. | 676 // TODO(rnystrom): Report the list of depending packages and their constraints. |
660 /** | 677 /** |
661 * Exception thrown when the most recent version of [package] must be selected, | 678 * Exception thrown when the most recent version of [package] must be selected, |
662 * but doesn't match the [VersionConstraint] imposed on the package. | 679 * but doesn't match the [VersionConstraint] imposed on the package. |
663 */ | 680 */ |
664 class CouldNotUpdateException implements Exception { | 681 class CouldNotUpdateException implements Exception { |
665 final String package; | 682 final String package; |
666 final VersionConstraint constraint; | 683 final VersionConstraint constraint; |
667 final Version best; | 684 final Version best; |
668 | 685 |
669 CouldNotUpdateException(this.package, this.constraint, this.best); | 686 CouldNotUpdateException(this.package, this.constraint, this.best); |
670 | 687 |
671 String toString() => | 688 String toString() => |
672 "The latest version of '$package', $best, does not match $constraint."; | 689 "The latest version of '$package', $best, does not match $constraint."; |
673 } | 690 } |
674 | 691 |
675 // TODO(rnystrom): Report the last of depending packages and their constraints. | |
676 /** | 692 /** |
677 * Exception thrown when the [VersionConstraint] used to match a package is | 693 * Exception thrown when the [VersionConstraint] used to match a package is |
678 * the empty set: in other words, multiple packages depend on it and have | 694 * the empty set: in other words, multiple packages depend on it and have |
679 * conflicting constraints that have no overlap. | 695 * conflicting constraints that have no overlap. |
680 */ | 696 */ |
681 class DisjointConstraintException implements Exception { | 697 class DisjointConstraintException implements Exception { |
682 final String package; | 698 final String package; |
699 final Map<String, PackageRef> _dependencies; | |
683 | 700 |
684 DisjointConstraintException(this.package); | 701 DisjointConstraintException(this.package, this._dependencies); |
685 | 702 |
686 String toString() => | 703 String toString() { |
687 "Package '$package' has disjoint constraints."; | 704 var buffer = new StringBuffer(); |
705 buffer.add("Shared dependency version conflict on '$package':\n"); | |
nweiz
2012/12/06 00:58:55
I don't think "shared dependency version conflict"
Bob Nystrom
2012/12/06 17:44:41
Done.
| |
706 | |
707 var keys = new List.from(_dependencies.keys); | |
708 keys.sort(); | |
709 | |
710 for (var key in keys) { | |
711 buffer.add("- '$key' depends on version " | |
712 "${_dependencies[key].constraint}\n"); | |
713 } | |
714 | |
715 return buffer.toString(); | |
716 } | |
688 } | 717 } |
689 | 718 |
690 /** | 719 /** |
691 * Exception thrown when the [VersionSolver] fails to find a solution after a | 720 * Exception thrown when the [VersionSolver] fails to find a solution after a |
692 * certain number of iterations. | 721 * certain number of iterations. |
693 */ | 722 */ |
694 class CouldNotSolveException implements Exception { | 723 class CouldNotSolveException implements Exception { |
695 CouldNotSolveException(); | 724 CouldNotSolveException(); |
696 | 725 |
697 String toString() => | 726 String toString() => |
698 "Could not find a solution that met all version constraints."; | 727 "Could not find a solution that met all version constraints."; |
699 } | 728 } |
700 | 729 |
701 /** | 730 /** |
702 * Exception thrown when two packages with the same name but different sources | 731 * Exception thrown when two packages with the same name but different sources |
703 * are depended upon. | 732 * are depended upon. |
704 */ | 733 */ |
705 class SourceMismatchException implements Exception { | 734 class SourceMismatchException implements Exception { |
706 final String package; | 735 final String package; |
736 final String depender1; | |
707 final Source source1; | 737 final Source source1; |
738 final String depender2; | |
708 final Source source2; | 739 final Source source2; |
709 | 740 |
710 SourceMismatchException(this.package, this.source1, this.source2); | 741 SourceMismatchException(this.package, this.depender1, this.source1, |
742 this.depender2, this.source2); | |
711 | 743 |
712 String toString() { | 744 String toString() { |
713 return "Package '$package' is depended on from both sources " | 745 return "Shared dependency conflict on '$package':\n" |
714 "'${source1.name}' and '${source2.name}'."; | 746 "- '$depender1' depends on it from source '$source1'\n" |
747 "- '$depender2' depends on it from source '$source2'"; | |
715 } | 748 } |
716 } | 749 } |
717 | 750 |
718 /** | 751 /** |
719 * Exception thrown when two packages with the same name and source but | 752 * Exception thrown when two packages with the same name and source but |
720 * different descriptions are depended upon. | 753 * different descriptions are depended upon. |
721 */ | 754 */ |
722 class DescriptionMismatchException implements Exception { | 755 class DescriptionMismatchException implements Exception { |
723 final String package; | 756 final String package; |
757 final String depender1; | |
724 final description1; | 758 final description1; |
759 final String depender2; | |
725 final description2; | 760 final description2; |
726 | 761 |
727 DescriptionMismatchException(this.package, this.description1, | 762 DescriptionMismatchException(this.package, this.depender1, this.description1, |
728 this.description2); | 763 this.depender2, this.description2); |
729 | 764 |
730 // TODO(nweiz): Dump to YAML when that's supported | 765 String toString() { |
731 String toString() => "Package '$package' has conflicting descriptions " | 766 // TODO(nweiz): Dump descriptions to YAML when that's supported. |
732 "'${JSON.stringify(description1)}' and '${JSON.stringify(description2)}'"; | 767 return "Shared dependency conflict on '$package':\n" |
768 "- '$depender1' depends on it with description " | |
769 "${JSON.stringify(description1)}\n" | |
770 "- '$depender2' depends on it with description " | |
771 "${JSON.stringify(description2)}"; | |
772 } | |
733 } | 773 } |
OLD | NEW |