Newer
Older
// Copyright (c) 2012, 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.
import 'package:pub_semver/pub_semver.dart';
import '../entrypoint.dart';
import '../log.dart' as log;
import '../package.dart';
import '../source/hosted.dart';
import '../validator.dart';
/// The range of all pub versions that don't support `^` version constraints.
///
/// This is the actual range of pub versions, whereas [_postCaretPubVersions] is
/// the nicer-looking range that doesn't include a prerelease tag.
final _preCaretPubVersions = new VersionConstraint.parse("<1.8.0-dev.3.0");
/// The range of all pub versions that do support `^` version constraints.
///
/// This is intersected with the user's SDK constraint to provide a suggested
/// constraint.
final _postCaretPubVersions = new VersionConstraint.parse("^1.8.0");
/// A validator that validates a package's dependencies.
class DependencyValidator extends Validator {
/// Whether the SDK constraint guarantees that `^` version constraints are
/// safe.
bool get _caretAllowed => entrypoint.root.pubspec.environment.sdkVersion
.intersect(_preCaretPubVersions).isEmpty;
DependencyValidator(Entrypoint entrypoint)
: super(entrypoint);
Future validate() async {
var caretDeps = [];
for (var dependency in entrypoint.root.pubspec.dependencies) {
if (dependency.source != "hosted") {
await _warnAboutSource(dependency);
} else if (dependency.constraint.isAny) {
_warnAboutNoConstraint(dependency);
} else if (dependency.constraint is Version) {
_warnAboutSingleVersionConstraint(dependency);
} else if (dependency.constraint is VersionRange) {
if (dependency.constraint.min == null) {
_warnAboutNoConstraintLowerBound(dependency);
} else if (dependency.constraint.max == null) {
_warnAboutNoConstraintUpperBound(dependency);
}
if (dependency.constraint.toString().startsWith("^")) {
caretDeps.add(dependency);
}
}
if (caretDeps.isNotEmpty && !_caretAllowed) {
_errorAboutCaretConstraints(caretDeps);
}
}
/// Warn that dependencies should use the hosted source.
Future _warnAboutSource(PackageDep dep) async {
var versions;
try {
var ids = await entrypoint.cache.sources['hosted']
.getVersions(HostedSource.refFor(dep.name));
versions = ids.map((id) => id.version).toList();
} catch (error) {
versions = [];
}
var constraint;
var primary = Version.primary(versions);
if (primary != null) {
constraint = _constraintForVersion(primary);
} else {
constraint = dep.constraint.toString();
if (!dep.constraint.isAny && dep.constraint is! Version) {
constraint = '"$constraint"';
rnystrom@google.com
committed
}
}
// Path sources are errors. Other sources are just warnings.
var messages = warnings;
if (dep.source == "path") {
messages = errors;
}
rnystrom@google.com
committed
messages.add('Don\'t depend on "${dep.name}" from the ${dep.source} '
'source. Use the hosted source instead. For example:\n'
'\n'
'dependencies:\n'
' ${dep.name}: $constraint\n'
'\n'
'Using the hosted source ensures that everyone can download your '
'package\'s dependencies along with your package.');
}
/// Warn that dependencies should have version constraints.
void _warnAboutNoConstraint(PackageDep dep) {
rnystrom@google.com
committed
var message = 'Your dependency on "${dep.name}" should have a version '
var locked = entrypoint.lockFile.packages[dep.name];
if (locked != null) {
message = '$message For example:\n'
'\n'
'dependencies:\n'
rnystrom@google.com
committed
' ${dep.name}: ${_constraintForVersion(locked.version)}\n';
}
warnings.add("$message\n"
'Without a constraint, you\'re promising to support ${log.bold("all")} '
'future versions of "${dep.name}".');
}
/// Warn that dependencies should allow more than a single version.
void _warnAboutSingleVersionConstraint(PackageDep dep) {
warnings.add(
'Your dependency on "${dep.name}" should allow more than one version. '
'For example:\n'
'\n'
'dependencies:\n'
' ${dep.name}: ${_constraintForVersion(dep.constraint)}\n'
'\n'
'Constraints that are too tight will make it difficult for people to '
'use your package\n'
'along with other packages that also depend on "${dep.name}".');
}
/// Warn that dependencies should have lower bounds on their constraints.
void _warnAboutNoConstraintLowerBound(PackageDep dep) {
var message = 'Your dependency on "${dep.name}" should have a lower bound.';
var locked = entrypoint.lockFile.packages[dep.name];
if (locked != null) {
var constraint;
if (locked.version == (dep.constraint as VersionRange).max) {
constraint = _constraintForVersion(locked.version);
} else {
constraint = '">=${locked.version} ${dep.constraint}"';
}
message = '$message For example:\n'
'\n'
'dependencies:\n'
' ${dep.name}: $constraint\n';
}
warnings.add("$message\n"
'Without a constraint, you\'re promising to support ${log.bold("all")} '
'previous versions of "${dep.name}".');
}
/// Warn that dependencies should have upper bounds on their constraints.
void _warnAboutNoConstraintUpperBound(PackageDep dep) {
var constraint;
if ((dep.constraint as VersionRange).includeMin) {
constraint = _constraintForVersion((dep.constraint as VersionRange).min);
} else {
constraint = '"${dep.constraint} '
'<${(dep.constraint as VersionRange).min.nextBreaking}"';
}
warnings.add(
'Your dependency on "${dep.name}" should have an upper bound. For '
'example:\n'
'\n'
'dependencies:\n'
'\n'
'Without an upper bound, you\'re promising to support '
'${log.bold("all")} future versions of ${dep.name}.');
/// Emits an error for any version constraints that use `^` without an
/// appropriate SDK constraint.
void _errorAboutCaretConstraints(List<PackageDep> caretDeps) {
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
var newSdkConstraint = entrypoint.root.pubspec.environment.sdkVersion
.intersect(_postCaretPubVersions);
if (newSdkConstraint.isEmpty) newSdkConstraint = _postCaretPubVersions;
var buffer = new StringBuffer(
"Older versions of pub don't support ^ version constraints.\n"
"Make sure your SDK constraint excludes those old versions:\n"
"\n"
"environment:\n"
" sdk: \"$newSdkConstraint\"\n"
"\n");
if (caretDeps.length == 1) {
buffer.writeln("Or use a fully-expanded constraint:");
} else {
buffer.writeln("Or use fully-expanded constraints:");
}
buffer.writeln();
buffer.writeln("dependencies:");
caretDeps.forEach((dep) {
VersionRange constraint = dep.constraint;
buffer.writeln(
" ${dep.name}: \">=${constraint.min} <${constraint.max}\"");
});
errors.add(buffer.toString().trim());
}
/// Returns the suggested version constraint for a dependency that was tested
/// against [version].
String _constraintForVersion(Version version) {
if (_caretAllowed) return "^$version";
return '">=$version <${version.nextBreaking}"';