removed constraint solver package in case it is a bad merge

This commit is contained in:
ekatek
2014-05-09 15:16:18 -07:00
parent f2d3972857
commit 229802020e
12 changed files with 0 additions and 11719 deletions

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -1,7 +0,0 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

View File

@@ -1,10 +0,0 @@
{
"dependencies": {
"mori": {
"version": "0.2.6"
},
"semver": {
"version": "2.2.1"
}
}
}

View File

@@ -1,395 +0,0 @@
var semver = Npm.require('semver');
// Setup mock data for tests
var Packages = new LocalCollection;
var Versions = new LocalCollection;
var Builds = new LocalCollection;
Packages.insert({ name: "sparky-forms" });
Packages.insert({ name: "forms" });
Packages.insert({ name: "sparkle" });
Packages.insert({ name: "awesome-dropdown" });
Packages.insert({ name: "dropdown" });
Packages.insert({ name: "jquery-widgets" });
Packages.insert({ name: "jquery" });
var insertVersion = function (name, version, ecv, deps) {
var constructedDeps = {};
_.each(deps, function (constraint, name) {
constructedDeps[name] = {
constraint: constraint,
references: [
{ slice: "main", arch: "os", targetSlice: "main", weak: false,
implied: false, unordered: false },
{ slice: "main", arch: "browser", targetSlice: "main", weak: false,
implied: false, unordered: false }]
};
});
Versions.insert({ packageName: name, version: version, earliestCompatibleVersion: ecv,
dependencies: constructedDeps });
};
insertVersion("sparky-forms", "1.1.2", "1.0.0", {"forms": "=1.0.1", "sparkle": "=2.1.1"});
insertVersion("forms", "1.0.1", "1.0.0", {"sparkle": "2.1.0", "jquery-widgets": "1.0.0"});
insertVersion("sparkle", "2.1.0", "2.1.0", {"jquery": "1.8.2"});
insertVersion("sparkle", "2.1.1", "2.1.0", {"jquery": "1.8.2"});
insertVersion("sparkle", "1.0.0", "1.0.0");
insertVersion("awesome-dropdown", "1.5.0", "1.0.0", {"dropdown": "=1.2.2"});
insertVersion("dropdown", "1.2.2", "1.0.0", {"jquery-widgets": "1.0.0"});
insertVersion("jquery-widgets", "1.0.0", "1.0.0", {"jquery": "1.8.0", "sparkle": "2.1.1"});
insertVersion("jquery-widgets", "1.0.2", "1.0.0", {"jquery": "1.8.0", "sparkle": "2.1.1"});
insertVersion("jquery", "1.8.0", "1.8.0");
insertVersion("jquery", "1.8.2", "1.8.0");
var insertBuild = function (name, version, ecv) {
Builds.insert({ packageName: name, version: version,
earliestCompatibleVersion: ecv, architecture: [ "browser", "os" ] });
};
insertBuild("sparky-forms", "1.1.2", "1.1.0");
insertBuild("forms", "1.0.1", "1.0.0");
insertBuild("sparkle", "2.1.1", "2.1.0");
insertBuild("sparkle", "2.1.0", "2.1.0");
insertBuild("sparkle", "1.0.0", "1.0.0");
insertBuild("awesome-dropdown", "1.5.0", "1.0.0");
insertBuild("dropdown", "1.2.2", "1.0.0");
insertBuild("jquery-widgets", "1.0.0", "1.0.0");
insertBuild("jquery-widgets", "1.0.2", "1.0.0");
insertBuild("jquery", "1.8.0", "1.0.0");
insertBuild("jquery", "1.9.0", "1.0.0");
// XXX Temporary hack: make a catalog stub to pass in to the constraint
// solver. We'll soon move constraint-solver into tools and just run
// tests with self-test, passing a real Catalog object.
var catalogStub = {
packages: Packages,
versions: Versions,
builds: Builds,
getAllPackageNames: function () {
return _.pluck(Packages.find().fetch(), 'name');
},
getPackage: function (name) {
return this.packages.findOne({ name: name });
},
getSortedVersions: function (name) {
return _.pluck(
this.versions.find({
packageName: name
}, { fields: { version: 1 } }).fetch(),
'version'
).sort(semver.compare);
},
getVersion: function (name, version) {
return this.versions.findOne({
packageName: name,
version: version
});
}
};
var resolver = new ConstraintSolver.PackagesResolver(catalogStub);
var currentTest = null;
var t = function (deps, expected, options) {
var resolvedDeps = resolver.resolve(deps, options);
currentTest.equal(resolvedDeps, expected);
};
var t_progagateExact = function (deps, expected) {
var resolvedDeps = resolver.propagatedExactDeps(deps);
currentTest.equal(resolvedDeps, expected);
};
var FAIL = function (deps, regexp) {
currentTest.throws(function () {
var resolvedDeps = resolver.resolve(deps);
}, regexp);
};
Tinytest.add("constraint solver - exact dependencies", function (test) {
currentTest = test;
t_progagateExact({ "sparky-forms": "=1.1.2" }, { "sparky-forms": "1.1.2", "forms": "1.0.1", "sparkle": "2.1.1" });
t_progagateExact({ "sparky-forms": "=1.1.2", "forms": "=1.0.1" }, { "sparky-forms": "1.1.2", "forms": "1.0.1", "sparkle": "2.1.1" });
t_progagateExact({ "sparky-forms": "=1.1.2", "sparkle": "=2.1.1" }, { "sparky-forms": "1.1.2", "forms": "1.0.1", "sparkle": "2.1.1" });
t_progagateExact({ "awesome-dropdown": "=1.5.0" }, { "awesome-dropdown": "1.5.0", "dropdown": "1.2.2" });
FAIL({ "sparky-forms": "=1.1.2", "sparkle": "=1.0.0" }, /(.*sparkle.*sparky-forms.*)|(.*sparky-forms.*sparkle.*).*sparkle/);
// something that isn't available for your architecture
FAIL({ "sparky-forms": "=1.1.2", "sparkle": "=2.0.0" });
FAIL({ "sparky-forms": "=0.0.1" });
FAIL({ "sparky-forms-nonexistent": "0.0.1" }, /Cannot find anything about.*sparky-forms-nonexistent/);
});
Tinytest.add("constraint solver - simple exact + regular deps", function (test) {
currentTest = test;
t({ "sparky-forms": "=1.1.2" }, {
"sparky-forms": "1.1.2",
"forms": "1.0.1",
"sparkle": "2.1.1",
"jquery-widgets": "1.0.0",
"jquery": "1.8.2"
}, { mode: "CONSERVATIVE" });
t({ "sparky-forms": "=1.1.2", "awesome-dropdown": "=1.5.0" }, {
"sparky-forms": "1.1.2",
"forms": "1.0.1",
"sparkle": "2.1.1",
"jquery-widgets": "1.0.0",
"jquery": "1.8.2",
"awesome-dropdown": "1.5.0",
"dropdown": "1.2.2"
}, { mode: "CONSERVATIVE" });
});
Tinytest.add("constraint solver - non-exact direct dependency", function (test) {
currentTest = test;
// sparky-forms 1.0.0 won't be chosen because it depends on a very old
// jquery, which is not compatible with the jquery that
// awesome-dropdown uses.
t({ "sparky-forms": "1.0.0", "awesome-dropdown": "=1.5.0" }, {
"sparky-forms": "1.1.2",
"forms": "1.0.1",
"sparkle": "2.1.1",
"jquery-widgets": "1.0.0",
"jquery": "1.8.2",
"awesome-dropdown": "1.5.0",
"dropdown": "1.2.2"
}, { mode: "CONSERVATIVE" });
});
Tinytest.add("constraint solver - no constraint dependency - anything", function (test) {
currentTest = test;
var versions = resolver.resolve({ "sparkle": "none" }, { mode: "CONSERVATIVE" });
test.isTrue(_.isString(versions.sparkle));
versions = resolver.resolve({ "sparkle": null }, { mode: "CONSERVATIVE" });
test.isTrue(_.isString(versions.sparkle));
});
Tinytest.add("constraint solver - no constraint dependency - transitive dep still picked right", function (test) {
currentTest = test;
var versions = resolver.resolve({ "sparkle": "none", "sparky-forms": "1.1.2" }, { mode: "CONSERVATIVE" });
test.equal(versions.sparkle, "2.1.1");
var versions = resolver.resolve({ "sparkle": null, "sparky-forms": "1.1.2" }, { mode: "CONSERVATIVE" });
test.equal(versions.sparkle, "2.1.1");
});
Tinytest.add("constraint solver - benchmark on gems - sinatra", function (test) {
var r = new ConstraintSolver.PackagesResolver(getCatalogStub(sinatraGems));
r.resolve({
'capistrano': '2.14.2',
'data_mapper': '1.2.0',
'dm-core': '1.2.0',
'dm-sqlite-adapter': '1.2.0',
'dm-timestamps': '1.2.0',
'haml': '3.1.7',
'sass': '3.2.1',
'shotgun': '0.9.0',
'sinatra': '1.3.5',
'sqlite3': '1.3.7'
});
});
var railsCatalog = getCatalogStub(railsGems);
Tinytest.add("constraint solver - benchmark on gems - rails", function (test) {
var r = new ConstraintSolver.PackagesResolver(railsCatalog);
r.resolve({
'rails': '4.0.4'
});
});
Tinytest.add("constraint solver - benchmark on gems - rails, gitlabhq", function (test) {
var r = new ConstraintSolver.PackagesResolver(railsCatalog);
r.resolve({
'rails': '4.0.0',
'protected_attributes': null,
'rails-observers': null,
'actionpack-page_caching': null,
'actionpack-action_caching': null,
'default_value_for': '3.0.0',
'mysql2': null,
'devise': '3.0.4',
'devise-async': '0.8.0',
'omniauth': '1.1.3',
'omniauth-google-oauth2': null,
'omniauth-twitter': null,
'omniauth-github': null,
'gitlab_git': '5.7.1',
'gitlab-grack': '2.0.0',
'gitlab_omniauth-ldap': '1.0.4',
'gitlab-gollum-lib': '1.1.0',
'gitlab-linguist': '3.0.0',
'grape': '0.6.1',
'rack-cors': null,
'email_validator': '1.4.0',
'stamp': null,
'enumerize': null,
'kaminari': '0.15.1',
'haml-rails': null,
'carrierwave': null,
'fog': '1.3.1',
'six': null,
'seed-fu': null,
'redcarpet': '2.2.2',
'github-markup': '0.7.4',
'asciidoctor': null,
'unicorn': '4.6.3',
'unicorn-worker-killer': null,
'state_machine': null,
'acts-as-taggable-on': null,
'slim': null,
'sinatra': null,
'sidekiq': null,
'httparty': null,
'colored': null,
'settingslogic': null,
'foreman': null,
'version_sorter': null,
'redis-rails': null,
'tinder': '1.9.2',
'hipchat': '0.14.0',
'gemnasium-gitlab-service': '0.2.0',
'slack-notifier': '0.2.0',
'd3_rails': '3.1.4',
'underscore-rails': '1.4.4',
'sanitize': null,
'rack-attack': null,
'ace-rails-ap': null,
'sass-rails': null,
'coffee-rails': null,
'uglifier': null,
'therubyracer': null,
'turbolinks': null,
'jquery-turbolinks': null,
'select2-rails': null,
'jquery-atwho-rails': '0.3.3',
'jquery-rails': '2.1.3',
'jquery-ui-rails': '2.0.2',
'modernizr': '2.6.2',
'raphael-rails': '2.1.2',
'bootstrap-sass': '3.0.0',
'font-awesome-rails': '3.2.0',
'gitlab_emoji': '0.0.1',
'gon': '5.0.0'
});
});
// Given a set of gems definitions returns a Catalog-like object
function getCatalogStub (gems) {
return {
getAllPackageNames: function () {
return _.uniq(_.pluck(gems, 'name'));
},
getPackage: function (name) {
throw new Error("Not implemeneted");
},
getSortedVersions: function (name) {
return _.chain(gems)
.filter(function (pv) { return pv.name === name; })
.pluck('number')
.map(function (version) {
var nv = exactVersion(version);
if (nv.length < version.length && version.split(".").length === 2)
return version;
return nv;
})
.filter(function (v) {
return semver.valid(v);
})
.value()
.sort(semver.compare);
},
getVersion: function (name, version) {
var gem = _.find(gems, function (pv) {
return pv.name === name && exactVersion(pv.number) === version;
});
var ecv = function (version) {
// hard-code to "x.0.0"
return parseInt(version) + ".0.0";
};
var packageVersion = {
packageName: gem.name,
version: gem.number,
earliestCompatibleVersion: ecv(gem.number),
dependencies: {}
};
// These gems didn't really follow the semver and they prevent our
// generated tests from being resolved. We set the ECV manually so we can
// get some results as the correctness is not as important as the speed.
if (_.contains(['railties', 'rails', 'activesupport', 'actionpack', 'activerecord', 'activemodel'], packageVersion.packageName) && semver.gte(exactVersion(packageVersion.version), "3.0.0"))
packageVersion.earliestCompatibleVersion = "3.0.0";
if (_.contains(['devise'], packageVersion.packageName) && semver.gte(exactVersion(packageVersion.version), "3.0.0") && semver.lte(exactVersion(packageVersion.version), "3.2.0"))
packageVersion.earliestCompatibleVersion = "2.2.0";
if (packageVersion.packageName === "redis")
packageVersion.earliestCompatibleVersion = "0.0.1";
_.each(gem.dependencies, function (dep) {
var name = dep[0];
var constraints = dep[1];
packageVersion.dependencies[name] = {
constraint: convertConstraints(constraints)[0], // XXX pick first one only
references: [{
"slice": "main",
"arch": "browser"
}, {
"slice": "main",
"arch": "os" }]
};
});
return packageVersion;
}
};
}
// Naively converts ruby-gems style constraints string to either exact
// constraint or a regular constraint.
function convertConstraints (inp) {
var out = inp.split(",").map(function (s) {
return s.trim();
})
// remove the constraints we don't support
.filter(function (s) {
return !/</g.test(s) && !/!=/.test(s);
})
// convert 1.2.3.beta2 => 1.2.3
// and 0.2 => 0.2.0
.map(function (s) {
var x = s.split(" ");
return [x[0], exactVersion(x[1])].join(" ");
})
// convert '= 1.2.3' => '=1.2.3'
// '~>1.2.3' => '1.2.3'
// '>=1.2.3' => '1.2.3'
.map(function (s) {
if (s === ">= 0.0.0")
return "none";
var x = s.split(' ');
if (x[0] === '~>' || x[0] === '>=' || x[0] === '>')
x[0] = '';
else if (x[0] === '=')
x[0] = '=';
else
throw new Error('unknown operator: ' + x[0]);
return x.join("");
});
return out;
}
function exactVersion (s) {
s = s.match(/\d+(\.\d+(\.\d+)?)?/)[0];
if (s.split('.').length < 3)
s += ".0";
if (s.split('.').length < 3)
s += ".0";
return s;
}

