diff --git a/packages/hydrojudge/src/compile.ts b/packages/hydrojudge/src/compile.ts index b8e5e5bd..132c019d 100644 --- a/packages/hydrojudge/src/compile.ts +++ b/packages/hydrojudge/src/compile.ts @@ -1,7 +1,6 @@ import { LangConfig } from '@hydrooj/utils/lib/lang'; import { STATUS } from '@hydrooj/utils/lib/status'; import { findFileSync } from '@hydrooj/utils/lib/utils'; -import checkers from './checkers'; import { CompileError, FormatError } from './error'; import { Execute } from './interface'; import { @@ -51,7 +50,10 @@ const testlibFile = { src: findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h'), }; -async function _compile(src: string, type: 'checker' | 'validator' | 'interactor', getLang, copyIn, withTestlib = true, next?: any) { +export async function compileWithTestlib( + src: string, type: 'checker' | 'validator' | 'interactor', + getLang, copyIn: CopyIn, withTestlib = true, next?: any, +) { const s = src.replace('@', '.').split('.'); let lang; let langId = s.pop(); @@ -65,19 +67,3 @@ async function _compile(src: string, type: 'checker' | 'validator' | 'interactor // TODO cache compiled binary return await compile(lang, { src }, copyIn, next); } - -export async function compileChecker(getLang: Function, checkerType: string, checker: string, copyIn: CopyIn, next?: any) { - if (['default', 'strict'].includes(checkerType)) { - return { execute: '', copyIn: {}, clean: () => Promise.resolve(null) }; - } - if (!checkers[checkerType]) throw new FormatError('Unknown checker type {0}.', [checkerType]); - return _compile(checker, 'checker', getLang, copyIn, checkerType === 'testlib', next); -} - -export async function compileInteractor(getLang: Function, interactor: string, copyIn: CopyIn) { - return _compile(interactor, 'interactor', getLang, copyIn); -} - -export async function compileValidator(getLang: Function, validator: string, copyIn: CopyIn) { - return _compile(validator, 'validator', getLang, copyIn); -} diff --git a/packages/hydrojudge/src/judge/default.ts b/packages/hydrojudge/src/judge/default.ts index 389ee09b..0f7b369a 100644 --- a/packages/hydrojudge/src/judge/default.ts +++ b/packages/hydrojudge/src/judge/default.ts @@ -1,9 +1,6 @@ -import { basename } from 'path'; import { STATUS } from '@hydrooj/utils/lib/status'; import checkers from '../checkers'; -import compile, { compileChecker } from '../compile'; import { runFlow } from '../flow'; -import { Execute } from '../interface'; import { Logger } from '../log'; import { del, runQueued } from '../sandbox'; import signals from '../signals'; @@ -101,30 +98,9 @@ function judgeCase(c: NormalizedCase) { export const judge = async (ctx: Context) => await runFlow(ctx, { compile: async () => { - const markCleanup = (i: Execute) => { - ctx.clean.push(i.clean); - return i; - }; [ctx.execute, ctx.checker] = await Promise.all([ - compile( - ctx.session.getLang(ctx.lang), ctx.code, - Object.fromEntries( - (ctx.config.user_extra_files || []).map((i) => [i.split('/').pop(), { src: i }]), - ), - ctx.next, - ).then(markCleanup), - compileChecker( - ctx.session.getLang, - ctx.config.checker_type, - ctx.config.checker, - { - user_code: ctx.code, - ...Object.fromEntries( - (ctx.config.judge_extra_files || []).map((i) => [basename(i), { src: i }]), - ), - }, - ctx.next, - ).then(markCleanup), + ctx.compile(ctx.lang, ctx.code), + ctx.compileWithTestlib('checker', ctx.config.checker, ctx.config.checker_type), ]); }, judgeCase, diff --git a/packages/hydrojudge/src/judge/hack.ts b/packages/hydrojudge/src/judge/hack.ts index 14d2fbb8..6f14a256 100644 --- a/packages/hydrojudge/src/judge/hack.ts +++ b/packages/hydrojudge/src/judge/hack.ts @@ -1,29 +1,18 @@ /* eslint-disable no-sequences */ -import { basename } from 'path'; import { fs } from '@hydrooj/utils'; import { STATUS } from '@hydrooj/utils/lib/status'; import checkers from '../checkers'; -import compile, { compileChecker, compileValidator } from '../compile'; -import { Execute } from '../interface'; import { del, runQueued } from '../sandbox'; import signals from '../signals'; import { parseMemoryMB, parseTimeMS } from '../utils'; import { Context } from './interface'; export async function judge(ctx: Context) { - const { config, session } = ctx; ctx.next({ status: STATUS.STATUS_COMPILING, progress: 0 }); - const userExtraFiles = Object.fromEntries( - (config.user_extra_files || []).map((i) => [basename(i), { src: i }]), - ); - const judgeExtraFiles = Object.fromEntries( - (config.judge_extra_files || []).map((i) => [basename(i), { src: i }]), - ); - const markCleanup = (i: Execute) => (ctx.clean.push(i.clean), i); const [execute, checker, validator, input] = await Promise.all([ - compile(session.getLang(ctx.lang), ctx.code, userExtraFiles, ctx.next).then(markCleanup), - compileChecker(session.getLang, config.checker_type, config.checker, judgeExtraFiles).then(markCleanup), - compileValidator(session.getLang, config.validator, judgeExtraFiles).then(markCleanup), + ctx.compile(ctx.lang, ctx.code), + ctx.compileWithTestlib('checker', ctx.config.checker, ctx.config.checker_type), + ctx.compileWithTestlib('validator', ctx.config.validator), ctx.session.fetchFile(ctx.files.hack), ]); ctx.clean.push(() => fs.unlink(input)); @@ -32,7 +21,7 @@ export async function judge(ctx: Context) { validator.execute, { stdin: { src: input }, - copyIn: { ...validator.copyIn, ...judgeExtraFiles }, + copyIn: validator.copyIn, time: parseTimeMS(ctx.config.time || '1s'), memory: parseMemoryMB(ctx.config.memory || '256m'), }, diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index 9ccc7038..02fc3229 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -1,8 +1,5 @@ -import { basename } from 'path'; import { STATUS } from '@hydrooj/utils/lib/status'; -import compile, { compileInteractor } from '../compile'; import { runFlow } from '../flow'; -import { Execute } from '../interface'; import { runPiped } from '../sandbox'; import signals from '../signals'; import { parse } from '../testlib'; @@ -11,8 +8,6 @@ import { Context, ContextSubTask } from './interface'; function judgeCase(c: NormalizedCase) { return async (ctx: Context, ctxSubtask: ContextSubTask) => { - ctx.executeInteractor.copyIn.in = c.input ? { src: c.input } : { content: '' }; - ctx.executeInteractor.copyIn.out = c.output ? { src: c.output } : { content: '' }; const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang); const [{ code, signalled, time, memory, @@ -27,7 +22,11 @@ function judgeCase(c: NormalizedCase) { }, { execute: `${ctx.executeInteractor.execute} /w/in /w/tout /w/out`, - copyIn: ctx.executeInteractor.copyIn, + copyIn: { + in: c.input ? { src: c.input } : { content: '' }, + out: c.output ? { src: c.output } : { content: '' }, + ...ctx.executeInteractor.copyIn, + }, time: c.time * 2, memory: c.memory * 2, copyOut: ['/w/tout?'], @@ -67,20 +66,9 @@ function judgeCase(c: NormalizedCase) { export const judge = async (ctx: Context) => await runFlow(ctx, { compile: async () => { - const markCleanup = (i: Execute) => { - ctx.clean.push(i.clean); - return i; - }; - const userExtraFiles = Object.fromEntries( - (ctx.config.user_extra_files || []).map((i) => [basename(i), { src: i }]), - ); - const interactorFiles = { user_code: ctx.code }; - for (const file of ctx.config.judge_extra_files) { - interactorFiles[basename(file)] = { src: file }; - } [ctx.executeUser, ctx.executeInteractor] = await Promise.all([ - compile(ctx.session.getLang(ctx.lang), ctx.code, userExtraFiles, ctx.next).then(markCleanup), - compileInteractor(ctx.session.getLang, ctx.config.interactor, interactorFiles).then(markCleanup), + ctx.compile(ctx.lang, ctx.code), + ctx.compileWithTestlib('interactor', ctx.config.interactor), ]); }, judgeCase, diff --git a/packages/hydrojudge/src/judge/run.ts b/packages/hydrojudge/src/judge/run.ts index aa6ef370..6a409977 100644 --- a/packages/hydrojudge/src/judge/run.ts +++ b/packages/hydrojudge/src/judge/run.ts @@ -1,11 +1,11 @@ import { STATUS } from '@hydrooj/utils/lib/status'; -import compile from '../compile'; import { CompileError } from '../error'; +import { Execute } from '../interface'; import { Logger } from '../log'; import { runQueued } from '../sandbox'; import signals from '../signals'; +import { JudgeTask } from '../task'; import { compilerText, parseMemoryMB, parseTimeMS } from '../utils'; -import { Context } from './interface'; const failure = (status: number, message?: string) => ({ status, @@ -17,16 +17,16 @@ const failure = (status: number, message?: string) => ({ const logger = new Logger('judge/run'); -export const judge = async (ctx: Context) => { +export const judge = async (ctx: JudgeTask) => { ctx.stat.judge = new Date(); ctx.next({ status: STATUS.STATUS_COMPILING }); + let execute: Execute; try { - ctx.execute = await compile( - ctx.session.getLang(ctx.lang), ctx.code, + execute = await ctx.compile( + ctx.lang, ctx.code, Object.fromEntries( (ctx.config.user_extra_files || []).map((i) => [i.split('/').pop(), { src: i }]), ), - ctx.next, ); } catch (e) { if (e instanceof CompileError) { @@ -44,14 +44,13 @@ export const judge = async (ctx: Context) => { } return; } - ctx.clean.push(ctx.execute.clean); ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 }); const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang); const res = await runQueued( - ctx.execute.execute, + execute.execute, { stdin: { content: ctx.input }, - copyIn: ctx.execute.copyIn, + copyIn: execute.copyIn, // Allow 2x limits for better debugging time: parseTimeMS(ctx.config.time || '1s') * 2, memory: parseMemoryMB(ctx.config.memory || '128m'), @@ -90,10 +89,9 @@ export const judge = async (ctx: Context) => { const langConfig = ctx.session.getLang(ctx.lang); if (langConfig.analysis) { try { - ctx.analysis = true; const r = await runQueued(langConfig.analysis, { copyIn: { - ...ctx.execute.copyIn, + ...execute.copyIn, input: { content: ctx.input }, [langConfig.code_file || 'foo']: ctx.code, compile: { content: langConfig.compile || '' }, diff --git a/packages/hydrojudge/src/judge/submit_answer.ts b/packages/hydrojudge/src/judge/submit_answer.ts index fda77332..ad308757 100644 --- a/packages/hydrojudge/src/judge/submit_answer.ts +++ b/packages/hydrojudge/src/judge/submit_answer.ts @@ -69,17 +69,7 @@ function judgeCase(c: NormalizedCase) { export const judge = async (ctx: Context) => { await runFlow(ctx, { compile: async () => { - const copyIn = { user_code: ctx.code }; - for (const file of ctx.config.judge_extra_files) { - copyIn[basename(file)] = { src: file }; - } - ctx.checker = await compileChecker( - ctx.session.getLang, - ctx.config.checker_type, - ctx.config.checker, - copyIn, - ); - ctx.clean.push(ctx.checker.clean); + ctx.checker = await ctx.compileWithTestlib('checker', ctx.config.checker, ctx.config.checker_type); }, judgeCase, }); diff --git a/packages/hydrojudge/src/task.ts b/packages/hydrojudge/src/task.ts index 5d766298..fb7fe92e 100644 --- a/packages/hydrojudge/src/task.ts +++ b/packages/hydrojudge/src/task.ts @@ -1,3 +1,4 @@ +import { basename } from 'path'; import { fs } from '@hydrooj/utils'; import { LangConfig } from '@hydrooj/utils/lib/lang'; import { STATUS } from '@hydrooj/utils/lib/status'; @@ -5,12 +6,14 @@ import type { FileInfo, JudgeMeta, JudgeRequest, JudgeResultBody, TestCase, } from 'hydrooj'; import readCases from './cases'; +import checkers from './checkers'; +import compile, { compileWithTestlib } from './compile'; import { getConfig } from './config'; import { CompileError, FormatError } from './error'; import { NextFunction, ParsedConfig } from './interface'; import judge from './judge'; import { Logger } from './log'; -import { CopyInFile } from './sandbox'; +import { CopyIn, CopyInFile } from './sandbox'; import { compilerText, md5 } from './utils'; interface Session { @@ -129,4 +132,28 @@ export class JudgeTask { if (!judge[type]) throw new FormatError('Unrecognized problemType: {0}', [type]); await judge[type].judge(this); } + + async compile(lang: string, code: CopyInFile) { + const copyIn = Object.fromEntries( + (this.config.user_extra_files || []).map((i) => [basename(i), { src: i }]), + ) as CopyIn; + const result = await compile(this.session.getLang(lang), code, copyIn, this.next); + this.clean.push(result.clean); + return result; + } + + async compileWithTestlib(type: 'interactor' | 'validator' | 'checker', file: string, checkerType?: string) { + if (type === 'checker' && ['default', 'strict'].includes(checkerType)) return { execute: '', copyIn: {}, clean: () => Promise.resolve(null) }; + if (!checkers[checkerType]) throw new FormatError('Unknown checker type {0}.', [checkerType]); + const withTestlib = type !== 'checker' || checkerType === 'testlib'; + const copyIn = { + user_code: this.code, + ...Object.fromEntries( + (this.config.judge_extra_files || []).map((i) => [basename(i), { src: i }]), + ), + } as CopyIn; + const result = await compileWithTestlib(file, type, this.session.getLang, copyIn, withTestlib, this.next); + this.clean.push(result.clean); + return result; + } }