From 0822033ca549716e5ff98cebfbb35ae061f5e56e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 12 Aug 2014 15:23:33 -0700 Subject: [PATCH] Tell resolver about ECVs of overridden versions Otherwise trying to use a package with constraint "livedata@1.0.0" when you are loading livedata 1.0.1 from a local package will fail. --- .../constraint-solver/constraint-solver.js | 20 ++++++-- .../constraint-solver/constraints-list.js | 9 ++-- packages/constraint-solver/resolver.js | 47 +++++++++++++++++-- tools/catalog.js | 18 +++++++ 4 files changed, 82 insertions(+), 12 deletions(-) diff --git a/packages/constraint-solver/constraint-solver.js b/packages/constraint-solver/constraint-solver.js index 49ff0aa0e7..45e96e0954 100644 --- a/packages/constraint-solver/constraint-solver.js +++ b/packages/constraint-solver/constraint-solver.js @@ -57,6 +57,12 @@ ConstraintSolver.PackagesResolver.prototype._ensurePackageInfoLoaded = function ConstraintSolver.PackagesResolver.prototype._loadPackageInfo = function ( packageName) { var self = this; + + // XXX in theory there might be different archs but in practice they are + // always "os", "web.browser" and "web.cordova". Fix this once we + // actually have different archs used. + var allArchs = ["os", "web.browser", "web.cordova"]; + // XXX is sortedness actually relevant? is there a minor optimization here // where we can only talk to self.catalog once? var sortedVersions = self.catalog.getSortedVersions(packageName); @@ -66,10 +72,6 @@ ConstraintSolver.PackagesResolver.prototype._loadPackageInfo = function ( var unibuilds = {}; - // XXX in theory there might be different archs but in practice they are - // always "os", "web.browser" and "web.cordova". Fix this once we - // actually have different archs used. - var allArchs = ["os", "web.browser", "web.cordova"]; _.each(allArchs, function (arch) { var unitName = packageName + "#" + arch; unibuilds[unitName] = new ConstraintSolver.UnitVersion( @@ -121,6 +123,16 @@ ConstraintSolver.PackagesResolver.prototype._loadPackageInfo = function ( }); }); }); + + // We need to be aware of the earliestCompatibleVersion values for any + // packages that are overridden by local packages, in order to evaluate + // 'compatible-with' constraints that name that version. + _.each(self.catalog.getForgottenECVs(packageName), function (ecv, version) { + _.each(allArchs, function (arch) { + var unitName = packageName + '#' + arch; + self.resolver.addExtraECV(unitName, version, ecv); + }); + }); }; // dependencies - an array of string names of packages (not slices) diff --git a/packages/constraint-solver/constraints-list.js b/packages/constraint-solver/constraints-list.js index 157746820f..3a62241743 100644 --- a/packages/constraint-solver/constraints-list.js +++ b/packages/constraint-solver/constraints-list.js @@ -213,15 +213,16 @@ ConstraintSolver.ConstraintsList.prototype.edgeMatchingVersionsFor = function ( } if (c.type === "compatible-with") { - var uv = resolver.getUnitVersion(packageName, c.version); - if (uv) { + var thisECV = resolver.getEarliestCompatibleVersion( + packageName, c.version); + if (thisECV) { if (earliestCompatibleVersion && - earliestCompatibleVersion !== uv.earliestCompatibleVersion) { + earliestCompatibleVersion !== thisECV) { // Two constraints name versions with different ECV. Nothing can be // compatible with both! impossible = true; } else if (! earliestCompatibleVersion) { - earliestCompatibleVersion = uv.earliestCompatibleVersion; + earliestCompatibleVersion = thisECV; } // else the ECV matches, which is great. } else { diff --git a/packages/constraint-solver/resolver.js b/packages/constraint-solver/resolver.js index f7c574ab7b..2926c747a1 100644 --- a/packages/constraint-solver/resolver.js +++ b/packages/constraint-solver/resolver.js @@ -34,6 +34,16 @@ ConstraintSolver.Resolver = function () { // Refs to all constraints. Mapping String -> instance self._constraints = {}; + + // Let's say that we that package P is available from source at version X.Y.Z. + // Then that's the only version that can actually be chosen by the resolver, + // and so it's the only version included as a UnitVersion. But let's say + // another unit depends on it with a 'compatible-with' dependency "@A.B.C". We + // need to be able to figure out the earliestCompatibleVersion of A.B.C, even + // though A.B.C is not a valid (selectable) UnitVersion. We store them here. + // + // Maps String unitName -> String version -> String earliestCompatibleVersion + self._extraECVs = {}; }; ConstraintSolver.Resolver.prototype.addUnitVersion = function (unitVersion) { @@ -78,6 +88,36 @@ ConstraintSolver.Resolver.prototype.getConstraint = new ConstraintSolver.Constraint(name, versionConstraint); }; +ConstraintSolver.Resolver.prototype.addExtraECV = function ( + unitName, version, earliestCompatibleVersion) { + var self = this; + check(unitName, String); + check(version, String); + check(earliestCompatibleVersion, String); + + if (!_.has(self._extraECVs, unitName)) { + self._extraECVs[unitName] = {}; + } + self._extraECVs[unitName][version] = earliestCompatibleVersion; +}; + +ConstraintSolver.Resolver.prototype.getEarliestCompatibleVersion = function ( + unitName, version) { + var self = this; + + var uv = self.getUnitVersion(unitName, version); + if (uv) { + return uv.earliestCompatibleVersion; + } + if (!_.has(self._extraECVs, unitName)) { + return null; + } + if (!_.has(self._extraECVs[unitName], version)) { + return null; + } + return self._extraECVs[unitName][version]; +}; + // options: Object: // - costFunction: function (state, options) - given a state evaluates its cost // - estimateCostFunction: function (state) - given a state, evaluates the @@ -552,19 +592,18 @@ ConstraintSolver.Constraint.prototype.isSatisfied = function (candidateUV, if (self.type === "at-least") return true; - var myUV = resolver.getUnitVersion(self.name, self.version); + var myECV = resolver.getEarliestCompatibleVersion(self.name, self.version); // If the constraint is "@1.2.3" and 1.2.3 doesn't exist, then nothing can // match. This is because we don't know the ECV (compatibility class) of // 1.2.3! - if (!myUV) + if (!myECV) return false; // To be compatible, the two versions must have the same // earliestCompatibleVersion. If the earliestCompatibleVersions haven't been // overridden from their default, this means that the two versions have the // same major version number. - return myUV.earliestCompatibleVersion === - candidateUV.earliestCompatibleVersion; + return myECV === candidateUV.earliestCompatibleVersion; }; // Returns any unit version satisfying the constraint in the resolver diff --git a/tools/catalog.js b/tools/catalog.js index c44c1275ac..5781654b05 100644 --- a/tools/catalog.js +++ b/tools/catalog.js @@ -192,6 +192,10 @@ var CompleteCatalog = function () { // XXX: use a future in the future maybe self.refreshing = false; + // See the documentation of the _extraECVs field in ConstraintSolver.Resolver. + // Maps packageName -> version -> its ECV + self.forgottenECVs = {}; + // We inherit from the protolog class, since we are a catalog. BaseCatalog.call(self); }; @@ -234,6 +238,12 @@ _.extend(CompleteCatalog.prototype, { self.refresh(); }, + reset: function () { + var self = this; + BaseCatalog.prototype.reset.call(self); + self.forgottenECVs = {}; + }, + // Given a set of constraints, returns a det of dependencies that satisfy the // constraint. // @@ -415,6 +425,11 @@ _.extend(CompleteCatalog.prototype, { _.extend(self.effectiveLocalPackages, self.localPackages); }, + getForgottenECVs: function (packageName) { + var self = this; + return self.forgottenECVs[packageName]; + }, + // Add all packages in self.effectiveLocalPackages to the catalog, // first removing any existing packages that have the same name. // @@ -430,7 +445,10 @@ _.extend(CompleteCatalog.prototype, { _.each(self.effectiveLocalPackages, function (dir, packageName) { if (!_.has(self.versions, packageName)) return; + self.forgottenECVs[packageName] = {}; _.each(self.versions[packageName], function (record) { + self.forgottenECVs[packageName][record.version] = + record.earliestCompatibleVersion; removedVersionIds[record._id] = true; }); delete self.versions[packageName];