mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
218 lines
7.4 KiB
JavaScript
218 lines
7.4 KiB
JavaScript
var fs = require("fs");
|
|
var path = require("path");
|
|
var files = require("./files.js");
|
|
var archinfo = require("./archinfo.js");
|
|
var compiler = require("./compiler.js");
|
|
var buildmessage = require("./buildmessage.js");
|
|
var PackageSource = require("./package-source.js");
|
|
var _ = require('underscore');
|
|
var unipackage = require("./unipackage.js");
|
|
|
|
var packageCache = exports;
|
|
|
|
var PackageCache = function () {
|
|
var self = this;
|
|
|
|
// both map from package load path to:
|
|
// - pkg: cached Unipackage object
|
|
// - sourceDir: directory that contained its source code, or null
|
|
// - buildDir: directory from which the built package was loaded
|
|
self.softReloadCache = {};
|
|
self.loadedPackages = {};
|
|
};
|
|
|
|
_.extend(PackageCache.prototype, {
|
|
|
|
|
|
// Force reload of changed packages. See description at loadPackageAtPath().
|
|
//
|
|
// If soft is false, the default, the cache is totally flushed and
|
|
// all packages are reloaded unconditionally.
|
|
//
|
|
// If soft is true, then built packages without dependency info (such as those
|
|
// from the warehouse) aren't reloaded (there's no way to rebuild them, after
|
|
// all), and if we loaded a built package with dependency info, we won't
|
|
// reload it if the dependency info says that its source files are still up to
|
|
// date. The ideas is that assuming the user is "following the rules", this
|
|
// will correctly reload any changed packages while in most cases avoiding
|
|
// nearly all reloading.
|
|
refresh: function (soft) {
|
|
var self = this;
|
|
soft = soft || false;
|
|
|
|
self.softReloadCache = soft ? self.loadedPackages : {};
|
|
self.loadedPackages = {};
|
|
},
|
|
|
|
// Adds a prebuilt package to the package cache.
|
|
//
|
|
// - name: package name
|
|
// - loadPath: path of the source to the package
|
|
// - unipackage (prebuilt package)
|
|
cachePackageAtPath : function (name, loadPath, unip) {
|
|
var self = this;
|
|
var key = name + "@" + loadPath;
|
|
var buildDir = path.join(loadPath, '.build.'+ name);
|
|
|
|
self.loadedPackages[key] = {
|
|
pkg: unip,
|
|
sourceDir: loadPath,
|
|
buildDir: buildDir
|
|
};
|
|
},
|
|
|
|
// Given a path to a package on disk, retrieve a Package
|
|
// object.
|
|
//
|
|
// loadPackageAtPath() caches the packages it returns, meaning if
|
|
// you call loadPackageAtPath('/foo/bar') and later /foo/bar changes
|
|
// on disk, you won't see the changes. To flush the package cache
|
|
// and force all of the packages to be reloaded the next time
|
|
// loadPackageAtPath() is called for them, see refresh().
|
|
loadPackageAtPath: function (name, loadPath) {
|
|
var self = this;
|
|
|
|
// We need to build and load both the test and normal package, which,
|
|
// frequently means 2 packages per directory/loadPath. Rather than
|
|
// special-casing it with some sort of a test flag, we can just
|
|
// differentiate by name (and extend the interface if it ever comes up).
|
|
var key = name + "@" + loadPath;
|
|
if (_.has(self.loadedPackages, key)) {
|
|
return self.loadedPackages[key].pkg;
|
|
}
|
|
|
|
// See if we can reuse a package that we have cached from before
|
|
// the last soft refresh.
|
|
// XXX XXX this is not very efficient. refactor
|
|
if (_.has(self.softReloadCache, key)) {
|
|
var entry = self.softReloadCache[key];
|
|
|
|
// Either we will decide that the cache is invalid, or we will "upgrade"
|
|
// this entry into loadedPackages. Either way, it's not needed in
|
|
// softReloadCache any more.
|
|
delete self.softReloadCache[key];
|
|
|
|
var isUpToDate;
|
|
var unip;
|
|
if (fs.existsSync(path.join(loadPath, 'unipackage.json'))) {
|
|
// We don't even have the source to this package, so it must
|
|
// be up to date.
|
|
isUpToDate = true;
|
|
} else {
|
|
|
|
buildmessage.enterJob({
|
|
title: "initializing package `" + name + "`",
|
|
rootPath: loadPath
|
|
}, function () {
|
|
var packageSource = new PackageSource;
|
|
packageSource.initFromPackageDir(name, loadPath);
|
|
unip = new unipackage.Unipackage;
|
|
unip.initFromPath(name, entry.buildDir);
|
|
isUpToDate = compiler.checkUpToDate(packageSource, entry.pkg);
|
|
});
|
|
}
|
|
if (isUpToDate) {
|
|
// Cache it
|
|
self.loadedPackages[key] = entry;
|
|
return entry.pkg;
|
|
}
|
|
}
|
|
|
|
// Load package from disk
|
|
|
|
// Does loadPath point directly at a unipackage (rather than a
|
|
// source tree?)
|
|
if (fs.existsSync(path.join(loadPath, 'unipackage.json'))) {
|
|
unip = new unipackage.Unipackage;
|
|
|
|
unip.initFromPath(name, loadPath);
|
|
self.loadedPackages[key] = {
|
|
pkg: unip,
|
|
sourceDir: null,
|
|
buildDir: loadPath
|
|
};
|
|
return unip;
|
|
};
|
|
|
|
// It's a source tree. Load it.
|
|
var packageSource = new PackageSource;
|
|
buildmessage.enterJob({
|
|
title: "initializing package `" + name + "`",
|
|
rootPath: loadPath
|
|
}, function () {
|
|
packageSource.initFromPackageDir(name, loadPath);
|
|
});
|
|
// Does it have an up-to-date build?
|
|
var buildDir = path.join(loadPath, '.build.'+ name);
|
|
if (fs.existsSync(buildDir)) {
|
|
unip = new unipackage.Unipackage;
|
|
var maybeUpToDate = true;
|
|
try {
|
|
unip.initFromPath(name, buildDir);
|
|
} catch (e) {
|
|
if (!(e instanceof unipackage.OldUnipackageFormatError))
|
|
throw e;
|
|
maybeUpToDate = false;
|
|
}
|
|
if (maybeUpToDate && compiler.checkUpToDate(packageSource, unip)) {
|
|
self.loadedPackages[key] = { pkg: unip,
|
|
sourceDir: loadPath,
|
|
buildDir: buildDir
|
|
};
|
|
return unip;
|
|
}
|
|
}
|
|
|
|
// Either we didn't have a build, or it was out of date, or the
|
|
// caller wanted us to rebuild no matter what. Build the package.
|
|
return buildmessage.enterJob({
|
|
title: "building package `" + name + "`",
|
|
rootPath: loadPath
|
|
}, function () {
|
|
// We used to take great care to first put a
|
|
// loaded-but-not-built package object (the equivalent of a
|
|
// PackageSource) into self.loadedPackages before calling
|
|
// build() as a hacky way of dealing with build-time
|
|
// dependencies.
|
|
//
|
|
// We don't do that anymore and at the moment, we rely on catalog to
|
|
// initalize ahead of us and swoop in and build all of the local packages
|
|
// informed by a topological sort
|
|
var unip = compiler.compile(packageSource).unipackage;
|
|
self.loadedPackages[key] = {
|
|
pkg: unip,
|
|
sourceDir: null,
|
|
buildDir: buildDir
|
|
};
|
|
|
|
if (! buildmessage.jobHasMessages()) {
|
|
// Save it, for a fast load next time
|
|
try {
|
|
files.addToGitignore(loadPath, '.build*');
|
|
unip.saveToPath(buildDir, { buildOfPath: loadPath });
|
|
} catch (e) {
|
|
// If we can't write to this directory, we don't get to cache our
|
|
// output, but otherwise life is good.
|
|
if (!(e && (e.code === 'EACCES' || e.code === 'EPERM')))
|
|
throw e;
|
|
}
|
|
}
|
|
return unip;
|
|
});
|
|
},
|
|
|
|
// Get a package that represents an app. (ignoreFiles is optional
|
|
// and if given, it should be an array of regexps for filenames to
|
|
// ignore when scanning for source files.)
|
|
loadAppAtPath: function (appDir, ignoreFiles) {
|
|
var self = this;
|
|
|
|
var packageSource = new PackageSource;
|
|
packageSource.initFromAppDir(appDir, ignoreFiles);
|
|
return compiler.compile(packageSource).unipackage;
|
|
}
|
|
|
|
});
|
|
|
|
packageCache.packageCache = new PackageCache;
|