View File

@@ -1,351 +0,0 @@
var semver = Npm.require('semver');
ConstraintSolver = {};
// catalog is a catalog.Catalog object. We have to pass this in because
// we're in a package and can't require('release.js'). If this code
// moves to the tool, or if all of the tool code moves to a star, we
// should get cat from release.current.catalog rather than passing it
// in.
ConstraintSolver.PackagesResolver = function (catalog, options) {
var self = this;
options = options || {};
self.catalog = catalog;
// The main resolver
self.resolver = new ConstraintSolver.Resolver();
// XXX for now we convert slices to unit versions as "deps:main.os"
var forEveryVersion = function (iter) {
_.each(catalog.getAllPackageNames(), function (packageName) {
_.each(catalog.getSortedVersions(packageName), function (version) {
var versionDef = catalog.getVersion(packageName, version);
iter(packageName, version, versionDef);
});
});
};
// Create a unit version for every package
// Set constraints and dependencies between units
forEveryVersion(function (packageName, version, versionDef) {
var slices = {};
_.each(versionDef.dependencies, function (dep, depName) {
_.each(dep.references, function (ref) {
var unitName = packageName + ":" + ref.slice + "." + ref.arch;
var unitVersion = slices[unitName];
if (! unitVersion) {
// if it is first time we met the slice of this version, register it
// in resolver.
slices[unitName] = new ConstraintSolver.UnitVersion(
unitName, version, versionDef.earliestCompatibleVersion);
unitVersion = slices[unitName];
self.resolver.addUnitVersion(unitVersion);
}
var targetSlice = ref.targetSlice || "main";
var targetUnitName = depName + ":" + targetSlice + "." + ref.arch;
// Add the dependency if needed
if (! ref.weak)
unitVersion.addDependency(targetUnitName);
// Add a constraint if such exists
if (dep.constraint && dep.constraint !== "none") {
var constraint =
self.resolver.getConstraint(targetUnitName, dep.constraint);
unitVersion.addConstraint(constraint);
}
});
});
if (_.isEmpty(versionDef.dependencies)) {
// XXX this is a hack to temporary solve the problem with packages w/o
// dependencies. Right now in order to understand what are slices of
// package, we look into its dependencies slice-wise. W/o dependencies we
// would need to do something else, like see what slices other slices
// depend on. Also if depending slices of other packages don't specify the
// version, there is no way we can resolve what slices different versions
// have as different versions of the same package can in theory have
// diverging sets of slices.
//
// But in practive we always have main/test + os/browser slices. So we
// will just hardcode two most improtant slices at the moment. Fix it
// later.
_.each(["os", "browser"], function (arch) {
var slice = "main";
var unitName = packageName + ":" + slice + "." + arch;
var unitVersion = slices[unitName];
if (! unitVersion) {
slices[unitName] = new ConstraintSolver.UnitVersion(
unitName, version, versionDef.earliestCompatibleVersion);
unitVersion = slices[unitName];
self.resolver.addUnitVersion(unitVersion);
}
});
}
// Every slice implies that if it is picked, other slices are constrained to
// the same version.
_.each(slices, function (slice, sliceName) {
_.each(slices, function (other, otherSliceName) {
if (slice === other)
return;
// Constraint is the exact same version of a slice
var constraintStr = "=" + version;
var constraint =
self.resolver.getConstraint(otherSliceName, constraintStr);
slice.addConstraint(constraint);
});
});
});
};
ConstraintSolver.PackagesResolver.prototype.resolve =
function (dependencies, options) {
var self = this;
options = _.defaults(options || {}, {
mode: 'LATEST'
});
var dc = self._splitDepsToConstraints(dependencies);
var resolverOptions = self._getResolverOptions(options, dc);
// XXX resolver.resolve can throw an error, should have error handling with
// proper error translation.
var res = self.resolver.resolve(dc.dependencies, dc.constraints, [],
resolverOptions);
var resultChoices = {};
_.each(res, function (uv) {
// Since we don't yet define the interface for a an app to depend only on
// certain slices of the packages (like only browser slices) and we know
// that each slice weakly depends on other sibling slices of the same
// version, we can safely output the whole package for each slice in the
// result.
resultChoices[uv.name.split(':')[0]] = uv.version;
});
return resultChoices;
};
ConstraintSolver.PackagesResolver.prototype.propagatedExactDeps =
function (dependencies) {
var self = this;
var dc = self._splitDepsToConstraints(dependencies);
// XXX resolver.resolve can throw an error, should have error handling with
// proper error translation.
var res = self.resolver.resolve(dc.dependencies, dc.constraints, null,
{ stopAfterFirstPropagation: true });
var resultChoices = {};
_.each(res, function (uv) {
resultChoices[uv.name.split(':')[0]] = uv.version;
});
return resultChoices;
};
// takes deps of form {'foo': '1.2.3', 'bar': null, 'quz': '=1.2.5'} and splits
// them into dependencies ['foo:main.os', 'bar:main.browser',
// 'quz:main.browser'] + constraints
// XXX right now creates a dependency for every 'main' slice it can find
ConstraintSolver.PackagesResolver.prototype._splitDepsToConstraints =
function (deps) {
var self = this;
var dependencies = [];
var constraints = [];
var slicesNames = _.keys(self.resolver.unitsVersions);
_.each(deps, function (constraint, packageName) {
var slicesForPackage = _.filter(slicesNames, function (slice) {
// we pick everything that starts with 'foo:main.'
var slicePrefix = packageName + ":main.";
return slice.substr(0, slicePrefix.length) === slicePrefix;
});
if (_.isEmpty(slicesForPackage))
throw new Error("Cannot find anything about package -- " + packageName);
_.each(slicesForPackage, function (sliceName) {
dependencies.push(sliceName);
// add the constraint if such exists
if (constraint !== null && constraint !== "none") {
constraints.push(self.resolver.getConstraint(sliceName, constraint));
}
});
});
return { dependencies: dependencies, constraints: constraints };
};
ConstraintSolver.PackagesResolver.prototype._getResolverOptions =
function (options, dc) {
var self = this;
var semverToNum = function (version) {
var v = _.map(version.split('.'), function (x) {
return parseInt(x);
});
return v[0] * 10000 + v[1] * 100 + v[2];
};
var resolverOptions = {};
switch (options.mode) {
case "LATEST":
// Poorman's enum
var VMAJOR = 0, MAJOR = 1, MEDIUM = 2, MINOR = 3;
// XXX reuse this code for "UPDATE" and "MINOR_UPDATE" and "FORCE"
resolverOptions.costFunction = function (choices, options) {
var rootDeps = options.rootDependencies || [];
var prevSol = options.previousSolution || [];
var isRootDep = {};
var prevSolMapping = {};
// XXX this is recalculated over and over again and can be cached
_.each(rootDeps, function (dep) { isRootDep[dep] = true; });
_.each(prevSol, function (uv) { prevSolMapping[uv.name] = uv; });
// very major, major, medium, minor costs
// XXX maybe these can be calculated lazily?
var cost = [0, 0, 0, 0];
_.each(choices, function (uv) {
if (_.has(prevSolMapping, uv.name)) {
// The package was present in the previous solution
var prev = prevSolMapping[uv.name];
var versionsDistance =
semverToNum(uv.version) -
semverToNum(prev.version);
var isCompatible =
semver.gte(prev.version, uv.earliestCompatibleVersion) ||
semver.gte(uv.version, prev.earliestCompatibleVersion);
if (isRootDep[uv.name]) {
// root dependency
if (versionsDistance < 0 || ! isCompatible) {
// the new pick is older or is incompatible with the prev. solution
// i.e. can have breaking changes, prefer not to do this
cost[VMAJOR]++;
} else {
// compatible but possibly newer
// prefer the version closest to the older solution
cost[MEDIUM] += versionsDistance;
}
} else {
// transitive dependency
// prefer to have less changed transitive dependencies
cost[MINOR] += versionsDistance === 0 ? 1 : 0;
}
} else {
var latestDistance =
semverToNum(self.resolver._latestVersion[uv.name]) -
semverToNum(uv.version);
if (isRootDep[uv.name]) {
// root dependency
// preferably latest
cost[MAJOR] += latestDistance;
} else {
// transitive dependency
// prefarable earliest possible to be conservative
// xcxc XXX but for now prefering the latest is OK too
cost[MINOR] += latestDistance;
}
}
});
return cost;
};
resolverOptions.estimateCostFunction = function (state, options) {
var dependencies = state.dependencies;
var constraints = state.constraints;
var rootDeps = options.rootDependencies || [];
var prevSol = options.previousSolution || [];
var isRootDep = {};
var prevSolMapping = {};
// XXX this is recalculated over and over again and can be cached
_.each(rootDeps, function (dep) { isRootDep[dep] = true; });
_.each(prevSol, function (uv) { prevSolMapping[uv.name] = uv; });
var cost = [0, 0, 0, 0];
return cost;
dependencies.each(function (dep) {
if (_.has(prevSolMapping, dep)) {
// was used in previous solution
// XXX do something smart here
} else {
var versions = self.resolver.unitsVersions[dep];
var latestFitting = null;
for (var i = versions.length - 1; i >= 0; i--) {
if (_.isEmpty(constraints.violatedConstraints(versions[i]))) {
latestFitting = versions[i];
break;
}
}
if (! latestFitting) {
cost[MAJOR] = Infinity;
return;
}
var latestDistance =
semverToNum(self.resolver._latestVersion[dep]) -
semverToNum(latestFitting.version);
if (isRootDep[dep]) {
cost[MAJOR] += latestDistance;
} else {
// one of the transitive dependencies
// XXX should really be a distance from the earlies fitting
cost[MINOR] += latestDistance;
}
}
});
return cost;
};
resolverOptions.combineCostFunction = function (costA, costB) {
if (costA.length !== costB.length)
throw new Error("Different cost types");
var arr = [];
_.each(costA, function (l, i) {
arr.push(l + costB[i]);
});
return arr;
};
break;
case "CONSERVATIVE":
// XXX this is not used anywhere but tests?
resolverOptions.costFunction = function (choices) {
return _.reduce(choices, function (sum, uv) {
return semverToNum(uv.version) + sum;
}, 0);
};
break;
}
return resolverOptions;
};

