From 6e673cf390ec8677bbda2f1024d915c501ca7f2a Mon Sep 17 00:00:00 2001 From: orosmatthew Date: Sat, 17 Feb 2024 15:01:29 -0500 Subject: [PATCH] [sandbox] Add cpp build --- sandbox/Dockerfile | 2 +- sandbox/src/index.ts | 17 +++++- sandbox/src/run/cpp.ts | 131 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 sandbox/src/run/cpp.ts diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index 480f817..9d0ec57 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -12,7 +12,7 @@ ENV NODE_MAJOR=18 RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list RUN apt-get update -RUN apt-get install nodejs git openjdk-17-jdk-headless dotnet-sdk-7.0 -y +RUN apt-get install nodejs git openjdk-17-jdk-headless dotnet-sdk-7.0 build-essential cmake -y ENV DOTNET_NOLOGO=true ENV DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true diff --git a/sandbox/src/index.ts b/sandbox/src/index.ts index e6654e7..b28f25f 100644 --- a/sandbox/src/index.ts +++ b/sandbox/src/index.ts @@ -7,10 +7,11 @@ import { join } from 'path'; import { simpleGit, SimpleGit } from 'simple-git'; import { runJava } from './run/java.js'; import { runCSharp } from './run/csharp.js'; +import { runCpp } from './run/cpp.js'; export const timeoutSeconds = 30; -const RunResultKind = z.enum(['CompileFailed', 'TimeLimitExceeded', 'Completed', 'SandboxError']); +const RunResultKind = z.enum(['CompileFailed', 'TimeLimitExceeded', 'Completed', 'SandboxError', 'RunError']); export type RunResultKind = z.infer; const RunResult = z @@ -40,7 +41,7 @@ const submissionGetData = z contestName: z.string(), teamId: z.number(), teamName: z.string(), - teamLanguage: z.enum(['Java', 'CSharp']), + teamLanguage: z.enum(['Java', 'CSharp', 'CPP']), problem: z.object({ id: z.number(), pascalName: z.string(), @@ -127,6 +128,18 @@ async function cloneAndRun(submissionData: SubmissionGetData) { } else { runResult = res.runResult; } + } else if (submissionData.submission.teamLanguage === 'CPP') { + let res = await runCpp({ + srcDir: repoDir, + input: submissionData.submission.problem.realInput, + cppPlatform: 'GCC', + problemName: submissionData.submission.problem.pascalName + }); + if (res.success === true) { + runResult = await res.runResult; + } else { + runResult = res.runResult; + } } } catch (error) { runResult = { diff --git a/sandbox/src/run/cpp.ts b/sandbox/src/run/cpp.ts new file mode 100644 index 0000000..6849837 --- /dev/null +++ b/sandbox/src/run/cpp.ts @@ -0,0 +1,131 @@ +import { join } from 'path'; +import { exec, spawn } from 'child_process'; +import util from 'util'; +import { RunResult, timeoutSeconds } from '../index.js'; +import { IRunner, IRunnerParams, IRunnerReturn } from './types.js'; +import kill from 'tree-kill'; +import * as fs from 'fs'; + +const execPromise = util.promisify(exec); + +export type CppPlatform = 'VisualStudio' | 'GCC'; + +interface IRunnerParamsCpp extends IRunnerParams { + srcDir: string; + problemName: string; + input: string; + cppPlatform: CppPlatform; + outputCallback?: (data: string) => void; +} + +export const runCpp: IRunner = async function ( + params: IRunnerParamsCpp +): IRunnerReturn { + console.log(`- BUILD: ${params.problemName}`); + + const configureCommand = `cmake -S ${params.srcDir} -B ${join(params.srcDir, 'build')}`; + try { + await execPromise(configureCommand); + } catch (e) { + const buildErrorText = e?.toString() ?? 'Unknown build errors.'; + console.log('Build errors: ' + buildErrorText); + return { + success: false, + runResult: { kind: 'CompileFailed', resultKindReason: buildErrorText } + }; + } + + const compileCommand = `cmake --build ${join(params.srcDir, 'build')} --target ${params.problemName}`; + try { + await execPromise(compileCommand); + } catch (e) { + const buildErrorText = e?.toString() ?? 'Unknown build errors.'; + console.log('Build errors: ' + buildErrorText); + return { + success: false, + runResult: { kind: 'CompileFailed', resultKindReason: buildErrorText } + }; + } + + console.log(`- RUN: ${params.problemName}`); + + let runCommand = ''; + if (params.cppPlatform === 'VisualStudio') { + runCommand = `${join(params.srcDir, 'build', 'Debug', `${params.problemName}.exe`)}`; + } else { + runCommand = `${join(params.srcDir, 'build', params.problemName)}`; + } + try { + let outputBuffer = ''; + const child = spawn(runCommand, { shell: true }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (data) => { + outputBuffer += data.toString(); + }); + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (data) => { + outputBuffer += data.toString(); + }); + + let runStartTime = performance.now(); + child.stdin.write(params.input); + child.stdin.end(); + + let timeLimitExceeded = false; + let completedNormally = false; + + return { + success: true, + runResult: new Promise(async (resolve) => { + child.on('close', () => { + completedNormally = !timeLimitExceeded; + + let runEndTime = performance.now(); + const runtimeMilliseconds = Math.floor(runEndTime - runStartTime); + + if (completedNormally) { + clearTimeout(timeoutHandle); + resolve({ + kind: 'Completed', + output: outputBuffer, + exitCode: child.exitCode!, + runtimeMilliseconds + }); + } else { + console.log(`Process terminated, total sandbox time: ${runtimeMilliseconds}ms`); + resolve({ + kind: 'TimeLimitExceeded', + output: outputBuffer, + resultKindReason: `Timeout after ${timeoutSeconds} seconds` + }); + } + }); + + let timeoutHandle = setTimeout(() => { + if (completedNormally) { + return; + } + + console.log(`Run timed out after ${timeoutSeconds} seconds, killing process...`); + timeLimitExceeded = true; + + child.stdin.end(); + child.stdin.destroy(); + child.stdout.destroy(); + child.stderr.destroy(); + child.kill('SIGKILL'); + }, timeoutSeconds * 1000); + }), + killFunc() { + if (child.pid !== undefined) { + if (!completedNormally && !timeLimitExceeded) { + kill(child.pid); + params.outputCallback?.('\n[Manually stopped]'); + } + } + } + }; + } catch (error) { + return { success: false, runResult: { kind: 'RunError' } }; + } +};