Chromium Code Reviews| 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 /// A back-tracking depth-first solver. | 5 /// A back-tracking depth-first solver. |
| 6 /// | 6 /// |
| 7 /// Attempts to find the best solution for a root package's transitive | 7 /// Attempts to find the best solution for a root package's transitive |
| 8 /// dependency graph, where a "solution" is a set of concrete package versions. | 8 /// dependency graph, where a "solution" is a set of concrete package versions. |
| 9 /// A valid solution will select concrete versions for every package reached | 9 /// A valid solution will select concrete versions for every package reached |
| 10 /// from the root package's dependency graph, and each of those packages will | 10 /// from the root package's dependency graph, and each of those packages will |
| (...skipping 516 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 527 /// Desctructively modifies [deps]. Completes to a list of packages if the | 527 /// Desctructively modifies [deps]. Completes to a list of packages if the |
| 528 /// traversal is complete. Completes it to an error if a failure occurred. | 528 /// traversal is complete. Completes it to an error if a failure occurred. |
| 529 /// Otherwise, recurses. | 529 /// Otherwise, recurses. |
| 530 Future<List<PackageId>> _traverseDeps(PackageId depender, | 530 Future<List<PackageId>> _traverseDeps(PackageId depender, |
| 531 DependencyQueue deps) { | 531 DependencyQueue deps) { |
| 532 // Move onto the next package if we've traversed all of these references. | 532 // Move onto the next package if we've traversed all of these references. |
| 533 if (deps.isEmpty) return _traversePackage(); | 533 if (deps.isEmpty) return _traversePackage(); |
| 534 | 534 |
| 535 return resetStack(() { | 535 return resetStack(() { |
| 536 return deps.advance().then((dep) { | 536 return deps.advance().then((dep) { |
| 537 _validateDependency(dep, depender); | 537 var dependency = new Dependency(depender.name, depender.version, dep); |
| 538 | 538 return _registerDependency(dependency).then((_) { |
| 539 // Add the dependency. | 539 if (dep.name == "barback") return _addImplicitDependencies(); |
| 540 var dependencies = _getDependencies(dep.name); | |
| 541 dependencies.add(new Dependency(depender.name, depender.version, dep)); | |
| 542 | |
| 543 // If the package is barback, pub has an implicit version constraint on | |
| 544 // it since pub itself uses barback too. Note that we don't check for | |
| 545 // the hosted source here because we still want to do this even when | |
| 546 // people on the Dart team are on the bleeding edge and have a path | |
| 547 // dependency on the tip version of barback in the Dart repo. | |
| 548 // | |
| 549 // The length check here is to ensure we only add the barback | |
| 550 // dependency once. | |
| 551 if (dep.name == "barback" && dependencies.length == 1) { | |
| 552 _solver.logSolve('add implicit ${barback.supportedVersions} pub ' | |
| 553 'dependency on barback'); | |
| 554 | |
| 555 // Use the same source and description as the explicit dependency. | |
| 556 // That way, this doesn't fail with a source/desc conflict if users | |
| 557 // (like Dart team members) use things like a path dependency to | |
| 558 // find barback. | |
| 559 var barbackDep = new PackageDep(dep.name, dep.source, | |
| 560 barback.supportedVersions, dep.description); | |
| 561 dependencies.add(new Dependency("pub itself", null, barbackDep)); | |
| 562 } | |
| 563 | |
| 564 var constraint = _getConstraint(dep.name); | |
| 565 | |
| 566 // See if it's possible for a package to match that constraint. | |
| 567 if (constraint.isEmpty) { | |
| 568 var constraints = _getDependencies(dep.name) | |
| 569 .map((dep) => " ${dep.dep.constraint} from ${dep.depender}") | |
| 570 .join('\n'); | |
| 571 _solver.logSolve( | |
| 572 'disjoint constraints on ${dep.name}:\n$constraints'); | |
| 573 throw new DisjointConstraintException(dep.name, dependencies); | |
| 574 } | |
| 575 | |
| 576 var selected = _validateSelected(dep, constraint); | |
| 577 if (selected != null) { | |
| 578 // The selected package version is good, so enqueue it to traverse | |
| 579 // into it. | |
| 580 _packages.add(selected); | |
| 581 return _traverseDeps(depender, deps); | |
| 582 } | |
| 583 | |
| 584 // We haven't selected a version. Try all of the versions that match | |
| 585 // the constraints we currently have for this package. | |
| 586 var locked = _getValidLocked(dep.name); | |
| 587 | |
| 588 return VersionQueue.create(locked, | |
| 589 () => _getAllowedVersions(dep)).then((versions) { | |
| 590 _packages.add(_solver.select(versions)); | |
| 591 }); | 540 }); |
| 592 }).then((_) => _traverseDeps(depender, deps)); | 541 }).then((_) => _traverseDeps(depender, deps)); |
| 593 }); | 542 }); |
| 594 } | 543 } |
| 595 | 544 |
| 545 /// Register [dependency]'s constraints on the package it depends on and | |
| 546 /// enqueues the package for processing if necessary. | |
| 547 Future _registerDependency(Dependency dependency) { | |
| 548 return syncFuture(() { | |
| 549 _validateDependency(dependency); | |
| 550 | |
| 551 var dep = dependency.dep; | |
| 552 var dependencies = _getDependencies(dep.name); | |
| 553 dependencies.add(dependency); | |
| 554 | |
| 555 var constraint = _getConstraint(dep.name); | |
| 556 | |
| 557 // See if it's possible for a package to match that constraint. | |
| 558 if (constraint.isEmpty) { | |
| 559 var constraints = dependencies | |
| 560 .map((dep) => " ${dep.dep.constraint} from ${dep.depender}") | |
| 561 .join('\n'); | |
| 562 _solver.logSolve( | |
| 563 'disjoint constraints on ${dep.name}:\n$constraints'); | |
| 564 throw new DisjointConstraintException(dep.name, dependencies); | |
| 565 } | |
| 566 | |
| 567 var selected = _validateSelected(dep, constraint); | |
| 568 if (selected != null) { | |
| 569 // The selected package version is good, so enqueue it to traverse | |
| 570 // into it. | |
| 571 _packages.add(selected); | |
| 572 return null; | |
| 573 } | |
| 574 | |
| 575 // We haven't selected a version. Try all of the versions that match | |
| 576 // the constraints we currently have for this package. | |
| 577 var locked = _getValidLocked(dep.name); | |
| 578 | |
| 579 return VersionQueue.create(locked, () { | |
| 580 return _getAllowedVersions(dep); | |
| 581 }).then((versions) => _packages.add(_solver.select(versions))); | |
| 582 }); | |
| 583 } | |
| 584 | |
| 596 /// Gets all versions of [dep] that match the current constraints placed on | 585 /// Gets all versions of [dep] that match the current constraints placed on |
| 597 /// it. | 586 /// it. |
| 598 Future<Iterable<PackageId>> _getAllowedVersions(PackageDep dep) { | 587 Future<Iterable<PackageId>> _getAllowedVersions(PackageDep dep) { |
| 599 var constraint = _getConstraint(dep.name); | 588 var constraint = _getConstraint(dep.name); |
| 600 return _solver.cache.getVersions(dep.toRef()).then((versions) { | 589 return _solver.cache.getVersions(dep.toRef()).then((versions) { |
| 601 var allowed = versions.where((id) => constraint.allows(id.version)); | 590 var allowed = versions.where((id) => constraint.allows(id.version)); |
| 602 | 591 |
| 603 if (allowed.isEmpty) { | 592 if (allowed.isEmpty) { |
| 604 _solver.logSolve('no versions for ${dep.name} match $constraint'); | 593 _solver.logSolve('no versions for ${dep.name} match $constraint'); |
| 605 throw new NoVersionException(dep.name, null, constraint, | 594 throw new NoVersionException(dep.name, null, constraint, |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 626 | 615 |
| 627 throw error; | 616 throw error; |
| 628 }); | 617 }); |
| 629 } | 618 } |
| 630 | 619 |
| 631 /// Ensures that dependency [dep] from [depender] is consistent with the | 620 /// Ensures that dependency [dep] from [depender] is consistent with the |
| 632 /// other dependencies on the same package. | 621 /// other dependencies on the same package. |
| 633 /// | 622 /// |
| 634 /// Throws a [SolveFailure] exception if not. Only validates sources and | 623 /// Throws a [SolveFailure] exception if not. Only validates sources and |
| 635 /// descriptions, not the version. | 624 /// descriptions, not the version. |
| 636 void _validateDependency(PackageDep dep, PackageId depender) { | 625 void _validateDependency(Dependency dependency) { |
| 626 var dep = dependency.dep; | |
| 627 | |
| 637 // Make sure the dependencies agree on source and description. | 628 // Make sure the dependencies agree on source and description. |
| 638 var required = _getRequired(dep.name); | 629 var required = _getRequired(dep.name); |
| 639 if (required == null) return; | 630 if (required == null) return; |
| 640 | 631 |
| 641 // Make sure all of the existing sources match the new reference. | 632 // Make sure all of the existing sources match the new reference. |
| 642 if (required.dep.source != dep.source) { | 633 if (required.dep.source != dep.source) { |
| 643 _solver.logSolve('source mismatch on ${dep.name}: ${required.dep.source} ' | 634 _solver.logSolve('source mismatch on ${dep.name}: ${required.dep.source} ' |
| 644 '!= ${dep.source}'); | 635 '!= ${dep.source}'); |
| 645 throw new SourceMismatchException(dep.name, | 636 throw new SourceMismatchException(dep.name, [required, dependency]); |
| 646 [required, new Dependency(depender.name, depender.version, dep)]); | |
| 647 } | 637 } |
| 648 | 638 |
| 649 // Make sure all of the existing descriptions match the new reference. | 639 // Make sure all of the existing descriptions match the new reference. |
| 650 var source = _solver.sources[dep.source]; | 640 var source = _solver.sources[dep.source]; |
| 651 if (!source.descriptionsEqual(dep.description, required.dep.description)) { | 641 if (!source.descriptionsEqual(dep.description, required.dep.description)) { |
| 652 _solver.logSolve('description mismatch on ${dep.name}: ' | 642 _solver.logSolve('description mismatch on ${dep.name}: ' |
| 653 '${required.dep.description} != ${dep.description}'); | 643 '${required.dep.description} != ${dep.description}'); |
| 654 throw new DescriptionMismatchException(dep.name, | 644 throw new DescriptionMismatchException(dep.name, [required, dependency]); |
| 655 [required, new Dependency(depender.name, depender.version, dep)]); | |
| 656 } | 645 } |
| 657 } | 646 } |
| 658 | 647 |
| 659 /// Validates the currently selected package against the new dependency that | 648 /// Validates the currently selected package against the new dependency that |
| 660 /// [dep] and [constraint] place on it. | 649 /// [dep] and [constraint] place on it. |
| 661 /// | 650 /// |
| 662 /// Returns `null` if there is no currently selected package, throws a | 651 /// Returns `null` if there is no currently selected package, throws a |
| 663 /// [SolveFailure] if the new reference it not does not allow the previously | 652 /// [SolveFailure] if the new reference it not does not allow the previously |
| 664 /// selected version, or returns the selected package if successful. | 653 /// selected version, or returns the selected package if successful. |
| 665 PackageId _validateSelected(PackageDep dep, VersionConstraint constraint) { | 654 PackageId _validateSelected(PackageDep dep, VersionConstraint constraint) { |
| 666 var selected = _solver.getSelected(dep.name); | 655 var selected = _solver.getSelected(dep.name); |
| 667 if (selected == null) return null; | 656 if (selected == null) return null; |
| 668 | 657 |
| 669 // Make sure it meets the constraint. | 658 // Make sure it meets the constraint. |
| 670 if (!dep.constraint.allows(selected.version)) { | 659 if (!dep.constraint.allows(selected.version)) { |
| 671 _solver.logSolve('selection $selected does not match $constraint'); | 660 _solver.logSolve('selection $selected does not match $constraint'); |
| 672 throw new NoVersionException(dep.name, selected.version, constraint, | 661 throw new NoVersionException(dep.name, selected.version, constraint, |
| 673 _getDependencies(dep.name)); | 662 _getDependencies(dep.name)); |
| 674 } | 663 } |
| 675 | 664 |
| 676 return selected; | 665 return selected; |
| 677 } | 666 } |
| 678 | 667 |
| 668 /// Register pub's implicit dependencies. | |
| 669 /// | |
| 670 /// Pub has an implicit version constraint on barback and various other | |
| 671 /// packages used in barback's plugin isolate. | |
| 672 Future _addImplicitDependencies() { | |
| 673 /// Ensure we only add the barback dependency once. | |
| 674 if (_getDependencies("barback").length != 1) return new Future.value(); | |
| 675 | |
| 676 return Future.wait(barback.pubConstraints.keys.map((depName) { | |
| 677 var constraint = barback.pubConstraints[depName]; | |
| 678 _solver.logSolve('add implicit $constraint pub dependency on ' | |
| 679 '$depName'); | |
| 680 | |
| 681 var override = _solver._overrides[depName]; | |
|
Bob Nystrom
2014/06/26 16:37:33
Nice. I didn't remember that the solver already ha
| |
| 682 | |
| 683 // Use the same source and description as the dependency override if | |
| 684 // one exists. | |
|
Bob Nystrom
2014/06/26 16:37:33
Document that this is mainly for the bots.
nweiz
2014/06/26 20:03:48
Done.
| |
| 685 var pubDep = override == null ? | |
| 686 new PackageDep(depName, "hosted", constraint, depName) : | |
| 687 override.withConstraint(constraint); | |
| 688 return _registerDependency(new Dependency("pub itself", null, pubDep)); | |
| 689 })); | |
| 690 } | |
| 691 | |
| 679 /// Gets the list of dependencies for package [name]. | 692 /// Gets the list of dependencies for package [name]. |
| 680 /// | 693 /// |
| 681 /// Creates an empty list if needed. | 694 /// Creates an empty list if needed. |
| 682 List<Dependency> _getDependencies(String name) { | 695 List<Dependency> _getDependencies(String name) { |
| 683 return _dependencies.putIfAbsent(name, () => <Dependency>[]); | 696 return _dependencies.putIfAbsent(name, () => <Dependency>[]); |
| 684 } | 697 } |
| 685 | 698 |
| 686 /// Gets a "required" reference to the package [name]. | 699 /// Gets a "required" reference to the package [name]. |
| 687 /// | 700 /// |
| 688 /// This is the first non-root dependency on that package. All dependencies | 701 /// This is the first non-root dependency on that package. All dependencies |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 745 /// | 758 /// |
| 746 /// Throws a [SolveFailure] if not. | 759 /// Throws a [SolveFailure] if not. |
| 747 void _validateSdkConstraint(Pubspec pubspec) { | 760 void _validateSdkConstraint(Pubspec pubspec) { |
| 748 if (pubspec.environment.sdkVersion.allows(sdk.version)) return; | 761 if (pubspec.environment.sdkVersion.allows(sdk.version)) return; |
| 749 | 762 |
| 750 throw new BadSdkVersionException(pubspec.name, | 763 throw new BadSdkVersionException(pubspec.name, |
| 751 'Package ${pubspec.name} requires SDK version ' | 764 'Package ${pubspec.name} requires SDK version ' |
| 752 '${pubspec.environment.sdkVersion} but the current SDK is ' | 765 '${pubspec.environment.sdkVersion} but the current SDK is ' |
| 753 '${sdk.version}.'); | 766 '${sdk.version}.'); |
| 754 } | 767 } |
| OLD | NEW |