| Index: sdk/lib/_internal/pub/lib/src/solver/dependency_queue.dart
|
| diff --git a/sdk/lib/_internal/pub/lib/src/solver/dependency_queue.dart b/sdk/lib/_internal/pub/lib/src/solver/dependency_queue.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4a94820c766342e4e7fcfee016ec76f548b71869
|
| --- /dev/null
|
| +++ b/sdk/lib/_internal/pub/lib/src/solver/dependency_queue.dart
|
| @@ -0,0 +1,126 @@
|
| +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library pub.solver.dependency_queue;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:collection' show Queue;
|
| +
|
| +import '../log.dart' as log;
|
| +import '../package.dart';
|
| +import 'backtracking_solver.dart';
|
| +
|
| +/// A queue of one package's dependencies, ordered by how the solver should
|
| +/// traverse them.
|
| +///
|
| +/// It prefers locked versions so that they stay locked if possible. Then it
|
| +/// prefers a currently selected package so that it only has to consider a
|
| +/// single version.
|
| +///
|
| +/// After that, it orders the remaining packages by the number of versions they
|
| +/// have so that packages with fewer versions are solved first. (If two
|
| +/// packages have the same number of versions, they are sorted alphabetically
|
| +/// just to be deterministic.)
|
| +///
|
| +/// Critically, this queue will *not* sort the dependencies by number of
|
| +/// versions until actually needed. This ensures we don't do any network
|
| +/// requests until we actually need to. In particular, it means that solving
|
| +/// a package graph with an already up-to-date lockfile will do no network
|
| +/// requests.
|
| +class DependencyQueue {
|
| + final BacktrackingSolver _solver;
|
| +
|
| + /// The dependencies for packages that have already been selected.
|
| + final Queue<PackageDep> _presorted;
|
| +
|
| + /// The dependencies on the remaining packages.
|
| + ///
|
| + /// This is lazily sorted right before the first item is requested.
|
| + final List<PackageDep> _remaining;
|
| +
|
| + bool _isSorted = false;
|
| +
|
| + /// Gets whether there are any dependencies left to iterate over.
|
| + bool get isEmpty => _presorted.isEmpty && _remaining.isEmpty;
|
| +
|
| + factory DependencyQueue(BacktrackingSolver solver,
|
| + Iterable<PackageDep> deps) {
|
| + // Separate out the presorted ones.
|
| + var presorted = <PackageDep>[];
|
| + var remaining = <PackageDep>[];
|
| +
|
| + for (var dep in deps) {
|
| + // Selected or locked packages come first.
|
| + if (solver.getSelected(dep.name) != null ||
|
| + solver.getLocked(dep.name) != null) {
|
| + presorted.add(dep);
|
| + } else {
|
| + remaining.add(dep);
|
| + }
|
| + }
|
| +
|
| + return new DependencyQueue._(solver, new Queue<PackageDep>.from(presorted),
|
| + remaining);
|
| + }
|
| +
|
| + DependencyQueue._(this._solver, this._presorted, this._remaining);
|
| +
|
| + /// Emits the next dependency in priority order.
|
| + ///
|
| + /// It is an error to call this if [isEmpty] returns `true`. Note that this
|
| + /// function is *not* re-entrant. You should only advance after the previous
|
| + /// advance has completed.
|
| + Future<PackageDep> advance() {
|
| + // Emit the sorted ones first.
|
| + if (_presorted.isNotEmpty) {
|
| + return new Future.value(_presorted.removeFirst());
|
| + }
|
| +
|
| + // Sort the remaining packages when we need the first one.
|
| + if (!_isSorted) return _sort().then((_) => _remaining.removeAt(0));
|
| +
|
| + return new Future.value(_remaining.removeAt(0));
|
| + }
|
| +
|
| + /// Sorts the unselected packages by number of versions and name.
|
| + Future _sort() {
|
| + return Future.wait(_remaining.map(_getNumVersions)).then((versions) {
|
| + // Map deps to the number of versions they have.
|
| + var versionMap = new Map.fromIterables(_remaining, versions);
|
| +
|
| + // Sort in best-first order to minimize backtracking.
|
| + _remaining.sort((a, b) {
|
| + // Traverse into packages with fewer versions since they will lead to
|
| + // less backtracking.
|
| + if (versionMap[a] != versionMap[b]) {
|
| + return versionMap[a].compareTo(versionMap[b]);
|
| + }
|
| +
|
| + // Otherwise, just sort by name so that it's deterministic.
|
| + return a.name.compareTo(b.name);
|
| + });
|
| +
|
| + _isSorted = true;
|
| + });
|
| + }
|
| +
|
| + /// Given a dependency, returns a future that completes to the number of
|
| + /// versions available for it.
|
| + Future<int> _getNumVersions(PackageDep dep) {
|
| + // There is only ever one version of the root package.
|
| + if (dep.isRoot) {
|
| + return new Future.value(1);
|
| + }
|
| +
|
| + return _solver.cache.getVersions(dep.toRef()).then((versions) {
|
| + return versions.length;
|
| + }).catchError((error, trace) {
|
| + // If it fails for any reason, just treat that as no versions. This
|
| + // will sort this reference higher so that we can traverse into it
|
| + // and report the error more properly.
|
| + log.solver("Could not get versions for $dep:\n$error\n\n$trace");
|
| + return 0;
|
| + });
|
| + }
|
| +}
|
|
|