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

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

Issue 11783009: Big merge from experimental to bleeding edge. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 11 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/version.dart ('k') | utils/pub/yaml/composer.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 /// Attempts to resolve a set of version constraints for a package dependency 5 /// Attempts to resolve a set of version constraints for a package dependency
6 /// graph and select an appropriate set of best specific versions for all 6 /// graph and select an appropriate set of best specific versions for all
7 /// dependent packages. It works iteratively and tries to reach a stable 7 /// dependent packages. It works iteratively and tries to reach a stable
8 /// solution where the constraints of all dependencies are met. If it fails to 8 /// solution where the constraints of all dependencies are met. If it fails to
9 /// reach a solution after a certain number of iterations, it assumes the 9 /// reach a solution after a certain number of iterations, it assumes the
10 /// dependency graph is unstable and reports and error. 10 /// dependency graph is unstable and reports and error.
(...skipping 17 matching lines...) Expand all
28 /// When a constraint on a package changes, we re-calculate the overall 28 /// When a constraint on a package changes, we re-calculate the overall
29 /// constraint on that package. I.e. with a shared dependency, we intersect all 29 /// constraint on that package. I.e. with a shared dependency, we intersect all
30 /// of the constraints that its depending packages place on it. If that overall 30 /// of the constraints that its depending packages place on it. If that overall
31 /// constraint changes (say from "<3.0.0" to "<2.5.0"), then the currently 31 /// constraint changes (say from "<3.0.0" to "<2.5.0"), then the currently
32 /// picked version for that package may fall outside of the new constraint. If 32 /// picked version for that package may fall outside of the new constraint. If
33 /// that happens, we find the new best version that meets the updated constraint 33 /// that happens, we find the new best version that meets the updated constraint
34 /// and then the change the package to use that version. That cycles back up to 34 /// and then the change the package to use that version. That cycles back up to
35 /// the beginning again. 35 /// the beginning again.
36 library version_solver; 36 library version_solver;
37 37
38 import 'dart:json'; 38 import 'dart:async';
39 import 'dart:json' as json;
39 import 'dart:math'; 40 import 'dart:math';
40 import 'lock_file.dart'; 41 import 'lock_file.dart';
41 import 'log.dart' as log; 42 import 'log.dart' as log;
42 import 'package.dart'; 43 import 'package.dart';
43 import 'pubspec.dart'; 44 import 'pubspec.dart';
44 import 'root_source.dart'; 45 import 'root_source.dart';
45 import 'source.dart'; 46 import 'source.dart';
46 import 'source_registry.dart'; 47 import 'source_registry.dart';
47 import 'utils.dart'; 48 import 'utils.dart';
48 import 'version.dart'; 49 import 'version.dart';
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 if (_numIterations > max(50, _packages.length * 5)) { 110 if (_numIterations > max(50, _packages.length * 5)) {
110 throw new CouldNotSolveException(); 111 throw new CouldNotSolveException();
111 } 112 }
112 113
113 // Run the first work item. 114 // Run the first work item.
114 var future = _work.removeFirst().process(this); 115 var future = _work.removeFirst().process(this);
115 116
116 // If we have an async operation to perform, chain the loop to resume 117 // If we have an async operation to perform, chain the loop to resume
117 // when it's done. Otherwise, just loop synchronously. 118 // when it's done. Otherwise, just loop synchronously.
118 if (future != null) { 119 if (future != null) {
119 return future.chain(processNextWorkItem); 120 return future.then(processNextWorkItem);
120 } 121 }
121 } 122 }
122 } 123 }
123 124
124 return processNextWorkItem(null); 125 return processNextWorkItem(null);
125 } 126 }
126 127
127 void enqueue(WorkItem work) { 128 void enqueue(WorkItem work) {
128 _work.add(work); 129 _work.add(work);
129 } 130 }
130 131
131 Dependency getDependency(String package) { 132 Dependency getDependency(String package) {
132 // There can be unused dependencies in the graph, so just create an empty 133 // There can be unused dependencies in the graph, so just create an empty
133 // one if needed. 134 // one if needed.
134 _packages.putIfAbsent(package, () => new Dependency(package)); 135 _packages.putIfAbsent(package, () => new Dependency(package));
135 return _packages[package]; 136 return _packages[package];
136 } 137 }
137 138
138 /// Sets the best selected version of [package] to [version]. 139 /// Sets the best selected version of [package] to [version].
139 void setVersion(String package, Version version) { 140 void setVersion(String package, Version version) {
140 _packages[package].version = version; 141 _packages[package].version = version;
141 } 142 }
142 143
143 /// Returns the most recent version of [dependency] that satisfies all of its 144 /// Returns the most recent version of [dependency] that satisfies all of its
144 /// version constraints. 145 /// version constraints.
145 Future<Version> getBestVersion(Dependency dependency) { 146 Future<Version> getBestVersion(Dependency dependency) {
146 return dependency.getVersions().transform((versions) { 147 return dependency.getVersions().then((versions) {
147 var best = null; 148 var best = null;
148 for (var version in versions) { 149 for (var version in versions) {
149 if (dependency.useLatestVersion || 150 if (dependency.useLatestVersion ||
150 dependency.constraint.allows(version)) { 151 dependency.constraint.allows(version)) {
151 if (best == null || version > best) best = version; 152 if (best == null || version > best) best = version;
152 } 153 }
153 } 154 }
154 155
155 // TODO(rnystrom): Better exception. 156 // TODO(rnystrom): Better exception.
156 if (best == null) { 157 if (best == null) {
(...skipping 25 matching lines...) Expand all
182 183
183 for (var dependerName in dependency.dependers) { 184 for (var dependerName in dependency.dependers) {
184 var depender = getDependency(dependerName); 185 var depender = getDependency(dependerName);
185 var locked = lockFile.packages[dependerName]; 186 var locked = lockFile.packages[dependerName];
186 if (locked != null && depender.version == locked.version) { 187 if (locked != null && depender.version == locked.version) {
187 enqueue(new UnlockPackage(depender)); 188 enqueue(new UnlockPackage(depender));
188 return true; 189 return true;
189 } 190 }
190 } 191 }
191 192
192 return dependency.dependers.map(getDependency).some((subdependency) => 193 return dependency.dependers.mappedBy(getDependency).any((subdependency) =>
193 tryUnlockDepender(subdependency, seen)); 194 tryUnlockDepender(subdependency, seen));
194 } 195 }
195 196
196 List<PackageId> buildResults() { 197 List<PackageId> buildResults() {
197 return _packages.values.filter((dep) => dep.isDependedOn).map((dep) { 198 return _packages.values.where((dep) => dep.isDependedOn).mappedBy((dep) {
198 var description = dep.description; 199 var description = dep.description;
199 200
200 // If the lockfile contains a fully-resolved description for the package, 201 // If the lockfile contains a fully-resolved description for the package,
201 // use that. This allows e.g. Git to ensure that the same commit is used. 202 // use that. This allows e.g. Git to ensure that the same commit is used.
202 var lockedPackage = lockFile.packages[dep.name]; 203 var lockedPackage = lockFile.packages[dep.name];
203 if (lockedPackage != null && lockedPackage.version == dep.version && 204 if (lockedPackage != null && lockedPackage.version == dep.version &&
204 lockedPackage.source.name == dep.source.name && 205 lockedPackage.source.name == dep.source.name &&
205 dep.source.descriptionsEqual( 206 dep.source.descriptionsEqual(
206 description, lockedPackage.description)) { 207 description, lockedPackage.description)) {
207 description = lockedPackage.description; 208 description = lockedPackage.description;
208 } 209 }
209 210
210 return new PackageId(dep.name, dep.source, dep.version, description); 211 return new PackageId(dep.name, dep.source, dep.version, description);
211 }); 212 })
213 .toList();
212 } 214 }
213 } 215 }
214 216
215 /// The constraint solver works by iteratively processing a queue of work items. 217 /// The constraint solver works by iteratively processing a queue of work items.
216 /// Each item is a single atomic change to the dependency graph. Handling them 218 /// Each item is a single atomic change to the dependency graph. Handling them
217 /// in a queue lets us handle asynchrony (resolving versions requires 219 /// in a queue lets us handle asynchrony (resolving versions requires
218 /// information from servers) as well as avoid deeply nested recursion. 220 /// information from servers) as well as avoid deeply nested recursion.
219 abstract class WorkItem { 221 abstract class WorkItem {
220 /// Processes this work item. Returns a future that completes when the work is 222 /// Processes this work item. Returns a future that completes when the work is
221 /// done. If `null` is returned, that means the work has completed 223 /// done. If `null` is returned, that means the work has completed
(...skipping 25 matching lines...) Expand all
247 log.fine("Changing $package to version $version."); 249 log.fine("Changing $package to version $version.");
248 250
249 var dependency = solver.getDependency(package); 251 var dependency = solver.getDependency(package);
250 var oldVersion = dependency.version; 252 var oldVersion = dependency.version;
251 solver.setVersion(package, version); 253 solver.setVersion(package, version);
252 254
253 // The dependencies between the old and new version may be different. Walk 255 // The dependencies between the old and new version may be different. Walk
254 // them both and update any constraints that differ between the two. 256 // them both and update any constraints that differ between the two.
255 return Futures.wait([ 257 return Futures.wait([
256 getDependencyRefs(solver, oldVersion), 258 getDependencyRefs(solver, oldVersion),
257 getDependencyRefs(solver, version)]).transform((list) { 259 getDependencyRefs(solver, version)]).then((list) {
258 var oldDependencyRefs = list[0]; 260 var oldDependencyRefs = list[0];
259 var newDependencyRefs = list[1]; 261 var newDependencyRefs = list[1];
260 262
261 for (var oldRef in oldDependencyRefs.values) { 263 for (var oldRef in oldDependencyRefs.values) {
262 if (newDependencyRefs.containsKey(oldRef.name)) { 264 if (newDependencyRefs.containsKey(oldRef.name)) {
263 // The dependency is in both versions of this package, but its 265 // The dependency is in both versions of this package, but its
264 // constraint may have changed. 266 // constraint may have changed.
265 var newRef = newDependencyRefs.remove(oldRef.name); 267 var newRef = newDependencyRefs.remove(oldRef.name);
266 solver.enqueue(new AddConstraint(package, newRef)); 268 solver.enqueue(new AddConstraint(package, newRef));
267 } else { 269 } else {
(...skipping 14 matching lines...) Expand all
282 /// Get the dependencies at [version] of the package being changed. 284 /// Get the dependencies at [version] of the package being changed.
283 Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver, 285 Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver,
284 Version version) { 286 Version version) {
285 // If there is no version, it means no package, so no dependencies. 287 // If there is no version, it means no package, so no dependencies.
286 if (version == null) { 288 if (version == null) {
287 return 289 return
288 new Future<Map<String, PackageRef>>.immediate(<String, PackageRef>{}); 290 new Future<Map<String, PackageRef>>.immediate(<String, PackageRef>{});
289 } 291 }
290 292
291 var id = new PackageId(package, source, version, description); 293 var id = new PackageId(package, source, version, description);
292 return solver._pubspecs.load(id).transform((pubspec) { 294 return solver._pubspecs.load(id).then((pubspec) {
293 var dependencies = <String, PackageRef>{}; 295 var dependencies = <String, PackageRef>{};
294 for (var dependency in pubspec.dependencies) { 296 for (var dependency in pubspec.dependencies) {
295 dependencies[dependency.name] = dependency; 297 dependencies[dependency.name] = dependency;
296 } 298 }
297 return dependencies; 299 return dependencies;
298 }); 300 });
299 } 301 }
300 } 302 }
301 303
302 /// A constraint that a depending package places on a dependent package has 304 /// A constraint that a depending package places on a dependent package has
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
357 var lockedVersion = lockedPackage.version; 359 var lockedVersion = lockedPackage.version;
358 if (newConstraint.allows(lockedVersion)) { 360 if (newConstraint.allows(lockedVersion)) {
359 solver.enqueue( 361 solver.enqueue(
360 new ChangeVersion(name, source, description, lockedVersion)); 362 new ChangeVersion(name, source, description, lockedVersion));
361 return null; 363 return null;
362 } 364 }
363 } 365 }
364 366
365 // The constraint has changed, so see what the best version of the package 367 // The constraint has changed, so see what the best version of the package
366 // that meets the new constraint is. 368 // that meets the new constraint is.
367 return solver.getBestVersion(newDependency).transform((best) { 369 return solver.getBestVersion(newDependency).then((best) {
368 if (best == null) { 370 if (best == null) {
369 undo(solver); 371 undo(solver);
370 } else if (newDependency.version != best) { 372 } else if (newDependency.version != best) {
371 solver.enqueue(new ChangeVersion(name, source, description, best)); 373 solver.enqueue(new ChangeVersion(name, source, description, best));
372 } 374 }
373 }); 375 });
374 } 376 }
375 } 377 }
376 378
377 /// The constraint given by [ref] is being placed by [depender]. 379 /// The constraint given by [ref] is being placed by [depender].
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
431 class UnlockPackage implements WorkItem { 433 class UnlockPackage implements WorkItem {
432 /// The package being unlocked. 434 /// The package being unlocked.
433 Dependency package; 435 Dependency package;
434 436
435 UnlockPackage(this.package); 437 UnlockPackage(this.package);
436 438
437 Future process(VersionSolver solver) { 439 Future process(VersionSolver solver) {
438 log.fine("Unlocking ${package.name}."); 440 log.fine("Unlocking ${package.name}.");
439 441
440 solver.lockFile.packages.remove(package.name); 442 solver.lockFile.packages.remove(package.name);
441 return solver.getBestVersion(package).transform((best) { 443 return solver.getBestVersion(package).then((best) {
442 if (best == null) return null; 444 if (best == null) return null;
443 solver.enqueue(new ChangeVersion( 445 solver.enqueue(new ChangeVersion(
444 package.name, package.source, package.description, best)); 446 package.name, package.source, package.description, best));
445 }); 447 });
446 } 448 }
447 } 449 }
448 450
449 // TODO(rnystrom): Instead of always pulling from the source (which will mean 451 // TODO(rnystrom): Instead of always pulling from the source (which will mean
450 // hitting a server), we should consider caching pubspecs of uninstalled 452 // hitting a server), we should consider caching pubspecs of uninstalled
451 // packages in the system cache. 453 // packages in the system cache.
(...skipping 11 matching lines...) Expand all
463 _pubspecs[id] = pubspec; 465 _pubspecs[id] = pubspec;
464 } 466 }
465 467
466 /// Loads the pubspec for the package identified by [id]. 468 /// Loads the pubspec for the package identified by [id].
467 Future<Pubspec> load(PackageId id) { 469 Future<Pubspec> load(PackageId id) {
468 // Complete immediately if it's already cached. 470 // Complete immediately if it's already cached.
469 if (_pubspecs.containsKey(id)) { 471 if (_pubspecs.containsKey(id)) {
470 return new Future<Pubspec>.immediate(_pubspecs[id]); 472 return new Future<Pubspec>.immediate(_pubspecs[id]);
471 } 473 }
472 474
473 return id.describe().transform((pubspec) { 475 return id.describe().then((pubspec) {
474 // Cache it. 476 // Cache it.
475 _pubspecs[id] = pubspec; 477 _pubspecs[id] = pubspec;
476 return pubspec; 478 return pubspec;
477 }); 479 });
478 } 480 }
479 } 481 }
480 482
481 /// Describes one [Package] in the [DependencyGraph] and keeps track of which 483 /// Describes one [Package] in the [DependencyGraph] and keeps track of which
482 /// packages depend on it and what constraints they place on it. 484 /// packages depend on it and what constraints they place on it.
483 class Dependency { 485 class Dependency {
484 /// The name of the this dependency's package. 486 /// The name of the this dependency's package.
485 final String name; 487 final String name;
486 488
487 /// The [PackageRefs] that represent constraints that depending packages have 489 /// The [PackageRefs] that represent constraints that depending packages have
488 /// placed on this one. 490 /// placed on this one.
489 final Map<String, PackageRef> _refs; 491 final Map<String, PackageRef> _refs;
490 492
491 /// The currently-selected best version for this dependency. 493 /// The currently-selected best version for this dependency.
492 Version version; 494 Version version;
493 495
494 /// Whether this dependency should always select the latest version. 496 /// Whether this dependency should always select the latest version.
495 bool useLatestVersion = false; 497 bool useLatestVersion = false;
496 498
497 /// Gets whether or not any other packages are currently depending on this 499 /// Gets whether or not any other packages are currently depending on this
498 /// one. If `false`, then it means this package is not part of the dependency 500 /// one. If `false`, then it means this package is not part of the dependency
499 /// graph and should be omitted. 501 /// graph and should be omitted.
500 bool get isDependedOn => !_refs.isEmpty; 502 bool get isDependedOn => !_refs.isEmpty;
501 503
502 /// The names of all the packages that depend on this dependency. 504 /// The names of all the packages that depend on this dependency.
503 Collection<String> get dependers => _refs.keys; 505 Iterable<String> get dependers => _refs.keys;
504 506
505 /// Gets the overall constraint that all packages are placing on this one. 507 /// Gets the overall constraint that all packages are placing on this one.
506 /// If no packages have a constraint on this one (which can happen when this 508 /// If no packages have a constraint on this one (which can happen when this
507 /// package is in the process of being added to the graph), returns `null`. 509 /// package is in the process of being added to the graph), returns `null`.
508 VersionConstraint get constraint { 510 VersionConstraint get constraint {
509 if (_refs.isEmpty) return null; 511 if (_refs.isEmpty) return null;
510 return new VersionConstraint.intersection( 512 return new VersionConstraint.intersection(
511 _refs.values.map((ref) => ref.constraint)); 513 _refs.values.mappedBy((ref) => ref.constraint));
512 } 514 }
513 515
514 /// The source of this dependency's package. 516 /// The source of this dependency's package.
515 Source get source { 517 Source get source {
516 var canonical = _canonicalRef(); 518 var canonical = _canonicalRef();
517 if (canonical == null) return null; 519 if (canonical == null) return null;
518 return canonical.source; 520 return canonical.source;
519 } 521 }
520 522
521 /// The description of this dependency's package. 523 /// The description of this dependency's package.
522 get description { 524 get description {
523 var canonical = _canonicalRef(); 525 var canonical = _canonicalRef();
524 if (canonical == null) return null; 526 if (canonical == null) return null;
525 return canonical.description; 527 return canonical.description;
526 } 528 }
527 529
528 /// Return the PackageRef that has the canonical source and description for 530 /// Return the PackageRef that has the canonical source and description for
529 /// this package. If any dependency requires that this package come from a 531 /// this package. If any dependency requires that this package come from a
530 /// [RootSource], that will be used; otherwise, it will be the source and 532 /// [RootSource], that will be used; otherwise, it will be the source and
531 /// description that all dependencies agree upon. 533 /// description that all dependencies agree upon.
532 PackageRef _canonicalRef() { 534 PackageRef _canonicalRef() {
533 if (_refs.isEmpty) return null; 535 if (_refs.isEmpty) return null;
534 var refs = _refs.values; 536 var refs = _refs.values;
535 for (var ref in refs) { 537 for (var ref in refs) {
536 if (ref is RootSource) return ref; 538 if (ref is RootSource) return ref;
537 } 539 }
538 return refs[0]; 540 return refs.first;
539 } 541 }
540 542
541 Dependency(this.name) 543 Dependency(this.name)
542 : _refs = <String, PackageRef>{}; 544 : _refs = <String, PackageRef>{};
543 545
544 Dependency._clone(Dependency other) 546 Dependency._clone(Dependency other)
545 : name = other.name, 547 : name = other.name,
546 version = other.version, 548 version = other.version,
547 _refs = new Map<String, PackageRef>.from(other._refs); 549 _refs = new Map<String, PackageRef>.from(other._refs);
548 550
(...skipping 20 matching lines...) Expand all
569 571
570 _refs[package] = ref; 572 _refs[package] = ref;
571 } 573 }
572 574
573 /// Returns the name of a package whose constraint source and description 575 /// Returns the name of a package whose constraint source and description
574 /// all other constraints must match. Returns null if there are no 576 /// all other constraints must match. Returns null if there are no
575 /// requirements on new constraints. 577 /// requirements on new constraints.
576 String _requiredDepender() { 578 String _requiredDepender() {
577 if (_refs.isEmpty) return null; 579 if (_refs.isEmpty) return null;
578 580
579 var dependers = _refs.keys; 581 var dependers = _refs.keys.toList();
580 if (dependers.length == 1) { 582 if (dependers.length == 1) {
581 var depender = dependers[0]; 583 var depender = dependers[0];
582 if (_refs[depender].source is RootSource) return null; 584 if (_refs[depender].source is RootSource) return null;
583 return depender; 585 return depender;
584 } 586 }
585 587
586 return dependers[1]; 588 return dependers[1];
587 } 589 }
588 590
589 /// Removes the constraint from [package] onto this. 591 /// Removes the constraint from [package] onto this.
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after
693 final String depender2; 695 final String depender2;
694 final description2; 696 final description2;
695 697
696 DescriptionMismatchException(this.package, this.depender1, this.description1, 698 DescriptionMismatchException(this.package, this.depender1, this.description1,
697 this.depender2, this.description2); 699 this.depender2, this.description2);
698 700
699 String toString() { 701 String toString() {
700 // TODO(nweiz): Dump descriptions to YAML when that's supported. 702 // TODO(nweiz): Dump descriptions to YAML when that's supported.
701 return "Incompatible dependencies on '$package':\n" 703 return "Incompatible dependencies on '$package':\n"
702 "- '$depender1' depends on it with description " 704 "- '$depender1' depends on it with description "
703 "${JSON.stringify(description1)}\n" 705 "${json.stringify(description1)}\n"
704 "- '$depender2' depends on it with description " 706 "- '$depender2' depends on it with description "
705 "${JSON.stringify(description2)}"; 707 "${json.stringify(description2)}";
706 } 708 }
707 } 709 }
OLDNEW
« no previous file with comments | « utils/pub/version.dart ('k') | utils/pub/yaml/composer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698