Mypal/devtools/client/webide/modules/build.js

200 lines
6.6 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {Cu, Cc, Ci} = require("chrome");
const promise = require("promise");
const { Task } = require("devtools/shared/task");
const { TextDecoder, OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
const Subprocess = require("sdk/system/child_process/subprocess");
const ProjectBuilding = exports.ProjectBuilding = {
fetchPackageManifest: Task.async(function* (project) {
let manifestPath = OS.Path.join(project.location, "package.json");
let exists = yield OS.File.exists(manifestPath);
if (!exists) {
// No explicit manifest, try to generate one if possible
return this.generatePackageManifest(project);
}
let data = yield OS.File.read(manifestPath);
data = new TextDecoder().decode(data);
let manifest;
try {
manifest = JSON.parse(data);
} catch (e) {
throw new Error("Error while reading WebIDE manifest at: '" + manifestPath +
"', invalid JSON: " + e.message);
}
return manifest;
}),
/**
* For common frameworks in the community, attempt to detect the build
* settings if none are defined. This makes it much easier to get started
* with WebIDE. Later on, perhaps an add-on could define such things for
* different frameworks.
*/
generatePackageManifest: Task.async(function* (project) {
// Cordova
let cordovaConfigPath = OS.Path.join(project.location, "config.xml");
let exists = yield OS.File.exists(cordovaConfigPath);
if (!exists) {
return;
}
let data = yield OS.File.read(cordovaConfigPath);
data = new TextDecoder().decode(data);
if (data.contains("cordova.apache.org")) {
return {
"webide": {
"prepackage": "cordova prepare",
"packageDir": "./platforms/firefoxos/www"
}
};
}
}),
hasPrepackage: Task.async(function* (project) {
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
return manifest && manifest.webide && "prepackage" in manifest.webide;
}),
// If the app depends on some build step, run it before pushing the app
build: Task.async(function* ({ project, logger }) {
if (!(yield this.hasPrepackage(project))) {
return;
}
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
logger("start");
try {
yield this._build(project, manifest, logger);
logger("succeed");
} catch (e) {
logger("failed", e);
}
}),
_build: Task.async(function* (project, manifest, logger) {
// Look for `webide` property
manifest = manifest.webide;
let command, cwd, args = [], env = [];
// Copy frequently used env vars
let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
["HOME", "PATH"].forEach(key => {
let value = envService.get(key);
if (value) {
env.push(key + "=" + value);
}
});
if (typeof (manifest.prepackage) === "string") {
command = manifest.prepackage.replace(/%project%/g, project.location);
} else if (manifest.prepackage.command) {
command = manifest.prepackage.command;
args = manifest.prepackage.args || [];
args = args.map(a => a.replace(/%project%/g, project.location));
env = env.concat(manifest.prepackage.env || []);
env = env.map(a => a.replace(/%project%/g, project.location));
if (manifest.prepackage.cwd) {
// Normalize path for Windows support (converts / to \)
let path = OS.Path.normalize(manifest.prepackage.cwd);
// Note that Path.join also support absolute path and argument.
// So that if cwd is absolute, it will return cwd.
let rel = OS.Path.join(project.location, path);
let exists = yield OS.File.exists(rel);
if (exists) {
cwd = rel;
}
}
} else {
throw new Error("pre-package manifest is invalid, missing or invalid " +
"`prepackage` attribute");
}
if (!cwd) {
cwd = project.location;
}
logger("Running pre-package hook '" + command + "' " +
args.join(" ") +
" with ENV=[" + env.join(", ") + "]" +
" at " + cwd);
// Run the command through a shell command in order to support non absolute
// paths.
// On Windows `ComSpec` env variable is going to refer to cmd.exe,
// Otherwise, on Linux and Mac, SHELL env variable should refer to
// the user chosen shell program.
// (We do not check for OS, as on windows, with cygwin, ComSpec isn't set)
let shell = envService.get("ComSpec") || envService.get("SHELL");
args.unshift(command);
// For cmd.exe, we have to pass the `/C` option,
// but for unix shells we need -c.
// That to interpret next argument as a shell command.
if (envService.exists("ComSpec")) {
args.unshift("/C");
} else {
args.unshift("-c");
}
// Subprocess changes CWD, we have to save and restore it.
let originalCwd = yield OS.File.getCurrentDirectory();
try {
let defer = promise.defer();
Subprocess.call({
command: shell,
arguments: args,
environment: env,
workdir: cwd,
stdout: data =>
logger(data),
stderr: data =>
logger(data),
done: result => {
logger("Terminated with error code: " + result.exitCode);
if (result.exitCode == 0) {
defer.resolve();
} else {
defer.reject("pre-package command failed with error code " + result.exitCode);
}
}
});
defer.promise.then(() => {
OS.File.setCurrentDirectory(originalCwd);
});
yield defer.promise;
} catch (e) {
throw new Error("Unable to run pre-package command '" + command + "' " +
args.join(" ") + ":\n" + (e.message || e));
}
}),
getPackageDir: Task.async(function* (project) {
let manifest = yield ProjectBuilding.fetchPackageManifest(project);
if (!manifest || !manifest.webide || !manifest.webide.packageDir) {
return project.location;
}
manifest = manifest.webide;
let packageDir = OS.Path.join(project.location, manifest.packageDir);
// On Windows, replace / by \\
packageDir = OS.Path.normalize(packageDir);
let exists = yield OS.File.exists(packageDir);
if (exists) {
return packageDir;
}
throw new Error("Unable to resolve application package directory: '" + manifest.packageDir + "'");
})
};