From 1f3c057aef356a28c8ce4c9bad9db6d1e6e29f51 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Mon, 15 Apr 2019 11:33:26 -0700 Subject: [PATCH 01/13] Initial commit for C# run command --- src/shared/codelens/csharpCodeLensProvider.ts | 86 ++++++++++++++++++- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/src/shared/codelens/csharpCodeLensProvider.ts b/src/shared/codelens/csharpCodeLensProvider.ts index 3f9aa3d32f1..92c0fc132c2 100644 --- a/src/shared/codelens/csharpCodeLensProvider.ts +++ b/src/shared/codelens/csharpCodeLensProvider.ts @@ -10,9 +10,17 @@ import * as vscode from 'vscode' import { LambdaHandlerCandidate } from '../lambdaHandlerSearch' import { getLogger } from '../logger' +import { + DefaultSamCliProcessInvoker, + DefaultSamCliTaskInvoker +} from '../sam/cli/samCliInvoker' +import { SamCliProcessInvoker, SamCliTaskInvoker } from '../sam/cli/samCliInvokerUtils' +import { SettingsConfiguration } from '../settingsConfiguration' import { Datum } from '../telemetry/telemetryEvent' +import { TelemetryService } from '../telemetry/telemetryService' import { registerCommand } from '../telemetry/telemetryUtils' import { dirnameWithTrailingSlash } from '../utilities/pathUtils' +import { getChannelLogger } from '../utilities/vsCodeUtils' import { CodeLensProviderParams, getInvokeCmdKey, @@ -20,8 +28,12 @@ import { makeCodeLenses, } from './codeLensUtils' import { + executeSamBuild, getRuntimeForLambda, + invokeLambdaFunction, LambdaLocalInvokeParams, + makeBuildDir, + makeInputTemplate } from './localLambdaRunner' export const CSHARP_LANGUAGE = 'csharp' @@ -44,6 +56,11 @@ export interface DotNetLambdaHandlerComponents { } export async function initialize({ + configuration, + outputChannel: toolkitOutputChannel, + processInvoker = new DefaultSamCliProcessInvoker(), + taskInvoker = new DefaultSamCliTaskInvoker(), + telemetryService: telemetryService, }: CodeLensProviderParams): Promise { const command = getInvokeCmdKey(CSHARP_LANGUAGE) registerCommand({ @@ -52,6 +69,11 @@ export async function initialize({ return await onLocalInvokeCommand({ commandName: command, lambdaLocalInvokeParams: params, + configuration, + toolkitOutputChannel, + processInvoker, + taskInvoker, + telemetryService }) }, }) @@ -61,24 +83,80 @@ export async function initialize({ * The command that is run when user clicks on Run Local or Debug Local CodeLens * @param commandName - Name of the VS Code Command currently running * @param lambdaLocalInvokeParams - Information about the Lambda Handler to invoke locally + * @param processInvoker - SAM CLI Process invoker + * @param taskInvoker - SAM CLI Task invoker */ async function onLocalInvokeCommand({ + configuration, + toolkitOutputChannel, commandName, lambdaLocalInvokeParams, + processInvoker, + taskInvoker, + telemetryService }: { + configuration: SettingsConfiguration + toolkitOutputChannel: vscode.OutputChannel, commandName: string, lambdaLocalInvokeParams: LambdaLocalInvokeParams, + processInvoker: SamCliProcessInvoker, + taskInvoker: SamCliTaskInvoker, + telemetryService: TelemetryService }): Promise<{ datum: Datum }> { + const channelLogger = getChannelLogger(toolkitOutputChannel) const runtime = await getRuntimeForLambda({ handlerName: lambdaLocalInvokeParams.handlerName, templatePath: lambdaLocalInvokeParams.samTemplate.fsPath }) - // TODO : Implement local run/debug in future backlog tasks - vscode.window.showInformationMessage( - `Local ${lambdaLocalInvokeParams.isDebug ? 'debug' : 'run'} support for csharp is currently not implemented.` - ) + const baseBuildDir = await makeBuildDir() + const codeDir = path.dirname(lambdaLocalInvokeParams.document.uri.fsPath) + const documentUri = lambdaLocalInvokeParams.document.uri + const originalHandlerName = lambdaLocalInvokeParams.handlerName + + const inputTemplatePath = await makeInputTemplate({ + baseBuildDir, + codeDir, + documentUri, + originalHandlerName, + handlerName: originalHandlerName, + runtime: runtime, + workspaceUri: lambdaLocalInvokeParams.workspaceFolder.uri + }) + + const samTemplatePath: string = await executeSamBuild({ + baseBuildDir, + channelLogger, + codeDir, + inputTemplatePath, + // TODO: add manifest path for debugger + manifestPath: undefined, + samProcessInvoker: processInvoker, + }) + + await invokeLambdaFunction({ + baseBuildDir, + channelLogger, + configuration, + // TODO: Delete this + debugConfig: { + type: 'node', + request: 'attach', + name: 'asdf', + port: 9999 + }, + documentUri, + originalHandlerName, + handlerName: originalHandlerName, + // TODO: set this appropriately + isDebug: false, + originalSamTemplatePath: inputTemplatePath, + samTemplatePath, + samTaskInvoker: taskInvoker, + telemetryService, + runtime: CSHARP_LANGUAGE, + }) return getMetricDatum({ isDebug: lambdaLocalInvokeParams.isDebug, From 8cdefe67fa9811d62032617afaf5f146d974b0f3 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Mon, 15 Apr 2019 14:43:30 -0700 Subject: [PATCH 02/13] Responding to feedback --- src/shared/codelens/csharpCodeLensProvider.ts | 89 +++++++++---------- src/shared/codelens/localLambdaRunner.ts | 10 ++- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/src/shared/codelens/csharpCodeLensProvider.ts b/src/shared/codelens/csharpCodeLensProvider.ts index 92c0fc132c2..e4d45eec417 100644 --- a/src/shared/codelens/csharpCodeLensProvider.ts +++ b/src/shared/codelens/csharpCodeLensProvider.ts @@ -60,7 +60,7 @@ export async function initialize({ outputChannel: toolkitOutputChannel, processInvoker = new DefaultSamCliProcessInvoker(), taskInvoker = new DefaultSamCliTaskInvoker(), - telemetryService: telemetryService, + telemetryService }: CodeLensProviderParams): Promise { const command = getInvokeCmdKey(CSHARP_LANGUAGE) registerCommand({ @@ -110,53 +110,50 @@ async function onLocalInvokeCommand({ templatePath: lambdaLocalInvokeParams.samTemplate.fsPath }) - const baseBuildDir = await makeBuildDir() - const codeDir = path.dirname(lambdaLocalInvokeParams.document.uri.fsPath) - const documentUri = lambdaLocalInvokeParams.document.uri - const originalHandlerName = lambdaLocalInvokeParams.handlerName - - const inputTemplatePath = await makeInputTemplate({ - baseBuildDir, - codeDir, - documentUri, - originalHandlerName, - handlerName: originalHandlerName, - runtime: runtime, - workspaceUri: lambdaLocalInvokeParams.workspaceFolder.uri - }) + if (!lambdaLocalInvokeParams.isDebug) { + const baseBuildDir = await makeBuildDir() + const codeDir = path.dirname(lambdaLocalInvokeParams.document.uri.fsPath) + const documentUri = lambdaLocalInvokeParams.document.uri + const originalHandlerName = lambdaLocalInvokeParams.handlerName + + const inputTemplatePath = await makeInputTemplate({ + baseBuildDir, + codeDir, + documentUri, + originalHandlerName, + handlerName: originalHandlerName, + runtime, + workspaceUri: lambdaLocalInvokeParams.workspaceFolder.uri + }) - const samTemplatePath: string = await executeSamBuild({ - baseBuildDir, - channelLogger, - codeDir, - inputTemplatePath, - // TODO: add manifest path for debugger - manifestPath: undefined, - samProcessInvoker: processInvoker, - }) + const samTemplatePath: string = await executeSamBuild({ + baseBuildDir, + channelLogger, + codeDir, + inputTemplatePath, + samProcessInvoker: processInvoker, + }) - await invokeLambdaFunction({ - baseBuildDir, - channelLogger, - configuration, - // TODO: Delete this - debugConfig: { - type: 'node', - request: 'attach', - name: 'asdf', - port: 9999 - }, - documentUri, - originalHandlerName, - handlerName: originalHandlerName, - // TODO: set this appropriately - isDebug: false, - originalSamTemplatePath: inputTemplatePath, - samTemplatePath, - samTaskInvoker: taskInvoker, - telemetryService, - runtime: CSHARP_LANGUAGE, - }) + await invokeLambdaFunction({ + baseBuildDir, + channelLogger, + configuration, + documentUri, + originalHandlerName, + handlerName: originalHandlerName, + originalSamTemplatePath: inputTemplatePath, + samTemplatePath, + samTaskInvoker: taskInvoker, + telemetryService, + runtime, + + // TODO: Set on debug + isDebug: lambdaLocalInvokeParams.isDebug, + debugConfig: undefined, + }) + } else { + vscode.window.showInformationMessage(`Local debug for ${runtime} is currently not implemented.`) + } return getMetricDatum({ isDebug: lambdaLocalInvokeParams.isDebug, diff --git a/src/shared/codelens/localLambdaRunner.ts b/src/shared/codelens/localLambdaRunner.ts index daa2dd67354..8cbd6151641 100644 --- a/src/shared/codelens/localLambdaRunner.ts +++ b/src/shared/codelens/localLambdaRunner.ts @@ -448,7 +448,7 @@ export const invokeLambdaFunction = async (params: { baseBuildDir: string, channelLogger: ChannelLogger, configuration: SettingsConfiguration, - debugConfig: DebugConfiguration, + debugConfig?: DebugConfiguration, documentUri: vscode.Uri, originalHandlerName: string, handlerName: string, @@ -499,7 +499,8 @@ export const invokeLambdaFunction = async (params: { templatePath: params.samTemplatePath, eventPath, environmentVariablePath, - debugPort: (params.isDebug) ? params.debugConfig.port.toString() : undefined, + debugPort: (params.isDebug) ? + (params.debugConfig ? params.debugConfig.port.toString() : undefined) : undefined, invoker: params.samTaskInvoker }) @@ -507,6 +508,11 @@ export const invokeLambdaFunction = async (params: { await command.execute() if (params.isDebug) { + if (!params.debugConfig) { + const err = new Error('Started debug run without a debugConfig') + params.channelLogger.logger.error(err) + throw err + } await waitForDebugPort({ debugPort: params.debugConfig.port, configuration: params.configuration, From 52cc403c10d3af213488bf4ba32298c23c8e5f93 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Tue, 16 Apr 2019 11:50:37 -0700 Subject: [PATCH 03/13] Housekeeping and tests --- src/shared/codelens/csharpCodeLensProvider.ts | 6 ++- src/shared/codelens/localLambdaRunner.ts | 9 ++-- .../shared/codelens/localLambdaRunner.test.ts | 54 +++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/shared/codelens/csharpCodeLensProvider.ts b/src/shared/codelens/csharpCodeLensProvider.ts index e4d45eec417..4583d81fc77 100644 --- a/src/shared/codelens/csharpCodeLensProvider.ts +++ b/src/shared/codelens/csharpCodeLensProvider.ts @@ -81,10 +81,14 @@ export async function initialize({ /** * The command that is run when user clicks on Run Local or Debug Local CodeLens + * Accepts object containing the following params: + * @param configuration - SettingsConfiguration (for invokeLambdaFunction) + * @param toolkitOutputChannel - "AWS Toolkit" output channel * @param commandName - Name of the VS Code Command currently running * @param lambdaLocalInvokeParams - Information about the Lambda Handler to invoke locally * @param processInvoker - SAM CLI Process invoker * @param taskInvoker - SAM CLI Task invoker + * @param telemetryService - Telemetry service for metrics */ async function onLocalInvokeCommand({ configuration, @@ -146,9 +150,9 @@ async function onLocalInvokeCommand({ samTaskInvoker: taskInvoker, telemetryService, runtime, + isDebug: lambdaLocalInvokeParams.isDebug, // TODO: Set on debug - isDebug: lambdaLocalInvokeParams.isDebug, debugConfig: undefined, }) } else { diff --git a/src/shared/codelens/localLambdaRunner.ts b/src/shared/codelens/localLambdaRunner.ts index 8cbd6151641..22824584a2d 100644 --- a/src/shared/codelens/localLambdaRunner.ts +++ b/src/shared/codelens/localLambdaRunner.ts @@ -414,7 +414,7 @@ export async function makeInputTemplate(params: { export async function executeSamBuild(params: { baseBuildDir: string, - channelLogger: ChannelLogger, + channelLogger: Pick, codeDir: string, inputTemplatePath: string, manifestPath?: string, @@ -499,8 +499,7 @@ export const invokeLambdaFunction = async (params: { templatePath: params.samTemplatePath, eventPath, environmentVariablePath, - debugPort: (params.isDebug) ? - (params.debugConfig ? params.debugConfig.port.toString() : undefined) : undefined, + debugPort: (params.isDebug && params.debugConfig) ? params.debugConfig.port.toString() : undefined, invoker: params.samTaskInvoker }) @@ -558,7 +557,9 @@ const getConfig = async (params: { return config } -const getEnvironmentVariables = (config: HandlerConfig): SAMTemplateEnvironmentVariables => { +const getEnvironmentVariables = ( + config: Pick +): SAMTemplateEnvironmentVariables => { if (!!config.environmentVariables) { return { [TEMPLATE_RESOURCE_NAME]: config.environmentVariables diff --git a/src/test/shared/codelens/localLambdaRunner.test.ts b/src/test/shared/codelens/localLambdaRunner.test.ts index 3f3771f53eb..db6f3cbea5e 100644 --- a/src/test/shared/codelens/localLambdaRunner.test.ts +++ b/src/test/shared/codelens/localLambdaRunner.test.ts @@ -10,7 +10,10 @@ import * as path from 'path' import * as vscode from 'vscode' import { DebugConfiguration } from '../../../lambda/local/debugConfiguration' import * as localLambdaRunner from '../../../shared/codelens/localLambdaRunner' +import * as fsUtils from '../../../shared/filesystemUtilities' import { BasicLogger, ErrorOrString } from '../../../shared/logger' +import { ChildProcessResult } from '../../../shared/utilities/childProcess' +import { ExtensionDisposableFiles } from '../../../shared/utilities/disposableFiles' import { ChannelLogger } from '../../../shared/utilities/vsCodeUtils' import { assertRejects } from '../utilities/assertUtils' @@ -53,6 +56,10 @@ class FakeBasicLogger implements BasicLogger { } describe('localLambdaRunner', async () => { + + const tempDir: string = await fsUtils.makeTemporaryToolkitFolder() + ExtensionDisposableFiles.getInstance().addFolder(tempDir) + describe('attachDebugger', async () => { let actualRetries: number = 0 let channelLogger: FakeChannelLogger @@ -378,4 +385,51 @@ describe('localLambdaRunner', async () => { }) } }) + + describe('makeBuildDir', () => { + it ('creates a temp directory', async () => { + const dir = await localLambdaRunner.makeBuildDir() + assert.strictEqual(fsUtils.fileExists(dir), true) + }) + }) + + describe('executeSamBuild', () => { + const failedChildProcess: ChildProcessResult = { + exitCode: 1, + error: new Error('you are already dead'), + stdout: 'friendly failure message', + stderr: 'big ugly failure message' + } + + const successfulChildProcess: ChildProcessResult = { + exitCode: 0, + error: undefined, + stdout: 'everything sunny all the time always', + stderr: 'nothing to report' + } + + const generateSamBuildParams = (isSuccessfulBuild: boolean) => { + return { + baseBuildDir: tempDir, + codeDir: tempDir, + inputTemplatePath: tempDir, + channelLogger: new FakeChannelLogger(), + // not needed for testing + manifestPath: undefined, + samProcessInvoker: { + invoke: async (): Promise => + isSuccessfulBuild ? successfulChildProcess : failedChildProcess + } + } + } + + it ('fails when the child process returns a nonzero exit code', async () => { + await assertRejects(async () => localLambdaRunner.executeSamBuild(generateSamBuildParams(false))) + }) + + it ('succeeds when the child process returns with an exit code of 0', async () => { + const samBuildResult = await localLambdaRunner.executeSamBuild(generateSamBuildParams(true)) + assert.strictEqual(samBuildResult === path.join(tempDir, 'output', 'template.yaml'), true) + }) + }) }) From 007e1d955215bef6b18a1167c74186495a7db1a6 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Tue, 16 Apr 2019 15:41:13 -0700 Subject: [PATCH 04/13] Picking nits --- src/test/shared/codelens/localLambdaRunner.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/shared/codelens/localLambdaRunner.test.ts b/src/test/shared/codelens/localLambdaRunner.test.ts index db6f3cbea5e..826786d4a47 100644 --- a/src/test/shared/codelens/localLambdaRunner.test.ts +++ b/src/test/shared/codelens/localLambdaRunner.test.ts @@ -429,7 +429,7 @@ describe('localLambdaRunner', async () => { it ('succeeds when the child process returns with an exit code of 0', async () => { const samBuildResult = await localLambdaRunner.executeSamBuild(generateSamBuildParams(true)) - assert.strictEqual(samBuildResult === path.join(tempDir, 'output', 'template.yaml'), true) + assert.strictEqual(samBuildResult, path.join(tempDir, 'output', 'template.yaml')) }) }) }) From 55fda65a8faaa7ab71260bc9eca7e258c9061812 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Wed, 17 Apr 2019 08:56:12 -0700 Subject: [PATCH 05/13] Updating runtimes to match versions currently supported by SAM CLI --- src/lambda/models/samLambdaRuntime.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lambda/models/samLambdaRuntime.ts b/src/lambda/models/samLambdaRuntime.ts index b5895afa8db..6bdd4c45e4a 100644 --- a/src/lambda/models/samLambdaRuntime.ts +++ b/src/lambda/models/samLambdaRuntime.ts @@ -16,7 +16,6 @@ export type SamLambdaRuntime = 'python' | 'nodejs6.10' | 'nodejs8.10' | - 'nodejs4.3' | 'nodejs' | 'dotnetcore2.1' | 'dotnetcore2.0' | @@ -27,6 +26,7 @@ export type SamLambdaRuntime = 'go' | 'java8' | 'java' | + 'ruby' | 'ruby2.5' export const samLambdaRuntimes: immutable.Set = immutable.Set([ @@ -36,7 +36,6 @@ export const samLambdaRuntimes: immutable.Set = immutable.Set( 'python', 'nodejs6.10', 'nodejs8.10', - 'nodejs4.3', 'nodejs', 'dotnetcore2.1', 'dotnetcore2.0', @@ -47,6 +46,7 @@ export const samLambdaRuntimes: immutable.Set = immutable.Set( 'go', 'java8', 'java', + 'ruby', 'ruby2.5' ] as SamLambdaRuntime[]) From d8a5823e54e6be57bb8cd39045e12f0281da230c Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Wed, 17 Apr 2019 09:02:48 -0700 Subject: [PATCH 06/13] Fixed getFamily call to reflect current runtimes --- src/lambda/models/samLambdaRuntime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lambda/models/samLambdaRuntime.ts b/src/lambda/models/samLambdaRuntime.ts index 6bdd4c45e4a..4d8f444b4ba 100644 --- a/src/lambda/models/samLambdaRuntime.ts +++ b/src/lambda/models/samLambdaRuntime.ts @@ -68,7 +68,6 @@ export function getFamily(runtime: string | undefined): SamLambdaRuntimeFamily { return SamLambdaRuntimeFamily.Python case 'nodejs6.10': case 'nodejs8.10': - case 'nodejs4.3': case 'nodejs': return SamLambdaRuntimeFamily.NodeJS case 'dotnetcore2.1': @@ -84,6 +83,7 @@ export function getFamily(runtime: string | undefined): SamLambdaRuntimeFamily { case 'java': return SamLambdaRuntimeFamily.Java case 'ruby2.5': + case 'ruby': return SamLambdaRuntimeFamily.Ruby default: throw new Error(`Unrecognized runtime: '${runtime}'`) From caee4d8813148792363e897999a5fab179ea5d1f Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Fri, 19 Apr 2019 11:04:48 -0700 Subject: [PATCH 07/13] localLambdaRunner handles nested functions in C# --- package.json | 2 +- src/lambda/local/detectLocalLambdas.ts | 12 ++- src/shared/codelens/csharpCodeLensProvider.ts | 92 ++++++++++--------- src/shared/codelens/localLambdaRunner.ts | 79 +++++++++++----- src/shared/codelens/pythonCodeLensProvider.ts | 16 +--- .../shared/codelens/localLambdaRunner.test.ts | 14 ++- 6 files changed, 135 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index feafadd7788..4847baab7c0 100644 --- a/package.json +++ b/package.json @@ -394,4 +394,4 @@ "winston-transport": "^4.3.0", "xml2js": "^0.4.19" } -} \ No newline at end of file +} diff --git a/src/lambda/local/detectLocalLambdas.ts b/src/lambda/local/detectLocalLambdas.ts index 8e0440e1eed..9bfd19bc04f 100644 --- a/src/lambda/local/detectLocalLambdas.ts +++ b/src/lambda/local/detectLocalLambdas.ts @@ -17,6 +17,7 @@ export interface LocalLambda { resource: CloudFormation.Resource templatePath: string handler?: string + codeUri?: string } export async function detectLocalLambdas( @@ -71,7 +72,8 @@ async function detectLambdasFromTemplate( templatePath, protocol: getDebugProtocol(resources[key]!), handler: getHandler(resources[key]!), - resource: resources[key]! + resource: resources[key]!, + codeUri: getCodeUri(resources[key]!) })) } @@ -100,3 +102,11 @@ function getHandler(resource: CloudFormation.Resource): string | undefined { return undefined } + +function getCodeUri(resource: CloudFormation.Resource): string | undefined { + if (resource.Properties && resource.Properties.CodeUri) { + return resource.Properties.CodeUri + } + + return undefined +} diff --git a/src/shared/codelens/csharpCodeLensProvider.ts b/src/shared/codelens/csharpCodeLensProvider.ts index 4583d81fc77..56d0a31917a 100644 --- a/src/shared/codelens/csharpCodeLensProvider.ts +++ b/src/shared/codelens/csharpCodeLensProvider.ts @@ -114,49 +114,59 @@ async function onLocalInvokeCommand({ templatePath: lambdaLocalInvokeParams.samTemplate.fsPath }) - if (!lambdaLocalInvokeParams.isDebug) { - const baseBuildDir = await makeBuildDir() - const codeDir = path.dirname(lambdaLocalInvokeParams.document.uri.fsPath) - const documentUri = lambdaLocalInvokeParams.document.uri - const originalHandlerName = lambdaLocalInvokeParams.handlerName - - const inputTemplatePath = await makeInputTemplate({ - baseBuildDir, - codeDir, - documentUri, - originalHandlerName, - handlerName: originalHandlerName, - runtime, - workspaceUri: lambdaLocalInvokeParams.workspaceFolder.uri - }) + try { + if (!lambdaLocalInvokeParams.isDebug) { + const baseBuildDir = await makeBuildDir() + const templateDir = path.dirname(lambdaLocalInvokeParams.samTemplate.fsPath) + const documentUri = lambdaLocalInvokeParams.document.uri + const originalHandlerName = lambdaLocalInvokeParams.handlerName + + const inputTemplate = await makeInputTemplate({ + baseBuildDir, + templateDir, + documentUri, + originalHandlerName, + handlerName: originalHandlerName, + runtime, + workspaceUri: lambdaLocalInvokeParams.workspaceFolder.uri, + channelLogger + }) - const samTemplatePath: string = await executeSamBuild({ - baseBuildDir, - channelLogger, - codeDir, - inputTemplatePath, - samProcessInvoker: processInvoker, - }) + const samTemplatePath: string = await executeSamBuild({ + baseBuildDir, + channelLogger, + codeDir: (inputTemplate.codeDir ? path.join(templateDir, inputTemplate.codeDir) : templateDir), + inputTemplatePath: inputTemplate.inputTemplatePath, + samProcessInvoker: processInvoker, + }) - await invokeLambdaFunction({ - baseBuildDir, - channelLogger, - configuration, - documentUri, - originalHandlerName, - handlerName: originalHandlerName, - originalSamTemplatePath: inputTemplatePath, - samTemplatePath, - samTaskInvoker: taskInvoker, - telemetryService, - runtime, - isDebug: lambdaLocalInvokeParams.isDebug, - - // TODO: Set on debug - debugConfig: undefined, - }) - } else { - vscode.window.showInformationMessage(`Local debug for ${runtime} is currently not implemented.`) + await invokeLambdaFunction({ + baseBuildDir, + channelLogger, + configuration, + documentUri, + originalHandlerName, + handlerName: originalHandlerName, + originalSamTemplatePath: inputTemplate.inputTemplatePath, + samTemplatePath, + samTaskInvoker: taskInvoker, + telemetryService, + runtime, + isDebug: lambdaLocalInvokeParams.isDebug, + + // TODO: Set on debug + debugConfig: undefined, + }) + } else { + vscode.window.showInformationMessage(`Local debug for ${runtime} is currently not implemented.`) + } + } catch (err) { + const error = err as Error + channelLogger.error( + 'AWS.error.during.sam.local', + 'An error occurred trying to run SAM Application locally: {0}', + error + ) } return getMetricDatum({ diff --git a/src/shared/codelens/localLambdaRunner.ts b/src/shared/codelens/localLambdaRunner.ts index 22824584a2d..011bdb822e9 100644 --- a/src/shared/codelens/localLambdaRunner.ts +++ b/src/shared/codelens/localLambdaRunner.ts @@ -9,7 +9,7 @@ import * as path from 'path' import * as tcpPortUsed from 'tcp-port-used' import * as vscode from 'vscode' import { getLocalLambdaConfiguration } from '../../lambda/local/configureLocalLambda' -import { detectLocalLambdas } from '../../lambda/local/detectLocalLambdas' +import { detectLocalLambdas, LocalLambda } from '../../lambda/local/detectLocalLambdas' import { CloudFormation } from '../cloudformation/cloudformation' import { writeFile } from '../filesystem' import { makeTemporaryToolkitFolder } from '../filesystemUtilities' @@ -48,6 +48,11 @@ export interface OnDidSamBuildParams { isDebug: boolean } +export interface InputTemplateInfo { + inputTemplatePath: string + codeDir: string | undefined +} + const TEMPLATE_RESOURCE_NAME: string = 'awsToolkitSamLocalResource' const SAM_LOCAL_PORT_CHECK_RETRY_INTERVAL_MILLIS: number = 125 const SAM_LOCAL_PORT_CHECK_RETRY_TIMEOUT_MILLIS_DEFAULT: number = 30000 @@ -357,51 +362,76 @@ export const makeBuildDir = async (): Promise => { export async function makeInputTemplate(params: { baseBuildDir: string, - codeDir: string, + templateDir: string, documentUri: vscode.Uri originalHandlerName: string, handlerName: string, runtime: string, workspaceUri: vscode.Uri, -}): Promise { + channelLogger: ChannelLogger +}): Promise { + + // Switch over to the output channel so the user has feedback that we're getting things ready + params.channelLogger.channel.show(true) + + params.channelLogger.info( + 'AWS.output.sam.local.start', + 'Preparing to run {0} locally...', + params.handlerName + ) + const inputTemplatePath: string = path.join(params.baseBuildDir, 'input', 'input-template.yaml') ExtensionDisposableFiles.getInstance().addFolder(inputTemplatePath) // Make function handler relative to baseDir const handlerFileRelativePath = path.relative( - params.codeDir, + params.templateDir, path.dirname(params.documentUri.fsPath) ) const workspaceFolder = vscode.workspace.getWorkspaceFolder(params.workspaceUri) + let existingLambda: LocalLambda | undefined let existingTemplateResource: CloudFormation.Resource | undefined if (workspaceFolder) { - const relativeOriginalFunctionHandler = normalizeSeparator( - path.join( - handlerFileRelativePath, - params.originalHandlerName, + let relativeOriginalFunctionHandler = params.originalHandlerName + if (!params.runtime.includes('dotnet')) { + relativeOriginalFunctionHandler = normalizeSeparator( + path.join( + handlerFileRelativePath, + params.originalHandlerName, + ) ) - ) + } const lambdas = await detectLocalLambdas([workspaceFolder]) - const existingLambda = lambdas.find(lambda => lambda.handler === relativeOriginalFunctionHandler) + existingLambda = lambdas.find(lambda => lambda.handler === relativeOriginalFunctionHandler) existingTemplateResource = existingLambda ? existingLambda.resource : undefined } - const relativeFunctionHandler = normalizeSeparator( - path.join( - handlerFileRelativePath, - params.handlerName, + let relativeFunctionHandler = params.handlerName + if (!params.runtime.includes('dotnet')) { + relativeFunctionHandler = normalizeSeparator( + path.join( + handlerFileRelativePath, + params.handlerName, + ) ) - ) + } let newTemplate = new SamTemplateGenerator() - .withCodeUri(params.codeDir) .withFunctionHandler(relativeFunctionHandler) .withResourceName(TEMPLATE_RESOURCE_NAME) .withRuntime(params.runtime) + if (params.runtime.includes('dotnet')) { + newTemplate.withCodeUri(existingLambda && existingLambda.codeUri ? + path.join(params.templateDir, existingLambda.codeUri) : params.templateDir + ) + } else { + newTemplate.withCodeUri(params.templateDir) + } + if (existingTemplateResource && existingTemplateResource.Properties && existingTemplateResource.Properties.Environment) { newTemplate = newTemplate.withEnvironment(existingTemplateResource.Properties.Environment) @@ -409,7 +439,10 @@ export async function makeInputTemplate(params: { await newTemplate.generate(inputTemplatePath) - return inputTemplatePath + return { + inputTemplatePath, + codeDir: ( existingLambda ? existingLambda.codeUri : undefined ) + } } export async function executeSamBuild(params: { @@ -459,6 +492,11 @@ export const invokeLambdaFunction = async (params: { telemetryService: TelemetryService, runtime: string, }): Promise => { + if (params.isDebug && !params.debugConfig) { + const err = new Error('Started debug run without a debugConfig') + params.channelLogger.logger.error(err) + throw err + } params.channelLogger.info( 'AWS.output.starting.sam.app.locally', 'Starting the SAM Application locally (see Terminal for output)' @@ -506,12 +544,7 @@ export const invokeLambdaFunction = async (params: { const startInvokeTime = new Date() await command.execute() - if (params.isDebug) { - if (!params.debugConfig) { - const err = new Error('Started debug run without a debugConfig') - params.channelLogger.logger.error(err) - throw err - } + if (params.isDebug && params.debugConfig) { await waitForDebugPort({ debugPort: params.debugConfig.port, configuration: params.configuration, diff --git a/src/shared/codelens/pythonCodeLensProvider.ts b/src/shared/codelens/pythonCodeLensProvider.ts index 2b400be890e..22a323622dc 100644 --- a/src/shared/codelens/pythonCodeLensProvider.ts +++ b/src/shared/codelens/pythonCodeLensProvider.ts @@ -190,14 +190,6 @@ export async function initialize({ const channelLogger = getChannelLogger(toolkitOutputChannel) const invokeLambda = async (args: LambdaLocalInvokeParams & { runtime: string }) => { - // Switch over to the output channel so the user has feedback that we're getting things ready - channelLogger.channel.show(true) - - channelLogger.info( - 'AWS.output.sam.local.start', - 'Preparing to run {0} locally...', - args.handlerName - ) const samProjectCodeRoot = await getSamProjectDirPathForFile(args.document.uri.fsPath) const baseBuildDir = await makeBuildDir() @@ -221,15 +213,17 @@ export async function initialize({ outputDir: baseBuildDir }) } - const inputTemplatePath = await makeInputTemplate({ + const inputTemplate = await makeInputTemplate({ baseBuildDir, - codeDir: samProjectCodeRoot, + templateDir: samProjectCodeRoot, documentUri: args.document.uri, originalHandlerName: args.handlerName, handlerName, runtime: args.runtime, - workspaceUri: args.workspaceFolder.uri + workspaceUri: args.workspaceFolder.uri, + channelLogger }) + const inputTemplatePath = inputTemplate.inputTemplatePath logger.debug(`pythonCodeLensProvider.initialize: ${ JSON.stringify({ samProjectCodeRoot, inputTemplatePath, handlerName, manifestPath }, undefined, 2) }`) diff --git a/src/test/shared/codelens/localLambdaRunner.test.ts b/src/test/shared/codelens/localLambdaRunner.test.ts index 826786d4a47..dd5a44ed67e 100644 --- a/src/test/shared/codelens/localLambdaRunner.test.ts +++ b/src/test/shared/codelens/localLambdaRunner.test.ts @@ -10,11 +10,13 @@ import * as path from 'path' import * as vscode from 'vscode' import { DebugConfiguration } from '../../../lambda/local/debugConfiguration' import * as localLambdaRunner from '../../../shared/codelens/localLambdaRunner' +import * as fs from '../../../shared/filesystem' import * as fsUtils from '../../../shared/filesystemUtilities' import { BasicLogger, ErrorOrString } from '../../../shared/logger' import { ChildProcessResult } from '../../../shared/utilities/childProcess' import { ExtensionDisposableFiles } from '../../../shared/utilities/disposableFiles' import { ChannelLogger } from '../../../shared/utilities/vsCodeUtils' +import { FakeExtensionContext } from '../../fakeExtensionContext' import { assertRejects } from '../utilities/assertUtils' class FakeChannelLogger implements Pick { @@ -57,8 +59,12 @@ class FakeBasicLogger implements BasicLogger { describe('localLambdaRunner', async () => { - const tempDir: string = await fsUtils.makeTemporaryToolkitFolder() - ExtensionDisposableFiles.getInstance().addFolder(tempDir) + let tempDir: string + before(async () => { + tempDir = await fsUtils.makeTemporaryToolkitFolder() + await ExtensionDisposableFiles.initialize(new FakeExtensionContext()) + ExtensionDisposableFiles.getInstance().addFolder(tempDir) + }) describe('attachDebugger', async () => { let actualRetries: number = 0 @@ -389,7 +395,9 @@ describe('localLambdaRunner', async () => { describe('makeBuildDir', () => { it ('creates a temp directory', async () => { const dir = await localLambdaRunner.makeBuildDir() - assert.strictEqual(fsUtils.fileExists(dir), true) + assert.strictEqual(await fsUtils.fileExists(dir), true) + const fsDir = await fs.readdir(dir) + assert.strictEqual(fsDir.length, 0) }) }) From 7f490042b12f36974cebe01e0a5f4ef1f1ea721f Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Fri, 19 Apr 2019 12:53:20 -0700 Subject: [PATCH 08/13] Addressing feedback --- src/shared/codelens/localLambdaRunner.ts | 9 +++++---- src/test/shared/codelens/localLambdaRunner.test.ts | 7 ++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/shared/codelens/localLambdaRunner.ts b/src/shared/codelens/localLambdaRunner.ts index 011bdb822e9..8816824d383 100644 --- a/src/shared/codelens/localLambdaRunner.ts +++ b/src/shared/codelens/localLambdaRunner.ts @@ -22,6 +22,7 @@ import { ExtensionDisposableFiles } from '../utilities/disposableFiles' import { generateDefaultHandlerConfig, HandlerConfig } from '../../lambda/config/templates' import { DebugConfiguration } from '../../lambda/local/debugConfiguration' +import { getFamily, SamLambdaRuntimeFamily } from '../../lambda/models/samLambdaRuntime' import { TelemetryService } from '../telemetry/telemetryService' import { normalizeSeparator } from '../utilities/pathUtils' import { ChannelLogger, getChannelLogger, localize } from '../utilities/vsCodeUtils' @@ -395,7 +396,7 @@ export async function makeInputTemplate(params: { if (workspaceFolder) { let relativeOriginalFunctionHandler = params.originalHandlerName - if (!params.runtime.includes('dotnet')) { + if (getFamily(params.runtime) !== SamLambdaRuntimeFamily.DotNet) { relativeOriginalFunctionHandler = normalizeSeparator( path.join( handlerFileRelativePath, @@ -410,21 +411,21 @@ export async function makeInputTemplate(params: { } let relativeFunctionHandler = params.handlerName - if (!params.runtime.includes('dotnet')) { + if (getFamily(params.runtime) !== SamLambdaRuntimeFamily.DotNet) { relativeFunctionHandler = normalizeSeparator( path.join( handlerFileRelativePath, params.handlerName, ) ) - } + } let newTemplate = new SamTemplateGenerator() .withFunctionHandler(relativeFunctionHandler) .withResourceName(TEMPLATE_RESOURCE_NAME) .withRuntime(params.runtime) - if (params.runtime.includes('dotnet')) { + if (getFamily(params.runtime) === SamLambdaRuntimeFamily.DotNet) { newTemplate.withCodeUri(existingLambda && existingLambda.codeUri ? path.join(params.templateDir, existingLambda.codeUri) : params.templateDir ) diff --git a/src/test/shared/codelens/localLambdaRunner.test.ts b/src/test/shared/codelens/localLambdaRunner.test.ts index dd5a44ed67e..d28b89fa9a4 100644 --- a/src/test/shared/codelens/localLambdaRunner.test.ts +++ b/src/test/shared/codelens/localLambdaRunner.test.ts @@ -6,6 +6,7 @@ 'use strict' import * as assert from 'assert' +import * as del from 'del' import * as path from 'path' import * as vscode from 'vscode' import { DebugConfiguration } from '../../../lambda/local/debugConfiguration' @@ -63,7 +64,10 @@ describe('localLambdaRunner', async () => { before(async () => { tempDir = await fsUtils.makeTemporaryToolkitFolder() await ExtensionDisposableFiles.initialize(new FakeExtensionContext()) - ExtensionDisposableFiles.getInstance().addFolder(tempDir) + }) + + after(async () => { + await del(tempDir, { force: true }) }) describe('attachDebugger', async () => { @@ -398,6 +402,7 @@ describe('localLambdaRunner', async () => { assert.strictEqual(await fsUtils.fileExists(dir), true) const fsDir = await fs.readdir(dir) assert.strictEqual(fsDir.length, 0) + await del(dir, { force: true }) }) }) From 9c55e1cde3bdf3cc13bb13940960ef3b1f859a32 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Fri, 19 Apr 2019 13:54:25 -0700 Subject: [PATCH 09/13] Fixing test --- src/test/shared/codelens/localLambdaRunner.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/shared/codelens/localLambdaRunner.test.ts b/src/test/shared/codelens/localLambdaRunner.test.ts index d28b89fa9a4..bac282f6b07 100644 --- a/src/test/shared/codelens/localLambdaRunner.test.ts +++ b/src/test/shared/codelens/localLambdaRunner.test.ts @@ -399,9 +399,10 @@ describe('localLambdaRunner', async () => { describe('makeBuildDir', () => { it ('creates a temp directory', async () => { const dir = await localLambdaRunner.makeBuildDir() + assert.ok(dir) assert.strictEqual(await fsUtils.fileExists(dir), true) const fsDir = await fs.readdir(dir) - assert.strictEqual(fsDir.length, 0) + assert.ok(!fsDir.length) await del(dir, { force: true }) }) }) From 86b74d9149d2a5d27ff0a419a807d121e23e4362 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Fri, 19 Apr 2019 14:05:11 -0700 Subject: [PATCH 10/13] Refixing test --- src/test/shared/codelens/localLambdaRunner.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/shared/codelens/localLambdaRunner.test.ts b/src/test/shared/codelens/localLambdaRunner.test.ts index bac282f6b07..6ea79a02aa9 100644 --- a/src/test/shared/codelens/localLambdaRunner.test.ts +++ b/src/test/shared/codelens/localLambdaRunner.test.ts @@ -402,7 +402,7 @@ describe('localLambdaRunner', async () => { assert.ok(dir) assert.strictEqual(await fsUtils.fileExists(dir), true) const fsDir = await fs.readdir(dir) - assert.ok(!fsDir.length) + assert.strictEqual(fsDir.length, 0) await del(dir, { force: true }) }) }) From b8b72ea28cd31ff5828973d8102176cb773e40a9 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Mon, 22 Apr 2019 16:12:24 -0700 Subject: [PATCH 11/13] Broke apart localLambdaRunner.makeInputTemplate call --- src/lambda/models/samLambdaRuntime.ts | 13 ++ src/shared/codelens/csharpCodeLensProvider.ts | 56 ++++++-- src/shared/codelens/localLambdaRunner.ts | 122 ++++++++---------- src/shared/codelens/pythonCodeLensProvider.ts | 43 ++++-- 4 files changed, 144 insertions(+), 90 deletions(-) diff --git a/src/lambda/models/samLambdaRuntime.ts b/src/lambda/models/samLambdaRuntime.ts index 4d8f444b4ba..eef02c1c029 100644 --- a/src/lambda/models/samLambdaRuntime.ts +++ b/src/lambda/models/samLambdaRuntime.ts @@ -90,3 +90,16 @@ export function getFamily(runtime: string | undefined): SamLambdaRuntimeFamily { } } + +export function isInterpretedRuntime(runtime: string): boolean { + switch (getFamily(runtime)) { + case SamLambdaRuntimeFamily.DotNet: + case SamLambdaRuntimeFamily.Java: + case SamLambdaRuntimeFamily.Go: + return false + case SamLambdaRuntimeFamily.NodeJS: + case SamLambdaRuntimeFamily.Python: + case SamLambdaRuntimeFamily.Ruby: + return true + } +} diff --git a/src/shared/codelens/csharpCodeLensProvider.ts b/src/shared/codelens/csharpCodeLensProvider.ts index 56d0a31917a..cb7ac342218 100644 --- a/src/shared/codelens/csharpCodeLensProvider.ts +++ b/src/shared/codelens/csharpCodeLensProvider.ts @@ -29,6 +29,9 @@ import { } from './codeLensUtils' import { executeSamBuild, + getHandlerRelativePath, + getLambdaInfoFromExistingTemplate, + getRelativeFunctionHandler, getRuntimeForLambda, invokeLambdaFunction, LambdaLocalInvokeParams, @@ -114,29 +117,54 @@ async function onLocalInvokeCommand({ templatePath: lambdaLocalInvokeParams.samTemplate.fsPath }) + // Switch over to the output channel so the user has feedback that we're getting things ready + channelLogger.channel.show(true) + + channelLogger.info( + 'AWS.output.sam.local.start', + 'Preparing to run {0} locally...', + lambdaLocalInvokeParams.handlerName + ) + try { if (!lambdaLocalInvokeParams.isDebug) { const baseBuildDir = await makeBuildDir() const templateDir = path.dirname(lambdaLocalInvokeParams.samTemplate.fsPath) const documentUri = lambdaLocalInvokeParams.document.uri - const originalHandlerName = lambdaLocalInvokeParams.handlerName + const handlerName = lambdaLocalInvokeParams.handlerName - const inputTemplate = await makeInputTemplate({ - baseBuildDir, - templateDir, - documentUri, - originalHandlerName, - handlerName: originalHandlerName, - runtime, + const handlerFileRelativePath = getHandlerRelativePath({ + codeRoot: templateDir, + filePath: documentUri.fsPath + }) + + const lambdaInfo = await getLambdaInfoFromExistingTemplate({ workspaceUri: lambdaLocalInvokeParams.workspaceFolder.uri, - channelLogger + originalHandlerName: handlerName, + runtime, + handlerFileRelativePath + }) + + const relativeFunctionHandler = getRelativeFunctionHandler({ + handlerName, + runtime, + handlerFileRelativePath + }) + + const inputTemplatePath = await makeInputTemplate({ + baseBuildDir, + codeDir: lambdaInfo && lambdaInfo.codeUri ? + path.join(templateDir, lambdaInfo.codeUri) : templateDir, + relativeFunctionHandler, + properties: lambdaInfo && lambdaInfo.resource.Properties ? lambdaInfo.resource.Properties : undefined, + runtime }) const samTemplatePath: string = await executeSamBuild({ baseBuildDir, channelLogger, - codeDir: (inputTemplate.codeDir ? path.join(templateDir, inputTemplate.codeDir) : templateDir), - inputTemplatePath: inputTemplate.inputTemplatePath, + codeDir: (lambdaInfo && lambdaInfo.codeUri ? path.join(templateDir, lambdaInfo.codeUri) : templateDir), + inputTemplatePath, samProcessInvoker: processInvoker, }) @@ -145,9 +173,9 @@ async function onLocalInvokeCommand({ channelLogger, configuration, documentUri, - originalHandlerName, - handlerName: originalHandlerName, - originalSamTemplatePath: inputTemplate.inputTemplatePath, + originalHandlerName: handlerName, + handlerName, + originalSamTemplatePath: inputTemplatePath, samTemplatePath, samTaskInvoker: taskInvoker, telemetryService, diff --git a/src/shared/codelens/localLambdaRunner.ts b/src/shared/codelens/localLambdaRunner.ts index 8816824d383..5ca155ac280 100644 --- a/src/shared/codelens/localLambdaRunner.ts +++ b/src/shared/codelens/localLambdaRunner.ts @@ -22,7 +22,7 @@ import { ExtensionDisposableFiles } from '../utilities/disposableFiles' import { generateDefaultHandlerConfig, HandlerConfig } from '../../lambda/config/templates' import { DebugConfiguration } from '../../lambda/local/debugConfiguration' -import { getFamily, SamLambdaRuntimeFamily } from '../../lambda/models/samLambdaRuntime' +import { isInterpretedRuntime } from '../../lambda/models/samLambdaRuntime' import { TelemetryService } from '../telemetry/telemetryService' import { normalizeSeparator } from '../utilities/pathUtils' import { ChannelLogger, getChannelLogger, localize } from '../utilities/vsCodeUtils' @@ -49,11 +49,6 @@ export interface OnDidSamBuildParams { isDebug: boolean } -export interface InputTemplateInfo { - inputTemplatePath: string - codeDir: string | undefined -} - const TEMPLATE_RESOURCE_NAME: string = 'awsToolkitSamLocalResource' const SAM_LOCAL_PORT_CHECK_RETRY_INTERVAL_MILLIS: number = 125 const SAM_LOCAL_PORT_CHECK_RETRY_TIMEOUT_MILLIS_DEFAULT: number = 30000 @@ -361,89 +356,80 @@ export const makeBuildDir = async (): Promise => { return buildDir } -export async function makeInputTemplate(params: { - baseBuildDir: string, - templateDir: string, - documentUri: vscode.Uri - originalHandlerName: string, +export function getHandlerRelativePath(params: { + codeRoot: string, + filePath: string +}): string { + return path.relative(params.codeRoot, path.dirname(params.filePath)) +} + +export function getRelativeFunctionHandler(params: { handlerName: string, runtime: string, - workspaceUri: vscode.Uri, - channelLogger: ChannelLogger -}): Promise { - - // Switch over to the output channel so the user has feedback that we're getting things ready - params.channelLogger.channel.show(true) - - params.channelLogger.info( - 'AWS.output.sam.local.start', - 'Preparing to run {0} locally...', - params.handlerName - ) - - const inputTemplatePath: string = path.join(params.baseBuildDir, 'input', 'input-template.yaml') - ExtensionDisposableFiles.getInstance().addFolder(inputTemplatePath) - + handlerFileRelativePath: string +}): string { // Make function handler relative to baseDir - const handlerFileRelativePath = path.relative( - params.templateDir, - path.dirname(params.documentUri.fsPath) - ) + let relativeFunctionHandler: string + if (isInterpretedRuntime(params.runtime)) { + relativeFunctionHandler = normalizeSeparator( + path.join( + params.handlerFileRelativePath, + params.handlerName, + ) + ) + } else { + relativeFunctionHandler = params.handlerName + } + + return relativeFunctionHandler +} +export async function getLambdaInfoFromExistingTemplate(params: { + workspaceUri: vscode.Uri, + originalHandlerName: string, + runtime: string, + handlerFileRelativePath: string +}): Promise { const workspaceFolder = vscode.workspace.getWorkspaceFolder(params.workspaceUri) let existingLambda: LocalLambda | undefined - let existingTemplateResource: CloudFormation.Resource | undefined if (workspaceFolder) { - let relativeOriginalFunctionHandler = params.originalHandlerName - if (getFamily(params.runtime) !== SamLambdaRuntimeFamily.DotNet) { - relativeOriginalFunctionHandler = normalizeSeparator( - path.join( - handlerFileRelativePath, - params.originalHandlerName, - ) - ) - } + const relativeOriginalFunctionHandler = getRelativeFunctionHandler({ + handlerName: params.originalHandlerName, + runtime: params.runtime, + handlerFileRelativePath: params.handlerFileRelativePath + }) const lambdas = await detectLocalLambdas([workspaceFolder]) existingLambda = lambdas.find(lambda => lambda.handler === relativeOriginalFunctionHandler) - existingTemplateResource = existingLambda ? existingLambda.resource : undefined } - let relativeFunctionHandler = params.handlerName - if (getFamily(params.runtime) !== SamLambdaRuntimeFamily.DotNet) { - relativeFunctionHandler = normalizeSeparator( - path.join( - handlerFileRelativePath, - params.handlerName, - ) - ) - } + return existingLambda +} - let newTemplate = new SamTemplateGenerator() - .withFunctionHandler(relativeFunctionHandler) +export async function makeInputTemplate(params: { + baseBuildDir: string, + codeDir: string, + relativeFunctionHandler: string, + properties?: CloudFormation.ResourceProperties, + runtime: string +}): Promise { + + const newTemplate = new SamTemplateGenerator() + .withFunctionHandler(params.relativeFunctionHandler) .withResourceName(TEMPLATE_RESOURCE_NAME) .withRuntime(params.runtime) - - if (getFamily(params.runtime) === SamLambdaRuntimeFamily.DotNet) { - newTemplate.withCodeUri(existingLambda && existingLambda.codeUri ? - path.join(params.templateDir, existingLambda.codeUri) : params.templateDir - ) - } else { - newTemplate.withCodeUri(params.templateDir) + .withCodeUri(params.codeDir) + if (params.properties && params.properties.Environment) { + newTemplate.withEnvironment(params.properties.Environment) } - if (existingTemplateResource && existingTemplateResource.Properties && - existingTemplateResource.Properties.Environment) { - newTemplate = newTemplate.withEnvironment(existingTemplateResource.Properties.Environment) - } + const inputTemplatePath: string = path.join(params.baseBuildDir, 'input', 'input-template.yaml') + ExtensionDisposableFiles.getInstance().addFolder(inputTemplatePath) await newTemplate.generate(inputTemplatePath) - return { - inputTemplatePath, - codeDir: ( existingLambda ? existingLambda.codeUri : undefined ) - } + return inputTemplatePath } export async function executeSamBuild(params: { diff --git a/src/shared/codelens/pythonCodeLensProvider.ts b/src/shared/codelens/pythonCodeLensProvider.ts index 22a323622dc..fcdf65b167f 100644 --- a/src/shared/codelens/pythonCodeLensProvider.ts +++ b/src/shared/codelens/pythonCodeLensProvider.ts @@ -30,6 +30,9 @@ import { } from './codeLensUtils' import { executeSamBuild, + getHandlerRelativePath, + getLambdaInfoFromExistingTemplate, + getRelativeFunctionHandler, getRuntimeForLambda, invokeLambdaFunction, LambdaLocalInvokeParams, @@ -190,6 +193,14 @@ export async function initialize({ const channelLogger = getChannelLogger(toolkitOutputChannel) const invokeLambda = async (args: LambdaLocalInvokeParams & { runtime: string }) => { + // Switch over to the output channel so the user has feedback that we're getting things ready + channelLogger.channel.show(true) + + channelLogger.info( + 'AWS.output.sam.local.start', + 'Preparing to run {0} locally...', + args.handlerName + ) const samProjectCodeRoot = await getSamProjectDirPathForFile(args.document.uri.fsPath) const baseBuildDir = await makeBuildDir() @@ -213,17 +224,33 @@ export async function initialize({ outputDir: baseBuildDir }) } - const inputTemplate = await makeInputTemplate({ - baseBuildDir, - templateDir: samProjectCodeRoot, - documentUri: args.document.uri, + + const handlerFileRelativePath = getHandlerRelativePath({ + codeRoot: samProjectCodeRoot, + filePath: args.document.uri.fsPath + }) + + const lambdaInfo = await getLambdaInfoFromExistingTemplate({ + workspaceUri: args.workspaceFolder.uri, originalHandlerName: args.handlerName, - handlerName, runtime: args.runtime, - workspaceUri: args.workspaceFolder.uri, - channelLogger + handlerFileRelativePath }) - const inputTemplatePath = inputTemplate.inputTemplatePath + + const relativeFunctionHandler = getRelativeFunctionHandler({ + handlerName: handlerName, + runtime: args.runtime, + handlerFileRelativePath + }) + + const inputTemplatePath = await makeInputTemplate({ + baseBuildDir, + codeDir: samProjectCodeRoot, + relativeFunctionHandler, + properties: lambdaInfo && lambdaInfo.resource.Properties ? lambdaInfo.resource.Properties : undefined, + runtime: args.runtime + }) + logger.debug(`pythonCodeLensProvider.initialize: ${ JSON.stringify({ samProjectCodeRoot, inputTemplatePath, handlerName, manifestPath }, undefined, 2) }`) From 9ea83c139ba2613fff57c550d94940fa44fc31cc Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Wed, 24 Apr 2019 15:13:34 -0700 Subject: [PATCH 12/13] Addressing PR feedback --- src/lambda/local/detectLocalLambdas.ts | 12 +------ src/lambda/models/samLambdaRuntime.ts | 13 -------- src/shared/codelens/csharpCodeLensProvider.ts | 22 ++++++------- src/shared/codelens/localLambdaRunner.ts | 33 +++++++++++-------- src/shared/codelens/pythonCodeLensProvider.ts | 10 ++++-- 5 files changed, 39 insertions(+), 51 deletions(-) diff --git a/src/lambda/local/detectLocalLambdas.ts b/src/lambda/local/detectLocalLambdas.ts index 9bfd19bc04f..8e0440e1eed 100644 --- a/src/lambda/local/detectLocalLambdas.ts +++ b/src/lambda/local/detectLocalLambdas.ts @@ -17,7 +17,6 @@ export interface LocalLambda { resource: CloudFormation.Resource templatePath: string handler?: string - codeUri?: string } export async function detectLocalLambdas( @@ -72,8 +71,7 @@ async function detectLambdasFromTemplate( templatePath, protocol: getDebugProtocol(resources[key]!), handler: getHandler(resources[key]!), - resource: resources[key]!, - codeUri: getCodeUri(resources[key]!) + resource: resources[key]! })) } @@ -102,11 +100,3 @@ function getHandler(resource: CloudFormation.Resource): string | undefined { return undefined } - -function getCodeUri(resource: CloudFormation.Resource): string | undefined { - if (resource.Properties && resource.Properties.CodeUri) { - return resource.Properties.CodeUri - } - - return undefined -} diff --git a/src/lambda/models/samLambdaRuntime.ts b/src/lambda/models/samLambdaRuntime.ts index eef02c1c029..4d8f444b4ba 100644 --- a/src/lambda/models/samLambdaRuntime.ts +++ b/src/lambda/models/samLambdaRuntime.ts @@ -90,16 +90,3 @@ export function getFamily(runtime: string | undefined): SamLambdaRuntimeFamily { } } - -export function isInterpretedRuntime(runtime: string): boolean { - switch (getFamily(runtime)) { - case SamLambdaRuntimeFamily.DotNet: - case SamLambdaRuntimeFamily.Java: - case SamLambdaRuntimeFamily.Go: - return false - case SamLambdaRuntimeFamily.NodeJS: - case SamLambdaRuntimeFamily.Python: - case SamLambdaRuntimeFamily.Ruby: - return true - } -} diff --git a/src/shared/codelens/csharpCodeLensProvider.ts b/src/shared/codelens/csharpCodeLensProvider.ts index cb7ac342218..ea4948fcb31 100644 --- a/src/shared/codelens/csharpCodeLensProvider.ts +++ b/src/shared/codelens/csharpCodeLensProvider.ts @@ -138,32 +138,32 @@ async function onLocalInvokeCommand({ filePath: documentUri.fsPath }) - const lambdaInfo = await getLambdaInfoFromExistingTemplate({ - workspaceUri: lambdaLocalInvokeParams.workspaceFolder.uri, - originalHandlerName: handlerName, - runtime, - handlerFileRelativePath - }) - const relativeFunctionHandler = getRelativeFunctionHandler({ handlerName, runtime, handlerFileRelativePath }) + const lambdaInfo = await getLambdaInfoFromExistingTemplate({ + workspaceUri: lambdaLocalInvokeParams.workspaceFolder.uri, + relativeOriginalFunctionHandler: relativeFunctionHandler + }) + + const properties = lambdaInfo ? lambdaInfo.resource.Properties : undefined + const codeDir = properties ? path.join(templateDir, properties.CodeUri) : templateDir + const inputTemplatePath = await makeInputTemplate({ baseBuildDir, - codeDir: lambdaInfo && lambdaInfo.codeUri ? - path.join(templateDir, lambdaInfo.codeUri) : templateDir, + codeDir, relativeFunctionHandler, - properties: lambdaInfo && lambdaInfo.resource.Properties ? lambdaInfo.resource.Properties : undefined, + properties, runtime }) const samTemplatePath: string = await executeSamBuild({ baseBuildDir, channelLogger, - codeDir: (lambdaInfo && lambdaInfo.codeUri ? path.join(templateDir, lambdaInfo.codeUri) : templateDir), + codeDir, inputTemplatePath, samProcessInvoker: processInvoker, }) diff --git a/src/shared/codelens/localLambdaRunner.ts b/src/shared/codelens/localLambdaRunner.ts index 5ca155ac280..82ae5130c44 100644 --- a/src/shared/codelens/localLambdaRunner.ts +++ b/src/shared/codelens/localLambdaRunner.ts @@ -22,7 +22,7 @@ import { ExtensionDisposableFiles } from '../utilities/disposableFiles' import { generateDefaultHandlerConfig, HandlerConfig } from '../../lambda/config/templates' import { DebugConfiguration } from '../../lambda/local/debugConfiguration' -import { isInterpretedRuntime } from '../../lambda/models/samLambdaRuntime' +import { getFamily, SamLambdaRuntimeFamily } from '../../lambda/models/samLambdaRuntime' import { TelemetryService } from '../telemetry/telemetryService' import { normalizeSeparator } from '../utilities/pathUtils' import { ChannelLogger, getChannelLogger, localize } from '../utilities/vsCodeUtils' @@ -370,7 +370,7 @@ export function getRelativeFunctionHandler(params: { }): string { // Make function handler relative to baseDir let relativeFunctionHandler: string - if (isInterpretedRuntime(params.runtime)) { + if (shouldAppendRelativePathToFunctionHandler(params.runtime)) { relativeFunctionHandler = normalizeSeparator( path.join( params.handlerFileRelativePath, @@ -386,22 +386,13 @@ export function getRelativeFunctionHandler(params: { export async function getLambdaInfoFromExistingTemplate(params: { workspaceUri: vscode.Uri, - originalHandlerName: string, - runtime: string, - handlerFileRelativePath: string + relativeOriginalFunctionHandler: string }): Promise { const workspaceFolder = vscode.workspace.getWorkspaceFolder(params.workspaceUri) let existingLambda: LocalLambda | undefined if (workspaceFolder) { - - const relativeOriginalFunctionHandler = getRelativeFunctionHandler({ - handlerName: params.originalHandlerName, - runtime: params.runtime, - handlerFileRelativePath: params.handlerFileRelativePath - }) - const lambdas = await detectLocalLambdas([workspaceFolder]) - existingLambda = lambdas.find(lambda => lambda.handler === relativeOriginalFunctionHandler) + existingLambda = lambdas.find(lambda => lambda.handler === params.relativeOriginalFunctionHandler) } return existingLambda @@ -740,3 +731,19 @@ function getAttachDebuggerMaxRetryLimit( defaultValue )! } + +export function shouldAppendRelativePathToFunctionHandler(runtime: string): boolean { + // getFamily will throw an error if the runtime doesn't exist + switch (getFamily(runtime)) { + case SamLambdaRuntimeFamily.NodeJS: + case SamLambdaRuntimeFamily.Python: + case SamLambdaRuntimeFamily.Ruby: + return true + case SamLambdaRuntimeFamily.DotNet: + case SamLambdaRuntimeFamily.Java: + case SamLambdaRuntimeFamily.Go: + // if the runtime exists but for some reason we forgot to cover it here + default: + return false + } +} diff --git a/src/shared/codelens/pythonCodeLensProvider.ts b/src/shared/codelens/pythonCodeLensProvider.ts index fcdf65b167f..e0617b79359 100644 --- a/src/shared/codelens/pythonCodeLensProvider.ts +++ b/src/shared/codelens/pythonCodeLensProvider.ts @@ -230,9 +230,8 @@ export async function initialize({ filePath: args.document.uri.fsPath }) - const lambdaInfo = await getLambdaInfoFromExistingTemplate({ - workspaceUri: args.workspaceFolder.uri, - originalHandlerName: args.handlerName, + const relativeOriginalFunctionHandler = getRelativeFunctionHandler({ + handlerName: args.handlerName, runtime: args.runtime, handlerFileRelativePath }) @@ -243,6 +242,11 @@ export async function initialize({ handlerFileRelativePath }) + const lambdaInfo = await getLambdaInfoFromExistingTemplate({ + workspaceUri: args.workspaceFolder.uri, + relativeOriginalFunctionHandler + }) + const inputTemplatePath = await makeInputTemplate({ baseBuildDir, codeDir: samProjectCodeRoot, From 712fd03d520c3c1fd2fecbc16009aa80776ddf30 Mon Sep 17 00:00:00 2001 From: Bryce Ito Date: Fri, 26 Apr 2019 09:11:25 -0700 Subject: [PATCH 13/13] Throwing error if runtime doesn't have its relative path appended to the handler --- src/shared/codelens/localLambdaRunner.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shared/codelens/localLambdaRunner.ts b/src/shared/codelens/localLambdaRunner.ts index 82ae5130c44..d642fa0ca1b 100644 --- a/src/shared/codelens/localLambdaRunner.ts +++ b/src/shared/codelens/localLambdaRunner.ts @@ -742,8 +742,9 @@ export function shouldAppendRelativePathToFunctionHandler(runtime: string): bool case SamLambdaRuntimeFamily.DotNet: case SamLambdaRuntimeFamily.Java: case SamLambdaRuntimeFamily.Go: - // if the runtime exists but for some reason we forgot to cover it here - default: return false + // if the runtime exists but for some reason we forgot to cover it here, throw anyway so we remember to cover it + default: + throw new Error('localLambdaRunner can not determine if runtime requires a relative path.') } }