in packages/core/src/shared/sam/debugger/awsSamDebugger.ts [393:685]
public async makeConfig(
folder: vscode.WorkspaceFolder | undefined,
config: AwsSamDebuggerConfiguration,
token?: vscode.CancellationToken
): Promise<SamLaunchRequestArgs> {
if (token?.isCancellationRequested) {
throw new ToolkitError('Cancellation requested', { cancelled: true })
}
folder =
folder ?? (vscode.workspace.workspaceFolders?.length ? vscode.workspace.workspaceFolders[0] : undefined)
if (!folder) {
const message = localize(
'AWS.sam.debugger.noWorkspace',
'Choose a workspace, then try again',
getIdeProperties().company
)
throw new SamLaunchRequestError(message, { code: 'NoWorkspaceFolder', extraButtons: [] })
}
// 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) {
const message = localize(
'AWS.sam.debugger.noLaunchJson',
'To debug a Lambda locally, create a launch.json from the Run panel, then select a configuration.'
)
throw new SamLaunchRequestError(message, {
code: 'NoLaunchConfig',
extraButtons: [
{
label: localize('AWS.gotoRunPanel', 'Run panel'),
onClick: () => vscode.commands.executeCommand('workbench.view.debug'),
},
],
})
} else {
const registry = await globals.templateRegistry
const rv = await configValidator.validate(config, registry)
if (!rv.isValid) {
throw new ToolkitError(`Invalid launch configuration: ${rv.message}`, { code: 'BadLaunchConfig' })
} else if (rv.message) {
void vscode.window.showInformationMessage(rv.message)
}
getLogger().verbose(`SAM debug: config %s:`, config.name)
}
const editor = vscode.window.activeTextEditor
const templateInvoke = config.invokeTarget as TemplateTargetProperties
const template = await getTemplate(folder, config)
const templateResource = await getTemplateResource(folder, config)
const codeRoot = await 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 = await getHandlerName(folder, config)
config.baseBuildDir = resolve(folder.uri.fsPath, config.sam?.buildDir ?? (await makeTemporaryToolkitFolder()))
await fs.mkdir(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 = getInputTemplatePath(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, minSamCliVersionForImageSupport)) {
const message = localize(
'AWS.output.sam.no.image.support',
'Support for Image-based Lambdas requires a minimum SAM CLI version of 1.13.0.'
)
throw new SamLaunchRequestError(message, { code: 'UnsupportedSamVersion', details: { samCliVersion } })
}
}
if (!runtime) {
const message = 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
)
throw new SamLaunchRequestError(message, { code: 'MissingRuntime' })
}
// 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, minSamCliVersionForGoSupport)) {
void 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.',
minSamCliVersionForGoSupport
)
)
config.noDebug = true
}
}
const runtimeFamily = getFamily(runtime)
// use region in debug config first, if not found, fall back to toolkit default region.
const region = config.aws?.region ?? this.ctx.awsContext.getCredentialDefaultRegion()
const documentUri =
vscode.window.activeTextEditor?.document.uri ??
// XXX: don't know what URI to choose...
vscode.Uri.parse(templateInvoke.templatePath!)
let awsCredentials = await this.ctx.awsContext.getCredentials()
if (!awsCredentials && !config.aws?.credentials) {
getLogger().warn('SAM debug: missing AWS credentials (Toolkit is not connected)')
} else if (config.aws?.credentials) {
// "aws.credentials" defined in the launch-config takes precedence
// over Toolkit's current active credentials.
let fromStore: Credentials | undefined
try {
const credentialsId = fromString(config.aws.credentials)
fromStore = await getCredentialsFromStore(credentialsId, this.ctx.credentialsStore)
} catch {
getLogger().error(`SAM debug: fromString('${config.aws.credentials}') failed`)
}
if (fromStore) {
awsCredentials = fromStore
} else {
const credentialsId = config.aws.credentials
const getHelp = localize('AWS.generic.message.getHelp', 'Get Help...')
throw new SamLaunchRequestError(`Invalid credentials found in launch configuration: ${credentialsId}`, {
code: 'InvalidCredentials',
extraButtons: [
{
label: getHelp,
onClick: () => openUrl(vscode.Uri.parse(credentialHelpUrl)),
},
],
})
}
}
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 as Runtime,
runtimeFamily: runtimeFamily,
handlerName: handlerName,
documentUri: documentUri,
templatePath: pathutil.normalize(templateInvoke?.templatePath),
eventPayloadFile: '', // Populated by makeConfig().
envFile: '', // Populated by makeConfig().
apiPort: apiPort,
debugPort: debugPort,
invokeTarget: {
...config.invokeTarget,
},
lambda: {
...config.lambda,
memoryMb: lambdaMemory,
timeoutSec: lambdaTimeout,
environmentVariables: { ...config.lambda?.environmentVariables },
},
region: region,
awsCredentials: awsCredentials,
parameterOverrides: parameterOverrideArr,
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.DotNet: {
// 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: {
const message = localize(
'AWS.sam.debugger.invalidRuntime',
'Unknown or unsupported runtime: {0}',
runtime
)
throw new ToolkitError(message, { code: 'UnsupportedRuntime' })
}
}
// generate template for target=code
if (launchConfig.invokeTarget.target === 'code') {
const codeConfig = launchConfig as SamLaunchRequestArgs & { invokeTarget: { target: 'code' } }
await makeInputTemplate(codeConfig)
}
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
}