mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
removed constraint solver package in case it is a bad merge
This commit is contained in:
@@ -1 +0,0 @@
|
||||
node_modules
|
||||
@@ -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.
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"mori": {
|
||||
"version": "0.2.6"
|
||||
},
|
||||
"semver": {
|
||||
"version": "2.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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, ""));
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user