diff --git a/extension/bwcontest/.vscode/settings.json b/extension/bwcontest/.vscode/settings.json index 19e321a..59d28e9 100644 --- a/extension/bwcontest/.vscode/settings.json +++ b/extension/bwcontest/.vscode/settings.json @@ -5,5 +5,6 @@ "search.exclude": { "out": true }, - "typescript.tsc.autoDetect": "off" + "typescript.tsc.autoDetect": "off", + "cSpell.enabled": false } diff --git a/extension/bwcontest/src/SidebarProvider.ts b/extension/bwcontest/src/SidebarProvider.ts index 232e45f..a161c11 100644 --- a/extension/bwcontest/src/SidebarProvider.ts +++ b/extension/bwcontest/src/SidebarProvider.ts @@ -4,7 +4,7 @@ import { cloneAndOpenRepo } from './extension'; import { BWPanel } from './problemPanel'; import urlJoin from 'url-join'; -export type ContestLanguage = 'Java' | 'CSharp'; +export type ContestLanguage = 'Java' | 'CSharp' | 'CPP'; export type TeamData = { teamId: number; diff --git a/extension/bwcontest/src/problemPanel.ts b/extension/bwcontest/src/problemPanel.ts index 8eba23e..289a9e8 100644 --- a/extension/bwcontest/src/problemPanel.ts +++ b/extension/bwcontest/src/problemPanel.ts @@ -7,6 +7,7 @@ import { join } from 'path'; import { TeamData } from './SidebarProvider'; import { submitProblem } from './submit'; import { runCSharp } from './run/csharp'; +import { runCpp } from './run/cpp'; export type ProblemData = { id: number; @@ -202,6 +203,21 @@ export class BWPanel { this.webviewPostMessage({ msg: 'onRunningDone' }); } ); + } else if (teamData.language === 'CPP') { + killFunc = await runCpp( + join(repoDir, 'BWContest', teamData.contestId.toString(), teamData.teamId.toString()), + problem.pascalName, + input, + process.platform === 'win32' ? 'VisualStudio' : 'GCC', + (data: string) => { + outputBuffer.push(data); + this.webviewPostMessage({ msg: 'onRunningOutput', data: outputBuffer.join('') }); + }, + () => { + this.runningProgram = undefined; + this.webviewPostMessage({ msg: 'onRunningDone' }); + } + ); } if (killFunc !== undefined) { diff --git a/extension/bwcontest/src/run/cpp.ts b/extension/bwcontest/src/run/cpp.ts new file mode 100644 index 0000000..3a786b4 --- /dev/null +++ b/extension/bwcontest/src/run/cpp.ts @@ -0,0 +1,93 @@ +import * as fs from 'fs-extra'; +import { join } from 'path'; +import os = require('os'); +import { exec, spawn } from 'child_process'; +import util = require('node:util'); +import kill = require('tree-kill'); + +const execPromise = util.promisify(exec); + +export type CppPlatform = 'VisualStudio' | 'GCC'; + +export async function runCpp( + srcDir: string, + problemName: string, + input: string, + cppPlatform: CppPlatform, + outputCallback: (data: string) => void, + doneCallback: () => void +): Promise<(() => void) | undefined> { + const tempDir = os.tmpdir(); + const buildDir = join(tempDir, 'bwcontest_cpp'); + if (await fs.exists(buildDir)) { + await fs.remove(buildDir); + } + await fs.mkdir(buildDir); + + const configureCommand = `cmake -S ${srcDir} -B ${buildDir}`; + try { + await execPromise(configureCommand); + } catch (error) { + outputCallback('[Configure Error]\n\n'); + outputCallback(String(error)); + return; + } + + const compileCommand = `cmake --build ${buildDir} --target ${problemName}`; + try { + await execPromise(compileCommand); + } catch (error) { + outputCallback('[Compile Error]\n\n'); + outputCallback(String(error)); + return; + } + + let runCommand: string = ''; + if (cppPlatform === 'VisualStudio') { + runCommand = `${join(buildDir, 'Debug', `${problemName}.exe`)}`; + } else if (cppPlatform === 'GCC') { + runCommand = `${(join(buildDir), problemName)}`; + } + + const child = spawn(runCommand, { shell: true }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + outputCallback(data.toString()); + }); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + outputCallback(data.toString()); + }); + child.stdin.write(input); + child.stdin.end(); + + let done = false; + + child.on('close', () => { + if (done === false) { + done = true; + doneCallback(); + } + }); + + setTimeout(() => { + if (done === false) { + console.log('\n[30 seconds reached, killing process...]'); + done = true; + if (child.pid) { + kill(child.pid); + } + outputCallback('\n[Timeout after 30 seconds]'); + doneCallback(); + } + }, 30000); + + return () => { + if (child.pid) { + done = true; + kill(child.pid); + outputCallback('\n[Manually stopped]'); + doneCallback(); + } + }; +}