| Index: sdk/lib/_internal/pub/lib/src/solver/backtracking_solver.dart
|
| diff --git a/sdk/lib/_internal/pub/lib/src/solver/backtracking_solver.dart b/sdk/lib/_internal/pub/lib/src/solver/backtracking_solver.dart
|
| index d9c5fb4dff9dce45787cab551a4a8b0739ef756f..743ef038dfbed8c5680caff8f609ed2d3475da7b 100644
|
| --- a/sdk/lib/_internal/pub/lib/src/solver/backtracking_solver.dart
|
| +++ b/sdk/lib/_internal/pub/lib/src/solver/backtracking_solver.dart
|
| @@ -126,7 +126,7 @@ class BacktrackingSolver {
|
| ///
|
| /// Completes with a list of specific package versions if successful or an
|
| /// error if it failed to find a solution.
|
| - Future<SolveResult> solve() {
|
| + Future<SolveResult> solve() async {
|
| var stopwatch = new Stopwatch();
|
|
|
| _logParameters();
|
| @@ -135,40 +135,36 @@ class BacktrackingSolver {
|
| var overrides = _overrides.values.toList();
|
| overrides.sort((a, b) => a.name.compareTo(b.name));
|
|
|
| - // TODO(nweiz): Use async/await here once
|
| - // https://github.com/dart-lang/async_await/issues/79 is fixed.
|
| - return new Future.sync(() {
|
| + try {
|
| stopwatch.start();
|
|
|
| // Pre-cache the root package's known pubspec.
|
| cache.cache(new PackageId.root(root), root.pubspec);
|
|
|
| _validateSdkConstraint(root.pubspec);
|
| - return _traverseSolution();
|
| - }).then((packages) {
|
| + var packages = await _traverseSolution();
|
| +
|
| var pubspecs = new Map.fromIterable(packages,
|
| key: (id) => id.name,
|
| value: (id) => cache.getCachedPubspec(id));
|
|
|
| - return Future.wait(
|
| - packages.map((id) => sources[id.source].resolveId(id)))
|
| - .then((packages) {
|
| - return new SolveResult.success(sources, root, lockFile, packages,
|
| - overrides, pubspecs, _getAvailableVersions(packages),
|
| - attemptedSolutions);
|
| - });
|
| - }).catchError((error) {
|
| - if (error is! SolveFailure) throw error;
|
| + var resolved = await Future.wait(
|
| + packages.map((id) => sources[id.source].resolveId(id)));
|
| +
|
| + return new SolveResult.success(sources, root, lockFile, resolved,
|
| + overrides, pubspecs, _getAvailableVersions(resolved),
|
| + attemptedSolutions);
|
| + } on SolveFailure catch (error) {
|
| // Wrap a failure in a result so we can attach some other data.
|
| return new SolveResult.failure(sources, root, lockFile, overrides,
|
| error, attemptedSolutions);
|
| - }).whenComplete(() {
|
| + } finally {
|
| // Gather some solving metrics.
|
| var buffer = new StringBuffer();
|
| buffer.writeln('${runtimeType} took ${stopwatch.elapsed} seconds.');
|
| buffer.writeln(cache.describeResults());
|
| log.solver(buffer);
|
| - });
|
| + }
|
| }
|
|
|
| /// Generates a map containing all of the known available versions for each
|
| @@ -274,9 +270,9 @@ class BacktrackingSolver {
|
| /// possible solution.
|
| ///
|
| /// Returns `true` if there is a new solution to try.
|
| - Future<bool> _backtrack(SolveFailure failure) {
|
| + Future<bool> _backtrack(SolveFailure failure) async {
|
| // Bail if there is nothing to backtrack to.
|
| - if (_selected.isEmpty) return new Future.value(false);
|
| + if (_selected.isEmpty) return false;
|
|
|
| // Mark any packages that may have led to this failure so that we know to
|
| // consider them when backtracking.
|
| @@ -289,28 +285,22 @@ class BacktrackingSolver {
|
| }
|
|
|
| // Advance past the current version of the leaf-most package.
|
| - advanceVersion() {
|
| + while (!_selected.isEmpty) {
|
| _backjump(failure);
|
| var previous = _selected.last.current;
|
| - return _selected.last.advance().then((success) {
|
| - if (success) {
|
| - logSolve();
|
| - return true;
|
| - }
|
| -
|
| - logSolve('$previous is last version, backtracking');
|
| -
|
| - // That package has no more versions, so pop it and try the next one.
|
| - _selected.removeLast();
|
| - if (_selected.isEmpty) return false;
|
| -
|
| - // If we got here, the leafmost package was discarded so we need to
|
| - // advance the next one.
|
| - return advanceVersion();
|
| - });
|
| + var success = await _selected.last.advance();
|
| + if (success) {
|
| + logSolve();
|
| + return true;
|
| + }
|
| +
|
| + logSolve('$previous is last version, backtracking');
|
| +
|
| + // That package has no more versions, so pop it and try the next one.
|
| + _selected.removeLast();
|
| }
|
|
|
| - return advanceVersion();
|
| + return false;
|
| }
|
|
|
| /// Walks the selected packages from most to least recent to determine which
|
| @@ -489,155 +479,156 @@ class Traverser {
|
| /// Completes to a list of package IDs if the traversal completed
|
| /// successfully and found a solution. Completes to an error if the traversal
|
| /// failed. Otherwise, recurses to the next package in the queue, etc.
|
| - Future<List<PackageId>> _traversePackage() {
|
| - if (_packages.isEmpty) {
|
| - // We traversed the whole graph. If we got here, we successfully found
|
| - // a solution.
|
| - return new Future<List<PackageId>>.value(_visited.toList());
|
| - }
|
| + Future<List<PackageId>> _traversePackage() async {
|
| + // TODO(nweiz): Use a real while loop when issue 23394 is fixed.
|
| + await Future.doWhile(() async {
|
| + // Move past any packages we've already traversed.
|
| + while (_packages.isNotEmpty && _visited.contains(_packages.first)) {
|
| + _packages.removeFirst();
|
| + }
|
|
|
| - var id = _packages.removeFirst();
|
| + // If there are no more packages to traverse, we've traversed the whole
|
| + // graph. If we got here, we successfully found a solution.
|
| + if (_packages.isEmpty) return false;
|
|
|
| - // Don't visit the same package twice.
|
| - if (_visited.contains(id)) {
|
| - return _traversePackage();
|
| + var id = _packages.removeFirst();
|
| + _visited.add(id);
|
| + await _traverseDeps(id, await _dependencyQueueFor(id));
|
| + return true;
|
| + });
|
| +
|
| + return _visited.toList();
|
| + }
|
| +
|
| + Future<DependencyQueue> _dependencyQueueFor(PackageId id) async {
|
| + var pubspec;
|
| + try {
|
| + pubspec = await _solver.cache.getPubspec(id);
|
| + } on PackageNotFoundException catch (error) {
|
| + // We can only get here if the lockfile refers to a specific package
|
| + // version that doesn't exist (probably because it was yanked).
|
| + throw new NoVersionException(id.name, null, id.version, []);
|
| }
|
| - _visited.add(id);
|
|
|
| - return _solver.cache.getPubspec(id).then((pubspec) {
|
| - _validateSdkConstraint(pubspec);
|
| + _validateSdkConstraint(pubspec);
|
|
|
| - var deps = pubspec.dependencies.toSet();
|
| + var deps = pubspec.dependencies.toSet();
|
|
|
| - if (id.isRoot) {
|
| - // Include dev dependencies of the root package.
|
| - deps.addAll(pubspec.devDependencies);
|
| + if (id.isRoot) {
|
| + // Include dev dependencies of the root package.
|
| + deps.addAll(pubspec.devDependencies);
|
|
|
| - // Add all overrides. This ensures a dependency only present as an
|
| - // override is still included.
|
| - deps.addAll(_solver._overrides.values);
|
| - }
|
| + // Add all overrides. This ensures a dependency only present as an
|
| + // override is still included.
|
| + deps.addAll(_solver._overrides.values);
|
| + }
|
|
|
| - // Replace any overridden dependencies.
|
| - deps = deps.map((dep) {
|
| - var override = _solver._overrides[dep.name];
|
| - if (override != null) return override;
|
| + // Replace any overridden dependencies.
|
| + deps = deps.map((dep) {
|
| + var override = _solver._overrides[dep.name];
|
| + if (override != null) return override;
|
|
|
| - // Not overridden.
|
| - return dep;
|
| - }).toSet();
|
| + // Not overridden.
|
| + return dep;
|
| + }).toSet();
|
|
|
| - // Make sure the package doesn't have any bad dependencies.
|
| - for (var dep in deps) {
|
| - if (!dep.isRoot && _solver.sources[dep.source] is UnknownSource) {
|
| - throw new UnknownSourceException(id.name,
|
| - [new Dependency(id.name, id.version, dep)]);
|
| - }
|
| + // Make sure the package doesn't have any bad dependencies.
|
| + for (var dep in deps) {
|
| + if (!dep.isRoot && _solver.sources[dep.source] is UnknownSource) {
|
| + throw new UnknownSourceException(id.name,
|
| + [new Dependency(id.name, id.version, dep)]);
|
| }
|
| + }
|
|
|
| - return _traverseDeps(id, new DependencyQueue(_solver, deps));
|
| - }).catchError((error) {
|
| - if (error is! PackageNotFoundException) throw error;
|
| -
|
| - // We can only get here if the lockfile refers to a specific package
|
| - // version that doesn't exist (probably because it was yanked).
|
| - throw new NoVersionException(id.name, null, id.version, []);
|
| - });
|
| + return new DependencyQueue(_solver, deps);
|
| }
|
|
|
| /// Traverses the references that [depender] depends on, stored in [deps].
|
| ///
|
| - /// Desctructively modifies [deps]. Completes to a list of packages if the
|
| - /// traversal is complete. Completes it to an error if a failure occurred.
|
| - /// Otherwise, recurses.
|
| - Future<List<PackageId>> _traverseDeps(PackageId depender,
|
| - DependencyQueue deps) {
|
| - // Move onto the next package if we've traversed all of these references.
|
| - if (deps.isEmpty) return _traversePackage();
|
| -
|
| - return resetStack(() {
|
| - return deps.advance().then((dep) {
|
| - var dependency = new Dependency(depender.name, depender.version, dep);
|
| - return _registerDependency(dependency).then((_) {
|
| - if (dep.name == "barback") return _addImplicitDependencies();
|
| - });
|
| - }).then((_) => _traverseDeps(depender, deps));
|
| + /// Desctructively modifies [deps].
|
| + Future _traverseDeps(PackageId depender, DependencyQueue deps) {
|
| + // TODO(nweiz): Use a real while loop when issue 23394 is fixed.
|
| + return Future.doWhile(() async {
|
| + if (deps.isEmpty) return false;
|
| +
|
| + var dep = await deps.advance();
|
| + var dependency = new Dependency(depender.name, depender.version, dep);
|
| + await _registerDependency(dependency);
|
| + if (dep.name == "barback") await _addImplicitDependencies();
|
| + return true;
|
| });
|
| }
|
|
|
| /// Register [dependency]'s constraints on the package it depends on and
|
| /// enqueues the package for processing if necessary.
|
| - Future _registerDependency(Dependency dependency) {
|
| - return new Future.sync(() {
|
| - _validateDependency(dependency);
|
| -
|
| - var dep = dependency.dep;
|
| - var dependencies = _getDependencies(dep.name);
|
| - dependencies.add(dependency);
|
| -
|
| - var constraint = _getConstraint(dep.name);
|
| -
|
| - // See if it's possible for a package to match that constraint.
|
| - if (constraint.isEmpty) {
|
| - var constraints = dependencies
|
| - .map((dep) => " ${dep.dep.constraint} from ${dep.depender}")
|
| - .join('\n');
|
| - _solver.logSolve(
|
| - 'disjoint constraints on ${dep.name}:\n$constraints');
|
| - throw new DisjointConstraintException(dep.name, dependencies);
|
| - }
|
| + Future _registerDependency(Dependency dependency) async {
|
| + _validateDependency(dependency);
|
|
|
| - var selected = _validateSelected(dep, constraint);
|
| - if (selected != null) {
|
| - // The selected package version is good, so enqueue it to traverse
|
| - // into it.
|
| - _packages.add(selected);
|
| - return null;
|
| - }
|
| + var dep = dependency.dep;
|
| + var dependencies = _getDependencies(dep.name);
|
| + dependencies.add(dependency);
|
| +
|
| + var constraint = _getConstraint(dep.name);
|
|
|
| - // We haven't selected a version. Try all of the versions that match
|
| - // the constraints we currently have for this package.
|
| - var locked = _getValidLocked(dep.name);
|
| + // See if it's possible for a package to match that constraint.
|
| + if (constraint.isEmpty) {
|
| + var constraints = dependencies
|
| + .map((dep) => " ${dep.dep.constraint} from ${dep.depender}")
|
| + .join('\n');
|
| + _solver.logSolve(
|
| + 'disjoint constraints on ${dep.name}:\n$constraints');
|
| + throw new DisjointConstraintException(dep.name, dependencies);
|
| + }
|
|
|
| - return VersionQueue.create(locked, () {
|
| - return _getAllowedVersions(dep);
|
| - }).then((versions) => _packages.add(_solver.select(versions)));
|
| - });
|
| + var selected = _validateSelected(dep, constraint);
|
| + if (selected != null) {
|
| + // The selected package version is good, so enqueue it to traverse
|
| + // into it.
|
| + _packages.add(selected);
|
| + return;
|
| + }
|
| +
|
| + // We haven't selected a version. Try all of the versions that match
|
| + // the constraints we currently have for this package.
|
| + var locked = _getValidLocked(dep.name);
|
| +
|
| + var versions = await VersionQueue.create(
|
| + locked, () => _getAllowedVersions(dep));
|
| + _packages.add(_solver.select(versions));
|
| }
|
|
|
| /// Gets all versions of [dep] that match the current constraints placed on
|
| /// it.
|
| - Future<Iterable<PackageId>> _getAllowedVersions(PackageDep dep) {
|
| + Future<Iterable<PackageId>> _getAllowedVersions(PackageDep dep) async {
|
| var constraint = _getConstraint(dep.name);
|
| - return _solver.cache.getVersions(dep.toRef()).then((versions) {
|
| - var allowed = versions.where((id) => constraint.allows(id.version));
|
| + var versions;
|
| + try {
|
| + versions = await _solver.cache.getVersions(dep.toRef());
|
| + } on PackageNotFoundException catch (error) {
|
| + // Show the user why the package was being requested.
|
| + throw new DependencyNotFoundException(
|
| + dep.name, error, _getDependencies(dep.name));
|
| + }
|
|
|
| - if (allowed.isEmpty) {
|
| - _solver.logSolve('no versions for ${dep.name} match $constraint');
|
| - throw new NoVersionException(dep.name, null, constraint,
|
| - _getDependencies(dep.name));
|
| - }
|
| + var allowed = versions.where((id) => constraint.allows(id.version));
|
|
|
| - // If we're doing an upgrade on this package, only allow the latest
|
| - // version.
|
| - if (_solver._forceLatest.contains(dep.name)) allowed = [allowed.first];
|
| + if (allowed.isEmpty) {
|
| + _solver.logSolve('no versions for ${dep.name} match $constraint');
|
| + throw new NoVersionException(dep.name, null, constraint,
|
| + _getDependencies(dep.name));
|
| + }
|
|
|
| - // Remove the locked version, if any, since that was already handled.
|
| - var locked = _getValidLocked(dep.name);
|
| - if (locked != null) {
|
| - allowed = allowed.where((dep) => dep.version != locked.version);
|
| - }
|
| + // If we're doing an upgrade on this package, only allow the latest
|
| + // version.
|
| + if (_solver._forceLatest.contains(dep.name)) allowed = [allowed.first];
|
|
|
| - return allowed;
|
| - }).catchError((error, stackTrace) {
|
| - if (error is PackageNotFoundException) {
|
| - // Show the user why the package was being requested.
|
| - throw new DependencyNotFoundException(
|
| - dep.name, error, _getDependencies(dep.name));
|
| - }
|
| + // Remove the locked version, if any, since that was already handled.
|
| + var locked = _getValidLocked(dep.name);
|
| + if (locked != null) {
|
| + allowed = allowed.where((dep) => dep.version != locked.version);
|
| + }
|
|
|
| - throw error;
|
| - });
|
| + return allowed;
|
| }
|
|
|
| /// Ensures that dependency [dep] from [depender] is consistent with the
|
| @@ -692,11 +683,11 @@ class Traverser {
|
| ///
|
| /// Pub has an implicit version constraint on barback and various other
|
| /// packages used in barback's plugin isolate.
|
| - Future _addImplicitDependencies() {
|
| + Future _addImplicitDependencies() async {
|
| /// Ensure we only add the barback dependency once.
|
| - if (_getDependencies("barback").length != 1) return new Future.value();
|
| + if (_getDependencies("barback").length != 1) return;
|
|
|
| - return Future.wait(barback.pubConstraints.keys.map((depName) {
|
| + await Future.wait(barback.pubConstraints.keys.map((depName) {
|
| var constraint = barback.pubConstraints[depName];
|
| _solver.logSolve('add implicit $constraint pub dependency on '
|
| '$depName');
|
|
|