View File

@@ -1,195 +0,0 @@
var mori = Npm.require('mori');
////////////////////////////////////////////////////////////////////////////////
// ConstraintsList
////////////////////////////////////////////////////////////////////////////////
// A persistent data-structure that keeps references to Constraint objects
// arranged by the "name" field of Constraint, exact field and version.
ConstraintSolver.ConstraintsList = function (prev) {
var self = this;
if (prev) {
self.byName = prev.byName;
self.length = prev.length;
} else {
self.byName = mori.hash_map();
self.length = 0;
}
};
ConstraintSolver.ConstraintsList.prototype.contains = function (c) {
var self = this;
if (! mori.has_key(self.byName, c.name))
return false;
var bn = mori.get(self.byName, c.name);
var constraints = mori.get(bn, c.exact ? "exact" : "inexact");
return mori.has_key(constraints, c.version);
};
// returns a new version containing passed constraint
ConstraintSolver.ConstraintsList.prototype.push = function (c) {
var self = this;
if (self.contains(c)) {
return self;
}
var newList = new ConstraintSolver.ConstraintsList(self);
// create a record or update the lookup table
if (! mori.has_key(self.byName, c.name)) {
var exactMap = mori.hash_map();
var inexactMap = mori.hash_map();
if (c.exact) {
exactMap = mori.assoc(exactMap, c.version, c);
} else {
inexactMap = mori.assoc(inexactMap, c.version, c);
}
var bn = mori.hash_map("exact", exactMap, "inexact", inexactMap);
newList.byName = mori.assoc(newList.byName, c.name, bn);
} else {
var exactStr = c.exact ? "exact" : "inexact";
var bn = mori.get(newList.byName, c.name);
var constraints = mori.get(bn, exactStr);
constraints = mori.assoc(constraints, c.version, c);
bn = mori.assoc(bn, exactStr, constraints);
newList.byName = mori.assoc(newList.byName, c.name, bn);
}
newList.length++;
return newList;
};
ConstraintSolver.ConstraintsList.prototype.forPackage = function (name, iter) {
var self = this;
var forPackage = mori.get(self.byName, name);
var exact = mori.get(forPackage, "exact");
var inexact = mori.get(forPackage, "inexact");
var niter = function (pair) {
iter(mori.last(pair));
};
mori.each(exact, niter);
mori.each(inexact, niter);
};
// doesn't break on the false return value
ConstraintSolver.ConstraintsList.prototype.each = function (iter) {
var self = this;
mori.each(self.byName, function (coll) {
mori.each(mori.last(coll), function (exactInexactColl) {
mori.each(mori.last(exactInexactColl), function (c) {
iter(mori.last(c));
});
});
});
};
// doesn't break on the false return value
ConstraintSolver.ConstraintsList.prototype.eachExact = function (iter) {
var self = this;
mori.each(self.byName, function (coll) {
mori.each(mori.get(coll, "exact"), function (c) {
iter(mori.last(c));
});
});
};
ConstraintSolver.ConstraintsList.prototype.union = function (anotherList) {
var self = this;
var newList, oldList;
if (self.length <= anotherList.length) {
newList = anotherList;
oldList = self;
} else {
newList = self;
oldList = anotherList;
}
oldList.each(function (c) {
newList = newList.push(c);
});
return newList;
};
// Checks if the passed unit version violates any of the constraints.
// Returns a list of constraints that are violated (empty if the unit
// version does not violate any constraints).
// XXX Returns a regular array, not a ConstraintsList.
ConstraintSolver.ConstraintsList.prototype.violatedConstraints = function (uv) {
var self = this;
var violated = [];
self.forPackage(uv.name, function (c) {
if (! c.isSatisfied(uv)) {
violated.push(c);
}
});
return violated;
};
// a weird method that returns a list of exact constraints those correspond to
// the dependencies in the passed list
ConstraintSolver.ConstraintsList.prototype.exactDependenciesIntersection =
function (deps) {
var self = this;
var newList = new ConstraintSolver.ConstraintsList();
self.eachExact(function (c) {
if (deps.contains(c.name))
newList = newList.push(c);
});
return newList;
};
ConstraintSolver.ConstraintsList.prototype.toString = function (simple) {
var self = this;
var str = "";
var strs = [];
self.each(function (c) {
strs.push(c.toString());
});
strs.sort();
_.each(strs, function (c) {
if (str !== "") {
str += simple ? " " : ", ";
}
str += c;
});
return simple ? str : "<constraints list: " + str + ">";
};
ConstraintSolver.ConstraintsList.prototype.toArray = function () {
var self = this;
var arr = [];
self.each(function (c) {
arr.push(c);
});
return arr;
};
ConstraintSolver.ConstraintsList.fromArray = function (arr) {
var list = new ConstraintSolver.ConstraintsList();
_.each(arr, function (c) {
list = list.push(c);
});
return list;
};

