Skip to content

Commit 19d0062

Browse files
authored
fix(appbuilder): SAM local Lambda debugging fails on Windows #5936
## Problem SAM local Lambda debugging fails on Windows #5933 #5918 ## Solution Skip resolveEnv logic on windows.
1 parent 98d8324 commit 19d0062

File tree

4 files changed

+100
-21
lines changed

4 files changed

+100
-21
lines changed

packages/core/src/shared/env/resolveEnv.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,29 +139,29 @@ export async function mergeResolvedShellPath(env: IProcessEnvironment): Promise<
139139
* - we hit a timeout of `MAX_SHELL_RESOLVE_TIME`
140140
* - any other error from spawning a shell to figure out the environment
141141
*/
142-
export async function getResolvedShellEnv(env?: IProcessEnvironment): Promise<typeof process.env> {
142+
export async function getResolvedShellEnv(env?: IProcessEnvironment): Promise<typeof process.env | undefined> {
143143
if (!env) {
144144
env = process.env
145145
}
146146
// Skip if forceResolveEnv is set to false
147147
if (DevSettings.instance._isSet('forceResolveEnv') && !DevSettings.instance.get('forceResolveEnv', false)) {
148148
getLogger().debug('resolveShellEnv(): skipped (forceResolveEnv)')
149149

150-
return {}
150+
return undefined
151151
}
152152

153153
// Skip on windows
154154
else if (process.platform === 'win32') {
155155
getLogger().debug('resolveShellEnv(): skipped (Windows)')
156156

157-
return {}
157+
return undefined
158158
}
159159

160160
// Skip if running from CLI already and forceResolveEnv is not true
161161
else if (isLaunchedFromCli(env) && !DevSettings.instance.get('forceResolveEnv', false)) {
162162
getLogger().info('resolveShellEnv(): skipped (VSCODE_CLI is set)')
163163

164-
return {}
164+
return undefined
165165
}
166166
// Otherwise resolve (macOS, Linux)
167167
else {
@@ -181,10 +181,15 @@ export async function getResolvedShellEnv(env?: IProcessEnvironment): Promise<ty
181181

182182
// Resolve shell env and handle errors
183183
try {
184-
resolve(await doResolveUnixShellEnv(timeout))
184+
const shellEnv = await doResolveUnixShellEnv(timeout)
185+
if (shellEnv && Object.keys(shellEnv).length > 0) {
186+
resolve(shellEnv)
187+
} else {
188+
return undefined
189+
}
185190
} catch {
186191
// failed resolve should not affect other feature.
187-
resolve({})
192+
return undefined
188193
}
189194
})
190195
}

