Chromium Code Reviews| 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 /** | 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 19 matching lines...) Expand all Loading... | |
| 30 * constraint on that package. I.e. with a shared dependency, we intersect all | 30 * constraint on that package. I.e. with a shared dependency, we intersect all |
| 31 * of the constraints that its depending packages place on it. If that overall | 31 * of the constraints that its depending packages place on it. If that overall |
| 32 * constraint changes (say from "<3.0.0" to "<2.5.0"), then the currently | 32 * constraint changes (say from "<3.0.0" to "<2.5.0"), then the currently |
| 33 * picked version for that package may fall outside of the new constraint. If | 33 * picked version for that package may fall outside of the new constraint. If |
| 34 * that happens, we find the new best version that meets the updated constraint | 34 * that happens, we find the new best version that meets the updated constraint |
| 35 * and then the change the package to use that version. That cycles back up to | 35 * and then the change the package to use that version. That cycles back up to |
| 36 * the beginning again. | 36 * the beginning again. |
| 37 */ | 37 */ |
| 38 #library('version_solver'); | 38 #library('version_solver'); |
| 39 | 39 |
| 40 #import('dart:json'); | |
| 40 #import('package.dart'); | 41 #import('package.dart'); |
| 41 #import('pubspec.dart'); | 42 #import('pubspec.dart'); |
| 43 #import('root_source.dart'); | |
| 42 #import('source.dart'); | 44 #import('source.dart'); |
| 43 #import('source_registry.dart'); | 45 #import('source_registry.dart'); |
| 44 #import('utils.dart'); | 46 #import('utils.dart'); |
| 45 #import('version.dart'); | 47 #import('version.dart'); |
| 46 | 48 |
| 47 /** | 49 /** |
| 48 * Attempts to select the best concrete versions for all of the transitive | 50 * Attempts to select the best concrete versions for all of the transitive |
| 49 * dependencies of [root] taking into account all of the [VersionConstraint]s | 51 * dependencies of [root] taking into account all of the [VersionConstraint]s |
| 50 * that those dependencies place on each other. If successful, completes to a | 52 * that those dependencies place on each other. If successful, completes to a |
| 51 * [Map] that maps package names to the selected version for that package. If | 53 * [Map] that maps package names to the selected version for that package. If |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 65 final Queue<WorkItem> _work; | 67 final Queue<WorkItem> _work; |
| 66 int _numIterations = 0; | 68 int _numIterations = 0; |
| 67 | 69 |
| 68 VersionSolver(SourceRegistry sources, Package root) | 70 VersionSolver(SourceRegistry sources, Package root) |
| 69 : _sources = sources, | 71 : _sources = sources, |
| 70 _root = root, | 72 _root = root, |
| 71 _pubspecs = new PubspecCache(sources), | 73 _pubspecs = new PubspecCache(sources), |
| 72 _packages = <Dependency>{}, | 74 _packages = <Dependency>{}, |
| 73 _work = new Queue<WorkItem>(); | 75 _work = new Queue<WorkItem>(); |
| 74 | 76 |
| 75 Future<Map<String, Version>> solve() { | 77 Future<List<PackageId>> solve() { |
| 76 // Kick off the work by adding the root package at its concrete version to | 78 // Kick off the work by adding the root package at its concrete version to |
| 77 // the dependency graph. | 79 // the dependency graph. |
| 78 _pubspecs.cache(_root); | 80 var ref = new PackageRef(new RootSource(_root), _root.version, _root.name); |
| 79 enqueue(new ChangeConstraint('(entrypoint)', _root.name, _root.version)); | 81 enqueue(new AddConstraint('(entrypoint)', ref)); |
| 82 _pubspecs.cache(ref.withVersion(_root.version), _root.pubspec); | |
| 80 | 83 |
| 81 Future processNextWorkItem(_) { | 84 Future processNextWorkItem(_) { |
| 82 while (true) { | 85 while (true) { |
| 83 // Stop if we are done. | 86 // Stop if we are done. |
| 84 if (_work.isEmpty()) return new Future.immediate(buildResults()); | 87 if (_work.isEmpty()) return new Future.immediate(buildResults()); |
| 85 | 88 |
| 86 // If we appear to be stuck in a loop, then we probably have an unstable | 89 // If we appear to be stuck in a loop, then we probably have an unstable |
| 87 // graph, bail. We guess this based on a rough heuristic that it should | 90 // graph, bail. We guess this based on a rough heuristic that it should |
| 88 // only take a certain number of steps to solve a graph with a given | 91 // only take a certain number of steps to solve a graph with a given |
| 89 // number of connections. | 92 // number of connections. |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 108 return processNextWorkItem(null); | 111 return processNextWorkItem(null); |
| 109 } | 112 } |
| 110 | 113 |
| 111 void enqueue(WorkItem work) { | 114 void enqueue(WorkItem work) { |
| 112 _work.add(work); | 115 _work.add(work); |
| 113 } | 116 } |
| 114 | 117 |
| 115 Dependency getDependency(String package) { | 118 Dependency getDependency(String package) { |
| 116 // There can be unused dependencies in the graph, so just create an empty | 119 // There can be unused dependencies in the graph, so just create an empty |
| 117 // one if needed. | 120 // one if needed. |
| 118 _packages.putIfAbsent(package, () => new Dependency()); | 121 _packages.putIfAbsent(package, () => new Dependency(package)); |
| 119 return _packages[package]; | 122 return _packages[package]; |
| 120 } | 123 } |
| 121 | 124 |
| 122 /** | 125 /** |
| 123 * Sets the best selected version of [package] to [version]. | 126 * Sets the best selected version of [package] to [version]. |
| 124 */ | 127 */ |
| 125 void setVersion(String package, Version version) { | 128 void setVersion(String package, Version version) { |
| 126 _packages[package].version = version; | 129 _packages[package].version = version; |
| 127 } | 130 } |
| 128 | 131 |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 147 interface WorkItem { | 150 interface WorkItem { |
| 148 /** | 151 /** |
| 149 * Processes this work item. Returns a future that completes when the work is | 152 * Processes this work item. Returns a future that completes when the work is |
| 150 * done. If `null` is returned, that means the work has completed | 153 * done. If `null` is returned, that means the work has completed |
| 151 * synchronously and the next item can be started immediately. | 154 * synchronously and the next item can be started immediately. |
| 152 */ | 155 */ |
| 153 Future process(VersionSolver solver); | 156 Future process(VersionSolver solver); |
| 154 } | 157 } |
| 155 | 158 |
| 156 /** | 159 /** |
| 157 * The best selected version for [package] has changed to [version]. | 160 * The best selected version for a package has changed to [version]. If the |
| 158 * If the previous version of the package is `null`, that means the package is | 161 * previous version of the package is `null`, that means the package is being |
| 159 * being added to the graph. If [version] is `null`, it is being removed. | 162 * added to the graph. If [version] is `null`, it is being removed. |
| 160 */ | 163 */ |
| 161 class ChangeVersion implements WorkItem { | 164 class ChangeVersion implements WorkItem { |
| 162 /** | 165 /** |
| 163 * The package whose version is changing. | 166 * The source of the package whose version is changing. |
| 164 */ | 167 */ |
| 165 final String package; | 168 final Source source; |
|
Bob Nystrom
2012/06/29 17:24:40
Can we replace these three fields with just a Pack
nweiz
2012/06/29 18:45:08
It's invalid for a PackageId to have a null versio
| |
| 169 | |
| 170 /** | |
| 171 * The description identifying the package whose version is changing. | |
| 172 */ | |
| 173 final description; | |
| 166 | 174 |
| 167 /** | 175 /** |
| 168 * The new selected version. | 176 * The new selected version. |
| 169 */ | 177 */ |
| 170 final Version version; | 178 final Version version; |
| 171 | 179 |
| 172 ChangeVersion(this.package, this.version); | 180 /** |
| 181 * The name of the package whose version is changing. | |
| 182 */ | |
| 183 String get package() => source.packageName(description); | |
| 184 | |
| 185 ChangeVersion(this.source, this.description, this.version) { | |
| 186 if (source == null) throw "null source"; | |
| 187 } | |
| 173 | 188 |
| 174 Future process(VersionSolver solver) { | 189 Future process(VersionSolver solver) { |
| 175 var oldVersion = solver.getDependency(package).version; | 190 var dependency = solver.getDependency(package); |
| 191 var oldVersion = dependency.version; | |
| 176 solver.setVersion(package, version); | 192 solver.setVersion(package, version); |
| 177 | 193 |
| 178 // The dependencies between the old and new version may be different. Walk | 194 // The dependencies between the old and new version may be different. Walk |
| 179 // them both and update any constraints that differ between the two. | 195 // them both and update any constraints that differ between the two. |
| 180 return Futures.wait([ | 196 return Futures.wait([ |
| 181 getDependencyRefs(solver, oldVersion), | 197 getDependencyRefs(solver, oldVersion), |
| 182 getDependencyRefs(solver, version)]).transform((list) { | 198 getDependencyRefs(solver, version)]).transform((list) { |
| 183 var oldDependencies = list[0]; | 199 var oldDependencyRefs = list[0]; |
| 184 var newDependencies = list[1]; | 200 var newDependencyRefs = list[1]; |
| 185 | 201 |
| 186 for (var dependency in oldDependencies.getValues()) { | 202 for (var oldRef in oldDependencyRefs.getValues()) { |
| 187 var constraint; | 203 if (newDependencyRefs.containsKey(oldRef.name)) { |
| 188 if (newDependencies.containsKey(dependency.name)) { | |
| 189 // The dependency is in both versions of this package, but its | 204 // The dependency is in both versions of this package, but its |
| 190 // constraint may have changed. | 205 // constraint may have changed. |
| 191 constraint = newDependencies.remove(dependency.name).constraint; | 206 var newRef = newDependencyRefs.remove(oldRef.name); |
| 207 solver.enqueue(new AddConstraint(package, newRef)); | |
| 192 } else { | 208 } else { |
| 193 // The dependency is not in the new version of the package, so just | 209 // The dependency is not in the new version of the package, so just |
| 194 // remove its constraint. | 210 // remove its constraint. |
| 195 constraint = null; | 211 solver.enqueue(new RemoveConstraint(package, oldRef.name)); |
| 196 } | 212 } |
| 197 | |
| 198 solver.enqueue(new ChangeConstraint( | |
| 199 package, dependency.name, constraint)); | |
| 200 } | 213 } |
| 201 | 214 |
| 202 // Everything that's left is a depdendency that's only in the new | 215 // Everything that's left is a depdendency that's only in the new |
| 203 // version of the package. | 216 // version of the package. |
| 204 for (var dependency in newDependencies.getValues()) { | 217 for (var newRef in newDependencyRefs.getValues()) { |
| 205 solver.enqueue(new ChangeConstraint( | 218 solver.enqueue(new AddConstraint(package, newRef)); |
| 206 package, dependency.name, dependency.constraint)); | |
| 207 } | 219 } |
| 208 }); | 220 }); |
| 209 } | 221 } |
| 210 | 222 |
| 211 /** | 223 /** |
| 212 * Get the dependencies that [package] has at [version]. | 224 * Get the dependencies of [id]. |
|
Bob Nystrom
2012/06/29 17:24:40
Comment doesn't line up with parameters.
nweiz
2012/06/29 18:45:08
Done.
| |
| 213 */ | 225 */ |
| 214 Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver, | 226 Future<Map<String, PackageRef>> getDependencyRefs(VersionSolver solver, |
| 215 Version version) { | 227 Version version) { |
| 216 // If there is no version, it means no package, so no dependencies. | 228 // If there is no version, it means no package, so no dependencies. |
| 217 if (version == null) { | 229 if (version == null) { |
| 218 return new Future<Map<String, PackageRef>>.immediate(<PackageRef>{}); | 230 return new Future<Map<String, PackageRef>>.immediate(<PackageRef>{}); |
| 219 } | 231 } |
| 220 | 232 |
| 221 return solver._pubspecs.load(package, version).transform((pubspec) { | 233 var id = new PackageId(source, version, description); |
| 234 return solver._pubspecs.load(id).transform((pubspec) { | |
| 222 var dependencies = <PackageRef>{}; | 235 var dependencies = <PackageRef>{}; |
| 223 for (var dependency in pubspec.dependencies) { | 236 for (var dependency in pubspec.dependencies) { |
| 224 dependencies[dependency.name] = dependency; | 237 dependencies[dependency.name] = dependency; |
| 225 } | 238 } |
| 226 return dependencies; | 239 return dependencies; |
| 227 }); | 240 }); |
| 228 } | 241 } |
| 229 } | 242 } |
| 230 | 243 |
| 231 /** | 244 /** |
| 232 * The [VersionConstraint] that [depender] places on [dependent] has changed. | 245 * A constraint that a depending package places on a dependent package has |
| 246 * changed. | |
| 247 * | |
| 248 * This is an abstract class that contains logic for updating the dependency | |
| 249 * graph once a dependency has changed. Changing the dependency is the | |
| 250 * responsibility of subclasses. | |
| 233 */ | 251 */ |
| 234 class ChangeConstraint implements WorkItem { | 252 class ChangeConstraint implements WorkItem { |
| 235 /** | 253 abstract Future process(VersionSolver solver); |
| 236 * The package that has the dependency. | |
| 237 */ | |
| 238 final String depender; | |
| 239 | 254 |
| 240 /** | 255 Future _processChange(VersionSolver solver, Source source, description, |
| 241 * The package being depended on. | 256 Dependency dependency, VersionConstraint oldConstraint, |
| 242 */ | 257 VersionConstraint newConstraint) { |
| 243 final String dependent; | 258 var name = dependency.name; |
| 244 | |
| 245 /** | |
| 246 * The constraint that [depender] places on [dependent]'s version. | |
| 247 */ | |
| 248 final VersionConstraint constraint; | |
| 249 | |
| 250 ChangeConstraint(this.depender, this.dependent, this.constraint); | |
| 251 | |
| 252 Future process(VersionSolver solver) { | |
| 253 var dependency = solver.getDependency(dependent); | |
| 254 var oldConstraint = dependency.constraint; | |
| 255 dependency.placeConstraint(depender, constraint); | |
| 256 var newConstraint = dependency.constraint; | |
| 257 | 259 |
| 258 // If the package is over-constrained, i.e. the packages depending have | 260 // If the package is over-constrained, i.e. the packages depending have |
| 259 // disjoint constraints, then stop. | 261 // disjoint constraints, then stop. |
| 260 if (newConstraint != null && newConstraint.isEmpty) { | 262 if (newConstraint != null && newConstraint.isEmpty) { |
| 261 throw new DisjointConstraintException(dependent); | 263 throw new DisjointConstraintException(name); |
| 262 } | 264 } |
| 263 | 265 |
| 264 // If this constraint change didn't cause the overall constraint on the | 266 // If this constraint change didn't cause the overall constraint on the |
| 265 // package to change, then we don't need to do any further work. | 267 // package to change, then we don't need to do any further work. |
| 266 if (oldConstraint == newConstraint) return null; | 268 if (oldConstraint == newConstraint) return null; |
| 267 | 269 |
| 268 // If the dependency has been cut free from the graph, just remove it. | 270 // If the dependency has been cut free from the graph, just remove it. |
| 269 if (!dependency.isDependedOn) { | 271 if (!dependency.isDependedOn) { |
| 270 solver.enqueue(new ChangeVersion(dependent, null)); | 272 solver.enqueue(new ChangeVersion(source, description, null)); |
| 271 return null; | 273 return null; |
| 272 } | 274 } |
| 273 | 275 |
| 274 // If the dependency is on the root package, then we don't need to do | 276 // If the dependency is on the root package, then we don't need to do |
| 275 // anything since it's already at the best version. | 277 // anything since it's already at the best version. |
| 276 if (dependent == solver._root.name) { | 278 if (name == solver._root.name) { |
| 277 solver.enqueue(new ChangeVersion(dependent, solver._root.version)); | 279 solver.enqueue(new ChangeVersion( |
| 280 source, description, solver._root.version)); | |
| 278 return null; | 281 return null; |
| 279 } | 282 } |
| 280 | 283 |
| 281 // The constraint has changed, so see what the best version of the package | 284 // The constraint has changed, so see what the best version of the package |
| 282 // that meets the new constraint is. | 285 // that meets the new constraint is. |
| 283 // TODO(rnystrom): Should this always be the default source? | 286 return source.getVersions(description).transform((versions) { |
| 284 var source = solver._sources.defaultSource; | |
| 285 return source.getVersions(dependent).transform((versions) { | |
| 286 var best = null; | 287 var best = null; |
| 287 for (var version in versions) { | 288 for (var version in versions) { |
| 288 if (newConstraint.allows(version)) { | 289 if (newConstraint.allows(version)) { |
| 289 if (best == null || version > best) best = version; | 290 if (best == null || version > best) best = version; |
| 290 } | 291 } |
| 291 } | 292 } |
| 292 | 293 |
| 293 // TODO(rnystrom): Better exception. | 294 // TODO(rnystrom): Better exception. |
| 294 if (best == null) throw new NoVersionException(dependent, newConstraint); | 295 if (best == null) throw new NoVersionException(name, newConstraint); |
| 295 | 296 |
| 296 if (dependency.version != best) { | 297 if (dependency.version != best) { |
| 297 solver.enqueue(new ChangeVersion(dependent, best)); | 298 solver.enqueue(new ChangeVersion(source, description, best)); |
| 298 } | 299 } |
| 299 }); | 300 }); |
| 300 } | 301 } |
| 301 } | 302 } |
| 302 | 303 |
| 304 /** | |
| 305 * The constraint given by [ref] is being placed by [depender]. | |
| 306 */ | |
| 307 class AddConstraint extends ChangeConstraint { | |
| 308 /** | |
| 309 * The package that has the dependency. | |
| 310 */ | |
| 311 final String depender; | |
| 312 | |
| 313 /** | |
| 314 * The package being depended on and the constraints being placed on it. The | |
| 315 * source, version, and description in this ref are all considered constraints | |
| 316 * on the dependent package. | |
| 317 */ | |
| 318 final PackageRef ref; | |
| 319 | |
| 320 AddConstraint(this.depender, this.ref); | |
| 321 | |
| 322 Future process(VersionSolver solver) { | |
| 323 var dependency = solver.getDependency(ref.name); | |
| 324 var oldConstraint = dependency.constraint; | |
| 325 dependency.placeConstraint(depender, ref); | |
| 326 var newConstraint = dependency.constraint; | |
| 327 return _processChange(solver, ref.source, ref.description, dependency, | |
| 328 oldConstraint, newConstraint); | |
| 329 } | |
| 330 } | |
| 331 | |
| 332 /** | |
| 333 * [depender] is no longer placing a constraint on [dependent]. | |
| 334 */ | |
| 335 class RemoveConstraint extends ChangeConstraint { | |
| 336 /** | |
| 337 * The package that was placing a constraint on [dependent]. | |
| 338 */ | |
| 339 String depender; | |
| 340 | |
| 341 /** | |
| 342 * The package that was being depended on. | |
| 343 */ | |
| 344 String dependent; | |
| 345 | |
| 346 RemoveConstraint(this.depender, this.dependent); | |
| 347 | |
| 348 Future process(VersionSolver solver) { | |
| 349 var dependency = solver.getDependency(dependent); | |
| 350 var oldConstraint = dependency.constraint; | |
| 351 var source = dependency.source; | |
| 352 var description = dependency.description; | |
| 353 dependency.removeConstraint(depender); | |
| 354 var newConstraint = dependency.constraint; | |
| 355 return _processChange(solver, source, description, dependency, | |
| 356 oldConstraint, newConstraint); | |
| 357 } | |
| 358 } | |
| 359 | |
| 303 // TODO(rnystrom): Instead of always pulling from the source (which will mean | 360 // TODO(rnystrom): Instead of always pulling from the source (which will mean |
| 304 // hitting a server), we should consider caching pubspecs of uninstalled | 361 // hitting a server), we should consider caching pubspecs of uninstalled |
| 305 // packages in the system cache. | 362 // packages in the system cache. |
| 306 /** | 363 /** |
| 307 * Maintains a cache of previously-loaded pubspecs. Used to avoid requesting | 364 * Maintains a cache of previously-loaded pubspecs. Used to avoid requesting |
| 308 * the same pubspec from the server repeatedly. | 365 * the same pubspec from the server repeatedly. |
| 309 */ | 366 */ |
| 310 class PubspecCache { | 367 class PubspecCache { |
| 311 final SourceRegistry _sources; | 368 final SourceRegistry _sources; |
| 312 final Map<String, Map<Version, Pubspec>> _pubspecs; | 369 final Map<PackageId, Pubspec> _pubspecs; |
| 313 | 370 |
| 314 PubspecCache(this._sources) | 371 PubspecCache(this._sources) |
| 315 : _pubspecs = <Map<Version, Pubspec>>{}; | 372 : _pubspecs = new Map<PackageId, Pubspec>(); |
| 316 | 373 |
| 317 /** | 374 /** |
| 318 * Adds the already loaded [package] to the cache. | 375 * Caches [pubspec] as the [Pubspec] for the package identified by [id]. |
| 319 */ | 376 */ |
| 320 void cache(Package package) { | 377 void cache(PackageId id, Pubspec pubspec) { |
| 321 _pubspecs.putIfAbsent(package.name, () => new Map<Version, Pubspec>()); | 378 _pubspecs[id] = pubspec; |
| 322 _pubspecs[package.name][package.version] = package.pubspec; | |
| 323 } | 379 } |
| 324 | 380 |
| 325 /** | 381 /** |
| 326 * Loads the pubspec for [package] at [version]. | 382 * Loads the pubspec for the package identified by [id]. |
| 327 */ | 383 */ |
| 328 Future<Pubspec> load(String package, Version version) { | 384 Future<Pubspec> load(PackageId id) { |
| 329 // Complete immediately if it's already cached. | 385 // Complete immediately if it's already cached. |
| 330 if (_pubspecs.containsKey(package) && | 386 if (_pubspecs.containsKey(id)) { |
| 331 _pubspecs[package].containsKey(version)) { | 387 return new Future<Pubspec>.immediate(_pubspecs[id]); |
| 332 return new Future<Pubspec>.immediate(_pubspecs[package][version]); | |
| 333 } | 388 } |
| 334 | 389 |
| 335 // TODO(rnystrom): Should this always be the default source? | 390 return id.describe().transform((pubspec) { |
| 336 var source = _sources.defaultSource; | |
| 337 return source.describe(package, version).transform((pubspec) { | |
| 338 // Cache it. | 391 // Cache it. |
| 339 _pubspecs.putIfAbsent(package, () => new Map<Version, Pubspec>()); | 392 _pubspecs[id] = pubspec; |
| 340 _pubspecs[package][version] = pubspec; | |
| 341 | |
| 342 return pubspec; | 393 return pubspec; |
| 343 }); | 394 }); |
| 344 } | 395 } |
| 345 } | 396 } |
| 346 | 397 |
| 347 /** | 398 /** |
| 348 * Describes one [Package] in the [DependencyGraph] and keeps track of which | 399 * Describes one [Package] in the [DependencyGraph] and keeps track of which |
| 349 * packages depend on it and what [VersionConstraint]s they place on it. | 400 * packages depend on it and what constraints they place on it. |
| 350 */ | 401 */ |
| 351 class Dependency { | 402 class Dependency { |
| 352 /** | 403 /** |
| 353 * The currently selected best version for this dependency. | 404 * The name of the this dependency's package. |
| 405 */ | |
| 406 final String name; | |
| 407 | |
| 408 /** | |
| 409 * The [PackageRefs] that represent constraints that depending packages have | |
| 410 * placed on this one. | |
| 411 */ | |
| 412 final Map<String, PackageRef> _refs; | |
| 413 | |
| 414 /** | |
| 415 * The source of this dependency's package. | |
| 416 * | |
| 417 * All constraints in [_refs] must have this as their source. | |
| 418 */ | |
| 419 Source source; | |
| 420 | |
| 421 /** | |
| 422 * The description of this dependency's package. | |
| 423 * | |
| 424 * All constraints in [_refs] must have a description equivalent to this one | |
| 425 * according to [source]. | |
| 426 */ | |
| 427 var description; | |
| 428 | |
| 429 /** | |
| 430 * The currently-selected best version for this dependency. | |
| 354 */ | 431 */ |
| 355 Version version; | 432 Version version; |
| 356 | 433 |
| 357 /** | 434 /** |
| 358 * The constraints that depending packages have placed on this one. | |
| 359 */ | |
| 360 final Map<String, VersionConstraint> _constraints; | |
| 361 | |
| 362 /** | |
| 363 * Gets whether or not any other packages are currently depending on this | 435 * Gets whether or not any other packages are currently depending on this |
| 364 * one. If `false`, then it means this package is not part of the dependency | 436 * one. If `false`, then it means this package is not part of the dependency |
| 365 * graph and should be omitted. | 437 * graph and should be omitted. |
| 366 */ | 438 */ |
| 367 bool get isDependedOn() => !_constraints.isEmpty(); | 439 bool get isDependedOn() => !_refs.isEmpty(); |
| 368 | 440 |
| 369 /** | 441 /** |
| 370 * Gets the overall constraint that all packages are placing on this one. | 442 * Gets the overall constraint that all packages are placing on this one. |
| 371 * If no packages have a constraint on this one (which can happen when this | 443 * If no packages have a constraint on this one (which can happen when this |
| 372 * package is in the process of being added to the graph), returns `null`. | 444 * package is in the process of being added to the graph), returns `null`. |
| 373 */ | 445 */ |
| 374 VersionConstraint get constraint() { | 446 VersionConstraint get constraint() { |
| 375 if (_constraints.isEmpty()) return null; | 447 if (_refs.isEmpty()) return null; |
| 376 return new VersionConstraint.intersect(_constraints.getValues()); | 448 return new VersionConstraint.intersect( |
| 449 _refs.getValues().map((ref) => ref.constraint)); | |
| 377 } | 450 } |
| 378 | 451 |
| 379 Dependency() | 452 Dependency(this.name) |
| 380 : _constraints = <VersionConstraint>{}; | 453 : _refs = <PackageRef>{}; |
| 381 | 454 |
| 382 /** | 455 /** |
| 383 * Places [constraint] from [package] onto this. | 456 * Places [ref] as a constraint from [package] onto this. |
| 384 */ | 457 */ |
| 385 void placeConstraint(String package, VersionConstraint constraint) { | 458 void placeConstraint(String package, PackageRef ref) { |
| 386 if (constraint == null) { | 459 // If this isn't the first constraint placed on this package, make sure it |
| 387 _constraints.remove(package); | 460 // matches the source and description of past constraints. |
| 388 } else { | 461 if (_refs.isEmpty()) { |
| 389 _constraints[package] = constraint; | 462 source = ref.source; |
| 463 description = ref.description; | |
| 464 } else if (source.name != ref.source.name) { | |
| 465 throw new SourceMismatchException(name, source, ref.source); | |
| 466 } else if (!source.descriptionsEqual(description, ref.description)) { | |
| 467 throw new DescriptionMismatchException( | |
| 468 name, description, ref.description); | |
| 469 } | |
| 470 | |
| 471 _refs[package] = ref; | |
| 472 } | |
| 473 | |
| 474 /** | |
| 475 * Removes the constraint from [package] onto this. | |
| 476 */ | |
| 477 void removeConstraint(String package) { | |
| 478 _refs.remove(package); | |
| 479 | |
| 480 if (_refs.isEmpty()) { | |
| 481 source = null; | |
| 482 description = null; | |
| 390 } | 483 } |
| 391 } | 484 } |
| 392 } | 485 } |
| 393 | 486 |
| 394 // TODO(rnystrom): Report the last of depending packages and their constraints. | 487 // TODO(rnystrom): Report the last of depending packages and their constraints. |
| 395 /** | 488 /** |
| 396 * Exception thrown when the [VersionConstraint] used to match a package is | 489 * Exception thrown when the [VersionConstraint] used to match a package is |
| 397 * valid (i.e. non-empty), but there are no released versions of the package | 490 * valid (i.e. non-empty), but there are no released versions of the package |
| 398 * that fit that constraint. | 491 * that fit that constraint. |
| 399 */ | 492 */ |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 425 /** | 518 /** |
| 426 * Exception thrown when the [VersionSolver] fails to find a solution after a | 519 * Exception thrown when the [VersionSolver] fails to find a solution after a |
| 427 * certain number of iterations. | 520 * certain number of iterations. |
| 428 */ | 521 */ |
| 429 class CouldNotSolveException implements Exception { | 522 class CouldNotSolveException implements Exception { |
| 430 CouldNotSolveException(); | 523 CouldNotSolveException(); |
| 431 | 524 |
| 432 String toString() => | 525 String toString() => |
| 433 "Could not find a solution that met all version constraints."; | 526 "Could not find a solution that met all version constraints."; |
| 434 } | 527 } |
| 528 | |
| 529 /** | |
| 530 * Exception thrown when two packages with the same name but different sources | |
| 531 * are depended upon. | |
| 532 */ | |
| 533 class SourceMismatchException implements Exception { | |
| 534 final String package; | |
| 535 final Source source1; | |
| 536 final Source source2; | |
| 537 | |
| 538 SourceMismatchException(this.package, this.source1, this.source2); | |
| 539 | |
| 540 String toString() { | |
| 541 return "Package '$package' is depended on from both sources " | |
| 542 "'${source1.name}' and '${source2.name}'."; | |
| 543 } | |
| 544 } | |
| 545 | |
| 546 /** | |
| 547 * Exception thrown when two packages with the same name and source but | |
| 548 * different descriptions are depended upon. | |
| 549 */ | |
| 550 class DescriptionMismatchException implements Exception { | |
| 551 final String package; | |
| 552 final description1; | |
| 553 final description2; | |
| 554 | |
| 555 DescriptionMismatchException(this.package, this.description1, | |
| 556 this.description2); | |
| 557 | |
| 558 // TODO(nweiz): Dump to YAML when that's supported | |
| 559 String toString() => "Package '$package' has conflicting descriptions " | |
| 560 "'${JSON.stringify(description1)}' and '${JSON.stringify(description2)}'"; | |
| 561 } | |
| OLD | NEW |