View File

@@ -1,147 +0,0 @@
var mori = Npm.require('mori');
////////////////////////////////////////////////////////////////////////////////
// DependenciesList
////////////////////////////////////////////////////////////////////////////////
// A persistent data-structure that wrapps persistent dictionary
ConstraintSolver.DependenciesList = function (prev) {
var self = this;
if (prev) {
self._mapping = prev._mapping;
self._prioritized = prev._prioritized;
} else {
self._mapping = mori.hash_map();
self._prioritized = mori.list();
}
};
ConstraintSolver.DependenciesList.prototype.contains = function (d) {
var self = this;
return mori.has_key(self._mapping, d);
};
// returns a new version containing passed dependency
ConstraintSolver.DependenciesList.prototype.push = function (d) {
var self = this;
if (self.contains(d)) {
return self;
}
var newList = new ConstraintSolver.DependenciesList(self);
newList._mapping = mori.assoc(self._mapping, d, d);
return newList;
};
ConstraintSolver.DependenciesList.prototype.remove = function (d) {
var self = this;
var newList = new ConstraintSolver.DependenciesList(self);
newList._mapping = mori.dissoc(self._mapping, d);
if (mori.peek(newList._prioritized) === d)
newList._prioritized = mori.pop(newList._prioritized);
return newList;
};
ConstraintSolver.DependenciesList.prototype.peek = function () {
var self = this;
var prioritized = mori.peek(self._prioritized);
if (prioritized)
return prioritized;
return mori.last(mori.first(self._mapping));
};
// a weird method that returns a list of exact constraints those correspond to
// the dependencies in this list
ConstraintSolver.DependenciesList.prototype.exactConstraintsIntersection =
function (constraintsList) {
var self = this;
var exactConstraints = new ConstraintSolver.ConstraintsList();
self.each(function (d) {
var c = mori.last(
// pick an exact constraint for this dependency if such exists
mori.last(mori.get(mori.get(constraintsList.byName, d), "exact")));
if (c)
exactConstraints = exactConstraints.push(c);
});
return exactConstraints;
};
ConstraintSolver.DependenciesList.prototype.union = function (anotherList) {
var self = this;
var newList = new ConstraintSolver.DependenciesList(self);
newList._mapping = mori.union(newList._mapping, anotherList._mapping);
return newList;
};
ConstraintSolver.DependenciesList.prototype.isEmpty = function () {
var self = this;
return mori.is_empty(self._mapping);
};
ConstraintSolver.DependenciesList.prototype.each = function (iter) {
var self = this;
mori.each(self._mapping, function (d) {
iter(mori.last(d));
});
};
ConstraintSolver.DependenciesList.prototype.toString = function (simple) {
var self = this;
var str = "";
var strs = [];
self.each(function (d) {
strs.push(d);
});
strs.sort();
_.each(strs, function (d) {
if (str !== "") {
str += simple ? " " : ", ";
}
str += d;
});
return simple ? str : "<dependencies list: " + str + ">";
};
ConstraintSolver.DependenciesList.prototype.toArray = function () {
var self = this;
var arr = [];
self.each(function (d) {
arr.push(d);
});
return arr;
};
ConstraintSolver.DependenciesList.fromArray = function (arr, prioritized) {
var list = new ConstraintSolver.DependenciesList();
var args = [];
_.each(arr, function (d) {
args.push(d);
args.push(d);
});
list._mapping = mori.hash_map.apply(mori, args);
// the whole list should also be added as prioritized
if (prioritized) {
_.each(arr, function (d) {
list._prioritized = mori.conj(list._prioritized, d);
});
}
return list;
};

