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

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

Issue 10825375: Reverting 10779 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 4 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
« no previous file with comments | « utils/pub/entrypoint.dart ('k') | utils/tests/pub/pub_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 /** 5 /**
6 * Attempts to resolve a set of version constraints for a package dependency 6 * Attempts to resolve a set of version constraints for a package dependency
7 * graph and select an appropriate set of best specific versions for all 7 * graph and select an appropriate set of best specific versions for all
8 * dependent packages. It works iteratively and tries to reach a stable 8 * dependent packages. It works iteratively and tries to reach a stable
9 * solution where the constraints of all dependencies are met. If it fails to 9 * solution where the constraints of all dependencies are met. If it fails to
10 * reach a solution after a certain number of iterations, it assumes the 10 * reach a solution after a certain number of iterations, it assumes the
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
69 final Map<String, Dependency> _packages; 69 final Map<String, Dependency> _packages;
70 final Queue<WorkItem> _work; 70 final Queue<WorkItem> _work;
71 int _numIterations = 0; 71 int _numIterations = 0;
72 72
73 VersionSolver(SourceRegistry sources, this._root, this.lockFile) 73 VersionSolver(SourceRegistry sources, this._root, this.lockFile)
74 : _sources = sources, 74 : _sources = sources,
75 _pubspecs = new PubspecCache(sources), 75 _pubspecs = new PubspecCache(sources),
76 _packages = <Dependency>{}, 76 _packages = <Dependency>{},
77 _work = new Queue<WorkItem>(); 77 _work = new Queue<WorkItem>();
78 78
79 /**
80 * Tell the version solver to use the most recent version of [package] that
81 * exists in whatever source it's installed from. If that version violates
82 * constraints imposed by other dependencies, an error will be raised when
83 * solving the versions, even if an earlier compatible version exists.
84 */
85 void useLatestVersion(String package) {
86 // TODO(nweiz): How do we want to detect and handle unknown dependencies
87 // here?
88 getDependency(package).useLatestVersion = true;
89 lockFile.packages.remove(package);
90 }
91
92 Future<List<PackageId>> solve() { 79 Future<List<PackageId>> solve() {
93 // Kick off the work by adding the root package at its concrete version to 80 // Kick off the work by adding the root package at its concrete version to
94 // the dependency graph. 81 // the dependency graph.
95 var ref = new PackageRef(new RootSource(_root), _root.version, _root.name); 82 var ref = new PackageRef(new RootSource(_root), _root.version, _root.name);
96 enqueue(new AddConstraint('(entrypoint)', ref)); 83 enqueue(new AddConstraint('(entrypoint)', ref));
97 _pubspecs.cache(ref.atVersion(_root.version), _root.pubspec); 84 _pubspecs.cache(ref.atVersion(_root.version), _root.pubspec);
98 85
99 Future processNextWorkItem(_) { 86 Future processNextWorkItem(_) {
100 while (true) { 87 while (true) {
101 // Stop if we are done. 88 // Stop if we are done.
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
137 return _packages[package]; 124 return _packages[package];
138 } 125 }
139 126
140 /** 127 /**
141 * Sets the best selected version of [package] to [version]. 128 * Sets the best selected version of [package] to [version].
142 */ 129 */
143 void setVersion(String package, Version version) { 130 void setVersion(String package, Version version) {
144 _packages[package].version = version; 131 _packages[package].version = version;
145 } 132 }
146 133
147 /**
148 * Returns the most recent version of [dependency] that satisfies all of its
149 * version constraints.
150 */
151 Future<Version> getBestVersion(Dependency dependency) {
152 return dependency.source.getVersions(dependency.description)
153 .transform((versions) {
154 var best = null;
155 for (var version in versions) {
156 if (dependency.useLatestVersion ||
157 dependency.constraint.allows(version)) {
158 if (best == null || version > best) best = version;
159 }
160 }
161
162 // TODO(rnystrom): Better exception.
163 if (best == null) {
164 if (tryUnlockDepender(dependency)) return null;
165 throw new NoVersionException(dependency.name, dependency.constraint);
166 } else if (!dependency.constraint.allows(best)) {
167 if (tryUnlockDepender(dependency)) return null;
168 throw new CouldNotUpdateException(
169 dependency.name, dependency.constraint, best);
170 }
171
172 return best;
173 });
174 }
175
176 /**
177 * Looks for a package that depends (transitively) on [dependency] and has its
178 * version locked in the lockfile. If one is found, enqueues an
179 * [UnlockPackage] work item for it and returns true. Otherwise, returns
180 * false.
181 *
182 * This does a breadth-first search; immediate dependers will be unlocked
183 * first, followed by transitive dependers.
184 */
185 bool tryUnlockDepender(Dependency dependency) {
186 for (var dependerName in dependency.dependers) {
187 var depender = getDependency(dependerName);
188 var locked = lockFile.packages[dependerName];
189 if (locked != null && depender.version == locked.version) {
190 enqueue(new UnlockPackage(depender));
191 return true;
192 }
193 }
194 return dependency.dependers.map(getDependency).some(tryUnlockDepender);
195 }
196
197 List<PackageId> buildResults() { 134 List<PackageId> buildResults() {
198 return _packages.getValues().filter((dep) => dep.isDependedOn).map((dep) { 135 return _packages.getValues().filter((dep) => dep.isDependedOn).map((dep) {
199 var description = dep.description; 136 var description = dep.description;
200 137
201 // If the lockfile contains a fully-resolved description for the package, 138 // If the lockfile contains a fully-resolved description for the package,
202 // use that. This allows e.g. Git to ensure that the same commit is used. 139 // use that. This allows e.g. Git to ensure that the same commit is used.
203 var lockedPackage = lockFile.packages[dep.name]; 140 var lockedPackage = lockFile.packages[dep.name];
204 if (lockedPackage != null && lockedPackage.version == dep.version && 141 if (lockedPackage != null && lockedPackage.version == dep.version &&
205 lockedPackage.source.name == dep.source.name && 142 lockedPackage.source.name == dep.source.name &&
206 dep.source.descriptionsEqual( 143 dep.source.descriptionsEqual(
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
317 * A constraint that a depending package places on a dependent package has 254 * A constraint that a depending package places on a dependent package has
318 * changed. 255 * changed.
319 * 256 *
320 * This is an abstract class that contains logic for updating the dependency 257 * This is an abstract class that contains logic for updating the dependency
321 * graph once a dependency has changed. Changing the dependency is the 258 * graph once a dependency has changed. Changing the dependency is the
322 * responsibility of subclasses. 259 * responsibility of subclasses.
323 */ 260 */
324 class ChangeConstraint implements WorkItem { 261 class ChangeConstraint implements WorkItem {
325 abstract Future process(VersionSolver solver); 262 abstract Future process(VersionSolver solver);
326 263
327 abstract void undo(VersionSolver solver); 264 Future _processChange(VersionSolver solver, Source source, description,
328 265 Dependency dependency, VersionConstraint oldConstraint,
329 Future _processChange(VersionSolver solver, Dependency dependency, 266 VersionConstraint newConstraint) {
330 VersionConstraint oldConstraint) {
331 var name = dependency.name; 267 var name = dependency.name;
332 var description = dependency.description;
333 var source = dependency.source;
334 var newConstraint = dependency.constraint;
335 268
336 // If the package is over-constrained, i.e. the packages depending have 269 // If the package is over-constrained, i.e. the packages depending have
337 // disjoint constraints, then try unlocking a depender that's locked by the 270 // disjoint constraints, then stop.
338 // lockfile. If there are no remaining locked dependencies, throw an error.
339 if (newConstraint != null && newConstraint.isEmpty) { 271 if (newConstraint != null && newConstraint.isEmpty) {
340 if (solver.tryUnlockDepender(dependency)) {
341 undo(solver);
342 return null;
343 }
344
345 throw new DisjointConstraintException(name); 272 throw new DisjointConstraintException(name);
346 } 273 }
347 274
348 // If this constraint change didn't cause the overall constraint on the 275 // If this constraint change didn't cause the overall constraint on the
349 // package to change, then we don't need to do any further work. 276 // package to change, then we don't need to do any further work.
350 if (oldConstraint == newConstraint) return null; 277 if (oldConstraint == newConstraint) return null;
351 278
352 // If the dependency has been cut free from the graph, just remove it. 279 // If the dependency has been cut free from the graph, just remove it.
353 if (!dependency.isDependedOn) { 280 if (!dependency.isDependedOn) {
354 solver.enqueue(new ChangeVersion(source, description, null)); 281 solver.enqueue(new ChangeVersion(source, description, null));
(...skipping 14 matching lines...) Expand all
369 if (lockedPackage != null) { 296 if (lockedPackage != null) {
370 var lockedVersion = lockedPackage.version; 297 var lockedVersion = lockedPackage.version;
371 if (newConstraint.allows(lockedVersion)) { 298 if (newConstraint.allows(lockedVersion)) {
372 solver.enqueue(new ChangeVersion(source, description, lockedVersion)); 299 solver.enqueue(new ChangeVersion(source, description, lockedVersion));
373 return null; 300 return null;
374 } 301 }
375 } 302 }
376 303
377 // The constraint has changed, so see what the best version of the package 304 // The constraint has changed, so see what the best version of the package
378 // that meets the new constraint is. 305 // that meets the new constraint is.
379 return solver.getBestVersion(dependency).transform((best) { 306 return source.getVersions(description).transform((versions) {
380 if (best == null) { 307 var best = null;
381 undo(solver); 308 for (var version in versions) {
382 } else if (dependency.version != best) { 309 if (newConstraint.allows(version)) {
310 if (best == null || version > best) best = version;
311 }
312 }
313
314 // TODO(rnystrom): Better exception.
315 if (best == null) throw new NoVersionException(name, newConstraint);
316
317 if (dependency.version != best) {
383 solver.enqueue(new ChangeVersion(source, description, best)); 318 solver.enqueue(new ChangeVersion(source, description, best));
384 } 319 }
385 }); 320 });
386 } 321 }
387 } 322 }
388 323
389 /** 324 /**
390 * The constraint given by [ref] is being placed by [depender]. 325 * The constraint given by [ref] is being placed by [depender].
391 */ 326 */
392 class AddConstraint extends ChangeConstraint { 327 class AddConstraint extends ChangeConstraint {
393 /** 328 /**
394 * The package that has the dependency. 329 * The package that has the dependency.
395 */ 330 */
396 final String depender; 331 final String depender;
397 332
398 /** 333 /**
399 * The package being depended on and the constraints being placed on it. The 334 * The package being depended on and the constraints being placed on it. The
400 * source, version, and description in this ref are all considered constraints 335 * source, version, and description in this ref are all considered constraints
401 * on the dependent package. 336 * on the dependent package.
402 */ 337 */
403 final PackageRef ref; 338 final PackageRef ref;
404 339
405 AddConstraint(this.depender, this.ref); 340 AddConstraint(this.depender, this.ref);
406 341
407 Future process(VersionSolver solver) { 342 Future process(VersionSolver solver) {
408 var dependency = solver.getDependency(ref.name); 343 var dependency = solver.getDependency(ref.name);
409 var oldConstraint = dependency.constraint; 344 var oldConstraint = dependency.constraint;
410 dependency.placeConstraint(depender, ref); 345 dependency.placeConstraint(depender, ref);
411 return _processChange(solver, dependency, oldConstraint); 346 var newConstraint = dependency.constraint;
412 } 347 return _processChange(solver, ref.source, ref.description, dependency,
413 348 oldConstraint, newConstraint);
414 void undo(VersionSolver solver) {
415 solver.getDependency(ref.name).removeConstraint(depender);
416 } 349 }
417 } 350 }
418 351
419 /** 352 /**
420 * [depender] is no longer placing a constraint on [dependent]. 353 * [depender] is no longer placing a constraint on [dependent].
421 */ 354 */
422 class RemoveConstraint extends ChangeConstraint { 355 class RemoveConstraint extends ChangeConstraint {
423 /** 356 /**
424 * The package that was placing a constraint on [dependent]. 357 * The package that was placing a constraint on [dependent].
425 */ 358 */
426 String depender; 359 String depender;
427 360
428 /** 361 /**
429 * The package that was being depended on. 362 * The package that was being depended on.
430 */ 363 */
431 String dependent; 364 String dependent;
432 365
433 /** The constraint that was removed. */
434 PackageRef _removed;
435
436 RemoveConstraint(this.depender, this.dependent); 366 RemoveConstraint(this.depender, this.dependent);
437 367
438 Future process(VersionSolver solver) { 368 Future process(VersionSolver solver) {
439 var dependency = solver.getDependency(dependent); 369 var dependency = solver.getDependency(dependent);
440 var oldConstraint = dependency.constraint; 370 var oldConstraint = dependency.constraint;
441 var source = dependency.source; 371 var source = dependency.source;
442 _removed = dependency.removeConstraint(depender); 372 var description = dependency.description;
443 return _processChange(solver, dependency, oldConstraint); 373 dependency.removeConstraint(depender);
444 } 374 var newConstraint = dependency.constraint;
445 375 return _processChange(solver, source, description, dependency,
446 void undo() { 376 oldConstraint, newConstraint);
447 solver.getDependency(dependent).placeConstraint(depender, _removed);
448 } 377 }
449 } 378 }
450 379
451 /** [package]'s version is no longer constrained by the lockfile. */
452 class UnlockPackage implements WorkItem {
453 /** The package being unlocked. */
454 Dependency package;
455
456 UnlockPackage(this.package);
457
458 Future process(VersionSolver solver) {
459 solver.lockFile.packages.remove(package.name);
460 return solver.getBestVersion(package).transform((best) {
461 if (best == null) return null;
462 solver.enqueue(new ChangeVersion(
463 package.source, package.description, best));
464 });
465 }
466 }
467
468 // TODO(rnystrom): Instead of always pulling from the source (which will mean 380 // TODO(rnystrom): Instead of always pulling from the source (which will mean
469 // hitting a server), we should consider caching pubspecs of uninstalled 381 // hitting a server), we should consider caching pubspecs of uninstalled
470 // packages in the system cache. 382 // packages in the system cache.
471 /** 383 /**
472 * Maintains a cache of previously-loaded pubspecs. Used to avoid requesting 384 * Maintains a cache of previously-loaded pubspecs. Used to avoid requesting
473 * the same pubspec from the server repeatedly. 385 * the same pubspec from the server repeatedly.
474 */ 386 */
475 class PubspecCache { 387 class PubspecCache {
476 final SourceRegistry _sources; 388 final SourceRegistry _sources;
477 final Map<PackageId, Pubspec> _pubspecs; 389 final Map<PackageId, Pubspec> _pubspecs;
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
533 * according to [source]. 445 * according to [source].
534 */ 446 */
535 var description; 447 var description;
536 448
537 /** 449 /**
538 * The currently-selected best version for this dependency. 450 * The currently-selected best version for this dependency.
539 */ 451 */
540 Version version; 452 Version version;
541 453
542 /** 454 /**
543 * Whether this dependency should always select the latest version.
544 */
545 bool useLatestVersion = false;
546
547 /**
548 * Gets whether or not any other packages are currently depending on this 455 * Gets whether or not any other packages are currently depending on this
549 * one. If `false`, then it means this package is not part of the dependency 456 * one. If `false`, then it means this package is not part of the dependency
550 * graph and should be omitted. 457 * graph and should be omitted.
551 */ 458 */
552 bool get isDependedOn() => !_refs.isEmpty(); 459 bool get isDependedOn() => !_refs.isEmpty();
553 460
554 /** The names of all the packages that depend on this dependency. */
555 Collection<String> get dependers() => _refs.getKeys();
556
557 /** 461 /**
558 * Gets the overall constraint that all packages are placing on this one. 462 * Gets the overall constraint that all packages are placing on this one.
559 * If no packages have a constraint on this one (which can happen when this 463 * If no packages have a constraint on this one (which can happen when this
560 * package is in the process of being added to the graph), returns `null`. 464 * package is in the process of being added to the graph), returns `null`.
561 */ 465 */
562 VersionConstraint get constraint() { 466 VersionConstraint get constraint() {
563 if (_refs.isEmpty()) return null; 467 if (_refs.isEmpty()) return null;
564 return new VersionConstraint.intersect( 468 return new VersionConstraint.intersect(
565 _refs.getValues().map((ref) => ref.constraint)); 469 _refs.getValues().map((ref) => ref.constraint));
566 } 470 }
(...skipping 16 matching lines...) Expand all
583 throw new DescriptionMismatchException( 487 throw new DescriptionMismatchException(
584 name, description, ref.description); 488 name, description, ref.description);
585 } 489 }
586 490
587 _refs[package] = ref; 491 _refs[package] = ref;
588 } 492 }
589 493
590 /** 494 /**
591 * Removes the constraint from [package] onto this. 495 * Removes the constraint from [package] onto this.
592 */ 496 */
593 PackageRef removeConstraint(String package) { 497 void removeConstraint(String package) {
594 var removed = _refs.remove(package); 498 _refs.remove(package);
595 499
596 if (_refs.isEmpty()) { 500 if (_refs.isEmpty()) {
597 source = null; 501 source = null;
598 description = null; 502 description = null;
599 } 503 }
600
601 return removed;
602 } 504 }
603 } 505 }
604 506
605 // TODO(rnystrom): Report the last of depending packages and their constraints. 507 // TODO(rnystrom): Report the last of depending packages and their constraints.
606 /** 508 /**
607 * Exception thrown when the [VersionConstraint] used to match a package is 509 * Exception thrown when the [VersionConstraint] used to match a package is
608 * valid (i.e. non-empty), but there are no released versions of the package 510 * valid (i.e. non-empty), but there are no released versions of the package
609 * that fit that constraint. 511 * that fit that constraint.
610 */ 512 */
611 class NoVersionException implements Exception { 513 class NoVersionException implements Exception {
612 final String package; 514 final String package;
613 final VersionConstraint constraint; 515 final VersionConstraint constraint;
614 516
615 NoVersionException(this.package, this.constraint); 517 NoVersionException(this.package, this.constraint);
616 518
617 String toString() => 519 String toString() =>
618 "Package '$package' has no versions that match $constraint."; 520 "Package '$package' has no versions that match $constraint.";
619 } 521 }
620 522
621 // TODO(rnystrom): Report the list of depending packages and their constraints.
622 /**
623 * Exception thrown when the most recent version of [package] must be selected,
624 * but doesn't match the [VersionConstraint] imposed on the package.
625 */
626 class CouldNotUpdateException implements Exception {
627 final String package;
628 final VersionConstraint constraint;
629 final Version best;
630
631 CouldNotUpdateException(this.package, this.constraint, this.best);
632
633 String toString() =>
634 "The latest version of '$package', $best, does not match $constraint.";
635 }
636
637 // TODO(rnystrom): Report the last of depending packages and their constraints. 523 // TODO(rnystrom): Report the last of depending packages and their constraints.
638 /** 524 /**
639 * Exception thrown when the [VersionConstraint] used to match a package is 525 * Exception thrown when the [VersionConstraint] used to match a package is
640 * the empty set: in other words, multiple packages depend on it and have 526 * the empty set: in other words, multiple packages depend on it and have
641 * conflicting constraints that have no overlap. 527 * conflicting constraints that have no overlap.
642 */ 528 */
643 class DisjointConstraintException implements Exception { 529 class DisjointConstraintException implements Exception {
644 final String package; 530 final String package;
645 531
646 DisjointConstraintException(this.package); 532 DisjointConstraintException(this.package);
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
686 final description1; 572 final description1;
687 final description2; 573 final description2;
688 574
689 DescriptionMismatchException(this.package, this.description1, 575 DescriptionMismatchException(this.package, this.description1,
690 this.description2); 576 this.description2);
691 577
692 // TODO(nweiz): Dump to YAML when that's supported 578 // TODO(nweiz): Dump to YAML when that's supported
693 String toString() => "Package '$package' has conflicting descriptions " 579 String toString() => "Package '$package' has conflicting descriptions "
694 "'${JSON.stringify(description1)}' and '${JSON.stringify(description2)}'"; 580 "'${JSON.stringify(description1)}' and '${JSON.stringify(description2)}'";
695 } 581 }
OLDNEW
« no previous file with comments | « utils/pub/entrypoint.dart ('k') | utils/tests/pub/pub_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698