mirror of
https://github.com/apache/cordova-android.git
synced 2025-04-03 05:11:59 +08:00
190 lines
6.6 KiB
JavaScript
190 lines
6.6 KiB
JavaScript
/**
|
|
Licensed to the Apache Software Foundation (ASF) under one
|
|
or more contributor license agreements. See the NOTICE file
|
|
distributed with this work for additional information
|
|
regarding copyright ownership. The ASF licenses this file
|
|
to you under the Apache License, Version 2.0 (the
|
|
"License"); you may not use this file except in compliance
|
|
with the License. You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing,
|
|
software distributed under the License is distributed on an
|
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
KIND, either express or implied. See the License for the
|
|
specific language governing permissions and limitations
|
|
under the License.
|
|
*/
|
|
|
|
var child_process = require('child_process');
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var _ = require('underscore');
|
|
var Q = require('q');
|
|
var shell = require('shelljs');
|
|
var events = require('./events');
|
|
var iswin32 = process.platform === 'win32';
|
|
|
|
// On Windows, spawn() for batch files requires absolute path & having the extension.
|
|
function resolveWindowsExe (cmd) {
|
|
var winExtensions = ['.exe', '.bat', '.cmd', '.js', '.vbs'];
|
|
function isValidExe (c) {
|
|
return winExtensions.indexOf(path.extname(c)) !== -1 && fs.existsSync(c);
|
|
}
|
|
if (isValidExe(cmd)) {
|
|
return cmd;
|
|
}
|
|
cmd = shell.which(cmd) || cmd;
|
|
if (!isValidExe(cmd)) {
|
|
winExtensions.some(function (ext) {
|
|
if (fs.existsSync(cmd + ext)) {
|
|
cmd = cmd + ext;
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
function maybeQuote (a) {
|
|
if (/^[^"].*[ &].*[^"]/.test(a)) return '"' + a + '"';
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* A special implementation for child_process.spawn that handles
|
|
* Windows-specific issues with batch files and spaces in paths. Returns a
|
|
* promise that succeeds only for return code 0. It is also possible to
|
|
* subscribe on spawned process' stdout and stderr streams using progress
|
|
* handler for resultant promise.
|
|
*
|
|
* @example spawn('mycommand', [], {stdio: 'pipe'}) .progress(function (stdio){
|
|
* if (stdio.stderr) { console.error(stdio.stderr); } })
|
|
* .then(function(result){ // do other stuff })
|
|
*
|
|
* @param {String} cmd A command to spawn
|
|
* @param {String[]} [args=[]] An array of arguments, passed to spawned
|
|
* process
|
|
* @param {Object} [opts={}] A configuration object
|
|
* @param {String|String[]|Object} opts.stdio Property that configures how
|
|
* spawned process' stdio will behave. Has the same meaning and possible
|
|
* values as 'stdio' options for child_process.spawn method
|
|
* (https://nodejs.org/api/child_process.html#child_process_options_stdio).
|
|
* @param {Object} [env={}] A map of extra environment variables
|
|
* @param {String} [cwd=process.cwd()] Working directory for the command
|
|
* @param {Boolean} [chmod=false] If truthy, will attempt to set the execute
|
|
* bit before executing on non-Windows platforms
|
|
*
|
|
* @return {Promise} A promise that is either fulfilled if the spawned
|
|
* process is exited with zero error code or rejected otherwise. If the
|
|
* 'stdio' option set to 'default' or 'pipe', the promise also emits progress
|
|
* messages with the following contents:
|
|
* {
|
|
* 'stdout': ...,
|
|
* 'stderr': ...
|
|
* }
|
|
*/
|
|
exports.spawn = function (cmd, args, opts) {
|
|
args = args || [];
|
|
opts = opts || {};
|
|
var spawnOpts = {};
|
|
var d = Q.defer();
|
|
|
|
if (iswin32) {
|
|
cmd = resolveWindowsExe(cmd);
|
|
// If we couldn't find the file, likely we'll end up failing,
|
|
// but for things like "del", cmd will do the trick.
|
|
if (path.extname(cmd) !== '.exe') {
|
|
var cmdArgs = '"' + [cmd].concat(args).map(maybeQuote).join(' ') + '"';
|
|
// We need to use /s to ensure that spaces are parsed properly with cmd spawned content
|
|
args = [['/s', '/c', cmdArgs].join(' ')];
|
|
cmd = 'cmd';
|
|
spawnOpts.windowsVerbatimArguments = true;
|
|
} else if (!fs.existsSync(cmd)) {
|
|
// We need to use /s to ensure that spaces are parsed properly with cmd spawned content
|
|
args = ['/s', '/c', cmd].concat(args).map(maybeQuote);
|
|
}
|
|
}
|
|
|
|
if (opts.stdio !== 'default') {
|
|
// Ignore 'default' value for stdio because it corresponds to child_process's default 'pipe' option
|
|
spawnOpts.stdio = opts.stdio;
|
|
}
|
|
|
|
if (opts.cwd) {
|
|
spawnOpts.cwd = opts.cwd;
|
|
}
|
|
|
|
if (opts.env) {
|
|
spawnOpts.env = _.extend(_.extend({}, process.env), opts.env);
|
|
}
|
|
|
|
if (opts.chmod && !iswin32) {
|
|
try {
|
|
// This fails when module is installed in a system directory (e.g. via sudo npm install)
|
|
fs.chmodSync(cmd, '755');
|
|
} catch (e) {
|
|
// If the perms weren't set right, then this will come as an error upon execution.
|
|
}
|
|
}
|
|
|
|
events.emit(opts.printCommand ? 'log' : 'verbose', 'Running command: ' + maybeQuote(cmd) + ' ' + args.map(maybeQuote).join(' '));
|
|
|
|
var child = child_process.spawn(cmd, args, spawnOpts);
|
|
var capturedOut = '';
|
|
var capturedErr = '';
|
|
|
|
if (child.stdout) {
|
|
child.stdout.setEncoding('utf8');
|
|
child.stdout.on('data', function (data) {
|
|
capturedOut += data;
|
|
d.notify({'stdout': data});
|
|
});
|
|
}
|
|
|
|
if (child.stderr) {
|
|
child.stderr.setEncoding('utf8');
|
|
child.stderr.on('data', function (data) {
|
|
capturedErr += data;
|
|
d.notify({'stderr': data});
|
|
});
|
|
}
|
|
|
|
child.on('close', whenDone);
|
|
child.on('error', whenDone);
|
|
function whenDone (arg) {
|
|
child.removeListener('close', whenDone);
|
|
child.removeListener('error', whenDone);
|
|
var code = typeof arg === 'number' ? arg : arg && arg.code;
|
|
|
|
events.emit('verbose', 'Command finished with error code ' + code + ': ' + cmd + ' ' + args);
|
|
if (code === 0) {
|
|
d.resolve(capturedOut.trim());
|
|
} else {
|
|
var errMsg = cmd + ': Command failed with exit code ' + code;
|
|
if (capturedErr) {
|
|
errMsg += ' Error output:\n' + capturedErr.trim();
|
|
}
|
|
var err = new Error(errMsg);
|
|
if (capturedErr) {
|
|
err.stderr = capturedErr;
|
|
}
|
|
if (capturedOut) {
|
|
err.stdout = capturedOut;
|
|
}
|
|
err.code = code;
|
|
d.reject(err);
|
|
}
|
|
}
|
|
|
|
return d.promise;
|
|
};
|
|
|
|
exports.maybeSpawn = function (cmd, args, opts) {
|
|
if (fs.existsSync(cmd)) {
|
|
return exports.spawn(cmd, args, opts);
|
|
}
|
|
return Q(null);
|
|
};
|