mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
270 lines
9.1 KiB
JavaScript
270 lines
9.1 KiB
JavaScript
var semver = require('semver');
|
|
var _ = require('underscore');
|
|
var catalog = require('./catalog.js').catalog;
|
|
var utils = require('./utils.js');
|
|
|
|
var constraintSolver = exports;
|
|
|
|
// Comment this out until we have a way to get Match here
|
|
/*
|
|
var Dependency = {
|
|
packageName: String,
|
|
version: Match.OneOf(String, null), // XXX 'x.y.z' or 'x.y.z'
|
|
exact: Match.Optional(Boolean),
|
|
weak: Match.Optional(Boolean),
|
|
unordered: Match.Optional(Boolean)
|
|
};
|
|
*/
|
|
|
|
// XXX copied (with simplifications) from EJSON.clone
|
|
var deepClone = function (v) {
|
|
var ret;
|
|
if (typeof v !== "object")
|
|
return v;
|
|
if (v === null)
|
|
return null; // null has typeof "object"
|
|
// XXX: Use something better than underscore's isArray
|
|
if (_.isArray(v) || _.isArguments(v)) {
|
|
// For some reason, _.map doesn't work in this context on Opera (weird test
|
|
// failures).
|
|
ret = [];
|
|
for (var i = 0; i < v.length; i++)
|
|
ret[i] = deepClone(v[i]);
|
|
return ret;
|
|
}
|
|
// handle other objects
|
|
ret = {};
|
|
_.each(v, function (value, key) {
|
|
ret[key] = deepClone(value);
|
|
});
|
|
return ret;
|
|
};
|
|
|
|
|
|
|
|
// main class
|
|
constraintSolver.Resolver = function (options) {
|
|
var self = this;
|
|
|
|
options = options || {};
|
|
var architecture = options.architecture || "all";
|
|
|
|
// map package-name -> map
|
|
// map version -> object
|
|
// - dependendencies
|
|
// - earliestCompatibleVersion
|
|
self.packageDeps = {};
|
|
|
|
// package name -> list of version strings that we know about for the
|
|
// package, sorted in ascending semver order
|
|
self.sortedVersionsForPackage = {};
|
|
|
|
_.each(catalog.getAllPackageNames(), function (packageName) {
|
|
var packageDef = catalog.getPackage(packageName);
|
|
self.packageDeps[packageDef.name] = {};
|
|
|
|
var versions = catalog.getSortedVersions(packageName);
|
|
self.sortedVersionsForPackage[packageDef.name] = versions;
|
|
|
|
_.each(versions, function (version) {
|
|
var versionDef = catalog.getVersion(packageName, version);
|
|
// version is a string #version-name-conflict
|
|
var packageDep = {};
|
|
packageDep.earliestCompatibleVersion = versionDef.earliestCompatibleVersion;
|
|
packageDep.dependencies = _.map(versionDef.dependencies, function (dep, packageName) {
|
|
return _.extend({packageName: packageName},
|
|
utils.parseVersionConstraint(dep.constraint));
|
|
});
|
|
|
|
self.packageDeps[packageDef.name][versionDef.version] = packageDep;
|
|
});
|
|
});
|
|
};
|
|
|
|
// The propagation of exact dependencies
|
|
// XXX empties the exactDepsStack
|
|
// XXX extends the depsDict
|
|
// XXX after this depsStack can contain duplicates
|
|
constraintSolver.Resolver.prototype._propagateExactDeps =
|
|
function (depsDict, exactDepsStack) {
|
|
var self = this;
|
|
var picks = {};
|
|
|
|
_.each(exactDepsStack, function (dep) { picks[dep.packageName] = dep.version; });
|
|
|
|
while (exactDepsStack.length > 0) {
|
|
var currentPick = exactDepsStack.pop();
|
|
try {
|
|
var currentDependencies =
|
|
self.packageDeps[currentPick.packageName][currentPick.version].dependencies;
|
|
} catch (err) {
|
|
if (! _.has(self.packageDeps, currentPick.packageName))
|
|
throw new Error("There is no required package found: " + currentPick.packageName);
|
|
if (! _.has(self.packageDeps[currentPick.packageName], currentPick.version))
|
|
throw new Error("There is no required package version found for the requested architecture: " + currentPick.packageName + "@" + currentPick.version);
|
|
}
|
|
|
|
_.each(pickExactDeps(currentDependencies), function (dep) {
|
|
if (_.has(picks, dep.packageName)) {
|
|
// XXX this error message should be improved so you can get a lot more
|
|
// context, like what are initial exact dependencies (those user
|
|
// specified) and what is the eventual conflict.
|
|
if (picks[dep.packageName] !== dep.version)
|
|
throw new Error("Unresolvable: two exact dependencies conflict: " +
|
|
dep.packageName + " versions: " +
|
|
[picks[dep.packageName], dep.version].join(", "));
|
|
} else {
|
|
picks[dep.packageName] = dep.version;
|
|
exactDepsStack.push(dep);
|
|
}
|
|
});
|
|
|
|
_.each(rejectExactDeps(currentDependencies), function (dep) {
|
|
depsDict[dep.packageName] = depsDict[dep.packageName] || [];
|
|
depsDict[dep.packageName].push(dep);
|
|
});
|
|
};
|
|
|
|
return picks;
|
|
};
|
|
|
|
constraintSolver.Resolver.prototype._resolve = function (dependencies, state) {
|
|
// Comment this out until we have a way to get check() here
|
|
// check(dependencies, [Dependency]);
|
|
|
|
state = state || {};
|
|
state.picks = state.picks || {};
|
|
|
|
var self = this;
|
|
|
|
state.depsDict = state.depsDict || {};
|
|
_.each(rejectExactDeps(dependencies), function (dep) {
|
|
state.depsDict[dep.packageName] = state.depsDict[dep.packageName] || [];
|
|
state.depsDict[dep.packageName].push(dep);
|
|
});
|
|
|
|
state.exactDepsStack = state.exactDepsStack || pickExactDeps(dependencies);
|
|
|
|
var exactPicks = self._propagateExactDeps(state.depsDict, state.exactDepsStack);
|
|
|
|
// add all exact dependencies the propagator picked to the set of picks
|
|
_.each(exactPicks, function (version, packageName) {
|
|
if (_.has(state.picks, packageName)) {
|
|
if (state.picks[packageName] !== version)
|
|
throw new Error("Exact dependencies contradict with already picked version for a package: "
|
|
+ packageName + " " + state.picks[packageName] + ": " + version);
|
|
} else {
|
|
state.picks[packageName] = version;
|
|
}
|
|
});
|
|
|
|
// check if all non-exact dependencies are still satisfied
|
|
_.each(state.picks, function (version, packageName) {
|
|
_.each(state.depsDict[packageName], function (dep) {
|
|
if (! self.dependencyIsSatisfied(dep, version))
|
|
throw new Error("Exact dependency contradicts on of the constraints for a package: "
|
|
+ packageName + " " + version + ": " + dep.version);
|
|
});
|
|
});
|
|
|
|
// calculate packages we depend on but didn't pick a version for yet
|
|
// pick one of those and try different versions
|
|
var candidatePackageNames = _.difference(_.keys(state.depsDict), _.keys(state.picks));
|
|
if (_.isEmpty(candidatePackageNames)) {
|
|
//console.log('there is nothing to look for! successsss');
|
|
return state.picks;
|
|
}
|
|
|
|
var candidatePackageName = candidatePackageNames[0];
|
|
var availableVersions = self.sortedVersionsForPackage[candidatePackageName];
|
|
var satisfyingVersions = _.filter(availableVersions, function (version) {
|
|
return _.all(state.depsDict[candidatePackageName], function (dep) {
|
|
return self.dependencyIsSatisfied(dep, version);
|
|
});
|
|
});
|
|
|
|
if (_.isEmpty(satisfyingVersions))
|
|
throw new Error("Cannot find a satisfying versions of package: "
|
|
+ candidatePackageName);
|
|
|
|
for (var i = 0; i < satisfyingVersions.length; i++) {
|
|
var version = satisfyingVersions[i];
|
|
var newState = deepClone(state);
|
|
newState.picks[candidatePackageName] = version;
|
|
newState.exactDepsStack.push({
|
|
packageName: candidatePackageName,
|
|
version: version
|
|
});
|
|
//console.log('trying ' + candidatePackageName + ' v.' + versionDef.version);
|
|
// recurse
|
|
try {
|
|
// if not failed, return a happy result
|
|
return self._resolve(dependencies, newState);
|
|
} catch (err) {
|
|
//console.log('picking ' + candidatePackageName + ' v.' + version + ' kinda failed, lets not do that: ' + err.message);
|
|
}
|
|
};
|
|
|
|
throw new Error("Cannot pick a satisfying version of package " + candidatePackageName);
|
|
};
|
|
|
|
constraintSolver.Resolver.prototype.resolve = function (dependencies) {
|
|
var self = this;
|
|
return self._resolve(toStructuredDeps(dependencies));
|
|
};
|
|
|
|
constraintSolver.Resolver.prototype.propagatedExactDeps = function (dependencies) {
|
|
var self = this;
|
|
|
|
dependencies = toStructuredDeps(dependencies);
|
|
var depsStack = rejectExactDeps(dependencies);
|
|
var exactDepsStack = pickExactDeps(dependencies);
|
|
return self._propagateExactDeps(depsStack, exactDepsStack);
|
|
};
|
|
|
|
constraintSolver.Resolver.prototype.dependencyIsSatisfied =
|
|
function (dep, version) {
|
|
// XXX check for exact
|
|
var self = this;
|
|
|
|
if (dep.version === null)
|
|
return true;
|
|
|
|
|
|
var versionSpec = self.packageDeps[dep.packageName][version];
|
|
return semver.lte(dep.version, version) &&
|
|
semver.lte(versionSpec.earliestCompatibleVersion, dep.version);
|
|
};
|
|
|
|
// helpers
|
|
var isExact = function (dep) { return dep.exact; };
|
|
var pickExactDeps = function (deps) { return _.filter(deps, isExact); };
|
|
var rejectExactDeps = function (deps) { return _.reject(deps, isExact); };
|
|
|
|
// converts dependencies from simple format to the structured format
|
|
var toStructuredDeps = function (dependencies) {
|
|
var structuredDeps = [];
|
|
|
|
var addStructuredDep = function (packageName, details) {
|
|
// if details is null, it means 'no constraint'
|
|
if (typeof details === "string" || details === null) {
|
|
structuredDeps.push(_.extend(
|
|
{ packageName: packageName },
|
|
utils.parseVersionConstraint(details)));
|
|
} else {
|
|
structuredDeps.push(_.extend({ packageName: packageName }, details));
|
|
}
|
|
};
|
|
|
|
_.each(dependencies, function (details, packageName) {
|
|
if (_.isArray(details)) {
|
|
_.each(details, function (constraint) {
|
|
addStructuredDep(packageName, constraint);
|
|
});
|
|
} else {
|
|
addStructuredDep(packageName, details);
|
|
}
|
|
});
|
|
return structuredDeps;
|
|
};
|