Files
meteor/tools/catalog.js
ekatek eda7709316 fix forgottenECVs and reduce refresh rate
Two things:
 - to determine if two versions are compatible, we need to know their ECVs. (earliest
compatible versions). If the version that we have is local, then we don't have access to the
version record of the server version, so we can't figure out its ECV. That's why in the olden
days, there was a hack to store ECVs separately ('forgotten ECVs'). The new catalog didn't
have that function implemented -- it might not need it, but in that case, it would need to make
changes to the constraint solver that might be risky at this point. In any case, implementing this
function in the new world is pretty easy and solves the problem for now.

- when we look for things, we look in the local catalog, then the server catalog and if the server
catalog can't find it, it will refresh. However, sometimes, we are looking for something that the
server catalog cannot POSSIBLY have (ie: it has a build ID). That's fine, actually, but it causes
an extra refresh on the server catalog that we don't need. I put in a break to make sure that, if we
know for a fact that the server catalog does not have a version record (ie: it has a build id), we don't
bother looking in it and just return null to begin with. That should help.
2014-10-02 17:46:57 -07:00

356 lines
13 KiB
JavaScript

var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var util = require('util');
var buildmessage = require('./buildmessage.js');
var tropohouse = require('./tropohouse.js');
var packageCache = require('./package-cache.js');
var localCatalog = require('./catalog-local.js');
var remoteCatalog = require('./catalog-remote.js');
var files = require('./files.js');
var prebuiltBootstrap = require('./catalog-bootstrap-prebuilt.js');
var checkoutBootstrap = require('./catalog-bootstrap-checkout.js');
var project = require('./project.js');
var utils = require('./utils.js');
var config = require('./config.js');
var packageClient = require('./package-client.js');
var Console = require('./console.js').Console;
// As a work-around for [] !== [], we use a function to check whether values are acceptable
var ACCEPT_NON_EMPTY = function (result) {
// null, undefined
if (result === null || result === undefined) {
return false;
}
// []
if (result.length === 0) {
return false;
}
return true;
};
// The LayeredCatalog provides a way to query multiple catalogs in a uniform way
// A LayeredCatalog typically contains:
// - a local catalog referencing the packages of the project
// - a reference to the official catalog
var LayeredCatalog = function() {
var self = this;
self.localCatalog = null;
self.otherCatalog = null;
// Constraint solver using this catalog.
self.resolver = null;
// Each complete catalog needs its own package cache.
self.packageCache = new packageCache.PackageCache(self);
};
_.extend(LayeredCatalog.prototype, {
toString: function () {
var self = this;
return "LayeredCatalog []";
},
setCatalogs: function(local, remote) {
var self = this;
self.localCatalog = local;
self.otherCatalog = remote;
},
addLocalPackage: function (directory) {
var self = this;
self.localCatalog.addLocalPackage(directory);
},
getAllBuilds: function (name, version) {
var self = this;
return self._returnFirst("getAllBuilds", arguments, ACCEPT_NON_EMPTY);
},
getLatestVersion: function (name) {
var self = this;
return self._returnFirst("getLatestVersion", arguments, ACCEPT_NON_EMPTY);
},
getAllPackageNames: function () {
var self = this;
return _.union(self.localCatalog.getAllPackageNames(), self.otherCatalog.getAllPackageNames());
},
_returnFirst: function(f, args, validityOracle) {
var self = this;
var splittedArgs = Array.prototype.slice.call(args,0);
var result = self.localCatalog[f].apply(self.localCatalog, splittedArgs);
if (validityOracle(result)) {
return result;
}
return self.otherCatalog[f].apply(self.otherCatalog, splittedArgs);
},
getBuildsForArches: function (name, version, arches) {
return this._returnFirst("getBuildsForArches", arguments, ACCEPT_NON_EMPTY);
},
getBuildWithPreciseBuildArchitectures: function (versionRecord, buildArchitectures) {
return this._returnFirst("getBuildWithPreciseBuildArchitectures", arguments, ACCEPT_NON_EMPTY);
},
getForgottenECVs: function (packageName) {
var self = this;
var versions = self.otherCatalog.getSortedVersions(packageName);
var forgottenECVs = {};
_.each(versions, function (v) {
var vr = self.otherCatalog.getVersion(packageName, v);
forgottenECVs[v] = vr.earliestCompatibleVersion;
});
return forgottenECVs;
},
getLoadPathForPackage: function (name, version, constraintSolverOpts) {
var self = this;
return self.localCatalog.getLoadPathForPackage(name, version, constraintSolverOpts);
},
getLocalPackageNames: function () {
return this.localCatalog.getLocalPackageNames();
},
getPackage: function (name) {
return this._returnFirst("getPackage", arguments, ACCEPT_NON_EMPTY);
},
// Find a release that uses the given version of a tool. See: publish-for-arch
// in command-packages for more explanation. Returns information about a
// particular release version, or null if such release version does not exist.
getReleaseWithTool: function (toolSpec) {
var self = this;
buildmessage.assertInCapture();
return self._recordOrRefresh(function () {
return _.findWhere(self.releaseVersions, { tool: toolSpec });
});
},
// Returns general (non-version-specific) information about a
// release track, or null if there is no such release track.
getReleaseTrack: function (name) {
return this.otherCatalog.getReleaseTrack(name);
},
getReleaseVersion: function (track, version) {
return this.otherCatalog.getReleaseVersion(track, version);
},
getSortedRecommendedReleaseVersions: function (track, laterThanOrderKey) {
return this.otherCatalog.getSortedRecommendedReleaseVersions(track, laterThanOrderKey);
},
getSortedVersions: function (name) {
return this._returnFirst("getSortedVersions", arguments, ACCEPT_NON_EMPTY);
},
getVersion: function (name, version) {
var self = this;
var result = self.localCatalog.getVersion(name, version);
if (!result) {
if (/\+/.test(version)) {
return null;
}
result = self.otherCatalog.getVersion(name, version);
}
return result;
},
initialize: function (options) {
this.localCatalog.initialize(options);
},
isLocalPackage: function (name) {
return this.localCatalog.isLocalPackage(name);
},
rebuildLocalPackages: function (namedPackages) {
this.packageCache.refresh();
return this.localCatalog.rebuildLocalPackages(namedPackages);
},
reset: function () {
this.localCatalog.reset();
},
// As getVersion, but returns info on the latest version of the
// package, or null if the package doesn't exist or has no versions.
// It does not include prereleases (with dashes in the version);
getLatestMainlineVersion: function (name) {
var self = this;
buildmessage.assertInCapture();
var versions = self.getSortedVersions(name);
versions.reverse();
var latest = _.find(versions, function (version) {
return !/-/.test(version);
});
if (!latest)
return null;
return self.getVersion(name, latest);
},
resolveConstraints: function (constraints, resolverOpts, opts) {
var self = this;
opts = opts || {};
// OK, since we are the complete catalog, the uniload catalog must be fully
// initialized, so it's safe to load a resolver if we didn't
// already. (Putting this off until the first call to resolveConstraints
// also helps with performance: no need to build this package and load the
// large mori module unless we actually need it.)
self.resolver || self._initializeResolver();
// Looks like we are not going to be able to avoid calling the constraint
// solver, so let's process the input (constraints) into the correct
// arguments to the constraint solver.
//
// -deps: list of package names that we depend on
// -constr: constraints of the proper form from parseConstraint in utils.js
//
// Weak dependencies are constraints (they constrain the result), but not
// dependencies.
var deps = [];
var constr = [];
_.each(constraints, function (constraint) {
constraint = _.clone(constraint);
if (!constraint.weak) {
deps.push(constraint.name);
}
delete constraint.weak;
constr.push(constraint);
});
// If we are called with 'ignore projectDeps', then we don't even look to
// see what the project thinks and recalculate everything. Similarly, if the
// project root path has not been initialized, we are probably running
// outside of a project, and have nothing to look at for guidance.
if (!opts.ignoreProjectDeps && project.project &&
project.project.viableDepSource) {
// Anything in the project's dependencies was calculated based on a
// previous constraint solver run, and needs to be taken as absolute truth
// for now: we can't use any packages that are of different versions from
// what we've already decided from the project!
_.each(project.project.getVersions(), function (version, name) {
constr.push(utils.parseConstraint(name + "@=" + version));
});
}
// Local packages can only be loaded from the version we have the source
// for: that's a weak exact constraint.
_.each(self.packageSources, function (packageSource, name) {
constr.push(utils.parseConstraint(name + "@=" + packageSource.version));
});
var ret = buildmessage.enterJob({ title: "Figuring out the best package versions to use." }, function () {
// Then, call the constraint solver, to get the valid transitive subset of
// those versions to record for our solution. (We don't just return the
// original version lock because we want to record the correct transitive
// dependencies)
try {
return self.resolver.resolve(deps, constr, resolverOpts);
} catch (e) {
console.log("Got error during resolve; trying refresh", e);
remoteCatalog.official.refresh();
self.resolver || self._initializeResolver();
return self.resolver.resolve(deps, constr, resolverOpts);
}
});
if (ret["usedRCs"]) {
var expPackages = [];
_.each(ret.answer, function(version, package) {
if (version.split('-').length > 1) {
if (!(resolverOpts.previousSolution &&
resolverOpts.previousSolution[package] === version)) {
var oldConstraints = _.where(constr, { name: package } );
var printMe = true;
_.each(oldConstraints, function (oC) {
_.each(oC.constraints, function (specOC) {
if (specOC.version === version) {
printMe = false;
}
});
});
if (printMe) {
expPackages.push({
name: " " + package + "@" + version,
description: self.getVersion(package, version).description
});
};
}}
});
if (!_.isEmpty(expPackages)) {
// XXX: Couldn't figure out how to word this better for better tenses.
//
// XXX: this shouldn't be here. This is library code... it
// shouldn't be printing.
// https://github.com/meteor/meteor/wiki/Meteor-Style-Guide#only-user-interface-code-should-engage-with-the-user
Console.info(
"\nIn order to resolve constraints, we had to use the following\n"+
"experimental package versions:");
Console.info(utils.formatList(expPackages));
}
}
return ret.answer;
},
// Refresh the catalogs referenced by this catalog.
// options:
// - forceRefresh: even if there is a future in progress, refresh the catalog
// anyway. When we are using hot code push, we may be restarting the app
// because of a local package change that impacts that catalog. Don't wait
// on the official catalog to refresh data.json, in this case.
// - watchSet: if provided, any files read in reloading packages will be added
// to this set.
refresh: function (options) {
var self = this;
self.localCatalog.refresh(options);
self.otherCatalog.refresh(options);
self.packageCache.refresh();
self.resolver = null;
},
_initializeResolver: function () {
var self = this;
var uniload = require('./uniload.js');
var constraintSolverPackage = uniload.load({
packages: [ 'constraint-solver']
})['constraint-solver'];
self.resolver =
new constraintSolverPackage.ConstraintSolver.PackagesResolver(self, {
nudge: function () {
// This may be a singleton, but the resolver is in a package so it
// doesn't have access to it.
utils.Patience.nudge();
buildmessage.nudge();
}
});
},
watchLocalPackageDirs: function (watchSet) {
var self = this;
self.localCatalog.watchLocalPackageDirs(watchSet);
}
});
exports.DEFAULT_TRACK = remoteCatalog.DEFAULT_TRACK;
exports.official = remoteCatalog.official;
//Instantiate the various catalogs
if (files.inCheckout()) {
exports.uniload = new checkoutBootstrap.BootstrapCatalogCheckout();
} else {
exports.uniload = new prebuiltBootstrap.BootstrapCatalogPrebuilt();
}
// This is the catalog that's used to actually drive the constraint solver: it
// contains local packages, and since local packages always beat server
// packages, it doesn't contain any information about the server version of
// local packages.
exports.complete = new LayeredCatalog();
exports.complete.setCatalogs(new localCatalog.LocalCatalog({containingCatalog : exports.complete}), remoteCatalog.official);