Files
Sprinter-SDCC/toolchain/vscode-sprinter-debug/extension.js
T

200 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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};