11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
3- import { exec , execSync , spawn } from 'child_process' ;
43import { EventEmitter } from 'events' ;
5- import { Observable } from 'rxjs/Observable' ;
6- import { Readable } from 'stream' ;
74
85import { IDisposable } from '../types' ;
9- import { createDeferred } from '../utils/async' ;
106import { EnvironmentVariables } from '../variables/types' ;
11- import { DEFAULT_ENCODING } from './constants ' ;
7+ import { execObservable , killPid , plainExec , shellExec } from './rawProcessApis ' ;
128import {
139 ExecutionResult ,
1410 IBufferDecoder ,
1511 IProcessService ,
1612 ObservableExecutionResult ,
17- Output ,
1813 ShellOptions ,
1914 SpawnOptions ,
20- StdErrError ,
2115} from './types' ;
2216
2317export class ProcessService extends EventEmitter implements IProcessService {
@@ -37,16 +31,7 @@ export class ProcessService extends EventEmitter implements IProcessService {
3731 }
3832
3933 public static kill ( pid : number ) : void {
40- try {
41- if ( process . platform === 'win32' ) {
42- // Windows doesn't support SIGTERM, so execute taskkill to kill the process
43- execSync ( `taskkill /pid ${ pid } /T /F` ) ; // NOSONAR
44- } else {
45- process . kill ( pid ) ;
46- }
47- } catch {
48- // Ignore.
49- }
34+ killPid ( pid ) ;
5035 }
5136
5237 public dispose ( ) : void {
@@ -61,199 +46,18 @@ export class ProcessService extends EventEmitter implements IProcessService {
6146 }
6247
6348 public execObservable ( file : string , args : string [ ] , options : SpawnOptions = { } ) : ObservableExecutionResult < string > {
64- const spawnOptions = this . getDefaultOptions ( options ) ;
65- const encoding = spawnOptions . encoding ? spawnOptions . encoding : 'utf8' ;
66- const proc = spawn ( file , args , spawnOptions ) ;
67- let procExited = false ;
68- const disposable : IDisposable = {
69- dispose ( ) {
70- if ( proc && ! proc . killed && ! procExited ) {
71- ProcessService . kill ( proc . pid ) ;
72- }
73- if ( proc ) {
74- proc . unref ( ) ;
75- }
76- } ,
77- } ;
78- this . processesToKill . add ( disposable ) ;
79-
80- const output = new Observable < Output < string > > ( ( subscriber ) => {
81- const disposables : IDisposable [ ] = [ ] ;
82-
83- // eslint-disable-next-line @typescript-eslint/ban-types
84- const on = ( ee : Readable | null , name : string , fn : Function ) => {
85- // eslint-disable-next-line @typescript-eslint/no-explicit-any
86- ee ?. on ( name , fn as any ) ;
87- // eslint-disable-next-line @typescript-eslint/no-explicit-any
88- disposables . push ( { dispose : ( ) => ee ?. removeListener ( name , fn as any ) as any } ) ;
89- } ;
90-
91- if ( options . token ) {
92- disposables . push (
93- options . token . onCancellationRequested ( ( ) => {
94- if ( ! procExited && ! proc . killed ) {
95- proc . kill ( ) ;
96- procExited = true ;
97- }
98- } ) ,
99- ) ;
100- }
101-
102- const sendOutput = ( source : 'stdout' | 'stderr' , data : Buffer ) => {
103- const out = this . decoder . decode ( [ data ] , encoding ) ;
104- if ( source === 'stderr' && options . throwOnStdErr ) {
105- subscriber . error ( new StdErrError ( out ) ) ;
106- } else {
107- subscriber . next ( { source, out } ) ;
108- }
109- } ;
110-
111- on ( proc . stdout , 'data' , ( data : Buffer ) => sendOutput ( 'stdout' , data ) ) ;
112- on ( proc . stderr , 'data' , ( data : Buffer ) => sendOutput ( 'stderr' , data ) ) ;
113-
114- proc . once ( 'close' , ( ) => {
115- procExited = true ;
116- subscriber . complete ( ) ;
117- disposables . forEach ( ( d ) => d . dispose ( ) ) ;
118- } ) ;
119- proc . once ( 'exit' , ( ) => {
120- procExited = true ;
121- subscriber . complete ( ) ;
122- disposables . forEach ( ( d ) => d . dispose ( ) ) ;
123- } ) ;
124- proc . once ( 'error' , ( ex ) => {
125- procExited = true ;
126- subscriber . error ( ex ) ;
127- disposables . forEach ( ( d ) => d . dispose ( ) ) ;
128- } ) ;
129- } ) ;
130-
49+ const result = execObservable ( file , args , options , this . decoder , this . env , this . processesToKill ) ;
13150 this . emit ( 'exec' , file , args , options ) ;
132-
133- return {
134- proc,
135- out : output ,
136- dispose : disposable . dispose ,
137- } ;
51+ return result ;
13852 }
13953
14054 public exec ( file : string , args : string [ ] , options : SpawnOptions = { } ) : Promise < ExecutionResult < string > > {
141- const spawnOptions = this . getDefaultOptions ( options ) ;
142- const encoding = spawnOptions . encoding ? spawnOptions . encoding : 'utf8' ;
143- const proc = spawn ( file , args , spawnOptions ) ;
144- const deferred = createDeferred < ExecutionResult < string > > ( ) ;
145- const disposable : IDisposable = {
146- dispose : ( ) => {
147- if ( ! proc . killed && ! deferred . completed ) {
148- proc . kill ( ) ;
149- }
150- } ,
151- } ;
152- this . processesToKill . add ( disposable ) ;
153- const disposables : IDisposable [ ] = [ ] ;
154-
155- // eslint-disable-next-line @typescript-eslint/ban-types
156- const on = ( ee : Readable | null , name : string , fn : Function ) => {
157- // eslint-disable-next-line @typescript-eslint/no-explicit-any
158- ee ?. on ( name , fn as any ) ;
159- // eslint-disable-next-line @typescript-eslint/no-explicit-any
160- disposables . push ( { dispose : ( ) => ee ?. removeListener ( name , fn as any ) as any } ) ;
161- } ;
162-
163- if ( options . token ) {
164- disposables . push ( options . token . onCancellationRequested ( disposable . dispose ) ) ;
165- }
166-
167- const stdoutBuffers : Buffer [ ] = [ ] ;
168- on ( proc . stdout , 'data' , ( data : Buffer ) => stdoutBuffers . push ( data ) ) ;
169- const stderrBuffers : Buffer [ ] = [ ] ;
170- on ( proc . stderr , 'data' , ( data : Buffer ) => {
171- if ( options . mergeStdOutErr ) {
172- stdoutBuffers . push ( data ) ;
173- stderrBuffers . push ( data ) ;
174- } else {
175- stderrBuffers . push ( data ) ;
176- }
177- } ) ;
178-
179- proc . once ( 'close' , ( ) => {
180- if ( deferred . completed ) {
181- return ;
182- }
183- const stderr : string | undefined =
184- stderrBuffers . length === 0 ? undefined : this . decoder . decode ( stderrBuffers , encoding ) ;
185- if ( stderr && stderr . length > 0 && options . throwOnStdErr ) {
186- deferred . reject ( new StdErrError ( stderr ) ) ;
187- } else {
188- const stdout = this . decoder . decode ( stdoutBuffers , encoding ) ;
189- deferred . resolve ( { stdout, stderr } ) ;
190- }
191- disposables . forEach ( ( d ) => d . dispose ( ) ) ;
192- } ) ;
193- proc . once ( 'error' , ( ex ) => {
194- deferred . reject ( ex ) ;
195- disposables . forEach ( ( d ) => d . dispose ( ) ) ;
196- } ) ;
197-
55+ const promise = plainExec ( file , args , options , this . decoder , this . env , this . processesToKill ) ;
19856 this . emit ( 'exec' , file , args , options ) ;
199-
200- return deferred . promise ;
57+ return promise ;
20158 }
20259
20360 public shellExec ( command : string , options : ShellOptions = { } ) : Promise < ExecutionResult < string > > {
204- const shellOptions = this . getDefaultOptions ( options ) ;
205- return new Promise ( ( resolve , reject ) => {
206- const proc = exec ( command , shellOptions , ( e , stdout , stderr ) => {
207- if ( e && e !== null ) {
208- reject ( e ) ;
209- } else if ( shellOptions . throwOnStdErr && stderr && stderr . length ) {
210- reject ( new Error ( stderr ) ) ;
211- } else {
212- // Make sure stderr is undefined if we actually had none. This is checked
213- // elsewhere because that's how exec behaves.
214- resolve ( { stderr : stderr && stderr . length > 0 ? stderr : undefined , stdout } ) ;
215- }
216- } ) ; // NOSONAR
217- const disposable : IDisposable = {
218- dispose : ( ) => {
219- if ( ! proc . killed ) {
220- proc . kill ( ) ;
221- }
222- } ,
223- } ;
224- this . processesToKill . add ( disposable ) ;
225- } ) ;
226- }
227-
228- private getDefaultOptions < T extends ShellOptions | SpawnOptions > ( options : T ) : T {
229- const defaultOptions = { ...options } ;
230- const execOptions = defaultOptions as SpawnOptions ;
231- if ( execOptions ) {
232- execOptions . encoding =
233- typeof execOptions . encoding === 'string' && execOptions . encoding . length > 0
234- ? execOptions . encoding
235- : DEFAULT_ENCODING ;
236- const { encoding } = execOptions ;
237- delete execOptions . encoding ;
238- execOptions . encoding = encoding ;
239- }
240- if ( ! defaultOptions . env || Object . keys ( defaultOptions . env ) . length === 0 ) {
241- const env = this . env ? this . env : process . env ;
242- defaultOptions . env = { ...env } ;
243- } else {
244- defaultOptions . env = { ...defaultOptions . env } ;
245- }
246-
247- if ( execOptions && execOptions . extraVariables ) {
248- defaultOptions . env = { ...defaultOptions . env , ...execOptions . extraVariables } ;
249- }
250-
251- // Always ensure we have unbuffered output.
252- defaultOptions . env . PYTHONUNBUFFERED = '1' ;
253- if ( ! defaultOptions . env . PYTHONIOENCODING ) {
254- defaultOptions . env . PYTHONIOENCODING = 'utf-8' ;
255- }
256-
257- return defaultOptions ;
61+ return shellExec ( command , options , this . env , this . processesToKill ) ;
25862 }
25963}
0 commit comments