2016-12-20 11:31:09 +01:00
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Copyright ( c ) Microsoft Corporation . All rights reserved .
* Licensed under the MIT License . See License . txt in the project root for license information .
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
2020-08-24 11:09:22 +02:00
import { spawn } from 'child_process' ;
2021-10-04 14:13:30 +02:00
import { basename } from 'vs/base/common/path' ;
2021-09-29 13:54:14 +02:00
import { localize } from 'vs/nls' ;
2021-08-06 00:32:53 +02:00
import { CancellationToken , CancellationTokenSource } from 'vs/base/common/cancellation' ;
import { toErrorMessage } from 'vs/base/common/errorMessage' ;
import { canceled , isPromiseCanceledError } from 'vs/base/common/errors' ;
2021-04-16 16:00:13 +02:00
import { IProcessEnvironment , isWindows , OS } from 'vs/base/common/platform' ;
2021-08-06 00:32:53 +02:00
import { generateUuid } from 'vs/base/common/uuid' ;
import { getSystemShell } from 'vs/base/node/shell' ;
2020-11-20 15:21:59 +01:00
import { NativeParsedArgs } from 'vs/platform/environment/common/argv' ;
2020-11-20 16:08:19 +01:00
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper' ;
2021-08-06 00:32:53 +02:00
import { ILogService } from 'vs/platform/log/common/log' ;
2021-10-11 13:27:43 +02:00
import { Promises } from 'vs/base/common/async' ;
2016-12-20 11:31:09 +01:00
2021-09-29 13:54:14 +02:00
/ * *
* The maximum of time we accept to wait on resolving the shell
* environment before giving up . This ensures we are not blocking
* other tasks from running for a too long time period .
* /
const MAX_SHELL_RESOLVE_TIME = 10000 ;
let unixShellEnvPromise : Promise < typeof process.env > | undefined = undefined ;
2020-11-20 15:21:59 +01:00
/ * *
* We need to get the environment from a user ' s shell .
* This should only be done when Code itself is not launched
* from within a shell .
2021-09-29 13:54:14 +02:00
*
* Will throw an error if :
* - we hit a timeout of ` MAX_SHELL_RESOLVE_TIME `
* - any other error from spawning a shell to figure out the environment
2020-11-20 15:21:59 +01:00
* /
2021-03-30 19:05:44 +02:00
export async function resolveShellEnv ( logService : ILogService , args : NativeParsedArgs , env : IProcessEnvironment ) : Promise < typeof process.env > {
2020-11-20 15:21:59 +01:00
// Skip if --force-disable-user-env
if ( args [ 'force-disable-user-env' ] ) {
logService . trace ( 'resolveShellEnv(): skipped (--force-disable-user-env)' ) ;
return { } ;
}
// Skip on windows
else if ( isWindows ) {
logService . trace ( 'resolveShellEnv(): skipped (Windows)' ) ;
return { } ;
}
// Skip if running from CLI already
2020-11-20 16:08:19 +01:00
else if ( isLaunchedFromCli ( env ) && ! args [ 'force-user-env' ] ) {
2020-11-20 15:21:59 +01:00
logService . trace ( 'resolveShellEnv(): skipped (VSCODE_CLI is set)' ) ;
return { } ;
}
// Otherwise resolve (macOS, Linux)
else {
2020-11-20 16:08:19 +01:00
if ( isLaunchedFromCli ( env ) ) {
2020-11-20 15:21:59 +01:00
logService . trace ( 'resolveShellEnv(): running (--force-user-env)' ) ;
} else {
logService . trace ( 'resolveShellEnv(): running (macOS/Linux)' ) ;
}
2021-06-15 11:48:54 +02:00
// Call this only once and cache the promise for
// subsequent calls since this operation can be
// expensive (spawns a process).
2020-11-20 15:21:59 +01:00
if ( ! unixShellEnvPromise ) {
2021-10-11 14:16:33 +02:00
unixShellEnvPromise = Promises . withAsyncBody < NodeJS.ProcessEnv > ( async ( resolve , reject ) = > {
2021-06-16 15:36:27 +02:00
const cts = new CancellationTokenSource ( ) ;
2021-06-15 11:48:54 +02:00
2021-09-29 13:54:14 +02:00
// Give up resolving shell env after some time
2021-06-15 11:48:54 +02:00
const timeout = setTimeout ( ( ) = > {
2021-06-16 15:36:27 +02:00
cts . dispose ( true ) ;
2021-10-11 14:16:33 +02:00
reject ( new Error ( localize ( 'resolveShellEnvTimeout' , "Unable to resolve your shell environment in a reasonable time. Please review your shell configuration." ) ) ) ;
2021-09-29 13:54:14 +02:00
} , MAX_SHELL_RESOLVE_TIME ) ;
2021-06-15 11:48:54 +02:00
// Resolve shell env and handle errors
try {
2021-09-29 15:55:21 +02:00
resolve ( await doResolveUnixShellEnv ( logService , cts . token ) ) ;
2021-06-15 11:48:54 +02:00
} catch ( error ) {
2021-09-29 13:54:14 +02:00
if ( ! isPromiseCanceledError ( error ) && ! cts . token . isCancellationRequested ) {
2021-10-11 14:16:33 +02:00
reject ( new Error ( localize ( 'resolveShellEnvError' , "Unable to resolve your shell environment: {0}" , toErrorMessage ( error ) ) ) ) ;
} else {
resolve ( { } ) ;
2021-06-16 15:36:27 +02:00
}
2021-06-15 11:48:54 +02:00
} finally {
clearTimeout ( timeout ) ;
2021-06-16 18:12:49 +02:00
cts . dispose ( ) ;
2021-06-15 11:48:54 +02:00
}
} ) ;
2020-11-20 15:21:59 +01:00
}
return unixShellEnvPromise ;
}
}
2021-06-16 15:36:27 +02:00
async function doResolveUnixShellEnv ( logService : ILogService , token : CancellationToken ) : Promise < typeof process.env > {
2021-10-12 10:50:44 +02:00
const runAsNode = process . env [ 'ELECTRON_RUN_AS_NODE' ] ;
logService . trace ( 'getUnixShellEnvironment#runAsNode' , runAsNode ) ;
2019-05-09 09:14:05 +02:00
2021-10-12 10:50:44 +02:00
const noAttach = process . env [ 'ELECTRON_NO_ATTACH_CONSOLE' ] ;
logService . trace ( 'getUnixShellEnvironment#noAttach' , noAttach ) ;
2019-05-09 09:14:05 +02:00
2021-10-12 10:50:44 +02:00
const mark = generateUuid ( ) . replace ( /-/g , '' ) . substr ( 0 , 12 ) ;
const regex = new RegExp ( mark + '(.*)' + mark ) ;
2016-12-20 11:31:09 +01:00
2021-10-12 10:50:44 +02:00
const env = {
. . . process . env ,
ELECTRON_RUN_AS_NODE : '1' ,
ELECTRON_NO_ATTACH_CONSOLE : '1'
} ;
2016-12-20 11:31:09 +01:00
2021-10-12 10:50:44 +02:00
logService . trace ( 'getUnixShellEnvironment#env' , env ) ;
const systemShellUnix = await getSystemShell ( OS , env ) ;
logService . trace ( 'getUnixShellEnvironment#shell' , systemShellUnix ) ;
2021-03-10 11:19:06 +01:00
2021-10-12 10:50:44 +02:00
return new Promise < typeof process.env > ( ( resolve , reject ) = > {
2021-06-16 15:36:27 +02:00
if ( token . isCancellationRequested ) {
2021-10-11 13:27:43 +02:00
return reject ( canceled ( ) ) ;
2021-06-16 15:36:27 +02:00
}
2021-04-12 09:50:36 +02:00
// handle popular non-POSIX shells
2021-09-29 13:54:14 +02:00
const name = basename ( systemShellUnix ) ;
2021-04-30 03:20:26 +02:00
let command : string , shellArgs : Array < string > ;
2021-04-12 09:50:36 +02:00
if ( /^pwsh(-preview)?$/ . test ( name ) ) {
2021-04-30 03:20:26 +02:00
// Older versions of PowerShell removes double quotes sometimes so we use "double single quotes" which is how
// you escape single quotes inside of a single quoted string.
2021-11-09 19:09:26 +01:00
command = ` & ' ${ process . execPath } ' --ms-enable-electron-run-as-node -p ''' ${ mark } '' + JSON.stringify(process.env) + '' ${ mark } ''' ` ;
2021-04-12 09:50:36 +02:00
shellArgs = [ '-Login' , '-Command' ] ;
2021-04-30 03:20:26 +02:00
} else {
2021-11-09 19:09:26 +01:00
command = ` ' ${ process . execPath } ' --ms-enable-electron-run-as-node -p '" ${ mark } " + JSON.stringify(process.env) + " ${ mark } "' ` ;
2021-11-01 15:30:34 +01:00
if ( name === 'tcsh' ) {
shellArgs = [ '-ic' ] ;
} else {
shellArgs = [ '-ilc' ] ;
}
2021-04-12 09:50:36 +02:00
}
logService . trace ( 'getUnixShellEnvironment#spawn' , JSON . stringify ( shellArgs ) , command ) ;
const child = spawn ( systemShellUnix , [ . . . shellArgs , command ] , {
2016-12-20 11:31:09 +01:00
detached : true ,
2021-04-09 10:17:17 +02:00
stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
2016-12-20 11:31:09 +01:00
env
} ) ;
2021-06-16 15:36:27 +02:00
token . onCancellationRequested ( ( ) = > {
child . kill ( ) ;
2021-10-11 13:27:43 +02:00
return reject ( canceled ( ) ) ;
2021-06-16 15:36:27 +02:00
} ) ;
2021-04-09 10:17:17 +02:00
child . on ( 'error' , err = > {
logService . error ( 'getUnixShellEnvironment#errorChildProcess' , toErrorMessage ( err ) ) ;
2021-09-29 13:54:14 +02:00
reject ( err ) ;
2021-04-09 10:17:17 +02:00
} ) ;
2016-12-20 11:31:09 +01:00
const buffers : Buffer [ ] = [ ] ;
2019-04-01 08:11:11 +02:00
child . stdout . on ( 'data' , b = > buffers . push ( b ) ) ;
2016-12-20 11:31:09 +01:00
2021-04-09 10:17:17 +02:00
const stderr : Buffer [ ] = [ ] ;
child . stderr . on ( 'data' , b = > stderr . push ( b ) ) ;
2016-12-20 11:31:09 +01:00
2021-04-09 10:17:17 +02:00
child . on ( 'close' , ( code , signal ) = > {
2016-12-20 11:31:09 +01:00
const raw = Buffer . concat ( buffers ) . toString ( 'utf8' ) ;
2019-05-09 09:14:05 +02:00
logService . trace ( 'getUnixShellEnvironment#raw' , raw ) ;
2021-04-09 10:17:17 +02:00
const stderrStr = Buffer . concat ( stderr ) . toString ( 'utf8' ) ;
if ( stderrStr . trim ( ) ) {
logService . trace ( 'getUnixShellEnvironment#stderr' , stderrStr ) ;
}
if ( code || signal ) {
2021-09-29 13:54:14 +02:00
return reject ( new Error ( localize ( 'resolveShellEnvExitError' , "Unexpected exit code from spawned shell (code {0}, signal {1})" , code , signal ) ) ) ;
2021-04-09 10:17:17 +02:00
}
2016-12-20 11:31:09 +01:00
const match = regex . exec ( raw ) ;
const rawStripped = match ? match [ 1 ] : '{}' ;
try {
const env = JSON . parse ( rawStripped ) ;
if ( runAsNode ) {
env [ 'ELECTRON_RUN_AS_NODE' ] = runAsNode ;
} else {
delete env [ 'ELECTRON_RUN_AS_NODE' ] ;
}
if ( noAttach ) {
env [ 'ELECTRON_NO_ATTACH_CONSOLE' ] = noAttach ;
} else {
delete env [ 'ELECTRON_NO_ATTACH_CONSOLE' ] ;
}
2020-09-16 01:13:49 +02:00
// https://github.com/microsoft/vscode/issues/22593#issuecomment-336050758
2017-10-30 15:13:29 +01:00
delete env [ 'XDG_RUNTIME_DIR' ] ;
2019-05-09 09:14:05 +02:00
logService . trace ( 'getUnixShellEnvironment#result' , env ) ;
2018-10-04 07:54:47 +02:00
resolve ( env ) ;
2016-12-20 11:31:09 +01:00
} catch ( err ) {
2021-04-09 10:17:17 +02:00
logService . error ( 'getUnixShellEnvironment#errorCaught' , toErrorMessage ( err ) ) ;
2018-10-04 07:54:47 +02:00
reject ( err ) ;
2016-12-20 11:31:09 +01:00
}
} ) ;
} ) ;
}