| |
| |
| |
| |
|
|
| import * as os from 'os' |
| import * as path from 'path' |
| import * as vscode from 'vscode' |
| import { GoDebugConfiguration, goDebuggerPath, isImageLambdaConfig } from '../../../lambda/local/debugConfiguration' |
| import { RuntimeFamily } from '../../../lambda/models/samLambdaRuntime' |
| import * as pathutil from '../../../shared/utilities/pathUtils' |
| import { ExtContext } from '../../extensions' |
| import { findParentProjectFile } from '../../utilities/workspaceUtils' |
| import { DefaultSamLocalInvokeCommand, waitForDebuggerMessages } from '../cli/samCliLocalInvoke' |
| import { runLambdaFunction } from '../localLambdaRunner' |
| import { SamLaunchRequestArgs } from './awsSamDebugger' |
| import { getLogger } from '../../logger/logger' |
| import fs from '../../fs/fs' |
| import { ChildProcess } from '../../utilities/processUtils' |
| import { Timeout } from '../../utilities/timeoutUtils' |
| import { SpawnOptions } from 'child_process' |
| import * as nls from 'vscode-nls' |
| import { sleep } from '../../utilities/timeoutUtils' |
| import globals from '../../extensionGlobals' |
| import { ToolkitError } from '../../errors' |
| const localize = nls.loadMessageBundle() |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function invokeGoLambda(ctx: ExtContext, config: GoDebugConfiguration): Promise<GoDebugConfiguration> { |
| config.samLocalInvokeCommand = new DefaultSamLocalInvokeCommand([waitForDebuggerMessages.GO_DELVE]) |
| |
| config.onWillAttachDebugger = waitForDelve |
|
|
| if (!config.noDebug && !(await installDebugger(config.debuggerPath!))) { |
| throw new ToolkitError( |
| localize('AWS.sam.debugger.godelve.failed', 'Failed to install Delve for the lambda container.'), |
| { |
| code: 'NoDelveInstallation', |
| } |
| ) |
| } |
|
|
| const c = (await runLambdaFunction(ctx, config, async () => {})) as GoDebugConfiguration |
| return c |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async function waitForDelve(debugPort: number, timeout: Timeout) { |
| await sleep(1000) |
| } |
|
|
| export async function getSamProjectDirPathForFile(filepath: string): Promise<string> { |
| const modulePath = await findParentProjectFile(vscode.Uri.parse(filepath), /^go\.mod$/) |
| if (!modulePath) { |
| throw new Error(`Cannot find go.mod for: ${filepath}`) |
| } |
|
|
| return path.dirname(modulePath.fsPath) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export async function makeGoConfig(config: SamLaunchRequestArgs): Promise<GoDebugConfiguration> { |
| if (!config.baseBuildDir) { |
| throw Error('invalid state: config.baseBuildDir was not set') |
| } |
|
|
| config.codeRoot = |
| config.codeRoot ?? |
| pathutil.normalize(await getSamProjectDirPathForFile(config?.templatePath ?? config.documentUri!.fsPath)) |
|
|
| if (!config.codeRoot) { |
| throw Error('missing launch.json, template.yaml, and failed to discover project root') |
| } |
|
|
| const port: number = config.debugPort ?? -1 |
|
|
| config.codeRoot = pathutil.normalize(config.codeRoot) |
|
|
| |
| config.debuggerPath = path.join(globals.context.globalStorageUri.fsPath, 'debuggers', 'delve') |
|
|
| const isImageLambda = await isImageLambdaConfig(config) |
|
|
| |
| |
| |
| if (isImageLambda && !config.noDebug) { |
| config.containerEnvVars = { |
| _AWS_LAMBDA_GO_DEBUGGING: '1', |
| _AWS_LAMBDA_GO_DELVE_API_VERSION: '2', |
| _AWS_LAMBDA_GO_DELVE_PATH: path.posix.join(goDebuggerPath, 'dlv'), |
| _AWS_LAMBDA_GO_DELVE_LISTEN_PORT: port.toString(), |
| } |
| } |
|
|
| |
| const goLaunchConfig: GoDebugConfiguration = { |
| ...config, |
| type: 'go', |
| processName: 'godelve', |
| request: 'attach', |
| mode: config.noDebug ? undefined : 'remote', |
| runtimeFamily: RuntimeFamily.Go, |
| preLaunchTask: undefined, |
| host: 'localhost', |
| port: port, |
| skipFiles: [], |
| debugArgs: isImageLambda || config.noDebug ? undefined : ['-delveAPI=2'], |
| debugAdapter: 'legacy', |
| } |
|
|
| return goLaunchConfig |
| } |
|
|
| interface InstallScript { |
| path: string |
| options?: SpawnOptions |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function makeInstallScript(debuggerPath: string, isWindows: boolean): Promise<InstallScript | undefined> { |
| let script: string = isWindows ? '' : '#!/bin/sh\n' |
| const delveRepo: string = 'github.com/go-delve/delve' |
| const scriptExt: string = isWindows ? 'cmd' : 'sh' |
| const delvePath: string = path.posix.join(debuggerPath, 'dlv') |
| const installOptions: SpawnOptions = { env: { ...process.env } } |
| let delveVersion: string = '' |
|
|
| |
| |
| |
| installOptions.env!['GO111MODULE'] = 'off' |
|
|
| async function getDelveVersion(repo: string, silent: boolean): Promise<string> { |
| try { |
| return ( |
| await ChildProcess.run('git', ['-C', repo, 'describe', '--tags', '--abbrev=0'], { collect: true }) |
| ).stdout.trim() |
| } catch (e) { |
| if (!silent) { |
| throw e |
| } |
| return '' |
| } |
| } |
|
|
| |
| try { |
| const result = await ChildProcess.run('go', ['env', '-json'], { collect: true }) |
| const goPath: string = JSON.parse(result.stdout).GOPATH |
| let repoPath: string = path.join(goPath, 'src', delveRepo) |
|
|
| if (!getDelveVersion(repoPath, true)) { |
| getLogger().info( |
| localize( |
| 'AWS.sam.debugger.godelve.download', |
| 'The Delve repo was not found in your GOPATH. Downloading in a temporary directory...' |
| ) |
| ) |
| installOptions.env!['GOPATH'] = debuggerPath |
| repoPath = path.join(debuggerPath, 'src', delveRepo) |
| const args = ['get', '-d', `${delveRepo}/cmd/dlv`] |
| const out = await ChildProcess.run('go', args, { ...(installOptions as any), collect: true }) |
| getLogger().debug('"go %O": %s', args, out) |
| } |
|
|
| delveVersion = await getDelveVersion(repoPath, false) |
| } catch (e) { |
| getLogger().debug('Failed to get latest Delve version: %O', e as Error) |
| } |
|
|
| delveVersion = delveVersion.replace('v', '-') |
| const installScriptPath: string = path.join(debuggerPath, `install${delveVersion}.${scriptExt}`) |
| const alreadyInstalled = await fs.exists(installScriptPath) |
|
|
| if (alreadyInstalled && delveVersion !== '') { |
| return undefined |
| } |
|
|
| installOptions.env!['GOARCH'] = 'amd64' |
| installOptions.env!['GOOS'] = 'linux' |
|
|
| script += `go build -o "${delvePath}" "${delveRepo}/cmd/dlv"\n` |
|
|
| await fs.writeFile(installScriptPath, script, 'utf8') |
| await fs.chmod(installScriptPath, 0o755) |
|
|
| return { path: installScriptPath, options: installOptions } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function installDebugger(debuggerPath: string): Promise<boolean> { |
| await fs.mkdir(debuggerPath) |
| const isWindows: boolean = os.platform() === 'win32' |
| let installScript: InstallScript | undefined |
|
|
| try { |
| installScript = await makeInstallScript(debuggerPath, isWindows) |
|
|
| if (!installScript) { |
| return true |
| } |
|
|
| const childProcess = new ChildProcess(installScript.path, [], { spawnOptions: installScript.options }) |
| const install = await childProcess.run({ |
| onStdout: (text: string) => getLogger().info(`[Delve install script] -> ${text}`), |
| onStderr: (text: string) => getLogger().error(`[Delve install script] -> ${text}`), |
| }) |
|
|
| const code = install.exitCode |
| if (!(await fs.exists(path.join(debuggerPath, 'dlv')))) { |
| throw new Error(`Install script did not generate the Delve binary: exit code ${code}`) |
| } else if (code) { |
| getLogger().warn(`Install script did not sucessfully run, using old Delve binary...`) |
| } else { |
| getLogger().info(`Installed Delve debugger in ${debuggerPath}`) |
| } |
| } catch (e) { |
| if (installScript && (await fs.exists(installScript.path))) { |
| await fs.delete(installScript.path) |
| } |
| getLogger().error('Failed to cross-compile Delve debugger: %O', e as Error) |
| return false |
| } |
|
|
| return true |
| } |
|
|