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 |
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 " |
| 663 "derived from:\n"); |
| 664 |
| 665 var keys = new List.from(_dependencies.keys); |
| 666 keys.sort(); |
| 667 |
| 668 for (var key in keys) { |
| 669 buffer.add("- '$key' depends on version " |
| 670 "${_dependencies[key].constraint}\n"); |
| 671 } |
| 672 |
| 673 return buffer.toString(); |
| 674 } |
657 } | 675 } |
658 | 676 |
659 // TODO(rnystrom): Report the list of depending packages and their constraints. | 677 // TODO(rnystrom): Report the list of depending packages and their constraints. |
660 /** | 678 /** |
661 * Exception thrown when the most recent version of [package] must be selected, | 679 * Exception thrown when the most recent version of [package] must be selected, |
662 * but doesn't match the [VersionConstraint] imposed on the package. | 680 * but doesn't match the [VersionConstraint] imposed on the package. |
663 */ | 681 */ |
664 class CouldNotUpdateException implements Exception { | 682 class CouldNotUpdateException implements Exception { |
665 final String package; | 683 final String package; |
666 final VersionConstraint constraint; | 684 final VersionConstraint constraint; |
667 final Version best; | 685 final Version best; |
668 | 686 |
669 CouldNotUpdateException(this.package, this.constraint, this.best); | 687 CouldNotUpdateException(this.package, this.constraint, this.best); |
670 | 688 |
671 String toString() => | 689 String toString() => |
672 "The latest version of '$package', $best, does not match $constraint."; | 690 "The latest version of '$package', $best, does not match $constraint."; |
673 } | 691 } |
674 | 692 |
675 // TODO(rnystrom): Report the last of depending packages and their constraints. | |
676 /** | 693 /** |
677 * Exception thrown when the [VersionConstraint] used to match a package is | 694 * 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 | 695 * the empty set: in other words, multiple packages depend on it and have |
679 * conflicting constraints that have no overlap. | 696 * conflicting constraints that have no overlap. |
680 */ | 697 */ |
681 class DisjointConstraintException implements Exception { | 698 class DisjointConstraintException implements Exception { |
682 final String package; | 699 final String package; |
| 700 final Map<String, PackageRef> _dependencies; |
683 | 701 |
684 DisjointConstraintException(this.package); | 702 DisjointConstraintException(this.package, this._dependencies); |
685 | 703 |
686 String toString() => | 704 String toString() { |
687 "Package '$package' has disjoint constraints."; | 705 var buffer = new StringBuffer(); |
| 706 buffer.add("Incompatible version constraints on '$package':\n"); |
| 707 |
| 708 var keys = new List.from(_dependencies.keys); |
| 709 keys.sort(); |
| 710 |
| 711 for (var key in keys) { |
| 712 buffer.add("- '$key' depends on version " |
| 713 "${_dependencies[key].constraint}\n"); |
| 714 } |
| 715 |
| 716 return buffer.toString(); |
| 717 } |
688 } | 718 } |
689 | 719 |
690 /** | 720 /** |
691 * Exception thrown when the [VersionSolver] fails to find a solution after a | 721 * Exception thrown when the [VersionSolver] fails to find a solution after a |
692 * certain number of iterations. | 722 * certain number of iterations. |
693 */ | 723 */ |
694 class CouldNotSolveException implements Exception { | 724 class CouldNotSolveException implements Exception { |
695 CouldNotSolveException(); | 725 CouldNotSolveException(); |
696 | 726 |
697 String toString() => | 727 String toString() => |
698 "Could not find a solution that met all version constraints."; | 728 "Could not find a solution that met all version constraints."; |
699 } | 729 } |
700 | 730 |
701 /** | 731 /** |
702 * Exception thrown when two packages with the same name but different sources | 732 * Exception thrown when two packages with the same name but different sources |
703 * are depended upon. | 733 * are depended upon. |
704 */ | 734 */ |
705 class SourceMismatchException implements Exception { | 735 class SourceMismatchException implements Exception { |
706 final String package; | 736 final String package; |
| 737 final String depender1; |
707 final Source source1; | 738 final Source source1; |
| 739 final String depender2; |
708 final Source source2; | 740 final Source source2; |
709 | 741 |
710 SourceMismatchException(this.package, this.source1, this.source2); | 742 SourceMismatchException(this.package, this.depender1, this.source1, |
| 743 this.depender2, this.source2); |
711 | 744 |
712 String toString() { | 745 String toString() { |
713 return "Package '$package' is depended on from both sources " | 746 return "Incompatible dependencies on '$package':\n" |
714 "'${source1.name}' and '${source2.name}'."; | 747 "- '$depender1' depends on it from source '$source1'\n" |
| 748 "- '$depender2' depends on it from source '$source2'"; |
715 } | 749 } |
716 } | 750 } |
717 | 751 |
718 /** | 752 /** |
719 * Exception thrown when two packages with the same name and source but | 753 * Exception thrown when two packages with the same name and source but |
720 * different descriptions are depended upon. | 754 * different descriptions are depended upon. |
721 */ | 755 */ |
722 class DescriptionMismatchException implements Exception { | 756 class DescriptionMismatchException implements Exception { |
723 final String package; | 757 final String package; |
| 758 final String depender1; |
724 final description1; | 759 final description1; |
| 760 final String depender2; |
725 final description2; | 761 final description2; |
726 | 762 |
727 DescriptionMismatchException(this.package, this.description1, | 763 DescriptionMismatchException(this.package, this.depender1, this.description1, |
728 this.description2); | 764 this.depender2, this.description2); |
729 | 765 |
730 // TODO(nweiz): Dump to YAML when that's supported | 766 String toString() { |
731 String toString() => "Package '$package' has conflicting descriptions " | 767 // TODO(nweiz): Dump descriptions to YAML when that's supported. |
732 "'${JSON.stringify(description1)}' and '${JSON.stringify(description2)}'"; | 768 return "Incompatible dependencies on '$package':\n" |
| 769 "- '$depender1' depends on it with description " |
| 770 "${JSON.stringify(description1)}\n" |
| 771 "- '$depender2' depends on it with description " |
| 772 "${JSON.stringify(description2)}"; |
| 773 } |
733 } | 774 } |
OLD | NEW |