mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
308 lines
11 KiB
JavaScript
308 lines
11 KiB
JavaScript
var _ = require('underscore');
|
|
|
|
var buildmessage = require('./buildmessage.js');
|
|
var compiler = require('./compiler.js');
|
|
var files = require('./files.js');
|
|
var isopackModule = require('./isopack.js');
|
|
var utils = require('./utils.js');
|
|
var watch = require('./watch.js');
|
|
var colonConverter = require("./colon-converter.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;
|
|
|
|
// This is a bit of a hack, but basically: we really don't want to spend time
|
|
// building web.cordova unibuilds in a project that doesn't have any Cordova
|
|
// platforms. (Note that we need to be careful with 'meteor publish' to still
|
|
// publish a web.cordova unibuild!)
|
|
self._includeCordovaUnibuild = !! options.includeCordovaUnibuild;
|
|
|
|
// 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.
|
|
self._isopacks = {};
|
|
|
|
self._noLineNumbers = !! options.noLineNumbers;
|
|
|
|
self.allLoadedLocalPackagesWatchSet = new watch.WatchSet;
|
|
};
|
|
|
|
_.extend(exports.IsopackCache.prototype, {
|
|
buildLocalPackages: function (rootPackageNames) {
|
|
var self = this;
|
|
buildmessage.assertInCapture();
|
|
|
|
if (self.cacheDir)
|
|
files.mkdir_p(self.cacheDir);
|
|
|
|
var onStack = {};
|
|
if (rootPackageNames) {
|
|
_.each(rootPackageNames, function (name) {
|
|
self._ensurePackageLoaded(name, onStack);
|
|
});
|
|
} else {
|
|
self._packageMap.eachPackage(function (name, packageInfo) {
|
|
self._ensurePackageLoaded(name, onStack);
|
|
});
|
|
}
|
|
},
|
|
|
|
wipeCachedPackages: function (packages) {
|
|
var self = this;
|
|
// If we're not saving things to disk, there's nothing to wipe!
|
|
if (! self.cacheDir)
|
|
return;
|
|
if (packages) {
|
|
// Wipe specific packages.
|
|
_.each(packages, function (packageName) {
|
|
files.rm_recursive(self._isopackDir(packageName));
|
|
});
|
|
} else {
|
|
// Wipe all packages.
|
|
files.rm_recursive(self.cacheDir);
|
|
}
|
|
},
|
|
|
|
// Returns the isopack (already loaded in memory) for a given name. It is an
|
|
// error to call this if it's not already loaded! So it should only be called
|
|
// after buildLocalPackages has returned, or in the process of building a
|
|
// package whose dependencies have all already been built.
|
|
getIsopack: function (name) {
|
|
var self = this;
|
|
if (! _.has(self._isopacks, name))
|
|
throw Error("isopack " + name + " not yet loaded?");
|
|
return self._isopacks[name];
|
|
},
|
|
|
|
eachBuiltIsopack: function (iterator) {
|
|
var self = this;
|
|
_.each(self._isopacks, function (isopack, packageName) {
|
|
iterator(packageName, isopack);
|
|
});
|
|
},
|
|
|
|
_ensurePackageLoaded: function (name, onStack) {
|
|
var self = this;
|
|
buildmessage.assertInCapture();
|
|
if (_.has(self._isopacks, name))
|
|
return;
|
|
|
|
var ensureLoaded = function (depName) {
|
|
if (_.has(onStack, depName)) {
|
|
buildmessage.error("circular dependency between packages " +
|
|
name + " and " + depName);
|
|
// recover by not enforcing one of the dependencies
|
|
return;
|
|
}
|
|
onStack[depName] = true;
|
|
self._ensurePackageLoaded(depName, onStack);
|
|
delete onStack[depName];
|
|
};
|
|
|
|
var packageInfo = self._packageMap.getInfo(name);
|
|
if (! packageInfo)
|
|
throw Error("Depend on unknown package " + name + "?");
|
|
var previousIsopack = null;
|
|
if (self._previousIsopackCache &&
|
|
_.has(self._previousIsopackCache._isopacks, name)) {
|
|
var previousInfo = self._previousIsopackCache._packageMap.getInfo(name);
|
|
if ((packageInfo.kind === 'versioned' &&
|
|
previousInfo.kind === 'versioned' &&
|
|
packageInfo.version === previousInfo.version) ||
|
|
(packageInfo.kind === 'local' &&
|
|
previousInfo.kind === 'local' &&
|
|
(packageInfo.packageSource.sourceRoot ===
|
|
previousInfo.packageSource.sourceRoot))) {
|
|
previousIsopack = self._previousIsopackCache._isopacks[name];
|
|
}
|
|
}
|
|
|
|
if (packageInfo.kind === 'local') {
|
|
var packageNames =
|
|
packageInfo.packageSource.getPackagesToLoadFirst(self._packageMap);
|
|
buildmessage.enterJob("preparing to build package " + name, function () {
|
|
_.each(packageNames, function (depName) {
|
|
ensureLoaded(depName);
|
|
});
|
|
// If we failed to load something that this package depends on, don't
|
|
// load it.
|
|
if (buildmessage.jobHasMessages())
|
|
return;
|
|
self._loadLocalPackage(name, packageInfo, previousIsopack);
|
|
});
|
|
} else if (packageInfo.kind === 'versioned') {
|
|
// We don't have to build this package, and we don't have to build its
|
|
// dependencies either! Just load it from disk.
|
|
|
|
if (!self._tropohouse) {
|
|
throw Error("Can't load versioned packages without a tropohouse!");
|
|
}
|
|
|
|
var isopack = null, packagesToLoad = [];
|
|
if (previousIsopack) {
|
|
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);
|
|
var Isopack = isopackModule.Isopack;
|
|
isopack = new 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;
|
|
// 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.
|
|
_.each(packagesToLoad, function (packageToLoad) {
|
|
ensureLoaded(packageToLoad);
|
|
});
|
|
} else {
|
|
throw Error("unknown packageInfo kind?");
|
|
}
|
|
},
|
|
|
|
_loadLocalPackage: function (name, packageInfo, previousIsopack) {
|
|
var self = this;
|
|
buildmessage.assertInCapture();
|
|
buildmessage.enterJob("building package " + name, function () {
|
|
var isopack;
|
|
if (previousIsopack && self._checkUpToDatePreloaded(previousIsopack)) {
|
|
isopack = previousIsopack;
|
|
} else {
|
|
// Do we have an up-to-date package on disk?
|
|
var isopackBuildInfoJson = self.cacheDir && files.readJSONOrNull(
|
|
self._isopackBuildInfoPath(name));
|
|
var upToDate = self._checkUpToDate(isopackBuildInfoJson);
|
|
|
|
if (upToDate) {
|
|
var Isopack = isopackModule.Isopack;
|
|
isopack = new Isopack();
|
|
isopack.initFromPath(name, self._isopackDir(name), {
|
|
isopackBuildInfoJson: isopackBuildInfoJson
|
|
});
|
|
} else {
|
|
// Nope! Compile it again.
|
|
isopack = compiler.compile(packageInfo.packageSource, {
|
|
packageMap: self._packageMap,
|
|
isopackCache: self,
|
|
noLineNumbers: self._noLineNumbers,
|
|
includeCordovaUnibuild: self._includeCordovaUnibuild,
|
|
includePluginProviderPackageMap: true
|
|
});
|
|
// Accept the compiler's result, even if there were errors (since it
|
|
// at least will have a useful WatchSet and will allow us to keep
|
|
// going and compile other packages that depend on this one).
|
|
if (self.cacheDir && ! buildmessage.jobHasMessages()) {
|
|
// Save to disk, for next time!
|
|
isopack.saveToPath(self._isopackDir(name), {
|
|
includeIsopackBuildInfo: true
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
self.allLoadedLocalPackagesWatchSet.merge(isopack.getMergedWatchSet());
|
|
self._isopacks[name] = isopack;
|
|
});
|
|
},
|
|
|
|
_checkUpToDate: function (isopackBuildInfoJson) {
|
|
var self = this;
|
|
// If there isn't an isopack-buildinfo.json file, then we definitely aren't
|
|
// up to date!
|
|
if (! isopackBuildInfoJson)
|
|
return false;
|
|
|
|
// If we include Cordova but this Isopack doesn't, or via versa, then we're
|
|
// not up to date.
|
|
if (self._includeCordovaUnibuild !==
|
|
isopackBuildInfoJson.includeCordovaUnibuild) {
|
|
return false;
|
|
}
|
|
|
|
// If any of the direct dependencies changed their version or location, we
|
|
// aren't up to date.
|
|
if (!self._packageMap.isSupersetOfJSON(
|
|
isopackBuildInfoJson.pluginProviderPackageMap)) {
|
|
return false;
|
|
}
|
|
// Merge in the watchsets for all unibuilds and plugins in the package, then
|
|
// check it once.
|
|
var watchSet = watch.WatchSet.fromJSON(
|
|
isopackBuildInfoJson.pluginDependencies);
|
|
|
|
_.each(isopackBuildInfoJson.unibuildDependencies, function (deps) {
|
|
watchSet.merge(watch.WatchSet.fromJSON(deps));
|
|
});
|
|
return watch.isUpToDate(watchSet);
|
|
},
|
|
|
|
_checkUpToDatePreloaded: function (previousIsopack) {
|
|
var self = this;
|
|
|
|
// If we include Cordova but this Isopack doesn't, or via versa, then we're
|
|
// not up to date.
|
|
if (self._includeCordovaUnibuild !== previousIsopack.hasCordovaUnibuild()) {
|
|
return false;
|
|
}
|
|
|
|
// If any of the direct dependencies changed their version or location, we
|
|
// aren't up to date.
|
|
if (!self._packageMap.isSupersetOfJSON(
|
|
previousIsopack.pluginProviderPackageMap)) {
|
|
return false;
|
|
}
|
|
// Merge in the watchsets for all unibuilds and plugins in the package, then
|
|
// check it once.
|
|
var watchSet = previousIsopack.getMergedWatchSet();
|
|
return watch.isUpToDate(watchSet);
|
|
},
|
|
|
|
_isopackDir: function (packageName) {
|
|
var self = this;
|
|
return files.pathJoin(self.cacheDir, colonConverter.convert(packageName));
|
|
},
|
|
|
|
_isopackBuildInfoPath: function (packageName) {
|
|
var self = this;
|
|
return files.pathJoin(
|
|
self._isopackDir(packageName), 'isopack-buildinfo.json');
|
|
},
|
|
|
|
forgetPreviousIsopackCache: function () {
|
|
var self = this;
|
|
self._previousIsopackCache = null;
|
|
}
|
|
});
|