Skip to content

Various refactors in preparation for adding error reporting #1779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/src/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import 'http.dart';
import 'io.dart';
import 'log.dart' as log;
import 'sdk.dart' as sdk;
import 'solver.dart';
import 'utils.dart';

class PubCommandRunner extends CommandRunner {
Expand Down Expand Up @@ -232,12 +231,13 @@ and include the logs in an issue on https://github.com/dart-lang/pub/issues/new
int _chooseExitCode(exception) {
while (exception is WrappedException) exception = exception.innerError;

// TODO(nweiz): Emit an UNAVAILABLE exit code for a SolveFailure that was
// (transitively) caused by a package not existing.
if (exception is HttpException ||
exception is http.ClientException ||
exception is SocketException ||
exception is TlsException ||
exception is PubHttpException ||
exception is DependencyNotFoundException) {
exception is PubHttpException) {
return exit_codes.UNAVAILABLE;
} else if (exception is FormatException || exception is DataException) {
return exit_codes.DATA;
Expand Down
2 changes: 0 additions & 2 deletions lib/src/entrypoint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,6 @@ class Entrypoint {
}
}

if (!result.succeeded) throw result.error;

result.showReport(type);

if (dryRun) {
Expand Down
11 changes: 4 additions & 7 deletions lib/src/global_packages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,11 @@ class GlobalPackages {
dependencies: [dep], sources: cache.sources));

// Resolve it and download its dependencies.
//
// TODO(nweiz): If this produces a SolveFailure that's caused by [dep] not
// being available, report that as a [dataError].
var result = await resolveVersions(SolveType.GET, cache, root);
if (!result.succeeded) {
// If the package specified by the user doesn't exist, we want to
// surface that as a [DataError] with the associated exit code.
if (result.error.package != dep.name) throw result.error;
if (result.error is NoVersionException) dataError(result.error.message);
throw result.error;
}

result.showReport(SolveType.GET);

// Make sure all of the dependencies are locally installed.
Expand Down
28 changes: 22 additions & 6 deletions lib/src/package_name.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ class PackageRef extends PackageName {
}

String toTerseString() {
if (isMagic) return name;
if (isRoot || source.name != 'hosted') return "$name";
if (isMagic || isRoot || source.name == 'hosted') return name;
return "$name from $source";
}

Expand Down Expand Up @@ -157,8 +156,8 @@ class PackageId extends PackageName {
}

String toTerseString() {
if (isMagic) return name;
if (isRoot || source.name == 'hosted') return "$name $version";
if (isMagic || isRoot) return name;
if (source.name == 'hosted') return "$name $version";
return "$name $version from $source";
}
}
Expand Down Expand Up @@ -230,8 +229,8 @@ class PackageRange extends PackageName {
}

String toTerseString() {
if (isMagic) return name;
if (isRoot || source.name == 'hosted') return "$name $constraint";
if (isMagic || isRoot) return name;
if (source.name == 'hosted') return "$name $constraint";
return "$name $constraint from $source";
}

Expand All @@ -242,6 +241,23 @@ class PackageRange extends PackageName {
features: new Map.from(this.features)..addAll(features));
}

/// Returns a copy of [this] with the same semantics, but with a `^`-style
/// constraint if possible.
PackageRange withTerseConstraint() {
if (constraint is! VersionRange) return this;
if (constraint.toString().startsWith("^")) return this;

var range = constraint as VersionRange;
if (range.includeMin &&
!range.includeMax &&
range.min != null &&
range.max == range.min.nextBreaking) {
return withConstraint(new VersionConstraint.compatibleWith(range.min));
} else {
return this;
}
}

/// Whether [id] satisfies this dependency.
///
/// Specifically, whether [id] refers to the same package as [this] *and*
Expand Down
18 changes: 0 additions & 18 deletions lib/src/solver/dependency.dart

This file was deleted.

198 changes: 10 additions & 188 deletions lib/src/solver/failure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,201 +2,23 @@
// 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.

import 'dart:convert';

import 'package:pub_semver/pub_semver.dart';
import 'package:stack_trace/stack_trace.dart';

import '../exceptions.dart';
import '../log.dart' as log;
import '../package_name.dart';
import 'dependency.dart';
import 'incompatibility.dart';