View File

@@ -1,27 +0,0 @@
Package.describe({
summary: "Given the set of the constraints, picks a satisfying configuration",
version: "1.0.0",
internal: true
});
Npm.depends({
'semver': '2.2.1',
'mori': '0.2.6'
});
Package.on_use(function (api) {
api.export('ConstraintSolver');
api.use(['underscore', 'ejson', 'check', 'package-version-parser',
'binary-heap', 'random'], 'server');
api.add_files(['constraint-solver.js', 'resolver.js', 'constraints-list.js',
'dependencies-list.js', 'priority-queue.js'], ['server']);
});
Package.on_test(function (api) {
api.use('constraint-solver', ['server']);
api.use(['tinytest', 'minimongo']);
// data for big benchmarky tests
api.add_files('test-data.js', ['server']);
api.add_files('constraint-solver-tests.js', ['server']);
api.add_files('resolver-tests.js', ['server']);
});

View File

@@ -1,54 +0,0 @@
PriorityQueue = function () {
var self = this;
var compareArrays = function (a, b) {
for (var i = 0; i < a.length; i++)
if (a[i] !== b[i])
if (typeof a[i] === 'function')
return compareArrays(a[i], b[i]);
else
return a[i] - b[i];
return 0;
};
// id -> cost
self._heap = new MinHeap(function (a, b) {
return compareArrays(a, b);
});
// id -> reference to item
self._items = {};
};
_.extend(PriorityQueue.prototype, {
push: function (item, cost) {
var self = this;
var id = Random.id();
self._heap.set(id, cost);
self._items[id] = item;
},
top: function () {
var self = this;
var id = self._heap.minElementId();
return self._items[id];
},
pop: function () {
var self = this;
var id = self._heap.minElementId();
var item = self._items[id];
delete self._items[id];
self._heap.remove(id);
return item;
},
empty: function () {
var self = this;
return self._heap.empty();
},
size: function () {
var self = this;
return self._heap.size();
}
});

View File

