c9-core/plugins/c9.ide.run.build/build.js

430 wiersze
17 KiB
JavaScript

define(function(require, module, exports) {
main.consumes = ["Plugin", "settings", "fs", "c9", "preferences", "run", "util"];
main.provides = ["build"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var settings = imports.settings;
var prefs = imports.preferences;
var run = imports.run;
var fs = imports.fs;
var c9 = imports.c9;
var util = imports.util;
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
var builders = util.cloneObject(options.builders);
var processes = [];
var base = options.base;
var builderPath = options.builderPath || "/.c9/builders";
var loaded = false;
function load() {
if (loaded) return false;
loaded = true;
// Settings
settings.on("read", function(e) {
// Defaults
settings.setDefaults("project/build", [
["path", builderPath]
]);
// @todo Could consider adding a watcher to ~/.c9/runners
listBuilders(function(err, files) {
files.forEach(function(file) {
if (!builders[file]) {
getBuilder(file, false, function(err, builder) {
if (!err)
builders[file] = builder;
});
}
});
});
}, plugin);
settings.on("write", function(e) {
}, plugin);
// Preferences
prefs.add({
"Project": {
"Build": {
position: 400,
"Builder Path in Workspace": {
type: "textbox",
path: "project/build/@path",
position: 1000
}
}
}
}, plugin);
prefs.add({
"Run": {
position: 600,
"Build": {
position: 400,
"Automatically Build Supported Files": {
type: "checkbox",
path: "user/build/@autobuild",
position: 100
}
}
}
}, plugin);
// Check after state.change
c9.on("stateChange", function(e) {
}, plugin);
}
/***** Methods *****/
function addBuilder(name, builder, plugin) {
builders[name] = builder;
plugin.addOther(function() { delete builders[name]; });
}
function listBuilders(callback) {
var _builders = Object.keys(builders || {});
fs.exists(settings.get("project/build/@path"), function(exists) {
if (!exists)
return callback(null, _builders);
fs.readdir(settings.get("project/build/@path"), function(err, files) {
// if (err && err.code == "ENOENT")
// return callback(err);
if (files) {
files.forEach(function(file) {
var name = file.name.match(/(.*)\.build$/);
if (!name) {
if (file.name.match(/\.\w+$/))
return console.warn("Builder ignored, doesn't have .build extension: " + file.name);
name = [0, file.name];
}
if (_builders.indexOf(name[1]) < 0)
_builders.push(name[1]);
});
}
callback(null, _builders);
});
});
}
function detectBuilder(options, callback) {
listBuilders(function(err, names) {
if (err) return callback(err);
var count = 0;
names.forEach(function(name) {
if (!builders[name]) {
count++;
getBuilder(name, false, function() {
if (--count === 0)
done();
});
}
});
if (count === 0) done();
});
function done() {
for (var name in builders) {
var builder = builders[name];
if (run.matchSelector(builder.selector, options.path))
return callback(null, builder);
}
var err = new Error("Could not find Builder");
err.code = "EBUILDERNOTFOUND";
callback(err);
}
}
function getBuilder(name, refresh, callback) {
var path = settings.get("project/build/@path") + "/" + name + ".build";
fs.exists(path, function test(exists) {
if (!exists) {
if (options.builders[name])
return callback(null, options.builders[name]);
if (/\.build$/.test(path)) {
path = settings.get("project/build/@path") + "/" + name;
return fs.exists(path, test);
}
callback("Builder does not exist");
}
else if (builders[name] && !refresh && (!exists || options.builders[name] === builders[name])) {
callback(null, builders[name]);
}
else {
fs.readFile(path, "utf8", function(err, data) {
if (err)
return callback(err);
var builder = util.safeParseJson(data, callback);
if (!builder) return;
builder.caption = name.replace(/\.build$/, "");
builders[builder.caption] = builder;
callback(null, builder);
});
}
});
}
function build(builder, options, name, callback) {
options.builder = true;
if (builder == "auto") {
return detectBuilder(options, function(err, detected) {
if (err) return callback(err);
build(detected, options, name, callback);
});
}
var proc = run.run(builder, options, name, callback);
processes.push(proc);
var event = { process: proc };
proc.on("starting", function() { emit("starting", event); });
proc.on("started", function() { emit("started", event); });
proc.on("stopping", function() { emit("stopping", event); });
proc.on("stopped", function() {
emit("stopped", event);
processes.remove(proc);
});
return proc;
}
/***** Lifecycle *****/
plugin.on("load", function() {
load();
});
plugin.on("enable", function() {
});
plugin.on("disable", function() {
});
plugin.on("unload", function() {
loaded = false;
});
/***** Register and define API *****/
/**
* Builds arbitrary code from within Cloud9 based on a builder.
*
* *NB.: The build plugin works almost identical to the run plugin. The
* builder format is a subset of the runner format. The major difference
* is that builders are used to build code into executables or
* deployables and that the runner is used to run executables.*
*
* Example:
*
* build.getBuilder("coffee", false, function(err, builder) {
* if (err) throw err.message;
*
* var process = build.build(builder, {
* path: "/helloworld.coffee"
* }, function(err, pid) {
* if (err) throw err.message;
*
* console.log("The PID is ", pid);
* });
* });
*
* You can also ask for auto-detection of the builder based on the file
* extension:
*
* var process = build.build("auto", {
* path: "/helloworld.coffee"
* }, function(err, pid) {
* if (err) throw err.message;
*
* console.log("The PID is ", pid);
* });
*
* A builder is a simple struct that describes how to build a
* certain subset of files. For instance a builder describing how to run
* Coffeescript files looks like this:
*
* {
* "caption" : "Coffee",
* "cmd": [coffee, "-c", "$file"],
* "selector": "source.coffee"
* }
*
* The concept of builders is based on the
* [Sublime Text(tm) Build Systems](http://docs.sublimetext.info/en/sublime-text-3/file_processing/build_systems.html),
* and is compatible with that format. There are a several
* built-in builders, and external plugins can add new builders as well.
* Users can also add builders to their .c9/builders directory in
* the workspace. We recommend users to commit these builders to their
* repository.
*
* The {@link run run plugin} also uses a compatible
* format for the cloud9 runners.
*
* It is possible to combine builders and runners, therefore it is
* often not needed to describe the build and run step in the same
* definition.
*
* A process is always started in a [TMUX](http://en.wikipedia.org/wiki/Tmux)
* session. TMUX is a PTY multi-plexer which has several advantages;
* multiple clients can connect to the same session and the sessions are
* kept even if no user is connected.
*
* You can connect an {@link output} pane to the started process to
* see the output of your running process. The name passed to
* {@link build#build} should be the same as the name of the output pane
* you open:
*
* tabManager.open({
* editorType : "output",
* active : true,
* document : {
* title : "My Process Name",
* output : {
* id : "name_of_process"
* }
* }
* }, function(){});
*
* Note that by default the process name is "output" and is shown in the
* default output panel (available via the View menu).
*
* @singleton
*/
plugin.freezePublicAPI({
/**
* Indicates the process is being killed. To be tested against
* the `running` property.
* @property {-1} STOPPING
*/
STOPPING: run.STOPPING,
/**
* Indicates the process is not running. To be tested against
* the `running` property.
* @property {0} STOPPED
*/
STOPPED: run.STOPPED,
/**
* Indicates the process is getting started. To be tested against
* the `running` property.
* @property {1} STARTING
*/
STARTING: run.STARTING,
/**
* Indicates the process is running. To be tested against
* the `running` property.
* @property {2} STARTED
*/
STARTED: run.STARTED,
/**
* @property {run.Process[]} processes List of running processes
*/
get processes() { return processes; },
/**
* @property {Object[]} builders List of available builders
*/
get builders() { return builders; },
/**
* @ignore
*/
get base() { return base; },
_events: [
/**
* Fires when the process is going to be killed
* @event stopping
* @param {Object} e
* @param {run.Process} e.process the process that is stopping
*/
"stopping",
/**
* Fires when the process stopped running
* @event stopped
* @param {Object} e
* @param {run.Process} e.process the process that is stopped
*/
"stopped",
/**
* Fires when the process is being started
* @event starting
* @param {Object} e
* @param {run.Process} e.process the process that is starting
*/
"starting",
/**
* Fires when the process is started. This event also fires
* during startup if there's a PID file present.
* @event started
* @param {Object} e
* @param {run.Process} e.process the process that is started
*/
"started"
],
/**
* Retrieves an array of names of builders available to the system.
* A builder is a JSON file that describes how a certain file can
* be executed. The JSON file format is based on and compatible with
* the sublime build scripts. Besides the build in builders, the
* user can store builders in ~/.c9/builders. This list will contain
* both the user's builders as well as the build-in builders.
* @param {Function} callback Called when the builders are retrieved
* @param {Error} callback.err The error object if an error occurred.
* @param {String[]} callback.builders A list of names of builders.
*/
listBuilders: listBuilders,
/**
* Retrieves an individual builder's JSON object based on it's name.
* The names of available builders can be retrieved using `listBuilders`.
* @param {Function} callback Called when the runner is retrieved
* @param {Function} callback.err The error object if an error occurred.
* @param {Function} callback.runner A builder object. See {@link run#run} for more information.
*/
getBuilder: getBuilder,
/**
* Adds a new builder to the list of builders
* @param {String} name The name of the builder to add
* @param {Object} builder The builder to add
*/
addBuilder: addBuilder,
/**
* Builds a file. See `run.run()` for the full documentation
* @param {Object/"auto"} builder Object describing how to build a process.
* @param {Object} options
* @param {String} options.path The path to the file to build
* @param {String} [options.cwd] The current working directory
* @param {Boolean} [options.debug] Specifies whether to start the process in debug mode
* @param {String} [name] The unique name of the output buffer. Defaults to "output".
* @param {Function} callback Called when the process is started
* @param {Error} callback.err The error object if an error occurred.
* @returns {run.Process} the process object
*/
build: build
});
register(null, {
build: plugin
});
}
});