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 |