private execWithPiping()

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;
    }