packages/core/src/shared/sam/localLambdaRunner.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,7 @@ async function invokeLambdaHandler(
212212
return config
213213
.samLocalInvokeCommand!.invoke({
214214
options: {
215-
env: await getSpawnEnv({
216-
...process.env,
217-
...env,
218-
}),
215+
env: env,
219216
},
220217
command: samCommand,
221218
args: samArgs,
@@ -236,7 +233,7 @@ async function invokeLambdaHandler(
236233
templatePath: config.templatePath,
237234
eventPath: config.eventPayloadFile,
238235
environmentVariablePath: config.envFile,
239-
environmentVariables: await getSpawnEnv(env),
236+
environmentVariables: env,
240237
invoker: config.samLocalInvokeCommand!,
241238
dockerNetwork: config.sam?.dockerNetwork,
242239
debugPort: debugPort,
@@ -290,15 +287,16 @@ export async function runLambdaFunction(
290287
getLogger().info(localize('AWS.output.sam.local.startRun', 'Preparing to run locally: {0}', config.handlerName))
291288
}
292289

293-
const envVars = {
290+
const envVars = await getSpawnEnv({
291+
...process.env,
294292
...(config.aws?.region ? { AWS_DEFAULT_REGION: config.aws.region } : {}),
295-
}
293+
})
296294

297295
const settings = SamCliSettings.instance
298296
const timer = new Timeout(settings.getLocalInvokeTimeout())
299297

300298
// TODO: refactor things to not mutate the config
301-
config.templatePath = await buildLambdaHandler(timer, await getSpawnEnv(envVars), config, settings)
299+
config.templatePath = await buildLambdaHandler(timer, envVars, config, settings)
302300

303301
await onAfterBuild()
304302
timer.refresh()
@@ -315,12 +313,13 @@ export async function runLambdaFunction(
315313

316314
// SAM CLI and any API requests are executed in parallel
317315
// A failure from either is a failure for the whole invocation
318-
const [process] = await Promise.all([invokeLambdaHandler(timer, envVars, config, settings), apiRequest]).catch(
319-
(err) => {
320-
timer.cancel()
321-
throw err
322-
}
323-
)
316+
const [processInvoker] = await Promise.all([
317+
invokeLambdaHandler(timer, envVars, config, settings),
318+
apiRequest,
319+
]).catch((err) => {
320+
timer.cancel()
321+
throw err
322+
})
324323

325324
if (config.noDebug) {
326325
return config
@@ -329,7 +328,7 @@ export async function runLambdaFunction(
329328
const terminationListener = vscode.debug.onDidTerminateDebugSession((session) => {
330329
const config = session.configuration as SamLaunchRequestArgs
331330
if (config.invokeTarget?.target === 'api') {
332-
stopApi(process, config)
331+
stopApi(processInvoker, config)
333332
}
334333
})
335334

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import assert from 'assert'
7+
import * as resolveEnv from '../../../shared/env/resolveEnv'
8+
import sinon from 'sinon'
9+
import path from 'path'
10+
11+
describe('resolveEnv', async function () {
12+
let sandbox: sinon.SinonSandbox
13+
beforeEach(function () {
14+
sandbox = sinon.createSandbox()
15+
})
16+
17+
afterEach(function () {
18+
sandbox.restore()
19+
})
20+
21+
// a copy of resolveEnv.mergeResolvedShellPath for stubbing getResolvedShellEnv
22+
// mergeResolvedShellPath is calling getResolvedShellEnv within the same file
23+
// thus we need a copy to stub getResolvedShellEnv correctly
24+
const testMergeResolveShellPath = async function mergeResolvedShellPath(
25+
env: resolveEnv.IProcessEnvironment
26+
): Promise<typeof process.env> {
27+
const shellEnv = await resolveEnv.getResolvedShellEnv(env)
28+
// resolve failed or doesn't need to resolve
29+
if (!shellEnv) {
30+
return env
31+
}
32+
try {
33+
const envPaths: string[] = env.PATH ? env.PATH.split(path.delimiter) : []
34+
const resolvedPaths: string[] = shellEnv.PATH ? shellEnv.PATH.split(path.delimiter) : []
35+
const envReturn = { ...env }
36+
// merge, dedup, join
37+
envReturn.PATH = [...new Set(envPaths.concat(resolvedPaths))].join(path.delimiter)
38+
39+
return envReturn
40+
} catch (err) {
41+
return env
42+
}
43+
}
44+
45+
describe('windows', async function () {
46+
beforeEach(function () {
47+
sandbox.stub(process, 'platform').value('win32')
48+
})
49+
50+
it('mergeResolvedShellPath should not change path on windows', async function () {
51+
const env = await resolveEnv.mergeResolvedShellPath(process.env)
52+
assert(env.PATH)
53+
assert.strictEqual(env, process.env)
54+
})
55+
})
56+
57+
describe('unix', async function () {
58+
const originalEnv = { ...process.env }
59+
// skip mac test on windows
60+
if (process.platform !== 'win32') {
61+
it('mergeResolvedShellPath should get path on mac/linux', async function () {
62+
sandbox.stub(process.env, 'PATH').value('')
63+
// stub the resolve Env logic cause this is platform sensitive.
64+
sandbox.stub(resolveEnv, 'getResolvedShellEnv').resolves(originalEnv)
65+
const env = await testMergeResolveShellPath(process.env)
66+
assert(env.PATH)
67+
assert.notEqual(env, process.env)
68+
})
69+
}
70+
})
71+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "System Path parsing should ignore Windows and only parse Mac/Linux system, Sam Local Invoke should get correct system Path on windows #5933 #5918"
4+
}

0 commit comments

Comments
 (0)