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 library pub.validator.dependency; | 5 library pub.validator.dependency; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 | 8 |
9 import 'package:pub_semver/pub_semver.dart'; | 9 import 'package:pub_semver/pub_semver.dart'; |
10 | 10 |
11 import '../entrypoint.dart'; | 11 import '../entrypoint.dart'; |
12 import '../log.dart' as log; | 12 import '../log.dart' as log; |
13 import '../package.dart'; | 13 import '../package.dart'; |
| 14 import '../utils.dart'; |
14 import '../validator.dart'; | 15 import '../validator.dart'; |
15 | 16 |
| 17 /// The range of all pub versions that don't support `^` version constraints. |
| 18 final _preCaretPubVersions = new VersionConstraint.parse("<1.8.0-dev.3.0"); |
| 19 |
| 20 // TODO(nweiz): replace this with "^1.8.0" for the 1.8 release. |
| 21 /// The range of all pub versions that do support `^` version constraints. |
| 22 /// |
| 23 /// This is intersected with the user's SDK constraint to provide a suggested |
| 24 /// constraint. |
| 25 final _postCaretPubVersions = new VersionConstraint.parse("^1.8.0-dev.3.0"); |
| 26 |
16 /// A validator that validates a package's dependencies. | 27 /// A validator that validates a package's dependencies. |
17 class DependencyValidator extends Validator { | 28 class DependencyValidator extends Validator { |
| 29 /// Whether the SDK constraint guarantees that `^` version constraints are |
| 30 /// safe. |
| 31 bool get _caretAllowed => entrypoint.root.pubspec.environment.sdkVersion |
| 32 .intersect(_preCaretPubVersions).isEmpty; |
| 33 |
18 DependencyValidator(Entrypoint entrypoint) | 34 DependencyValidator(Entrypoint entrypoint) |
19 : super(entrypoint); | 35 : super(entrypoint); |
20 | 36 |
21 Future validate() { | 37 Future validate() async { |
22 return Future.forEach(entrypoint.root.pubspec.dependencies, (dependency) { | 38 var caretDeps = []; |
| 39 |
| 40 // TODO(nweiz): Replace this with a real for/in loop when we update |
| 41 // async_await. |
| 42 await Future.forEach(entrypoint.root.pubspec.dependencies, |
| 43 (dependency) async { |
23 if (dependency.source != "hosted") { | 44 if (dependency.source != "hosted") { |
24 return _warnAboutSource(dependency); | 45 await _warnAboutSource(dependency); |
25 } | 46 } else if (dependency.constraint.isAny) { |
26 | |
27 if (dependency.constraint.isAny) { | |
28 _warnAboutNoConstraint(dependency); | 47 _warnAboutNoConstraint(dependency); |
29 } else if (dependency.constraint is Version) { | 48 } else if (dependency.constraint is Version) { |
30 _warnAboutSingleVersionConstraint(dependency); | 49 _warnAboutSingleVersionConstraint(dependency); |
31 } else if (dependency.constraint is VersionRange) { | 50 } else if (dependency.constraint is VersionRange) { |
32 if (dependency.constraint.min == null) { | 51 if (dependency.constraint.min == null) { |
33 _warnAboutNoConstraintLowerBound(dependency); | 52 _warnAboutNoConstraintLowerBound(dependency); |
34 } else if (dependency.constraint.max == null) { | 53 } else if (dependency.constraint.max == null) { |
35 _warnAboutNoConstraintUpperBound(dependency); | 54 _warnAboutNoConstraintUpperBound(dependency); |
36 } | 55 } |
| 56 |
| 57 if (dependency.constraint.toString().startsWith("^")) { |
| 58 caretDeps.add(dependency); |
| 59 } |
37 } | 60 } |
| 61 }); |
38 | 62 |
39 return new Future.value(); | 63 if (caretDeps.isNotEmpty && !_caretAllowed) { |
40 }); | 64 _errorAboutCaretConstraints(caretDeps); |
| 65 } |
41 } | 66 } |
42 | 67 |
43 /// Warn that dependencies should use the hosted source. | 68 /// Warn that dependencies should use the hosted source. |
44 Future _warnAboutSource(PackageDep dep) { | 69 Future _warnAboutSource(PackageDep dep) { |
45 return entrypoint.cache.sources['hosted'] | 70 return entrypoint.cache.sources['hosted'] |
46 .getVersions(dep.name, dep.name) | 71 .getVersions(dep.name, dep.name) |
47 .catchError((e) => <Version>[]) | 72 .catchError((e) => <Version>[]) |
48 .then((versions) { | 73 .then((versions) { |
49 var constraint; | 74 var constraint; |
50 var primary = Version.primary(versions); | 75 var primary = Version.primary(versions); |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
121 'dependencies:\n' | 146 'dependencies:\n' |
122 ' ${dep.name}: $constraint\n'; | 147 ' ${dep.name}: $constraint\n'; |
123 } | 148 } |
124 warnings.add("$message\n" | 149 warnings.add("$message\n" |
125 'Without a constraint, you\'re promising to support ${log.bold("all")} ' | 150 'Without a constraint, you\'re promising to support ${log.bold("all")} ' |
126 'previous versions of "${dep.name}".'); | 151 'previous versions of "${dep.name}".'); |
127 } | 152 } |
128 | 153 |
129 /// Warn that dependencies should have upper bounds on their constraints. | 154 /// Warn that dependencies should have upper bounds on their constraints. |
130 void _warnAboutNoConstraintUpperBound(PackageDep dep) { | 155 void _warnAboutNoConstraintUpperBound(PackageDep dep) { |
| 156 var constraint; |
| 157 if ((dep.constraint as VersionRange).includeMin) { |
| 158 constraint = _constraintForVersion(dep.constraint.min); |
| 159 } else { |
| 160 constraint = '"${dep.constraint} ' |
| 161 '<${(dep.constraint as VersionRange).min.nextBreaking}"'; |
| 162 } |
| 163 |
131 warnings.add( | 164 warnings.add( |
132 'Your dependency on "${dep.name}" should have an upper bound. For ' | 165 'Your dependency on "${dep.name}" should have an upper bound. For ' |
133 'example:\n' | 166 'example:\n' |
134 '\n' | 167 '\n' |
135 'dependencies:\n' | 168 'dependencies:\n' |
136 ' ${dep.name}: "${dep.constraint} ' | 169 ' ${dep.name}: $constraint\n' |
137 '${_upperBoundForVersion((dep.constraint as VersionRange).min)}"\n' | |
138 '\n' | 170 '\n' |
139 'Without an upper bound, you\'re promising to support ' | 171 'Without an upper bound, you\'re promising to support ' |
140 '${log.bold("all")} future versions of ${dep.name}.'); | 172 '${log.bold("all")} future versions of ${dep.name}.'); |
141 } | 173 } |
142 | 174 |
| 175 /// Emits an error for any version constraints that use `^` without an |
| 176 /// appropriate SDK constraint. |
| 177 void _errorAboutCaretConstraints(List<PackageDeps> caretDeps) { |
| 178 var newSdkConstraint = entrypoint.root.pubspec.environment.sdkVersion |
| 179 .intersect(_postCaretPubVersions); |
| 180 |
| 181 if (newSdkConstraint.isEmpty) newSdkConstraint = _postCaretPubVersions; |
| 182 |
| 183 var buffer = new StringBuffer( |
| 184 "Older versions of pub don't support ^ version constraints.\n" |
| 185 "Make sure your SDK constraint excludes those old versions:\n" |
| 186 "\n" |
| 187 "environment:\n" |
| 188 " sdk: \"$newSdkConstraint\"\n" |
| 189 "\n"); |
| 190 |
| 191 if (caretDeps.length == 1) { |
| 192 buffer.writeln("Or use a fully-expanded constraint:"); |
| 193 } else { |
| 194 buffer.writeln("Or use fully-expanded constraints:"); |
| 195 } |
| 196 |
| 197 buffer.writeln(); |
| 198 buffer.writeln("dependencies:"); |
| 199 |
| 200 caretDeps.forEach((dep) { |
| 201 VersionRange constraint = dep.constraint; |
| 202 buffer.writeln( |
| 203 " ${dep.name}: \">=${constraint.min} <${constraint.max}\""); |
| 204 }); |
| 205 |
| 206 errors.add(buffer.toString().trim()); |
| 207 } |
| 208 |
143 /// Returns the suggested version constraint for a dependency that was tested | 209 /// Returns the suggested version constraint for a dependency that was tested |
144 /// against [version]. | 210 /// against [version]. |
145 String _constraintForVersion(Version version) => | 211 String _constraintForVersion(Version version) { |
146 '">=$version ${_upperBoundForVersion(version)}"'; | 212 if (_caretAllowed) return "^$version"; |
147 | 213 return '">=$version <${version.nextBreaking}"'; |
148 /// Returns the suggested upper bound for a dependency that was tested against | |
149 /// [version]. | |
150 String _upperBoundForVersion(Version version) { | |
151 if (version.major != 0) return '<${version.major + 1}.0.0'; | |
152 return '<${version.major}.${version.minor + 1}.0'; | |
153 } | 214 } |
154 } | 215 } |
OLD | NEW |