/// Base class for all failures that can occur while trying to resolve versions.
abstract class SolveFailure implements ApplicationException {
/// The name of the package whose version could not be solved.
///
/// Will be `null` if the failure is not specific to one package.
final String package;

/// The known dependencies on [package] at the time of the failure.
class SolveFailure implements ApplicationException {
/// The root incompatibility.
///
/// Will be an empty collection if the failure is not specific to one package.
final Iterable<Dependency> dependencies;
/// This will always indicate that the root package is unselectable. That is,
/// it will have one term, which will be the root package.
final Incompatibility incompatibility;

String get message => toString();

/// A message describing the specific kind of solve failure.
String get _message {
throw new UnimplementedError("Must override _message or toString().");
}

SolveFailure(this.package, Iterable<Dependency> dependencies)
: dependencies = dependencies != null ? dependencies : <Dependency>[];

String toString() {
if (dependencies.isEmpty) return _message;

var buffer = new StringBuffer();
buffer.write("$_message:");

var sorted = dependencies.toList();
sorted.sort((a, b) => a.depender.name.compareTo(b.depender.name));

for (var dep in sorted) {
buffer.writeln();
buffer.write("- ${log.bold(dep.depender.name)}");
if (!dep.depender.isMagic && !dep.depender.isRoot) {
buffer.write(" ${dep.depender.version}");
}
buffer.write(" ${_describeDependency(dep.dep)}");
}

return buffer.toString();
}

/// Describes a dependency's reference in the output message.
///
/// Override this to highlight which aspect of [dep] led to the failure.
String _describeDependency(PackageRange dep) {
var description = "depends on version ${dep.constraint}";
if (dep.features.isNotEmpty) description += " ${dep.featureDescription}";
return description;
SolveFailure(this.incompatibility) {
assert(incompatibility.terms.single.package.isRoot);
}
}

/// Exception thrown when the current SDK's version does not match a package's
/// constraint on it.
class BadSdkVersionException extends SolveFailure {
final String _message;

BadSdkVersionException(String package, String message)
: _message = message,
super(package, null);
}

/// Exception thrown when the [VersionConstraint] used to match a package is
/// valid (i.e. non-empty), but there are no available versions of the package
/// that fit that constraint.
class NoVersionException extends SolveFailure {
final VersionConstraint constraint;

/// The last selected version of the package that failed to meet the new
/// constraint.
///
/// This will be `null` when the failure occurred because there are no
/// versions of the package *at all* that match the constraint. It will be
/// non-`null` when a version was selected, but then the solver tightened a
/// constraint such that that version was no longer allowed.
final Version version;

NoVersionException(String package, this.version, this.constraint,
Iterable<Dependency> dependencies)
: super(package, dependencies);

String get _message {
if (version == null) {
return "Package $package has no versions that match $constraint derived "
"from";
}

return "Package $package $version does not match $constraint derived from";
}
}

// TODO(rnystrom): Report the list of depending packages and their constraints.
/// Exception thrown when the most recent version of [package] must be selected,
/// but doesn't match the [VersionConstraint] imposed on the package.
class CouldNotUpgradeException extends SolveFailure {
final VersionConstraint constraint;
final Version best;

CouldNotUpgradeException(String package, this.constraint, this.best)
: super(package, null);

String get _message =>
"The latest version of $package, $best, does not match $constraint.";
}

/// Exception thrown when the [VersionConstraint] used to match a package is
/// the empty set: in other words, multiple packages depend on it and have
/// conflicting constraints that have no overlap.
class DisjointConstraintException extends SolveFailure {
DisjointConstraintException(String package, Iterable<Dependency> dependencies)
: super(package, dependencies);

String get _message => "Incompatible version constraints on $package";
}

/// Exception thrown when two packages with the same name but different sources
/// are depended upon.
class SourceMismatchException extends SolveFailure {
String get _message => "Incompatible dependencies on $package";

SourceMismatchException(String package, Iterable<Dependency> dependencies)
: super(package, dependencies);

String _describeDependency(PackageRange dep) =>
"depends on it from source ${dep.source}";
}

/// Exception thrown when a dependency on an unknown source name is found.
class UnknownSourceException extends SolveFailure {
UnknownSourceException(String package, Iterable<Dependency> dependencies)
: super(package, dependencies);

String toString() {
var dep = dependencies.single;
return 'Package ${dep.depender.name} depends on ${dep.dep.name} from '
'unknown source "${dep.dep.source}".';
}
}

/// Exception thrown when two packages with the same name and source but
/// different descriptions are depended upon.
class DescriptionMismatchException extends SolveFailure {
String get _message => "Incompatible dependencies on $package";

DescriptionMismatchException(
String package, Iterable<Dependency> dependencies)
: super(package, dependencies);

String _describeDependency(PackageRange dep) {
// TODO(nweiz): Dump descriptions to YAML when that's supported.
return "depends on it with description ${JSON.encode(dep.description)}";
}
}

/// Exception thrown when a dependency could not be found in its source.
///
/// Unlike [PackageNotFoundException], this includes information about the
/// dependent packages requesting the missing one.
class DependencyNotFoundException extends SolveFailure
implements WrappedException {
final PackageNotFoundException innerError;
Chain get innerChain => innerError.innerChain;

String get _message => "${innerError.message}\nDepended on by";

DependencyNotFoundException(
String package, this.innerError, Iterable<Dependency> dependencies)
: super(package, dependencies);

/// The failure isn't because of the version of description of the package,
/// it's the package itself that can't be found, so just show the name and no
/// descriptive details.
String _describeDependency(PackageRange dep) => "";
}

/// An exception thrown when a dependency requires a feature that doesn't exist.
class MissingFeatureException extends SolveFailure {
final Version version;
final String feature;

String get _message =>
"$package $version doesn't have a feature named $feature";

MissingFeatureException(String package, this.version, this.feature,
Iterable<Dependency> dependencies)
: super(package, dependencies);
// TODO(nweiz): Produce a useful error message.
String toString() => "Tough luck, Chuck!";
}
Loading