Sprinter: добавить отладку C-исходников и интеграцию VS Code
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
const path = require('path');
|
||||
const vscode = require('vscode');
|
||||
const {resolvePython, pyenvEnvironment} = 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;
|
||||
if (!configured && !folder) {
|
||||
throw new Error('Откройте workspace C-Compiler или задайте adapterPath');
|
||||
}
|
||||
const adapter = configured || path.join(folder.uri.fsPath, '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 env = 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;
|
||||
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};
|
||||
Reference in New Issue
Block a user