From a937aa606d777468e1ed6fea600b66914269c6c8 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 1 Dec 2014 19:48:06 -0800 Subject: [PATCH] Soft refresh, versioned packages only First half of #3213. --- tools/isopack-cache.js | 67 ++++++++++++++++++++++++++++++---------- tools/project-context.js | 19 ++++++++---- tools/run-app.js | 4 +-- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/tools/isopack-cache.js b/tools/isopack-cache.js index 65efb4d14ca..ad60f9f19d4 100644 --- a/tools/isopack-cache.js +++ b/tools/isopack-cache.js @@ -11,17 +11,31 @@ var watch = require('./watch.js'); exports.IsopackCache = function (options) { var self = this; options = options || {}; + // cacheDir may be null; in this case, we just don't ever save things to disk. self.cacheDir = options.cacheDir; + // Defines the versions of packages that we build. Must be set. self._packageMap = options.packageMap; + // tropohouse may be null; in this case, we can't load versioned packages. // eg, for building isopackets. self._tropohouse = options.tropohouse; + + // If provided, this is another IsopackCache for the same cache dir; when + // loading Isopacks, if they are definitely unchanged we can load the + // in-memory objects from this cache instead of recompiling. + self._previousIsopackCache = options.previousIsopackCache; + if (self._previousIsopackCache && + self._previousIsopackCache.cacheDir !== self.cacheDir) { + throw Error("previousIsopackCache has different cacheDir!"); + } + // Map from package name to {isopack, pluginProviderPackageMap} object. // pluginProviderPackageMap is null for isopacks that are loaded from the // tropohouse, and otherwise is a PackageMap object listing self._isopacks = {}; + self.allLoadedLocalPackagesWatchSet = new watch.WatchSet; }; @@ -124,24 +138,37 @@ _.extend(exports.IsopackCache.prototype, { throw Error("Can't load versioned packages without a tropohouse!"); } - var packagesToLoad; - // Load the isopack from disk. - buildmessage.enterJob( - "loading package " + name + "@" + packageInfo.version, - function () { - var isopackPath = self._tropohouse.packagePath( - name, packageInfo.version); - var isopack = new isopackModule.Isopack(); - isopack.initFromPath(name, isopackPath); - self._isopacks[name] = { - isopack: isopack, - pluginProviderPackageMap: null - }; - if (buildmessage.jobHasMessages()) - return; + var isopack = null, packagesToLoad = []; + if (self._previousIsopackCache + && _.has(self._previousIsopackCache._isopacks, name)) { + var previousIsopack = self._previousIsopackCache._isopacks[name]; + if (previousIsopack.version === packageInfo.version) { + isopack = previousIsopack; packagesToLoad = isopack.getStrongOrderedUsedAndImpliedPackages(); - }); + } + } + if (! isopack) { + // Load the isopack from disk. + buildmessage.enterJob( + "loading package " + name + "@" + packageInfo.version, + function () { + var isopackPath = self._tropohouse.packagePath( + name, packageInfo.version); + isopack = new isopackModule.Isopack(); + isopack.initFromPath(name, isopackPath); + // If loading the isopack fails, then we don't need to look for more + // packages to load, but we should still recover by putting it in + // self._isopacks. + if (buildmessage.jobHasMessages()) + return; + packagesToLoad = isopack.getStrongOrderedUsedAndImpliedPackages(); + }); + } + self._isopacks[name] = { + isopack: isopack, + pluginProviderPackageMap: null + }; // Also load its dependencies. This is so that if this package is being // built as part of a plugin, all the transitive dependencies of the // plugin are loaded. @@ -157,6 +184,9 @@ _.extend(exports.IsopackCache.prototype, { var self = this; buildmessage.assertInCapture(); buildmessage.enterJob("building package " + name, function () { + // XXX #3213 use _previousIsopackCache here too (which involves moving + // pluginProviderPackageMap into the Isopack object) + // Do we have an up-to-date package on disk? var isopackBuildInfoJson = self.cacheDir && files.readJSONOrNull( self._isopackBuildInfoPath(name)); @@ -231,5 +261,10 @@ _.extend(exports.IsopackCache.prototype, { _isopackBuildInfoPath: function (packageName) { var self = this; return path.join(self._isopackDir(packageName), 'isopack-buildinfo.json'); + }, + + forgetPreviousIsopackCache: function () { + var self = this; + self._previousIsopackCache = null; } }); diff --git a/tools/project-context.js b/tools/project-context.js index d5ef5134be9..13077865c81 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -55,11 +55,13 @@ var STAGE = { }; _.extend(exports.ProjectContext.prototype, { - reset: function (moreOptions) { + reset: function (moreOptions, resetOptions) { var self = this; // Allow overriding some options until the next call to reset; used by // 'meteor update' code to try various values of releaseForConstraints. var options = _.extend({}, self.originalOptions, moreOptions); + // This is options that are actually directed at reset itself. + resetOptions = resetOptions || {}; self.projectDir = options.projectDir; self.tropohouse = options.tropohouse || tropohouse.default; @@ -137,11 +139,15 @@ _.extend(exports.ProjectContext.prototype, { self.packageMap = null; self.packageMapDelta = null; + if (resetOptions.softRefreshIsopacks && self.isopackCache) { + // Make sure we only hold on to one old isopack cache, not a linked list + // of all of them. + self.isopackCache.forgetPreviousIsopackCache(); + self._previousIsopackCache = self.isopackCache; + } else { + self._previousIsopackCache = null; + } // Initialized by _buildLocalPackages. - // XXX #SoftRefresh Perhaps save the old IsopackCache and try - // to reuse unchanged in-memory Isopack objects? - // Be careful to only save one old IsopackCache, not a - // linked list of them. #3213 self.isopackCache = null; self._completedStage = STAGE.INITIAL; @@ -565,7 +571,8 @@ _.extend(exports.ProjectContext.prototype, { self.isopackCache = new isopackCacheModule.IsopackCache({ packageMap: self.packageMap, cacheDir: self.getProjectLocalDirectory('isopacks'), - tropohouse: self.tropohouse + tropohouse: self.tropohouse, + previousIsopackCache: self._previousIsopackCache }); if (self._forceRebuildPackages) { diff --git a/tools/run-app.js b/tools/run-app.js index 241364fd53a..0cef6fe42b9 100644 --- a/tools/run-app.js +++ b/tools/run-app.js @@ -405,14 +405,12 @@ _.extend(AppRunner.prototype, { // If this isn't the first time we've run, we need to reset the project // context since everything we have cached may have changed. // XXX We can try to be a little less conservative here: - // - Keep around some in-memory Isopack objects and validate them - // by their buildinfo (we used to call this #SoftRefresh). // - Don't re-build the whole local catalog if we know which local // packages have changed. (This one might be a little trickier due // to how the WatchSets are laid out. Might be possible to avoid // re-building the local catalog at all if packages didn't change // at all, though.) - self.projectContext.reset(); + self.projectContext.reset({}, { softRefreshIsopacks: true }); var messages = buildmessage.capture(function () { self.projectContext.readProjectMetadata(); });