Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(866)

Side by Side Diff: utils/pub/version_solver2.dart

Issue 13095015: Use backtracking when solving dependency constraints. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Clean up some test code. Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 /// Pub's constraint solver. It is a back-tracking depth-first solver.
6 ///
7 /// Note that internally it uses explicit [Completer]s instead of chaining
8 /// futures like most async code. This is to avoid accumulating very long
9 /// chains of futures. Since this may iterate through many states, hanging an
10 /// increasing long series of `.then()` calls off each other can end up eating
11 /// piles of memory for both the futures and the stack traces.
nweiz 2013/03/29 01:58:25 If you're iterating enough that this becomes a ser
Bob Nystrom 2013/03/30 00:15:55 The only case where I've seen it be a problem is t
nweiz 2013/04/03 00:28:42 If the issue is the aesthetics of the stack traces
Bob Nystrom 2013/04/08 22:12:59 Your workaround fixes the stack traces, which is g
nweiz 2013/04/10 22:56:34 Of course there are N calls to setValue. The value
Bob Nystrom 2013/04/11 00:55:10 Right. I'm not crazy about building up a linked li
12 library version_solver2;
13
14 import 'dart:async';
15 import 'dart:collection' show Queue;
16 import 'dart:json' as json;
17 import 'dart:math';
18 import 'lock_file.dart';
19 import 'log.dart' as log;
20 import 'package.dart';
21 import 'pubspec.dart';
22 import 'source.dart';
23 import 'source_registry.dart';
24 import 'utils.dart';
25 import 'version.dart';
26
27 /// Attempts to select the best concrete versions for all of the transitive
28 /// dependencies of [root] taking into account all of the [VersionConstraint]s
29 /// that those dependencies place on each other and the requirements imposed by
30 /// [lockFile]. If successful, completes to a [Map] that maps package names to
nweiz 2013/03/29 01:58:25 The type annotation says it completes to a List, n
Bob Nystrom 2013/03/30 00:15:55 Done.
31 /// the selected version for that package. If it fails, the future will complete
32 /// with a [NoVersionException], [DisjointConstraintException], or
33 /// [CouldNotSolveException].
34 Future<List<PackageId>> resolveVersions(SourceRegistry sources, Package root,
35 LockFile lockFile) {
36 log.message('Resolving dependencies...');
37 return new VersionSolver(sources, root, lockFile).solve();
38 }
39
40 class VersionSolver {
nweiz 2013/03/29 01:58:25 This needs a lot more explanation of the structure
Bob Nystrom 2013/03/30 00:15:55 Added lots more docs.
nweiz 2013/04/03 00:28:42 These are lots better, but I've added some more co
41 final SourceRegistry _sources;
42 final Package _root;
43 final LockFile _lockFile;
44 final PubspecCache _cache;
45
46 /// The set of packages that are being explicitly updated. The solver will
47 /// only allow the very latest version for each of these packages.
nweiz 2013/03/29 01:58:25 I don't know if you care, but this isn't how bundl
Bob Nystrom 2013/03/30 00:15:55 I do care. The previous solver will always use the
nweiz 2013/04/03 00:28:42 My preference would be to use the current behavior
Bob Nystrom 2013/04/08 22:12:59 Works for me.
48 final _forceLatest = new Set<String>();
49
50 /// The current state being explored. Its parent links enable us to walk
51 /// back up the tree to other earlier states.
52 SolveState _state;
53
54 VersionSolver(SourceRegistry sources, this._root, this._lockFile)
55 : _sources = sources,
56 _cache = new PubspecCache(sources);
57
58 void useLatestVersion(String package) {
59 _forceLatest.add(package);
60 _lockFile.packages.remove(package);
61 }
62
63 Future<List<PackageId>> solve() {
64 var completer = new Completer<List<PackageId>>();
65 _processState(completer);
66 return completer.future;
67 }
68
69 /// Creates a new node in the solution space that tries [versions] for
70 /// package [name]. Returns the new node.
71 SolveState push(String name, List<Version> versions) {
72 _state = new SolveState(_state, name, versions);
73 _state.trace();
74 return _state;
75 }
76
77 /// Loads the pubspec for the package identified by [id].
78 Future<Pubspec> getPubspec(PackageId id) {
79 // The root package doesn't have a source, so special case it.
80 if (id.isRoot) return new Future.immediate(_root.pubspec);
81
82 return _cache.getPubspec(id);
83 }
84
85 /// Gets the list of versions for [package].
86 Future<List<PackageId>> getVersions(String package, Source source,
87 description) {
88 return _cache.getVersions(package, source, description);
89 }
90
91 /// Gets the version of [package] currently locked in the lock file. Returns
92 /// `null` if it isn't in the lockfile (or has been unlocked).
93 PackageId getLocked(String package) => _lockFile.packages[package];
94
95 /// Processes the current possible solution state. If successful, completes
96 /// [completer] with the solution. If not, tries the next state (and so on).
97 /// If there are no more states, completes to the last error that occurred.
98 void _processState(Completer<List<PackageId>> completer) {
99 var state = _state;
100
101 var propagator = new Propagator(this, state);
nweiz 2013/03/29 01:58:25 There's no need for a variable here.
Bob Nystrom 2013/03/30 00:15:55 Done.
102 propagator.propagate().then((result) {
103 completer.complete(result);
104 }).catchError((error) {
105 if (error.error is! SolverFailure) {
106 completer.completeError(error);
107 return;
108 }
109
110 // Try the next state, if there is one.
111 if (state != _state || _advanceState()) {
112 _processState(completer);
113 } else {
114 // All out of solutions, so fail.
115 completer.completeError(error);
116 }
117 });
118 }
119
120 /// Advances to the next node in the possible solution tree.
121 bool _advanceState() {
122 while (_state != null) {
123 if (_state.advance()) return true;
124
125 // The current state is done, so pop it and try the parent.
126 _state = _state._parent;
127 }
128
129 return false;
130 }
131 }
132
133 /// Given a [SolveState] that selects a set of package versions, this tries to
134 /// traverse the dependency graph and see if a complete set of valid versions
135 /// has been selected.
nweiz 2013/03/29 01:58:25 This description implies that this just examines t
Bob Nystrom 2013/03/30 00:15:55 Added better docs.
136 class Propagator {
nweiz 2013/03/29 01:58:25 Move this and most of the other classes below into
Bob Nystrom 2013/03/30 00:15:55 Reorganized stuff. version_solver.dart is now the
137 final VersionSolver _solver;
138
139 /// The queue of packages left to traverse. We do a bread-first traversal
nweiz 2013/03/29 01:58:25 "bread" -> "breadth"
Bob Nystrom 2013/03/30 00:15:55 I would love it if it actually was bread first. I
140 /// using an explicit queue just to avoid the code complexity of a recursive
141 /// asynchronous traversal.
142 final _packages = new Queue<PackageId>();
143
144 /// The packages we have already traversed. Used to avoid traversing the same
145 /// package multiple times, and to build the complete solution results.
146 final _visited = new Set<PackageId>();
147
148 /// The known dependencies visited so far.
nweiz 2013/03/29 01:58:25 This isn't a very descriptive comment. What are th
Bob Nystrom 2013/03/30 00:15:55 Done.
149 final _dependencies = <String, List<Dependency>>{};
150
151 /// The solution being tested.
152 SolveState _state;
153
154 Propagator(this._solver, this._state);
155
156 Future<List<PackageId>> propagate() {
157 // Start at the root.
158 _packages.add(new PackageId.root(_solver._root));
159
160 var completer = new Completer<List<PackageId>>();
161 _processPackage(completer);
162 return completer.future;
163 }
164
165 void trace([String message]) {
nweiz 2013/03/29 01:58:25 "trace" is a confusing name. Maybe "printLog" or "
Bob Nystrom 2013/03/30 00:15:55 "log" collides with the library prefix. Changed to
166 if (_state == null) {
167 // Don't waste time building the string if it will be ignored.
168 if (!log.isEnabled(log.Level.SOLVER)) return;
nweiz 2013/03/29 01:58:25 This is unnecessary optimization, and we don't do
Bob Nystrom 2013/03/30 00:15:55 Done.
169
170 // No message means just describe the current state.
171 if (message == null) {
172 message = "* start at root";
173 } else {
174 // Otherwise, indent it under the current state.
175 message = "| $message";
176 }
177
178 log.solver("| $message");
179 } else {
180 _state.trace(message);
181 }
182 }
183
184 /// Traverses the next package in the queue. Completes [completer] with a
185 /// list of package IDs if the traversal completely successfully and found a
nweiz 2013/03/29 01:58:25 "completely" -> "completed"
Bob Nystrom 2013/03/30 00:15:55 Done.
186 /// solution. Completes to an error if the traversal failed. Otherwise,
187 /// recurses to the next package in the queue, etc.
188 void _processPackage(Completer<List<PackageId>> completer) {
189 if (_packages.isEmpty) {
190 // We traversed the whole graph. If we got here, we successfully found
191 // a solution.
192 completer.complete(_visited.toList());
193 return;
194 }
195
196 var id = _packages.removeFirst();
197
198 // Don't visit the same package twice.
199 if (_visited.contains(id)) {
200 _processPackage(completer);
201 return;
202 }
203 _visited.add(id);
204
205 _solver.getPubspec(id).then((pubspec) {
206 var refs = pubspec.dependencies.toList();
207
208 // Include dev dependencies of the root package.
209 if (id.isRoot) {
210 refs.addAll(pubspec.devDependencies);
211 }
212
213 // TODO(rnystrom): Sort in some best-first order to minimize backtracking.
214 // Bundler's model is:
215 // Easiest to resolve is defined by:
216 // 1) Is this gem already activated?
217 // 2) Do the version requirements include prereleased gems?
218 // 3) Sort by number of gems available in the source.
219 // Can probably do something similar, but we should profile against
220 // actual package graphs first.
221 refs.sort((a, b) => a.name.compareTo(b.name));
222
223 _traverseRefs(completer, id.name, new Queue<PackageRef>.from(refs));
224 }).catchError((error){
225 completer.completeError(error);
226 });
227 }
228
229 /// Traverses the references that [depender] depends on, stored in [refs].
230 /// Desctructively modifies [refs]. Completes [completer] to a list of
231 /// packages if the traversal is complete. Completes it to an error if a
232 /// failure occurred. Otherwise, recurses.
233 void _traverseRefs(Completer<List<PackageId>> completer,
nweiz 2013/03/29 01:58:25 This function is gigantic. Split it up?
Bob Nystrom 2013/03/30 00:15:55 Done.
234 String depender, Queue<PackageRef> refs) {
235 // Move onto the next package if we've traversed all of these references.
236 if (refs.isEmpty) {
237 _processPackage(completer);
238 return;
239 }
240
241 var ref = refs.removeFirst();
242
243 // Note the dependency.
244 var dependencies = _dependencies.putIfAbsent(ref.name,
245 () => <Dependency>[]);
246 dependencies.add(new Dependency(depender, ref));
247
248 // Make sure the dependencies agree on source and description.
249 var required = _getRequired(ref.name);
250 if (required != null) {
251 // Make sure all of the existing sources match the new reference.
252 if (required.ref.source.name != ref.source.name) {
253 trace('source mismatch on ${ref.name}: ${required.ref.source} '
254 '!= ${ref.source}');
255 completer.completeError(new SourceMismatchException(ref.name,
256 required.depender, required.ref.source, depender, ref.source));
nweiz 2013/03/29 01:58:25 Never call completeError without a stack trace. Th
Bob Nystrom 2013/03/30 00:15:55 Done.
257 return;
258 }
259
260 // Make sure all of the existing descriptions match the new reference.
261 if (!ref.descriptionEquals(required.ref)) {
262 trace('description mismatch on ${ref.name}: '
263 '${required.ref.description} != ${ref.description}');
264 completer.completeError(new DescriptionMismatchException(ref.name,
265 required.depender, required.ref.description,
266 depender, ref.description));
267 return;
268 }
269 }
270
271 // Determine the overall version constraint.
272 var constraint = dependencies
273 .map((dep) => dep.ref.constraint)
274 .reduce(VersionConstraint.any, (a, b) => a.intersect(b));
275
276 // See if it's possible for a package to match that constraint. We
277 // check this first so that this error is preferred over "no versions"
278 // which can be thrown if the current selection does not match the
279 // constraint.
280 if (constraint.isEmpty) {
281 trace('disjoint constraints on ${ref.name}');
282 completer.completeError(
283 new DisjointConstraintException(ref.name, dependencies));
284 return;
285 }
286
287 var selected = _getSelected(ref.name);
288 if (selected != null) {
289 // Make sure it meets the constraint.
290 if (!ref.constraint.allows(selected.version)) {
291 trace('selection $selected does not match $constraint');
292 completer.completeError(
293 new NoVersionException(ref.name, constraint, dependencies));
294 return;
295 }
296
297 // Traverse into it.
nweiz 2013/03/29 01:58:25 You're not actually traversing into the new versio
Bob Nystrom 2013/03/30 00:15:55 Done.
298 _packages.add(selected);
299 _traverseRefs(completer, depender, refs);
300 return;
301 }
302
303 // We haven't selected a version. Create a substate that tries all
304 // versions that match the constraints we currently have for this
305 // package.
306 _solver.getVersions(ref.name, ref.source, ref.description).then((versions) {
307 var allowed = versions.where(
308 (id) => constraint.allows(id.version)).toList();
309
310 // See if it's in the lockfile. If so, try that version first. If the
311 // locked version doesn't match our constraint, just ignore it.
312 var locked = _getLocked(ref.name, constraint);
313 if (locked != null) {
314 allowed.removeWhere((ref) => ref.version == locked.version);
315 allowed.insert(0, locked);
316 }
317
318 if (allowed.isEmpty) {
319 trace('no versions for ${ref.name} match $constraint');
320 completer.completeError(new NoVersionException(ref.name, constraint,
321 dependencies));
322 return;
323 }
324
325 // If we're doing an upgrade on this package, only allow the latest
326 // version.
327 if (_solver._forceLatest.contains(ref.name)) allowed = [allowed.first];
328
329 // Try to continue solving with the first selected package.
nweiz 2013/03/29 01:58:25 This doesn't just try the first selected package.
Bob Nystrom 2013/03/30 00:15:55 This Propagator will only try the first one. Rewor
330 _state = _solver.push(ref.name, allowed);
331 selected = _getSelected(ref.name);
332 assert(selected != null);
333
334 _packages.add(selected);
335 _traverseRefs(completer, depender, refs);
336 }).catchError((error) {
337 print(error);
nweiz 2013/03/29 01:58:25 Left over from debugging?
Bob Nystrom 2013/03/30 00:15:55 Done.
338 completer.completeError(error);
339 });
340 }
341
342 /// Gets the currently selected package named [package] or `null` if no
nweiz 2013/03/29 01:58:25 "Gets" -> "Returns", "package named" -> "id for th
Bob Nystrom 2013/03/30 00:15:55 Done.
343 /// concrete package has been selected with that name yet.
344 PackageId _getSelected(String name) {
345 // Always prefer the root package.
346 if (_solver._root.name == name) return new PackageId.root(_solver._root);
347
348 // Nothing is selected if we're in the starting state.
349 if (_state == null) return null;
350
351 return _state.getSelected(name);
352 }
353
354 /// Gets a "required" reference to the package [name]. This is the first
355 /// non-root dependency on that package. All dependencies on a package must
nweiz 2013/03/29 01:58:25 I don't understand "non-root" here. You're only ch
Bob Nystrom 2013/03/30 00:15:55 All refs will be to the root package, but they may
356 /// agree on source and description, except for references to the root
357 /// package. This will return a reference to that "canonical" source and
358 /// description, or `null` if there is no required reference yet.
359 Dependency _getRequired(String name) {
360 var dependencies = _dependencies[name];
361 assert(dependencies != null);
362
363 return dependencies
364 .firstWhere((dep) => !dep.ref.isRoot, orElse: () => null);
365 }
366
367 /// Gets the package [name] that's currently contained in the lockfile if it
368 /// meets [constraint] and has the same source and description as other
369 /// references to that package. Returns `null` otherwise.
370 PackageId _getLocked(String name, VersionConstraint constraint) {
nweiz 2013/03/29 01:58:25 It's confusing that this has the same name as [Ver
Bob Nystrom 2013/03/30 00:15:55 Changed to _getValidLocked().
371 var package = _solver.getLocked(name);
372 if (package == null) return null;
373
374 if (!constraint.allows(package.version)) return null;
375
376 var dependencies = _dependencies[name];
nweiz 2013/03/29 01:58:25 Unused variable.
Bob Nystrom 2013/03/30 00:15:55 Done.
377 assert(dependencies != null);
378
379 var required = _getRequired(name);
380 if (required != null) {
381 if (package.source.name != required.ref.source.name) return null;
382 if (!package.descriptionEquals(required.ref)) return null;
383 }
384
385 return package;
386 }
387 }
388
389 /// One node in the possible solution tree that is being traversed. Each node
390 /// reflects one of a set of speculative choices that may turn out to be wrong.
nweiz 2013/03/29 01:58:25 "possible solution tree" is confusing. Is this jus
Bob Nystrom 2013/03/30 00:15:55 Removed this class entirely.
391 class SolveState {
392 final SolveState _parent;
393
394 /// The name of the package this state selects.
395 final String _package;
396
397 /// The list of versions that can possibly be selected.
398 final List<PackageId> _possible;
399
400 /// The currently selected version in [_possible].
401 int _current = 0;
402
403 SolveState(this._parent, this._package, this._possible);
404
405 void trace([Object message]) {
406 // Don't waste time building the string if it will be ignored.
407 if (!log.isEnabled(log.Level.SOLVER)) return;
nweiz 2013/03/29 01:58:25 Unnecessary optimization.
Bob Nystrom 2013/03/30 00:15:55 Done.
408
409 // No message means just describe the current state.
410 if (message == null) {
411 message = "* select ${_possible[_current]} "
412 "($_current/${_possible.length})";
413 } else {
414 // Otherwise, indent it under the current state.
415 message = "| $message";
416 }
417
418 var buffer = new StringBuffer();
419
420 // Indent for the parent states.
421 var state = _parent;
422 while (state != null) {
423 buffer.write('| ');
424 state = state._parent;
425 }
426
427 buffer.write(message);
428 log.solver(buffer);
429 }
430
431 /// Tries to move to the next version in the list. Returns `false` if there
432 /// are no more versions.
433 bool advance() {
434 _current++;
435 if (_current < _possible.length) {
436 trace();
437 return true;
438 } else {
439 return false;
440 }
441 }
442
443 /// Gets the selected version of [package]. If no version has been selected
444 /// yet, returns `null`.
445 PackageId getSelected(String package) {
446 if (_package == package) return _possible[_current];
447 if (_parent == null) return null;
448 return _parent.getSelected(package);
449 }
450 }
451
452 /// A reference from a depending package to a package that it depends on.
453 class Dependency {
454 /// The name of the package that has this dependency.
455 final String depender;
456
457 /// The referenced dependent package.
458 final PackageRef ref;
459
460 Dependency(this.depender, this.ref);
461 }
462
463 /// Maintains a cache of previously-requested data: pubspecs and version lists.
464 /// Used to avoid requesting the same pubspec from the server repeatedly.
465 class PubspecCache {
466 final SourceRegistry _sources;
467 final _versions = new Map<PackageId, List<Version>>();
nweiz 2013/03/29 01:58:25 The type of the map is wrong. The value type shoul
Bob Nystrom 2013/03/30 00:15:55 Done.
468 final _pubspecs = new Map<PackageId, Pubspec>();
469
470 PubspecCache(this._sources);
471
472 /// Caches [pubspec] as the [Pubspec] for the package identified by [id].
473 void cache(PackageId id, Pubspec pubspec) {
nweiz 2013/03/29 01:58:25 This is never called.
Bob Nystrom 2013/03/30 00:15:55 The previous solver uses it. This class is unified
474 _pubspecs[id] = pubspec;
475 }
476
477 /// Loads the pubspec for the package identified by [id].
478 Future<Pubspec> getPubspec(PackageId id) {
479 // Complete immediately if it's already cached.
480 if (_pubspecs.containsKey(id)) {
481 return new Future<Pubspec>.immediate(_pubspecs[id]);
482 }
483
484 return id.describe().then((pubspec) {
485 log.solver('requested ${id} pubspec');
486
487 // Cache it.
488 _pubspecs[id] = pubspec;
489 return pubspec;
490 });
491 }
492
493 /// Gets the list of versions for [package] in descending order.
494 Future<List<PackageId>> getVersions(String package, Source source, description ) {
nweiz 2013/03/29 01:58:25 long line
Bob Nystrom 2013/03/30 00:15:55 Done.
495 // Create a fake ID to use as a key.
496 var id = new PackageId(package, source, Version.none, description);
nweiz 2013/03/29 01:58:25 I don't like using PackageId here just because a s
Bob Nystrom 2013/03/30 00:15:55 I'm fine with doing this split, but I would like t
497
498 // See if we have it cached.
499 var versions = _versions[id];
500 if (versions != null) return new Future.immediate(versions);
501
502 return source.getVersions(package, description).then((versions) {
503 var ids = versions
504 .map((version) => new PackageId(package, source, version, description) )
nweiz 2013/03/29 01:58:25 long line
Bob Nystrom 2013/03/30 00:15:55 Done.
505 .toList();
506
507 // Sort by descending version so we try newer versions first.
508 ids.sort((a, b) => b.version.compareTo(a.version));
509
510 log.solver('requested $package versions: ${ids.join(", ")}');
511 _versions[id] = ids;
512 return ids;
513 });
514 }
515 }
516
517
518 /// Base class for all failures that can occur while trying to resolve versions.
519 class SolverFailure implements Exception {
520 /// Writes [deps] to [buffer] as a bullet list.
521 void _writeDependencies(StringBuffer buffer, List<Dependency> deps) {
nweiz 2013/03/29 01:58:25 There's no particular reason for this to be an ins
Bob Nystrom 2013/03/30 00:15:55 Not really. It keeps it out of global scope but ma
nweiz 2013/04/03 00:28:42 Yes, I think so.
Bob Nystrom 2013/04/08 22:12:59 Done.
522 var map = {};
523 for (var dep in deps) {
524 map[dep.depender] = dep.ref;
525 }
526
527 var names = map.keys.toList();
528 names.sort();
529
530 for (var name in names) {
531 buffer.writeln("- '$name' depends on version ${map[name].constraint}");
532 }
533 }
534 }
535
536 /// Exception thrown when the [VersionConstraint] used to match a package is
537 /// valid (i.e. non-empty), but there are no available versions of the package
538 /// that fit that constraint.
539 class NoVersionException extends SolverFailure {
540 final String package;
541 final VersionConstraint constraint;
542 final List<Dependency> _dependencies;
543
544 NoVersionException(this.package, this.constraint, this._dependencies);
545
546 String toString() {
547 var buffer = new StringBuffer();
548 buffer.write("Package '$package' has no versions that match $constraint "
549 "derived from:\n");
550 _writeDependencies(buffer, _dependencies);
551 return buffer.toString();
552 }
553 }
554
555 /*
556 // TODO(rnystrom): Report the list of depending packages and their constraints.
557 /// Exception thrown when the most recent version of [package] must be selected,
558 /// but doesn't match the [VersionConstraint] imposed on the package.
559 class CouldNotUpdateException implements Exception {
nweiz 2013/03/29 01:58:25 Why is this commented out? Isn't this a state the
Bob Nystrom 2013/03/30 00:15:55 The new solver doesn't seem to use it. As far as I
560 final String package;
561 final VersionConstraint constraint;
562 final Version best;
563
564 CouldNotUpdateException(this.package, this.constraint, this.best);
565
566 String toString() =>
567 "The latest version of '$package', $best, does not match $constraint.";
568 }
569 */
570
571 /// Exception thrown when the [VersionConstraint] used to match a package is
572 /// the empty set: in other words, multiple packages depend on it and have
573 /// conflicting constraints that have no overlap.
574 class DisjointConstraintException extends SolverFailure {
575 final String package;
576 final List<Dependency> _dependencies;
577
578 DisjointConstraintException(this.package, this._dependencies);
579
580 String toString() {
581 var buffer = new StringBuffer();
582 buffer.write("Incompatible version constraints on '$package':\n");
583 _writeDependencies(buffer, _dependencies);
584 return buffer.toString();
585 }
586 }
587
588 /// Exception thrown when two packages with the same name but different sources
589 /// are depended upon.
590 class SourceMismatchException extends SolverFailure {
591 final String package;
592 final String depender1;
593 final Source source1;
594 final String depender2;
595 final Source source2;
596
597 SourceMismatchException(this.package, this.depender1, this.source1,
598 this.depender2, this.source2);
599
600 String toString() {
601 return "Incompatible dependencies on '$package':\n"
602 "- '$depender1' depends on it from source '$source1'\n"
603 "- '$depender2' depends on it from source '$source2'";
604 }
605 }
606
607 /// Exception thrown when two packages with the same name and source but
608 /// different descriptions are depended upon.
609 class DescriptionMismatchException extends SolverFailure {
610 final String package;
611 final String depender1;
612 final description1;
613 final String depender2;
614 final description2;
615
616 DescriptionMismatchException(this.package, this.depender1, this.description1,
617 this.depender2, this.description2);
618
619 String toString() {
620 // TODO(nweiz): Dump descriptions to YAML when that's supported.
621 return "Incompatible dependencies on '$package':\n"
622 "- '$depender1' depends on it with description "
623 "${json.stringify(description1)}\n"
624 "- '$depender2' depends on it with description "
625 "${json.stringify(description2)}";
626 }
627 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698