@@ -1,129 +0,0 @@
Tinytest.add("constraint solver - resolver, get exact deps", function (test) {
// Fat arrow - exact deps
// Thin arrow - inexact dep or no constraint
// A => B => C
// \ \-> D => E
// \-> \-> F
var resolver = new ConstraintSolver.Resolver();
var A100 = new ConstraintSolver.UnitVersion("A", "1.0.0", "1.0.0");
var B100 = new ConstraintSolver.UnitVersion("B", "1.0.0", "1.0.0");
var C100 = new ConstraintSolver.UnitVersion("C", "1.0.0", "1.0.0");
var D100 = new ConstraintSolver.UnitVersion("D", "1.1.0", "1.0.0");
var E100 = new ConstraintSolver.UnitVersion("E", "1.0.0", "1.0.0");
var F100 = new ConstraintSolver.UnitVersion("F", "1.2.0", "1.0.0");
resolver.addUnitVersion(A100);
resolver.addUnitVersion(B100);
resolver.addUnitVersion(C100);
resolver.addUnitVersion(D100);
resolver.addUnitVersion(E100);
resolver.addUnitVersion(F100);
A100.addDependency("B");
A100.addConstraint(resolver.getConstraint("B", "=1.0.0"));
B100.addDependency("C");
B100.addConstraint(resolver.getConstraint("C", "=1.0.0"));
// a dependency w/o a constraint, still should pick it
B100.addDependency("D");
D100.addDependency("E");
D100.addConstraint(resolver.getConstraint("E", "=1.0.0"));
B100.addDependency("F");
// a non-exact constraint
B100.addConstraint(resolver.getConstraint("F", "1.0.0"));
A100.addDependency("F");
A100.addConstraint(resolver.getConstraint("F", "1.1.0"));
test.equal(A100.exactTransitiveDependenciesVersions(resolver), [B100, C100]);
test.equal(A100.inexactTransitiveDependencies(resolver).toArray(), ["D", "F"]);
test.equal(resolver.resolve(["A"]), [A100, B100, C100, D100, E100, F100]);
});
Tinytest.add("constraint solver - resolver, cost function - pick latest", function (test) {
var resolver = new ConstraintSolver.Resolver();
var A100 = new ConstraintSolver.UnitVersion("A", "1.0.0", "1.0.0");
var A110 = new ConstraintSolver.UnitVersion("A", "1.1.0", "1.0.0");
var B100 = new ConstraintSolver.UnitVersion("B", "1.0.0", "1.0.0");
var C100 = new ConstraintSolver.UnitVersion("C", "1.0.0", "1.0.0");
var C110 = new ConstraintSolver.UnitVersion("C", "1.1.0", "1.0.0");
var C120 = new ConstraintSolver.UnitVersion("C", "1.2.0", "1.0.0");
resolver.addUnitVersion(A100);
resolver.addUnitVersion(A110);
resolver.addUnitVersion(B100);
resolver.addUnitVersion(C100);
resolver.addUnitVersion(C110);
resolver.addUnitVersion(C120);
A100.addDependency("C");
A110.addDependency("C");
B100.addDependency("A");
B100.addConstraint(resolver.getConstraint("A", "=1.0.0"));
B100.addDependency("C");
B100.addConstraint(resolver.getConstraint("C", "1.1.0"));
// Run looking for a conservative solution for A
var AOnlySolution = resolver.resolve(["A"], [], [], {
costFunction: function (choices) {
var A = _.find(choices, function (uv) { return uv.name === "A"; });
var distanceA = A ? semver2number(A.version) : 0;
return distanceA - 100;
}
});
test.equal(AOnlySolution, [A100, C100]);
var AnBSolution = resolver.resolve(["A", "B"], [], [], {
costFunction: function (choices) {
var C = _.find(choices, function (uv) { return uv.name === "C"; });
var A = _.find(choices, function (uv) { return uv.name === "A"; });
var distanceC = C ? semver2number(C.version) : 0;
var distanceA = A ? semver2number(A.version) : 0;
return 1000000000 - distanceC - distanceA;
}
}).sort();
test.equal(AnBSolution, [A100, B100, C120]);
});
Tinytest.add("constraint solver - resolver, cost function - avoid upgrades", function (test) {
var resolver = new ConstraintSolver.Resolver();
var A100 = new ConstraintSolver.UnitVersion("A", "1.0.0", "1.0.0");
var A110 = new ConstraintSolver.UnitVersion("A", "1.1.0", "1.0.0");
var B100 = new ConstraintSolver.UnitVersion("B", "1.0.0", "1.0.0");
var B110 = new ConstraintSolver.UnitVersion("B", "1.1.0", "1.0.0");
var C100 = new ConstraintSolver.UnitVersion("C", "1.0.0", "1.0.0");
resolver.addUnitVersion(A100);
resolver.addUnitVersion(A110);
resolver.addUnitVersion(B100);
resolver.addUnitVersion(B110);
resolver.addUnitVersion(C100);
A100.addDependency("B");
A100.addConstraint(resolver.getConstraint("B", "1.1.0"));
A110.addDependency("C");
A110.addConstraint(resolver.getConstraint("C", "1.0.0"));
// We had one dependency on B and the previous run of resolver told us to us
// B@1.0.0. Now we are adding the package A in a conservative manner. The
// constraint solver should keep B from upgrading by picking a newer version
// of A that uses C.
var lockedVersions = [B100];
var solution = resolver.resolve(["A", "B"], [], [], {
costFunction: function (choices) {
return _.reduce(choices, function (sum, uv) {
var lockedVersion = _.find(lockedVersions, function (luv) { return luv.name === uv.name; });
if (! lockedVersion || lockedVersion === uv)
return sum;
return sum + 100;
}, 0);
}
}).sort();
test.equal(solution, [A110, B100, C100]);
});
function semver2number (semverStr) {
return parseInt(semverStr.replace(/\./g, ""));
}

View File

