in node/toolrunner.ts [604:801]
private execWithPiping(pipeOutputToTool: ToolRunner, options?: IExecOptions): Q.Promise<number> {
var defer = Q.defer<number>();
this._debug('exec tool: ' + this.toolPath);
this._debug('arguments:');
this.args.forEach((arg) => {
this._debug(' ' + arg);
});
let success = true;
const optionsNonNull = this._cloneExecOptions(options);
if (!optionsNonNull.silent) {
optionsNonNull.outStream!.write(this._getCommandString(optionsNonNull) + os.EOL);
}
let cp: child.ChildProcess;
let toolPath: string = pipeOutputToTool.toolPath;
let toolPathFirst: string;
let successFirst = true;
let returnCodeFirst: number;
let fileStream: fs.WriteStream | null;
let waitingEvents: number = 0; // number of process or stream events we are waiting on to complete
let returnCode: number = 0;
let error: any;
toolPathFirst = this.toolPath;
// Following node documentation example from this link on how to pipe output of one process to another
// https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
//start the child process for both tools
waitingEvents++;
var cpFirst = child.spawn(
this._getSpawnFileName(optionsNonNull),
this._getSpawnArgs(optionsNonNull),
this._getSpawnOptions(optionsNonNull));
waitingEvents ++;
cp = child.spawn(
pipeOutputToTool._getSpawnFileName(optionsNonNull),
pipeOutputToTool._getSpawnArgs(optionsNonNull),
pipeOutputToTool._getSpawnOptions(optionsNonNull));
fileStream = this.pipeOutputToFile ? fs.createWriteStream(this.pipeOutputToFile) : null;
if (fileStream) {
waitingEvents++;
fileStream.on('finish', () => {
waitingEvents--; //file write is complete
fileStream = null;
if(waitingEvents == 0) {
if (error) {
defer.reject(error);
} else {
defer.resolve(returnCode);
}
}
});
fileStream.on('error', (err: Error) => {
waitingEvents--; //there were errors writing to the file, write is done
this._debug(`Failed to pipe output of ${toolPathFirst} to file ${this.pipeOutputToFile}. Error = ${err}`);
fileStream = null;
if(waitingEvents == 0) {
if (error) {
defer.reject(error);
} else {
defer.resolve(returnCode);
}
}
});
}
//pipe stdout of first tool to stdin of second tool
cpFirst.stdout?.on('data', (data: Buffer) => {
try {
if (fileStream) {
fileStream.write(data);
}
cp.stdin?.write(data);
} catch (err) {
this._debug('Failed to pipe output of ' + toolPathFirst + ' to ' + toolPath);
this._debug(toolPath + ' might have exited due to errors prematurely. Verify the arguments passed are valid.');
}
});
cpFirst.stderr?.on('data', (data: Buffer) => {
if (fileStream) {
fileStream.write(data);
}
successFirst = !optionsNonNull.failOnStdErr;
if (!optionsNonNull.silent) {
var s = optionsNonNull.failOnStdErr ? optionsNonNull.errStream! : optionsNonNull.outStream!;
s.write(data);
}
});
cpFirst.on('error', (err: Error) => {
waitingEvents--; //first process is complete with errors
if (fileStream) {
fileStream.end();
}
cp.stdin?.end();
error = new Error(toolPathFirst + ' failed. ' + err.message);
if(waitingEvents == 0) {
defer.reject(error);
}
});
cpFirst.on('close', (code: number, signal: any) => {
waitingEvents--; //first process is complete
if (code != 0 && !optionsNonNull.ignoreReturnCode) {
successFirst = false;
returnCodeFirst = code;
returnCode = returnCodeFirst;
}
this._debug('success of first tool:' + successFirst);
if (fileStream) {
fileStream.end();
}
cp.stdin?.end();
if(waitingEvents == 0) {
if (error) {
defer.reject(error);
} else {
defer.resolve(returnCode);
}
}
});
var stdbuffer: string = '';
cp.stdout?.on('data', (data: Buffer) => {
this.emit('stdout', data);
if (!optionsNonNull.silent) {
optionsNonNull.outStream!.write(data);
}
this._processLineBuffer(data, stdbuffer, (line: string) => {
this.emit('stdline', line);
});
});
var errbuffer: string = '';
cp.stderr?.on('data', (data: Buffer) => {
this.emit('stderr', data);
success = !optionsNonNull.failOnStdErr;
if (!optionsNonNull.silent) {
var s = optionsNonNull.failOnStdErr ? optionsNonNull.errStream! : optionsNonNull.outStream!;
s.write(data);
}
this._processLineBuffer(data, errbuffer, (line: string) => {
this.emit('errline', line);
});
});
cp.on('error', (err: Error) => {
waitingEvents--; //process is done with errors
error = new Error(toolPath + ' failed. ' + err.message);
if(waitingEvents == 0) {
defer.reject(error);
}
});
cp.on('close', (code: number, signal: any) => {
waitingEvents--; //process is complete
this._debug('rc:' + code);
returnCode = code;
if (stdbuffer.length > 0) {
this.emit('stdline', stdbuffer);
}
if (errbuffer.length > 0) {
this.emit('errline', errbuffer);
}
if (code != 0 && !optionsNonNull.ignoreReturnCode) {
success = false;
}
this._debug('success:' + success);
if (!successFirst) { //in the case output is piped to another tool, check exit code of both tools
error = new Error(toolPathFirst + ' failed with return code: ' + returnCodeFirst);
} else if (!success) {
error = new Error(toolPath + ' failed with return code: ' + code);
}
if(waitingEvents == 0) {
if (error) {
defer.reject(error);
} else {
defer.resolve(returnCode);
}
}
});
return <Q.Promise<number>>defer.promise;
}