diff options
Diffstat (limited to 'devtools/client/webide/modules/build.js')
-rw-r--r-- | devtools/client/webide/modules/build.js | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/devtools/client/webide/modules/build.js b/devtools/client/webide/modules/build.js new file mode 100644 index 000000000..34cbcc0b7 --- /dev/null +++ b/devtools/client/webide/modules/build.js @@ -0,0 +1,199 @@ +/* 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 + "'"); + }) +}; |