export async function makePythonDebugConfig()

in src/shared/sam/debugger/pythonSamDebug.ts [79:212]


export async function makePythonDebugConfig(
    config: SamLaunchRequestArgs
): Promise<PythonDebugConfiguration | PythonCloud9DebugConfiguration> {
    if (!config.baseBuildDir) {
        throw Error('invalid state: config.baseBuildDir was not set')
    }
    if (!config.codeRoot) {
        // Last-resort attempt to discover the project root (when there is no
        // `launch.json` nor `template.yaml`).
        config.codeRoot = await getSamProjectDirPathForFile(config?.templatePath ?? config.documentUri!.fsPath)
        if (!config.codeRoot) {
            // TODO: return error and show it at the caller.
            throw Error('missing launch.json, template.yaml, and failed to discover project root')
        }
    }
    config.codeRoot = pathutil.normalize(config.codeRoot)

    let manifestPath: string | undefined
    if (!config.noDebug) {
        const isImageLambda = isImageLambdaConfig(config)

        if (!config.useIkpdb) {
            // Mounted in the Docker container as: /tmp/lambci_debug_files
            config.debuggerPath = globals.context.asAbsolutePath(path.join('resources', 'debugger'))
            // NOTE: SAM CLI splits on each *single* space in `--debug-args`!
            //       Extra spaces will be passed as spurious "empty" arguments :(
            const debugArgs = `${DEBUGPY_WRAPPER_PATH} --listen 0.0.0.0:${config.debugPort} --wait-for-client --log-to-stderr`
            if (isImageLambda) {
                const params = getPythonExeAndBootstrap(config.runtime)
                config.debugArgs = [`${params.python} ${debugArgs} ${params.boostrap}`]
            } else {
                config.debugArgs = [debugArgs]
            }
        } else {
            // -ikpdb-log:  https://ikpdb.readthedocs.io/en/1.x/api.html?highlight=log#ikpdb.IKPdbLogger
            //    n,N: Network  (noisy)
            //    b,B: Breakpoints
            //    e,E: Expression evaluation
            //    x,X: Execution
            //    f,F: Frame
            //    p,P: Path manipulation
            //    g,G: Global debugger
            //
            // Level "G" is not too noisy, and is required because it emits the
            // "IKP3db listening on" string (`WAIT_FOR_DEBUGGER_MESSAGES`).
            const logArg = getLogger().logLevelEnabled('debug') ? '--ikpdb-log=BEXFPG' : '--ikpdb-log=G'
            const ccwd = pathutil.normalize(
                getWorkspaceRelativePath(config.codeRoot, { workspaceFolders: [config.workspaceFolder] }) ?? 'error'
            )

            // NOTE: SAM CLI splits on each *single* space in `--debug-args`!
            //       Extra spaces will be passed as spurious "empty" arguments :(
            //
            // -u: (python arg) unbuffered binary stdout/stderr
            //
            // -ik_ccwd: Must be relative to /var/task, because ikpdb tries to
            //           resolve filepaths in the Docker container and produces
            //           nonsense as a "fallback". See `ikp3db.py:normalize_path_in()`:
            //           https://github.com/cmorisse/ikp3db/blob/eda176a1d4e0b1167466705a26ae4dd5c4188d36/ikp3db.py#L659
            // --ikpdb-protocol=vscode:
            //           For https://github.com/cmorisse/vscode-ikp3db
            //           Requires ikp3db 1.5 (unreleased): https://github.com/cmorisse/ikp3db/pull/12
            config.debugArgs = [
                `-m ikp3db --ikpdb-address=0.0.0.0 --ikpdb-port=${config.debugPort} -ik_ccwd=${ccwd} -ik_cwd=/var/task ${logArg}`,
            ]
        }

        manifestPath = await makePythonDebugManifest({
            isImageLambda: isImageLambda,
            samProjectCodeRoot: config.codeRoot,
            outputDir: config.baseBuildDir,
            useIkpdb: !!config.useIkpdb,
        })
    }

    let pathMappings: PythonPathMapping[]
    if (config.lambda?.pathMappings !== undefined) {
        pathMappings = config.lambda.pathMappings
    } else {
        pathMappings = getLocalRootVariants(config.codeRoot).map<PythonPathMapping>(variant => {
            return {
                localRoot: variant,
                remoteRoot: '/var/task',
            }
        })
    }

    if (config.useIkpdb) {
        // Documentation:
        // https://github.com/cmorisse/vscode-ikp3db/blob/master/documentation/debug_configurations_reference.md
        return {
            ...config,
            type: 'ikp3db',
            request: config.noDebug ? 'launch' : 'attach',
            runtimeFamily: RuntimeFamily.Python,
            manifestPath: manifestPath,
            sam: {
                ...config.sam,
                // Needed to build ikp3db which has a C build step.
                // https://github.com/aws/aws-sam-cli/issues/1840
                containerBuild: true,
            },

            // cloud9 debugger fields:
            port: config.debugPort ?? -1,
            localRoot: config.codeRoot,
            remoteRoot: '/var/task',
            address: 'localhost',
        }
    }

    // Make debugpy output log information if our loglevel is at 'debug'
    if (!config.noDebug && getLogger().logLevelEnabled('debug')) {
        config.debugArgs![0] += ' --debug'
    }

    return {
        ...config,
        type: 'python',
        request: config.noDebug ? 'launch' : 'attach',
        runtimeFamily: RuntimeFamily.Python,

        //
        // Python-specific fields.
        //
        manifestPath: manifestPath,
        port: config.debugPort ?? -1,
        host: 'localhost',
        pathMappings,
        // Disable redirectOutput, we collect child process stdout/stderr and
        // explicitly write to Debug Console.
        redirectOutput: false,
    }
}