196 lines
8.1 KiB
JavaScript
196 lines
8.1 KiB
JavaScript
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};
|