Avoid exponential run time in the dependency resolution algorithm if possible.
When going through all combinations of package versions, all sub trees of the search space are now skipped for a conflicting node, as those are guaranteed to conflict anyway.
1 parent 35fd51b commit e7072aab0a41c0ce42a259735b8214f5d3e4b162
@Sönke Ludwig Sönke Ludwig authored on 22 Mar 2014
Showing 2 changed files
View
2
■■■
source/dub/commandline.d
else m_build_type = "debug";
}
 
if (!m_nodeps) {
// TODO: only upgrade(select) if necessary, only upgrade(upgrade) every now and then
 
// retrieve missing packages
logDiagnostic("Checking for missing dependencies.");
dub.upgrade(UpdateOptions.select);
// check for updates
View
57
source/dub/dependencyresolver.d
auto config_indices = new size_t[all_configs.length];
config_indices[] = 0;
 
visited = null;
bool validateConfigs(TreeNode parent)
sizediff_t validateConfigs(TreeNode parent)
{
if (parent in visited) return true;
import std.algorithm : max;
 
if (parent in visited) return -1;
visited[parent] = true;
sizediff_t maxcpi = -1;
foreach (ch; getChildren(parent)) {
auto basepack = rootPackage(ch.pack);
assert(basepack in package_indices, format("%s not in packages %s", basepack, package_indices));
auto pidx = package_indices[basepack];
auto config = all_configs[pidx][config_indices[pidx]];
auto chnode = TreeNode(ch.pack, config);
if (!matches(ch.configs, config) || !validateConfigs(chnode))
return false;
if (!all_configs[pidx].length) {
auto pbase = rootPackage(parent.pack);
auto ppi = pbase in package_indices;
enforce(ppi !is null, format("Root package %s contains reference to invalid package %s", parent.pack, ch.pack));
// choose another parent config to avoid the invalid child
maxcpi = max(maxcpi, *ppi);
} else {
auto config = all_configs[pidx][config_indices[pidx]];
auto chnode = TreeNode(ch.pack, config);
if (!matches(ch.configs, config)) {
maxcpi = max(maxcpi, pidx);
assert(maxcpi >= 0);
}
maxcpi = max(maxcpi, validateConfigs(chnode));
}
}
return true;
return maxcpi;
}
 
while (true) {
// check if the current combination of configurations works out
visited = null;
if (validateConfigs(root)) {
auto conflict_index = validateConfigs(root);
 
if (conflict_index < 0) {
CONFIG[string] ret;
foreach (p, i; package_indices)
ret[p] = all_configs[i][config_indices[i]];
if (all_configs[i].length)
ret[p] = all_configs[i][config_indices[i]];
return ret;
}
 
// find the next combination of configurations
foreach_reverse (pi, ref i; config_indices) {
if (++i >= all_configs[pi].length) i = 0;
if (pi > conflict_index) i = 0;
else if (++i >= all_configs[pi].length) i = 0;
else break;
}
if (config_indices.all!"a==0") {
if (throw_on_failure) throw new Exception("Could not find a valid dependency tree configuration.");
"c:1": [], "c:2": [], "c:3": [],
"d:1": [], "d:2": [],
"e:1": [], "e:2": [],
]);
assert(res.resolve(TreeNode("a", 0)) == ["b":2u, "c":3u, "d":1u, "e":2u]);
assert(res.resolve(TreeNode("a", 0)) == ["b":2u, "c":3u, "d":1u, "e":2u], format("%s", res.resolve(TreeNode("a", 0))));
}
 
// handle cyclic dependencies gracefully
with (TestResolver) {