const path = require('path'); const vscode = require('vscode'); const {resolvePython, pyenvEnvironment, resolveSdkRoot} = require('./runtime'); const {projectForLaunch, isSprinterMakefile, taskLabel, makeCommand} = require('./build'); class SprinterAdapterFactory { constructor(output) { this.output = output; } createDebugAdapterDescriptor(session) { const settings = vscode.workspace.getConfiguration('sprinterDebugger'); const folder = session.workspaceFolder || vscode.workspace.workspaceFolders?.[0]; const configured = session.configuration.adapterPath; const sdk = configured ? null : resolveSdkRoot( settings.get('sdkRoot'), folder?.uri.fsPath); const adapter = configured || path.join(sdk, 'toolchain', 'sdbg_dap.py'); const runtime = resolvePython({ command: settings.get('pythonCommand', 'auto'), args: settings.get('pythonArguments', []), workspace: folder?.uri.fsPath, }); const cwd = folder?.uri.fsPath || path.dirname(adapter); this.output.appendLine(`Python: ${runtime.command}`); this.output.appendLine(`DAP: ${adapter}`); const options = {cwd}; const env = pyenvEnvironment(runtime.command, cwd); if (env) options.env = env; return new vscode.DebugAdapterExecutable( runtime.command, [...runtime.args, adapter], options); } } class SprinterTaskProvider { createTask(folder, project, definitionOverride) { const settings = vscode.workspace.getConfiguration('sprinterDebugger'); const runtime = resolvePython({ command: settings.get('pythonCommand', 'auto'), args: settings.get('pythonArguments', []), workspace: folder.uri.fsPath, }); const relative = path.relative(folder.uri.fsPath, project); const definition = definitionOverride || {type: 'sprinter', project: relative || '.'}; const options = {cwd: project}; const sdk = resolveSdkRoot(settings.get('sdkRoot'), folder.uri.fsPath); const env = {SPRINTER_ROOT: sdk, ...( pyenvEnvironment(runtime.command, project) || pyenvEnvironment(runtime.command, folder.uri.fsPath) || {})}; if (env) options.env = env; const task = new vscode.Task( definition, folder, taskLabel(project, folder.uri.fsPath), 'sprinter', new vscode.ProcessExecution(makeCommand(), [ 'SRC_DEBUG=1', `PYTHON=${runtime.command}`, ], options), ['$sprinter-sdcc']); task.group = vscode.TaskGroup.Build; return task; } async provideTasks() { const folders = vscode.workspace.workspaceFolders || []; const results = []; for (const folder of folders) { const files = await vscode.workspace.findFiles( new vscode.RelativePattern(folder, '**/Makefile'), '**/{third_party,mame,libc,libbgi,toolchain}/**'); for (const file of files) { if (isSprinterMakefile(file.fsPath)) { results.push(this.createTask(folder, path.dirname(file.fsPath))); } } } return results; } resolveTask(task) { const folder = task.scope?.uri ? task.scope : vscode.workspace.workspaceFolders?.[0]; const project = task.definition.project; if (!folder || typeof project !== 'string') return undefined; const directory = path.resolve(folder.uri.fsPath, project); if (!isSprinterMakefile(path.join(directory, 'Makefile'))) return undefined; return this.createTask(folder, directory, task.definition); } } async function runBuildTask(task) { const early = [], ended = []; let execution, resolveResult, completed = false; const result = new Promise(resolve => {resolveResult = resolve;}); function finish(code) { if (!completed) { completed = true; resolveResult(code); } } const processListener = vscode.tasks.onDidEndTaskProcess(event => { early.push(event); if (execution && event.execution === execution) finish(event.exitCode); }); const endListener = vscode.tasks.onDidEndTask(event => { ended.push(event); if (execution && event.execution === execution) { // При отмене задачи process exit event может не появиться. setTimeout(() => finish(undefined), 100); } }); try { execution = await vscode.tasks.executeTask(task); const finished = early.find(event => event.execution === execution); if (finished) finish(finished.exitCode); if (ended.some(event => event.execution === execution)) { setTimeout(() => finish(undefined), 100); } return await result; } finally { processListener.dispose(); endListener.dispose(); } } class SprinterConfigurationProvider { constructor(tasks, output) { this.tasks = tasks; this.output = output; } async resolveDebugConfiguration(folder, configuration) { if (configuration.type !== 'sprinter-mame' || configuration.request !== 'launch' || configuration.autoBuild === false || configuration.preLaunchTask) { return configuration; } const workspace = folder?.uri.fsPath || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; if (!workspace) return configuration; const mameHome = vscode.workspace.getConfiguration('sprinterDebugger') .get('mameHome'); if (!configuration.mameHome && mameHome) configuration.mameHome = mameHome; try { const project = projectForLaunch(configuration, workspace); if (project && isSprinterMakefile(path.join(project, 'Makefile'))) { const scope = folder || vscode.workspace.workspaceFolders[0]; const task = this.tasks.createTask(scope, project); this.output.appendLine(`Build: ${project}`); const code = await runBuildTask(task); if (code !== 0) { vscode.window.showErrorMessage( `Сборка Sprinter не прошла (код ${code ?? 'отмена'}); MAME не запущен`); return undefined; } } } catch (error) { vscode.window.showErrorMessage(`Sprinter Build: ${error.message}`); return undefined; } return configuration; } } function activate(context) { const output = vscode.window.createOutputChannel('Sprinter MAME Debug'); context.subscriptions.push(output); context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory( 'sprinter-mame', new SprinterAdapterFactory(output))); const tasks = new SprinterTaskProvider(); context.subscriptions.push(vscode.tasks.registerTaskProvider('sprinter', tasks)); context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider( 'sprinter-mame', new SprinterConfigurationProvider(tasks, output))); context.subscriptions.push(vscode.commands.registerCommand('sprinter.buildActive', async () => { const editor = vscode.window.activeTextEditor; const folder = editor && vscode.workspace.getWorkspaceFolder(editor.document.uri); if (!editor || !folder) { vscode.window.showErrorMessage('Откройте C-файл проекта Sprinter'); return; } let directory = path.dirname(editor.document.uri.fsPath); const root = folder.uri.fsPath; while (directory !== path.dirname(directory)) { if (isSprinterMakefile(path.join(directory, 'Makefile'))) { try { await vscode.tasks.executeTask(tasks.createTask(folder, directory)); } catch (error) { vscode.window.showErrorMessage(`Sprinter Build: ${error.message}`); } return; } if (directory === root) break; directory = path.dirname(directory); } vscode.window.showErrorMessage('Не найден проект Sprinter с app.mk'); })); } function deactivate() {} module.exports = {activate, deactivate, SprinterTaskProvider, SprinterConfigurationProvider, runBuildTask};