in src/shared/sam/debugger/awsSamDebugger.ts [330:603]
public async makeConfig(
folder: vscode.WorkspaceFolder | undefined,
config: AwsSamDebuggerConfiguration,
token?: vscode.CancellationToken
): Promise<SamLaunchRequestArgs | undefined> {
if (token?.isCancellationRequested) {
return undefined
}
folder =
folder ?? (vscode.workspace.workspaceFolders?.length ? vscode.workspace.workspaceFolders[0] : undefined)
if (!folder) {
getLogger().error(`SAM debug: no workspace folder`)
vscode.window.showErrorMessage(
localize(
'AWS.sam.debugger.noWorkspace',
'{0} SAM debug: choose a workspace, then try again',
getIdeProperties().company
)
)
return undefined
}
// If "request" field is missing this means launch.json does not exist.
// User/vscode expects us to dynamically decide defaults if possible.
const hasLaunchJson = !!config.request
const configValidator: AwsSamDebugConfigurationValidator = new DefaultAwsSamDebugConfigurationValidator(folder)
if (!hasLaunchJson) {
vscode.window
.showErrorMessage(
localize(
'AWS.sam.debugger.noLaunchJson',
'{0} SAM: To debug a Lambda locally, create a launch.json from the Run panel, then select a configuration.',
getIdeProperties().company
),
localize('AWS.gotoRunPanel', 'Run panel')
)
.then(async result => {
if (!result) {
return
}
await vscode.commands.executeCommand('workbench.view.debug')
})
return undefined
} else {
const rv = configValidator.validate(config)
if (!rv.isValid) {
getLogger().error(`SAM debug: invalid config: ${rv.message!}`)
vscode.window.showErrorMessage(rv.message!)
return undefined
} else if (rv.message) {
vscode.window.showInformationMessage(rv.message)
}
getLogger().verbose(`SAM debug: config: ${JSON.stringify(config.name)}`)
}
const editor = vscode.window.activeTextEditor
const templateInvoke = config.invokeTarget as TemplateTargetProperties
const template = getTemplate(folder, config)
const templateResource = getTemplateResource(folder, config)
const codeRoot = getCodeRoot(folder, config)
const architecture = getArchitecture(template, templateResource, config.invokeTarget)
// Handler is the only field that we need to parse refs for.
// This is necessary for Python debugging since we have to create the temporary entry file
// Other refs can fail; SAM will handle them.
const handlerName = getHandlerName(folder, config)
config.baseBuildDir = resolve(folder.uri.fsPath, config.sam?.buildDir ?? (await makeTemporaryToolkitFolder()))
fs.ensureDir(config.baseBuildDir)
if (templateInvoke?.templatePath) {
// Normalize to absolute path.
// TODO: If path is relative, it is relative to launch.json (i.e. .vscode directory).
templateInvoke.templatePath = pathutil.normalize(tryGetAbsolutePath(folder, templateInvoke.templatePath))
} else if (config.invokeTarget.target === 'code') {
const codeConfig = config as SamLaunchRequestArgs & { invokeTarget: { target: 'code' } }
// 'projectRoot' may be a relative path
// Older code left this property as relative, but there's no benefit in doing that since it's relative to the workspace
codeConfig.invokeTarget.projectRoot = pathutil.normalize(
resolve(folder.uri.fsPath, config.invokeTarget.projectRoot)
)
templateInvoke.templatePath = await makeInputTemplate(codeConfig)
}
const isZip = CloudFormation.isZipLambdaResource(templateResource?.Properties)
const runtime: string | undefined =
config.lambda?.runtime ??
(template && isZip
? CloudFormation.getStringForProperty(templateResource?.Properties, 'Runtime', template)
: undefined) ??
getDefaultRuntime(getRuntimeFamily(editor?.document?.languageId ?? 'unknown'))
const lambdaMemory =
(template
? CloudFormation.getNumberForProperty(templateResource?.Properties, 'MemorySize', template)
: undefined) ?? config.lambda?.memoryMb
const lambdaTimeout =
(template
? CloudFormation.getNumberForProperty(templateResource?.Properties, 'Timeout', template)
: undefined) ?? config.lambda?.timeoutSec
// TODO: Remove this when min sam version is > 1.13.0
if (!isZip) {
const samCliVersion = await getSamCliVersion(this.ctx.samCliContext())
if (semver.lt(samCliVersion, MINIMUM_SAM_CLI_VERSION_INCLUSIVE_FOR_IMAGE_SUPPORT)) {
getLogger().error(`SAM debug: version (${samCliVersion}) too low for Image lambdas: ${config})`)
vscode.window.showErrorMessage(
localize(
'AWS.output.sam.no.image.support',
'Support for Image-based Lambdas requires a minimum SAM CLI version of 1.13.0.'
)
)
return undefined
}
}
if (!runtime) {
getLogger().error(`SAM debug: failed to launch config (missing runtime): ${config})`)
vscode.window.showErrorMessage(
localize(
'AWS.sam.debugger.failedLaunch.missingRuntime',
'Toolkit could not infer a runtime for config: {0}. Add a "lambda.runtime" field to your launch configuration.',
config.name
)
)
return undefined
}
// SAM CLI versions before 1.18.1 do not work correctly for Go debugging.
// TODO: remove this when min sam version is >= 1.18.1
if (goRuntimes.includes(runtime) && !config.noDebug) {
const samCliVersion = await getSamCliVersion(this.ctx.samCliContext())
if (semver.lt(samCliVersion, MINIMUM_SAM_CLI_VERSION_INCLUSIVE_FOR_GO_SUPPORT)) {
vscode.window.showWarningMessage(
localize(
'AWS.output.sam.local.no.go.support',
'Debugging go1.x lambdas requires a minimum SAM CLI version of {0}. Function will run locally without debug.',
MINIMUM_SAM_CLI_VERSION_INCLUSIVE_FOR_GO_SUPPORT
)
)
config.noDebug = true
}
}
const runtimeFamily = getFamily(runtime)
const documentUri =
vscode.window.activeTextEditor?.document.uri ??
// XXX: don't know what URI to choose...
vscode.Uri.parse(templateInvoke.templatePath!)
let awsCredentials: Credentials | undefined
if (config.aws?.credentials) {
const credentials = fromString(config.aws.credentials)
try {
awsCredentials = await getCredentialsFromStore(credentials, this.ctx.credentialsStore)
} catch (err) {
getLogger().error(err as Error)
notifyUserInvalidCredentials(credentials)
return undefined
}
}
if (config.api) {
config.api.headers = {
'content-type': 'application/json',
...(config.api.headers ? config.api.headers : {}),
}
}
let parameterOverrideArr: string[] | undefined
const params = config.sam?.template?.parameters
if (params) {
parameterOverrideArr = []
for (const key of Object.keys(params)) {
parameterOverrideArr.push(`${key}=${params[key].toString()}`)
}
}
// TODO: Let the OS (or SAM CLI) assign the port, then we need to
// scrape SAM CLI to find the port that was actually used?
const apiPort = config.invokeTarget.target === 'api' ? await getStartPort() : undefined
const debugPort = config.noDebug ? undefined : await getStartPort(apiPort ? apiPort + 1 : undefined)
let launchConfig: SamLaunchRequestArgs = {
...config,
request: 'attach',
codeRoot: codeRoot ?? '',
workspaceFolder: folder,
runtime: runtime,
runtimeFamily: runtimeFamily,
handlerName: handlerName,
documentUri: documentUri,
templatePath: pathutil.normalize(templateInvoke?.templatePath),
eventPayloadFile: '', // Populated by makeConfig().
envFile: '', // Populated by makeConfig().
apiPort: apiPort,
debugPort: debugPort,
lambda: {
...config.lambda,
memoryMb: lambdaMemory,
timeoutSec: lambdaTimeout,
environmentVariables: { ...config.lambda?.environmentVariables },
},
awsCredentials: awsCredentials,
parameterOverrides: parameterOverrideArr,
useIkpdb: isCloud9() || !!(config as any).useIkpdb,
architecture: architecture,
}
//
// Configure and launch.
//
// 1. prepare a bunch of arguments
// 2. do `sam build`
// 3. do `sam local invoke`
//
switch (launchConfig.runtimeFamily) {
case RuntimeFamily.NodeJS: {
// Make a NodeJS launch-config from the generic config.
launchConfig = await tsDebug.makeTypescriptConfig(launchConfig)
break
}
case RuntimeFamily.Python: {
// Make a Python launch-config from the generic config.
launchConfig = await pythonDebug.makePythonDebugConfig(launchConfig)
break
}
case RuntimeFamily.DotNetCore: {
// Make a DotNet launch-config from the generic config.
launchConfig = await csharpDebug.makeCsharpConfig(launchConfig)
break
}
case RuntimeFamily.Go: {
launchConfig = await goDebug.makeGoConfig(launchConfig)
break
}
case RuntimeFamily.Java: {
// Make a Java launch-config from the generic config.
launchConfig = await javaDebug.makeJavaConfig(launchConfig)
break
}
default: {
getLogger().error(`SAM debug: unknown runtime: ${runtime})`)
vscode.window.showErrorMessage(
localize(
'AWS.sam.debugger.invalidRuntime',
'{0} SAM debug: unknown runtime: {1}',
getIdeProperties().company,
runtime
)
)
return undefined
}
}
await makeJsonFiles(launchConfig)
// Set the type, then vscode will pass the config to SamDebugSession.attachRequest().
// (Registered in sam/activation.ts which calls registerDebugAdapterDescriptorFactory()).
// By this point launchConfig.request is now set to "attach" (not "direct-invoke").
launchConfig.type = AWS_SAM_DEBUG_TYPE
if (launchConfig.request !== 'attach' && launchConfig.request !== 'launch') {
// The "request" field must be updated so that it routes to the
// DebugAdapter (SamDebugSession.attachRequest()), else this will
// just cycle back (and it indicates a bug in the config logic).
throw Error(
`resolveDebugConfiguration: launchConfig was not correctly resolved before return: ${JSON.stringify(
launchConfig
)}`
)
}
return launchConfig
}