OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library pub.validator.dependency; |
| 6 |
| 7 import 'dart:async'; |
| 8 |
| 9 import 'package:pub_semver/pub_semver.dart'; |
| 10 |
| 11 import '../entrypoint.dart'; |
| 12 import '../log.dart' as log; |
| 13 import '../package.dart'; |
| 14 import '../utils.dart'; |
| 15 import '../validator.dart'; |
| 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 |
| 27 /// A validator that validates a package's dependencies. |
| 28 class DependencyValidator extends Validator { |
| 29 /// Whether the SDK constraint guarantees that `^` version constraints are |
| 30 /// safe. |
| 31 bool get _caretAllowed => |
| 32 entrypoint.root.pubspec.environment.sdkVersion.intersect( |
| 33 _preCaretPubVersions).isEmpty; |
| 34 |
| 35 DependencyValidator(Entrypoint entrypoint) |
| 36 : super(entrypoint); |
| 37 |
| 38 Future validate() { |
| 39 final completer0 = new Completer(); |
| 40 scheduleMicrotask(() { |
| 41 try { |
| 42 var caretDeps = []; |
| 43 var it0 = entrypoint.root.pubspec.dependencies.iterator; |
| 44 break0() { |
| 45 join0() { |
| 46 completer0.complete(); |
| 47 } |
| 48 if (caretDeps.isNotEmpty && !_caretAllowed) { |
| 49 _errorAboutCaretConstraints(caretDeps); |
| 50 join0(); |
| 51 } else { |
| 52 join0(); |
| 53 } |
| 54 } |
| 55 var trampoline0; |
| 56 continue0() { |
| 57 trampoline0 = null; |
| 58 if (it0.moveNext()) { |
| 59 var dependency = it0.current; |
| 60 join1() { |
| 61 trampoline0 = continue0; |
| 62 do trampoline0(); while (trampoline0 != null); |
| 63 } |
| 64 if (dependency.source != "hosted") { |
| 65 new Future.value(_warnAboutSource(dependency)).then((x0) { |
| 66 trampoline0 = () { |
| 67 trampoline0 = null; |
| 68 try { |
| 69 x0; |
| 70 join1(); |
| 71 } catch (e0, s0) { |
| 72 completer0.completeError(e0, s0); |
| 73 } |
| 74 }; |
| 75 do trampoline0(); while (trampoline0 != null); |
| 76 }, onError: completer0.completeError); |
| 77 } else { |
| 78 join2() { |
| 79 join1(); |
| 80 } |
| 81 if (dependency.constraint.isAny) { |
| 82 _warnAboutNoConstraint(dependency); |
| 83 join2(); |
| 84 } else { |
| 85 join3() { |
| 86 join2(); |
| 87 } |
| 88 if (dependency.constraint is Version) { |
| 89 _warnAboutSingleVersionConstraint(dependency); |
| 90 join3(); |
| 91 } else { |
| 92 join4() { |
| 93 join3(); |
| 94 } |
| 95 if (dependency.constraint is VersionRange) { |
| 96 join5() { |
| 97 join6() { |
| 98 join4(); |
| 99 } |
| 100 if (dependency.constraint.toString().startsWith("^")) { |
| 101 caretDeps.add(dependency); |
| 102 join6(); |
| 103 } else { |
| 104 join6(); |
| 105 } |
| 106 } |
| 107 if (dependency.constraint.min == null) { |
| 108 _warnAboutNoConstraintLowerBound(dependency); |
| 109 join5(); |
| 110 } else { |
| 111 join7() { |
| 112 join5(); |
| 113 } |
| 114 if (dependency.constraint.max == null) { |
| 115 _warnAboutNoConstraintUpperBound(dependency); |
| 116 join7(); |
| 117 } else { |
| 118 join7(); |
| 119 } |
| 120 } |
| 121 } else { |
| 122 join4(); |
| 123 } |
| 124 } |
| 125 } |
| 126 } |
| 127 } else { |
| 128 break0(); |
| 129 } |
| 130 } |
| 131 trampoline0 = continue0; |
| 132 do trampoline0(); while (trampoline0 != null); |
| 133 } catch (e, s) { |
| 134 completer0.completeError(e, s); |
| 135 } |
| 136 }); |
| 137 return completer0.future; |
| 138 } |
| 139 |
| 140 /// Warn that dependencies should use the hosted source. |
| 141 Future _warnAboutSource(PackageDep dep) { |
| 142 return entrypoint.cache.sources['hosted'].getVersions( |
| 143 dep.name, |
| 144 dep.name).catchError((e) => <Version>[]).then((versions) { |
| 145 var constraint; |
| 146 var primary = Version.primary(versions); |
| 147 if (primary != null) { |
| 148 constraint = _constraintForVersion(primary); |
| 149 } else { |
| 150 constraint = dep.constraint.toString(); |
| 151 if (!dep.constraint.isAny && dep.constraint is! Version) { |
| 152 constraint = '"$constraint"'; |
| 153 } |
| 154 } |
| 155 |
| 156 // Path sources are errors. Other sources are just warnings. |
| 157 var messages = warnings; |
| 158 if (dep.source == "path") { |
| 159 messages = errors; |
| 160 } |
| 161 |
| 162 messages.add( |
| 163 'Don\'t depend on "${dep.name}" from the ${dep.source} ' |
| 164 'source. Use the hosted source instead. For example:\n' '\n' 'depe
ndencies:\n' |
| 165 ' ${dep.name}: $constraint\n' '\n' |
| 166 'Using the hosted source ensures that everyone can download your ' |
| 167 'package\'s dependencies along with your package.'); |
| 168 }); |
| 169 } |
| 170 |
| 171 /// Warn that dependencies should have version constraints. |
| 172 void _warnAboutNoConstraint(PackageDep dep) { |
| 173 var message = |
| 174 'Your dependency on "${dep.name}" should have a version ' 'constraint.'; |
| 175 var locked = entrypoint.lockFile.packages[dep.name]; |
| 176 if (locked != null) { |
| 177 message = |
| 178 '$message For example:\n' '\n' 'dependencies:\n' |
| 179 ' ${dep.name}: ${_constraintForVersion(locked.version)}\n'; |
| 180 } |
| 181 warnings.add( |
| 182 "$message\n" |
| 183 'Without a constraint, you\'re promising to support ${log.bold("all"
)} ' |
| 184 'future versions of "${dep.name}".'); |
| 185 } |
| 186 |
| 187 /// Warn that dependencies should allow more than a single version. |
| 188 void _warnAboutSingleVersionConstraint(PackageDep dep) { |
| 189 warnings.add( |
| 190 'Your dependency on "${dep.name}" should allow more than one version. ' |
| 191 'For example:\n' '\n' 'dependencies:\n' |
| 192 ' ${dep.name}: ${_constraintForVersion(dep.constraint)}\n' '\n' |
| 193 'Constraints that are too tight will make it difficult for people to
' |
| 194 'use your package\n' |
| 195 'along with other packages that also depend on "${dep.name}".'); |
| 196 } |
| 197 |
| 198 /// Warn that dependencies should have lower bounds on their constraints. |
| 199 void _warnAboutNoConstraintLowerBound(PackageDep dep) { |
| 200 var message = 'Your dependency on "${dep.name}" should have a lower bound.'; |
| 201 var locked = entrypoint.lockFile.packages[dep.name]; |
| 202 if (locked != null) { |
| 203 var constraint; |
| 204 if (locked.version == (dep.constraint as VersionRange).max) { |
| 205 constraint = _constraintForVersion(locked.version); |
| 206 } else { |
| 207 constraint = '">=${locked.version} ${dep.constraint}"'; |
| 208 } |
| 209 |
| 210 message = |
| 211 '$message For example:\n' '\n' 'dependencies:\n' ' ${dep.name}: $cons
traint\n'; |
| 212 } |
| 213 warnings.add( |
| 214 "$message\n" |
| 215 'Without a constraint, you\'re promising to support ${log.bold("all"
)} ' |
| 216 'previous versions of "${dep.name}".'); |
| 217 } |
| 218 |
| 219 /// Warn that dependencies should have upper bounds on their constraints. |
| 220 void _warnAboutNoConstraintUpperBound(PackageDep dep) { |
| 221 var constraint; |
| 222 if ((dep.constraint as VersionRange).includeMin) { |
| 223 constraint = _constraintForVersion((dep.constraint as VersionRange).min); |
| 224 } else { |
| 225 constraint = |
| 226 '"${dep.constraint} ' '<${(dep.constraint as VersionRange).min.nextBre
aking}"'; |
| 227 } |
| 228 |
| 229 warnings.add( |
| 230 'Your dependency on "${dep.name}" should have an upper bound. For ' 'exa
mple:\n' |
| 231 '\n' 'dependencies:\n' ' ${dep.name}: $constraint\n' '\n' |
| 232 'Without an upper bound, you\'re promising to support ' |
| 233 '${log.bold("all")} future versions of ${dep.name}.'); |
| 234 } |
| 235 |
| 236 /// Emits an error for any version constraints that use `^` without an |
| 237 /// appropriate SDK constraint. |
| 238 void _errorAboutCaretConstraints(List<PackageDep> caretDeps) { |
| 239 var newSdkConstraint = |
| 240 entrypoint.root.pubspec.environment.sdkVersion.intersect(_postCaretPubVe
rsions); |
| 241 |
| 242 if (newSdkConstraint.isEmpty) newSdkConstraint = _postCaretPubVersions; |
| 243 |
| 244 var buffer = new StringBuffer( |
| 245 "Older versions of pub don't support ^ version constraints.\n" |
| 246 "Make sure your SDK constraint excludes those old versions:\n" "\n" |
| 247 "environment:\n" " sdk: \"$newSdkConstraint\"\n" "\n"); |
| 248 |
| 249 if (caretDeps.length == 1) { |
| 250 buffer.writeln("Or use a fully-expanded constraint:"); |
| 251 } else { |
| 252 buffer.writeln("Or use fully-expanded constraints:"); |
| 253 } |
| 254 |
| 255 buffer.writeln(); |
| 256 buffer.writeln("dependencies:"); |
| 257 |
| 258 caretDeps.forEach((dep) { |
| 259 VersionRange constraint = dep.constraint; |
| 260 buffer.writeln( |
| 261 " ${dep.name}: \">=${constraint.min} <${constraint.max}\""); |
| 262 }); |
| 263 |
| 264 errors.add(buffer.toString().trim()); |
| 265 } |
| 266 |
| 267 /// Returns the suggested version constraint for a dependency that was tested |
| 268 /// against [version]. |
| 269 String _constraintForVersion(Version version) { |
| 270 if (_caretAllowed) return "^$version"; |
| 271 return '">=$version <${version.nextBreaking}"'; |
| 272 } |
| 273 } |
OLD | NEW |