@@ -1,537 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
// Resolver
////////////////////////////////////////////////////////////////////////////////
// XXX the whole resolver heavily relies on these statements to be true:
// - every unit version ever used was added to the resolver with addUnitVersion
// - every constraint ever used was instantiated with getConstraint
// - every constraint was added exactly once
// - every unit version was added exactly once
// - if two unit versions are the same, their refs point at the same object
// - if two constraints are the same, their refs point at the same object
ConstraintSolver.Resolver = function () {
var self = this;
// Maps unit name string to an array of version definitions
self.unitsVersions = {};
// Maps name@version string to a unit version
self._unitsVersionsMap = {};
// Maps unit name string to the greatest version string we have
self._latestVersion = {};
// Refs to all constraints. Mapping String -> instance
self._constraints = {};
};
ConstraintSolver.Resolver.prototype.addUnitVersion = function (unitVersion) {
var self = this;
check(unitVersion, ConstraintSolver.UnitVersion);
if (! _.has(self.unitsVersions, unitVersion.name)) {
self.unitsVersions[unitVersion.name] = [];
self._latestVersion[unitVersion.name] = unitVersion.version;
}
if (! _.has(self._unitsVersionsMap, unitVersion.toString())) {
self.unitsVersions[unitVersion.name].push(unitVersion);
self._unitsVersionsMap[unitVersion.toString()] = unitVersion;
}
if (semver.lt(self._latestVersion[unitVersion.name], unitVersion.version))
self._latestVersion[unitVersion.name] = unitVersion.version;
};
// name - String - "someUnit"
// versionConstraint - String - "=1.2.3" or "2.1.0"
ConstraintSolver.Resolver.prototype.getConstraint =
function (name, versionConstraint) {
var self = this;
check(name, String);
check(versionConstraint, String);
var idString = JSON.stringify([name, versionConstraint]);
if (_.has(self._constraints, idString))
return self._constraints[idString];
return self._constraints[idString] =
new ConstraintSolver.Constraint(name, versionConstraint);
};
// options: Object:
// - costFunction: function (choices) - given a state evaluates its cost
// - estimateCostFunction: function (state) - given a state, evaluates the
// estimated cost of the best path from state to a final state
// - combineCostFunction: function (cost, cost) - given two costs (obtained by
// evaluating states with costFunction and estimateCostFunction)
ConstraintSolver.Resolver.prototype.resolve =
function (dependencies, constraints, choices, options) {
var self = this;
constraints = constraints || [];
choices = choices || [];
options = _.extend({
costFunction: function (choices) { return 0; },
estimateCostFunction: function (state) {
return 0;
},
combineCostFunction: function (cost, anotherCost) {
return cost + anotherCost;
}
}, options);
var rootDependencies = _.clone(dependencies);
// required for error reporting later
var constraintAncestor = {};
_.each(constraints, function (c) {
constraintAncestor[c.toString()] = c.name;
});
dependencies = ConstraintSolver.DependenciesList.fromArray(dependencies, true);
constraints = ConstraintSolver.ConstraintsList.fromArray(constraints);
// create a fake unit version to represnt the app or the build target
var appUV = new ConstraintSolver.UnitVersion("target", "1.0.0", "0.0.0");
appUV.dependencies = dependencies;
appUV.constraints = constraints;
// state is an object:
// - dependencies: DependenciesList
// - constraints: ConstraintsList
// - choices: array of UnitVersion
// - constraintAncestor: mapping Constraint.toString() -> Constraint
var startState = self._propagateExactTransDeps(appUV, dependencies, constraints, choices, constraintAncestor);
startState.choices = _.filter(startState.choices, function (uv) { return uv.name !== "target"; });
if (options.stopAfterFirstPropagation)
return startState.choices;
var pq = new PriorityQueue();
var opts = { rootDependencies: rootDependencies };
var costFunction = options.costFunction;
var estimateCostFunction = options.estimateCostFunction;
var combineCostFunction = options.combineCostFunction;
var estimatedStartingCost =
combineCostFunction(costFunction(startState.choices, opts),
estimateCostFunction(startState, opts));
pq.push(startState, [estimatedStartingCost, 0]);
var someError = null;
var solution = null;
while (! pq.empty()) {
var currentState = pq.pop();
if (currentState.dependencies.isEmpty()) {
solution = currentState.choices;
break;
}
var neighborsObj = self._stateNeighbors(currentState);
if (! neighborsObj.success) {
someError = someError || neighborsObj.failureMsg;
} else {
_.each(neighborsObj.neighbors, function (state) {
var tentativeCost =
combineCostFunction(costFunction(state.choices, opts),
estimateCostFunction(state, opts));
pq.push(state, [tentativeCost, -state.choices.length]);
});
}
}
if (solution)
return solution;
// XXX should be much much better
if (someError)
throw new Error(someError);
throw new Error("Couldn't resolve, I am sorry");
};
// state is an object:
// - dependencies: DependenciesList - remaining dependencies
// - constraints: ConstraintsList - constraints to satisfy
// - choices: array of UnitVersion - current fixed set of choices
// - constraintAncestor: Constraint (string representation) ->
// Dependency name. Used for error reporting to indicate which direct
// dependencies have caused a failure. For every constraint, this is
// the list of direct dependencies which led to this constraint being
// present.
//
// returns {
// success: Boolean,
// failureMsg: String,
// neighbors: [state]
// }
//
// NOTE: assumes that exact dependencies are already propagated
ConstraintSolver.Resolver.prototype._stateNeighbors =
function (state) {
var self = this;
var dependencies = state.dependencies;
var constraints = state.constraints;
var choices = state.choices;
var constraintAncestor = state.constraintAncestor;
var candidateName = dependencies.peek();
dependencies = dependencies.remove(candidateName);
var candidateVersions =
_.filter(self.unitsVersions[candidateName], function (uv) {
return _.isEmpty(constraints.violatedConstraints(uv));
});
var generateError = function (uv, violatedConstraints) {
var directDepsString = "";
_.each(violatedConstraints, function (c) {
directDepsString += constraintAncestor[c.toString()] +
"(" + c.toString() + "), ";
});
return {
success: false,
// XXX We really want to say "directDep1 depends on X@1.0 and
// directDep2 depends on X@2.0"
failureMsg: "Direct dependencies " + directDepsString + "conflict on " + uv.name
};
};
if (_.isEmpty(candidateVersions)) {
var uv = self.unitsVersions[candidateName][0];
if (! uv)
return { success: false, failureMsg: "Cannot find anything about package -- " + candidateName };
return generateError(uv, constraints.violatedConstraints(uv));
}
var firstError = null;
var neighbors = _.chain(candidateVersions).map(function (uv) {
var nChoices = _.clone(choices);
var nConstraintAncestors = _.clone(constraintAncestor);
nChoices.push(uv);
return self._propagateExactTransDeps(uv, dependencies, constraints, nChoices, nConstraintAncestors);
}).filter(function (state) {
var vcfc =
violatedConstraintsForSomeChoice(state.choices, state.constraints);
if (! vcfc)
return true;
if (! firstError) {
firstError = generateError(vcfc.choice, vcfc.constraints);
}
return false;
}).value();
if (firstError && ! neighbors.length)
return firstError;
// Should never be true as !!firstError === !neighbors.length but still check
// just in case.
if (! neighbors.length)
return { success: false,
failureMsg: "None of the versions unit produces a sensible result -- "
+ candidateName };
return { success: true, neighbors: neighbors };
};
// Propagates exact dependencies (which have exact constraints) from
// the given unit version taking into account the existing set of dependencies
// and constraints.
// Assumes that the unit versions graph without passed unit version is already
// propagated (i.e. doesn't try to propagate anything not related to the passed
// unit version).
ConstraintSolver.Resolver.prototype._propagateExactTransDeps =
function (uv, dependencies, constraints, choices, constraintAncestor) {
var self = this;
// XXX representing a queue as an array with push/shift operations is not
// efficient as Array.shift is O(N). Replace if it becomes a problem.
var queue = [];
// Boolean map to avoid adding the same stuff to queue over and over again.
// Keeps the time complexity the same but can save some memory.
var isEnqueued = {};
// For keeping track of new choices in this iteration
var oldChoice = {};
_.each(choices, function (uv) { oldChoice[uv.name] = uv; });
queue.push(uv);
isEnqueued[uv.name] = true;
while (queue.length > 0) {
uv = queue[0];
queue.shift();
choices = _.clone(choices);
choices.push(uv);
var exactTransitiveDepsVersions =
uv.exactTransitiveDependenciesVersions(self);
var inexactTransitiveDeps = uv.inexactTransitiveDependencies(self);
var transitiveConstraints = new ConstraintSolver.ConstraintsList();
_.each(_.union(exactTransitiveDepsVersions, [uv]), function (uv) {
transitiveConstraints = transitiveConstraints.union(uv.constraints);
});
var newChoices = exactTransitiveDepsVersions;
dependencies = dependencies.union(inexactTransitiveDeps);
constraints = constraints.union(transitiveConstraints);
choices = _.union(choices, newChoices);
// Since exact transitive deps are put into choices, there is no need to
// keep them in dependencies.
_.each(newChoices, function (uv) {
dependencies = dependencies.remove(uv.name);
});
// There could be new combination of exact constraint/dependency outgoing
// from existing state and the new node.
// We don't need to look for all previously considered combinations.
// Looking for newNode.dependencies+exact constraints and
// newNode.exactConstraints+dependencies is enough.
var newExactConstraintsList = uv.dependencies
.exactConstraintsIntersection(constraints)
.union(uv.constraints.exactDependenciesIntersection(uv.dependencies));
var exactDeps = [];
newExactConstraintsList.each(function (c) {
var uv = c.getSatisfyingUnitVersion(self);
if (! uv)
throw new Error("No unit version was found for the constraint -- " + c.toString());
exactDeps.push(uv);
});
// Enqueue all new exact dependencies.
_.each(exactDeps, function (dep) {
if (_.has(isEnqueued, dep.name))
return;
queue.push(dep);
isEnqueued[dep.name] = true;
});
// for error reporting
uv.constraints.each(function (c) {
constraintAncestor[c.toString()] = uv.name; });
}
// Update the constraintAncestor table
_.each(choices, function (uv) {
if (oldChoice[uv.name])
return;
var relevantConstraint = null;
constraints.forPackage(uv.name, function (c) { relevantConstraint = c; });
var rootAnc = null;
if (relevantConstraint) {
rootAnc = constraintAncestor[relevantConstraint.toString()];
} else {
// XXX this probably only works correctly when uv was a root dependency
// w/o a constraint or dependency of one of the root deps.
_.each(choices, function (choice) {
if (rootAnc)
return;
if (choice.dependencies.contains(uv.name))
rootAnc = choice.name;
});
if (! rootAnc)
rootAnc = uv.name;
}
uv.constraints.each(function (c) {
if (! constraintAncestor[c.toString()])
constraintAncestor[c.toString()] = rootAnc;
});
});
return {
dependencies: dependencies,
constraints: constraints,
choices: choices,
constraintAncestor: constraintAncestor
};
};
var violatedConstraintsForSomeChoice = function (choices, constraints) {
var ret = null;
_.each(choices, function (choice) {
if (ret)
return;
var violatedConstraints = constraints.violatedConstraints(choice);
if (! _.isEmpty(violatedConstraints))
ret = { constraints: violatedConstraints, choice: choice };
});
return ret;
};
////////////////////////////////////////////////////////////////////////////////
// UnitVersion
////////////////////////////////////////////////////////////////////////////////
ConstraintSolver.UnitVersion = function (name, unitVersion, ecv) {
var self = this;
check(name, String);
check(unitVersion, String);
check(ecv, String);
check(self, ConstraintSolver.UnitVersion);
self.name = name;
self.version = unitVersion;
self.dependencies = new ConstraintSolver.DependenciesList();
self.constraints = new ConstraintSolver.ConstraintsList();
// a string in a form of "1.2.0"
self.ecv = ecv;
};
_.extend(ConstraintSolver.UnitVersion.prototype, {
addDependency: function (name) {
var self = this;
check(name, String);
if (self.dependencies.contains(name))
throw new Error("Dependency already exists -- " + name);
self.dependencies = self.dependencies.push(name);
},
addConstraint: function (constraint) {
var self = this;
check(constraint, ConstraintSolver.Constraint);
if (self.constraints.contains(constraint))
throw new Error("Constraint already exists -- " + constraint.toString());
self.constraints = self.constraints.push(constraint);
},
// Returns a list of transitive exact constraints, those could be found as
// transitive dependencies.
_exactTransitiveConstraints: function (resolver) {
var self = this;
var exactTransitiveConstraints =
self.dependencies.exactConstraintsIntersection(self.constraints);
exactTransitiveConstraints.each(function (c) {
var unitVersion = c.getSatisfyingUnitVersion(resolver);
if (! unitVersion)
throw new Error("No unit version was found for the constraint -- " + c.toString());
// Collect the transitive dependencies of the direct exact dependencies.
exactTransitiveConstraints = exactTransitiveConstraints.union(
unitVersion._exactTransitiveConstraints(resolver));
});
return exactTransitiveConstraints;
},
// XXX weirdly returns an array as opposed to some UVCollection
exactTransitiveDependenciesVersions: function (resolver) {
var self = this;
var uvs = [];
self._exactTransitiveConstraints(resolver).each(function (c) {
var unitVersion = c.getSatisfyingUnitVersion(resolver);
if (! unitVersion)
throw new Error("No unit version was found for the constraint -- " + c.toString());
uvs.push(unitVersion);
});
return uvs;
},
inexactTransitiveDependencies: function (resolver) {
var self = this;
var exactTransitiveConstraints = self._exactTransitiveConstraints(resolver);
var deps = self.dependencies;
exactTransitiveConstraints.each(function (c) {
var unitVersion = c.getSatisfyingUnitVersion(resolver);
if (! unitVersion)
throw new Error("No unit version was found for the constraint -- " + c.toString());
deps = deps.union(unitVersion.dependencies);
});
// remove the the exact constraints
exactTransitiveConstraints.each(function (c) {
deps = deps.remove(c.name);
});
return deps;
},
toString: function () {
var self = this;
return self.name + "@" + self.version;
}
});
////////////////////////////////////////////////////////////////////////////////
// Constraint
////////////////////////////////////////////////////////////////////////////////
// Can be called either:
// new PackageVersion.Constraint("packageA", "=2.1.0")
// or:
// new PackageVersion.Constraint("pacakgeA@=2.1.0")
ConstraintSolver.Constraint = function (name, versionString) {
var self = this;
if (versionString) {
_.extend(self, PackageVersion.parseVersionConstraint(versionString));
self.name = name;
} else {
_.extend(self, PackageVersion.parseConstraint(name));
}
};
ConstraintSolver.Constraint.prototype.toString = function () {
var self = this;
return self.name + "@" + (self.exact ? "=" : "") + self.version;
};
var semver = Npm.require('semver');
ConstraintSolver.Constraint.prototype.isSatisfied = function (unitVersion) {
var self = this;
check(unitVersion, ConstraintSolver.UnitVersion);
if (self.exact)
return self.version === unitVersion.version;
return semver.lte(self.version, unitVersion.version) &&
semver.lte(unitVersion.ecv, self.version);
};
// Returns any unit version satisfying the constraint in the resolver
ConstraintSolver.Constraint.prototype.getSatisfyingUnitVersion =
function (resolver) {
var self = this;
if (self.exact)
return resolver._unitsVersionsMap[self.toString().replace("=", "")];
var unitVersion = _.find(resolver.unitsVersions[self.name],
_.bind(self.isSatisfied, self));
return unitVersion;
};

File diff suppressed because it is too large Load Diff