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 /// A back-tracking depth-first solver. Attempts to find the best solution for | 5 /// A back-tracking depth-first solver. Attempts to find the best solution for |
| 6 /// a root package's transitive dependency graph, where a "solution" is a set | 6 /// a root package's transitive dependency graph, where a "solution" is a set |
| 7 /// of concrete package versions. A valid solution will select concrete | 7 /// of concrete package versions. A valid solution will select concrete |
| 8 /// versions for every package reached from the root package's dependency graph, | 8 /// versions for every package reached from the root package's dependency graph, |
| 9 /// and each of those packages will fit the version constraints placed on it. | 9 /// and each of those packages will fit the version constraints placed on it. |
| 10 /// | 10 /// |
| (...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 168 } | 168 } |
| 169 } | 169 } |
| 170 | 170 |
| 171 // Advance past the current version of the leaf-most package. | 171 // Advance past the current version of the leaf-most package. |
| 172 var previous = _selected.last.removeFirst(); | 172 var previous = _selected.last.removeFirst(); |
| 173 if (!_selected.last.isEmpty) { | 173 if (!_selected.last.isEmpty) { |
| 174 logSolve(); | 174 logSolve(); |
| 175 return true; | 175 return true; |
| 176 } | 176 } |
| 177 | 177 |
| 178 logSolve('${previous} is last version, backtracking'); | 178 logSolve('$previous is last version, backtracking'); |
| 179 | 179 |
| 180 // That package has no more versions, so pop it and try the next one. | 180 // That package has no more versions, so pop it and try the next one. |
| 181 _selected.removeLast(); | 181 _selected.removeLast(); |
| 182 } | 182 } |
| 183 | 183 |
| 184 return false; | 184 return false; |
| 185 } | 185 } |
| 186 | 186 |
| 187 /// Determines if [depender] has a direct or indirect dependency on | 187 /// Determines if [depender] has a direct or indirect dependency on |
| 188 /// [dependent] based on the currently selected versions of all packages. | 188 /// [dependent] based on the currently selected versions of all packages. |
| 189 /// Returns a string describing the dependency chain if it does, or `null` if | 189 /// Returns a string describing the dependency chain if it does, or `null` if |
| 190 /// there is no dependency. | 190 /// there is no dependency. |
| 191 String _getDependencyPath(PackageId depender, String dependent) { | 191 String _getDependencyPath(PackageId depender, String dependent) { |
| 192 // TODO(rnystrom): This is O(n^2) where n is the number of selected | |
| 193 // packages. Could store the reverse dependency graph to address that. If | |
| 194 // we do that, we need to make sure it gets correctly rolled back when | |
| 195 // backtracking occurs. | |
| 192 var visited = new Set<String>(); | 196 var visited = new Set<String>(); |
| 193 var resultPath; | 197 var currentPath = depender.name; |
|
nweiz
2013/04/17 00:59:55
I think my comment on this may have been swallowed
Bob Nystrom
2013/04/17 17:11:03
Oh, now I see. I read that earlier comment as just
| |
| 194 var currentPath = '${depender.name}'; | |
| 195 | 198 |
| 196 walkDeps(PackageId package) { | 199 walkDeps(PackageId package) { |
| 197 if (visited.contains(package.name)) return false; | 200 if (visited.contains(package.name)) return null; |
| 198 visited.add(package.name); | 201 visited.add(package.name); |
| 199 | 202 |
| 200 var pubspec = cache.getCachedPubspec(package); | 203 var pubspec = cache.getCachedPubspec(package); |
| 201 if (pubspec == null) return false; | 204 if (pubspec == null) return null; |
| 202 | 205 |
| 203 for (var dep in pubspec.dependencies) { | 206 for (var dep in pubspec.dependencies) { |
| 204 var previousPath = currentPath; | 207 var previousPath = currentPath; |
| 205 currentPath = '$currentPath -> ${dep.name}'; | 208 currentPath = '$currentPath -> ${dep.name}'; |
| 206 | 209 |
| 207 if (dep.name == dependent) { | 210 if (dep.name == dependent) return currentPath; |
| 208 resultPath = currentPath; | |
| 209 return true; | |
| 210 } | |
| 211 | 211 |
| 212 var selected = getSelected(dep.name); | 212 var selected = getSelected(dep.name); |
| 213 // Ignore unselected dependencies. We haven't traversed into them yet, | 213 // Ignore unselected dependencies. We haven't traversed into them yet, |
| 214 // so they can't affect backjumping. | 214 // so they can't affect backjumping. |
| 215 if (selected == null) continue; | 215 if (selected == null) continue; |
| 216 | 216 |
| 217 if (walkDeps(selected)) return true; | 217 var depPath = walkDeps(selected); |
| 218 if (depPath != null) return depPath; | |
| 218 | 219 |
| 219 currentPath = previousPath; | 220 currentPath = previousPath; |
| 220 } | 221 } |
| 221 | 222 |
| 222 return false; | 223 return null; |
| 223 } | 224 } |
| 224 | 225 |
| 225 return walkDeps(depender) ? resultPath : null; | 226 return walkDeps(depender); |
| 226 } | 227 } |
| 227 | 228 |
| 228 /// Logs [message] in the context of the current selected packages. If | 229 /// Logs [message] in the context of the current selected packages. If |
| 229 /// [message] is omitted, just logs a description of leaf-most selection. | 230 /// [message] is omitted, just logs a description of leaf-most selection. |
| 230 void logSolve([String message]) { | 231 void logSolve([String message]) { |
| 231 if (message == null) { | 232 if (message == null) { |
| 232 if (_selected.isEmpty) { | 233 if (_selected.isEmpty) { |
| 233 message = "* start at root"; | 234 message = "* start at root"; |
| 234 } else { | 235 } else { |
| 235 var versions = _selected.last.map((id) => id.version).toList(); | 236 var count = _selected.last.length; |
| 236 if (versions.length > 5) { | 237 message = "* select ${_selected.last.first} ($count versions)"; |
| 237 versions = versions.take(5).join(', ') + '...'; | |
| 238 } else { | |
| 239 versions = versions.join(', '); | |
| 240 } | |
| 241 message = "* select ${_selected.last.first} (from $versions)"; | |
| 242 } | 238 } |
| 243 } else { | 239 } else { |
| 244 // Otherwise, indent it under the current selected package. | 240 // Otherwise, indent it under the current selected package. |
| 245 message = "| $message"; | 241 message = "| $message"; |
| 246 } | 242 } |
| 247 | 243 |
| 248 // Indent for the previous selections. | 244 // Indent for the previous selections. |
| 249 var buffer = new StringBuffer(); | 245 var buffer = new StringBuffer(); |
| 250 buffer.writeAll(_selected.skip(1).map((_) => '| ')); | 246 buffer.writeAll(_selected.skip(1).map((_) => '| ')); |
| 251 buffer.write(message); | 247 buffer.write(message); |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 320 return new Future.value(new Pair<PackageRef, int>(ref, 1)); | 316 return new Future.value(new Pair<PackageRef, int>(ref, 1)); |
| 321 } | 317 } |
| 322 | 318 |
| 323 return _solver.cache.getVersions(ref.name, ref.source, ref.description) | 319 return _solver.cache.getVersions(ref.name, ref.source, ref.description) |
| 324 .then((versions) { | 320 .then((versions) { |
| 325 return new Pair<PackageRef, int>(ref, versions.length); | 321 return new Pair<PackageRef, int>(ref, versions.length); |
| 326 }).catchError((error) { | 322 }).catchError((error) { |
| 327 // If it fails for any reason, just treat that as no versions. This | 323 // If it fails for any reason, just treat that as no versions. This |
| 328 // will sort this reference higher so that we can traverse into it | 324 // will sort this reference higher so that we can traverse into it |
| 329 // and report the error more properly. | 325 // and report the error more properly. |
| 326 log.solver("Could not get versions for $ref:\n$error\n\n" | |
| 327 "${getAttachedStackTrace(error)}"); | |
| 330 return new Pair<PackageRef, int>(ref, 0); | 328 return new Pair<PackageRef, int>(ref, 0); |
| 331 }); | 329 }); |
| 332 } | 330 } |
| 333 | 331 |
| 334 return Future.wait(refs.map(getNumVersions)).then((pairs) { | 332 return Future.wait(refs.map(getNumVersions)).then((pairs) { |
| 335 // Future.wait() returns an immutable list, so make a copy. | 333 // Future.wait() returns an immutable list, so make a copy. |
| 336 pairs = pairs.toList(); | 334 pairs = pairs.toList(); |
| 337 | 335 |
| 338 // Sort in best-first order to minimize backtracking. | 336 // Sort in best-first order to minimize backtracking. |
| 339 pairs.sort((a, b) { | 337 pairs.sort((a, b) { |
| (...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 533 | 531 |
| 534 var required = _getRequired(name); | 532 var required = _getRequired(name); |
| 535 if (required != null) { | 533 if (required != null) { |
| 536 if (package.source.name != required.ref.source.name) return null; | 534 if (package.source.name != required.ref.source.name) return null; |
| 537 if (!package.descriptionEquals(required.ref)) return null; | 535 if (!package.descriptionEquals(required.ref)) return null; |
| 538 } | 536 } |
| 539 | 537 |
| 540 return package; | 538 return package; |
| 541 } | 539 } |
| 542 } | 540 } |
| OLD | NEW |