统一语言配置位置
支持langs继承
支持归类
This commit is contained in:
undefined committed 2021-05-07 20:00:04 +08:00
1 parent 5902b4d4b6
commit 377ef55d49
27 files changed
+253 -658

No files matched your search

+2 -3
View File
@@ -1,13 +1,12 @@
{
"name": "@hydrooj/hydrojudge",
"bin": "bin/hydrojudge.js",
"version": "2.5.21",
"version": "2.6.0",
"main": "package.json",
"author": "undefined <i@undefined.moe>",
"repository": "https://github.com/hydro-dev/Hydro.git",
"dependencies": {
"@hydrooj/utils": "^1.0.9",
"adm-zip": "^0.5.5",
"@hydrooj/utils": "^1.0.14",
"axios": "^0.21.1",
"bson": "^4.3.0",
"fs-extra": "^9.1.0",
+1 -99
View File
@@ -51,102 +51,4 @@ disable:
type: boolean
name: disable
desc: Disable builtin judge
default: false
langs:
type: textarea
name: langs
desc: Language file settings
default: |
ccWithoutO2: # Do not remove this entry
type: compiler
compile: /usr/bin/g++ -Wall -o ${name} foo.cc
code_file: foo.cc
execute: ${dir}/${name}
c:
type: compiler
compile: /usr/bin/gcc -O2 -Wall -std=c99 -o ${name} foo.c -lm
code_file: foo.c
execute: ${dir}/${name}
cc:
type: compiler
compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm
code_file: foo.cc
execute: ${dir}/${name}
cc98:
type: compiler
compile: /usr/bin/g++ -O2 -Wall -std=c++98 -o ${name} foo.cc -lm
code_file: foo.cc
execute: ${dir}/${name}
cc11:
type: compiler
compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm
code_file: foo.cc
execute: ${dir}/${name}
cc17:
type: compiler
compile: /usr/bin/g++ -O2 -Wall -std=c++17 -o ${name} foo.cc -lm
code_file: foo.cc
execute: ${dir}/${name}
pas:
type: compiler
compile: /usr/bin/fpc -O2 -o${dir}/${name} foo.pas
code_file: foo.pas
execute: ${dir}/${name}
java:
type: compiler
compile: /usr/bin/javac -d ${dir} -encoding utf8 ./Main.java
code_file: Main.java
target: Main.class
execute: /usr/bin/java Main
time_limit_rate: 2
py:
type: compiler
compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)"
code_file: foo.py
execute: /usr/bin/python ${name}
py2:
type: compiler
compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)"
code_file: foo.py
execute: /usr/bin/python ${name}
py3:
type: compiler
compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)"
code_file: foo.py
execute: /usr/bin/python3 ${name}
php:
type: interpreter
code_file: foo.php
execute: /usr/bin/php foo.php
rs:
type: compiler
compile: /usr/bin/rustc -O -o ${dir}/${name} ${dir}/foo.rs
code_file: foo.rs
execute: ${dir}/${name}
hs:
type: compiler
compile: /usr/bin/ghc -O -outputdir /tmp -o ${name} foo.hs
code_file: foo.hs
execute: ${dir}/${name}
js:
type: interpreter
code_file: foo.js
execute: /usr/bin/jsc ${dir}/foo.js
go:
type: compiler
compile: /usr/bin/go build -o ${name} foo.go
code_file: foo.go
execute: ${dir}/${name}
rb:
type: interpreter
code_file: foo.rb
execute: /usr/bin/ruby foo.rb
cs:
type: compiler
compile: /usr/bin/mcs -optimize+ -out:${dir}/${name} ${dir}/foo.cs
code_file: foo.cs
execute: /usr/bin/mono ${name}
jl:
type: interpreter
code_file: foo.jl
execute: /usr/bin/julia ${dir}/foo.jl
default: false
+2 -2
View File
@@ -24,10 +24,10 @@ export async function check(config): Promise<[number, number, string]> {
return [status, score, message];
}
export async function compileChecker(checkerType: string, checker: string, copyIn: any) {
export async function compileChecker(getLang: Function, checkerType: string, checker: string, copyIn: any) {
if (!checkers[checkerType]) throw new SystemError('Unknown checker type {0}.', [checkerType]);
if (checkerType === 'testlib') copyIn['testlib.h'] = { src: testlibSrc };
const file = await fs.readFile(checker);
// TODO cache compiled checker
return await compile(parseFilename(checker).split('.')[1], file.toString(), 'checker', copyIn);
return await compile(getLang(parseFilename(checker).split('.')[1]), file.toString(), 'checker', copyIn);
}
+12 -18
View File
@@ -1,25 +1,21 @@
import yaml from 'js-yaml';
import { LangConfig } from '@hydrooj/utils/lib/lang';
import * as STATUS from './status';
import { run, del } from './sandbox';
import { CompileError, SystemError } from './error';
import { CompileError } from './error';
import { compilerText } from './utils';
import { Execute } from './interface';
import { getConfig } from './config';
export = async function compile(
lang: string, code: string, target: string, copyIn: any, next?: Function,
lang: LangConfig, code: string, target: string, copyIn: any, next?: Function,
): Promise<Execute> {
const LANGS = yaml.load(getConfig('langs'));
if (!LANGS[lang]) throw new SystemError('Unsupported language {0}.', [lang]);
const info = LANGS[lang];
target = info.target || target;
target = lang.target || target;
const f = {};
if (info.type === 'compiler') {
copyIn[info.code_file] = { content: code };
if (lang.compile) {
copyIn[lang.code_file] = { content: code };
const {
status, stdout, stderr, fileIds,
} = await run(
info.compile.replace(/\$\{name\}/g, target),
lang.compile.replace(/\$\{name\}/g, target),
{ copyIn, copyOutCached: [target] },
);
if (status !== STATUS.STATUS_ACCEPTED) throw new CompileError({ status, stdout, stderr });
@@ -27,13 +23,11 @@ export = async function compile(
if (next) next({ compiler_text: compilerText(stdout, stderr) });
f[target] = { fileId: fileIds[target] };
return {
execute: info.execute, copyIn: f, clean: () => del(fileIds[target]), time: info.time || 1,
};
} if (info.type === 'interpreter') {
f[target] = { content: code };
return {
execute: info.execute, copyIn: f, clean: () => Promise.resolve(null), time: info.time || 1,
execute: lang.execute, copyIn: f, clean: () => del(fileIds[target]), time: lang.time_limit_rate || 1,
};
}
throw new SystemError('Unknown language type.');
f[target] = { content: code };
return {
execute: lang.execute, copyIn: f, clean: () => Promise.resolve(null), time: lang.time_limit_rate || 1,
};
};
+14 -11
View File
@@ -6,6 +6,7 @@ import WebSocket from 'ws';
import { argv } from 'yargs';
import { noop } from 'lodash';
import { ObjectID } from 'bson';
import { LangConfig } from '@hydrooj/utils/lib/lang';
import * as tmpfs from '../tmpfs';
import log from '../log';
import { compilerText, Queue } from '../utils';
@@ -37,6 +38,7 @@ class JudgeTask {
config: any;
nextId = 1;
nextWaiting = [];
getLang: (name: string) => LangConfig;
constructor(session: Hydro, request, ws: WebSocket) {
this.stat = {};
@@ -45,6 +47,7 @@ class JudgeTask {
this.host = session.config.host;
this.request = request;
this.ws = ws;
this.getLang = session.getLang;
}
async handle() {
@@ -160,10 +163,9 @@ class JudgeTask {
export default class Hydro {
config: any;
axios: any;
ws: WebSocket;
language: Record<string, LangConfig>;
constructor(config) {
this.config = config;
@@ -172,6 +174,7 @@ export default class Hydro {
this.config.last_update_at = this.config.last_update_at || 0;
if (!this.config.server_url.startsWith('http')) this.config.server_url = `http://${this.config.server_url}`;
if (!this.config.server_url.endsWith('/')) this.config.server_url = `${this.config.server_url}/`;
this.getLang = this.getLang.bind(this);
}
async init() {
@@ -224,6 +227,11 @@ export default class Hydro {
return filePath;
}
getLang(name: string) {
if (this.language[name]) return this.language[name];
throw new SystemError('Unsupported language {0}', [name]);
}
async consume(queue: Queue<any>) {
log.info('正在连接 %sjudge/conn', this.config.server_url);
const res = await this.axios.get('judge/conn/info');
@@ -238,19 +246,16 @@ export default class Hydro {
});
this.ws.on('message', (data) => {
const request = JSON.parse(data.toString());
if (request.language) this.language = request.language;
if (request.task) queue.push(new JudgeTask(this, request.task, this.ws));
});
this.ws.on('close', (data, reason) => {
log.warn(`[${this.config.host}] Websocket 断开:`, data, reason);
setTimeout(() => {
this.retry(queue);
}, 30000);
setTimeout(() => this.retry(queue), 30000);
});
this.ws.on('error', (e) => {
log.error(`[${this.config.host}] Websocket 错误:`, e);
setTimeout(() => {
this.retry(queue);
}, 30000);
setTimeout(() => this.retry(queue), 30000);
});
await new Promise((resolve) => {
this.ws.once('open', async () => {
@@ -327,9 +332,7 @@ export default class Hydro {
async retry(queue: Queue<any>) {
this.consume(queue).catch(() => {
setTimeout(() => {
this.retry(queue);
}, 30000);
setTimeout(() => this.retry(queue), 30000);
});
}
}
+4 -416
View File
@@ -1,419 +1,7 @@
/* eslint-disable no-await-in-loop */
import path from 'path';
import axios from 'axios';
import AdmZip from 'adm-zip';
import fs from 'fs-extra';
import WebSocket from 'ws';
import { noop } from 'lodash';
import * as tmpfs from '../tmpfs';
import log from '../log';
import { compilerText, Queue } from '../utils';
import { getConfig } from '../config';
import { FormatError, CompileError, SystemError } from '../error';
import { STATUS_COMPILE_ERROR, STATUS_SYSTEM_ERROR } from '../status';
import readCases from '../cases';
import judge from '../judge';
class JudgeTask {
stat: Record<string, Date>;
session: any;
host: string;
request: any;
ws: WebSocket;
tag: any;
type: any;
domain_id: string;
pid: string;
rid: string;
lang: string;
code: string;
tmpdir: string;
clean: Function[];
folder: string;
config: any;
nextId = 1;
nextWaiting = [];
constructor(session: VJ4, request, ws: WebSocket) {
this.stat = {};
this.stat.receive = new Date();
this.session = session;
this.host = session.config.host;
this.request = request;
this.ws = ws;
}
async handle() {
this.next = this.next.bind(this);
this.end = this.end.bind(this);
this.stat.handle = new Date();
this.tag = this.request.tag;
this.type = this.request.type;
this.domain_id = this.request.domain_id;
this.pid = this.request.pid;
this.rid = this.request.rid;
this.lang = this.request.lang;
this.code = this.request.code;
this.tmpdir = path.resolve(getConfig('tmp_dir'), 'tmp', this.host, this.rid);
this.clean = [];
fs.ensureDirSync(this.tmpdir);
tmpfs.mount(this.tmpdir, '512m');
log.info('Submission: %s/%s/%s pid=%s', this.host, this.domain_id, this.rid, this.pid);
try {
if (this.type === 0) await this.doSubmission();
else if (this.type === 1) await this.doPretest();
else throw new SystemError(`Unsupported type: ${this.type}`);
} catch (e) {
if (e instanceof CompileError) {
this.next({ compiler_text: compilerText(e.stdout, e.stderr) });
this.end({
status: STATUS_COMPILE_ERROR, score: 0, time_ms: 0, memory_kb: 0,
});
} else if (e instanceof FormatError) {
this.next({ judge_text: '题目配置出现错误。请联系题目上传者。' });
this.next({ judge_text: `${e.message}\n${JSON.stringify(e.params)}` });
this.end({
status: STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0,
});
} else {
log.error(e);
this.next({ judge_text: `${e.message}\n${e.stack}\n${JSON.stringify(e.params)}` });
this.end({
status: STATUS_SYSTEM_ERROR, score: 0, time_ms: 0, memory_kb: 0,
});
}
}
for (const clean of this.clean) await clean().catch(noop);
tmpfs.umount(this.tmpdir);
fs.removeSync(this.tmpdir);
}
async doSubmission() {
this.stat.cache_start = new Date();
this.folder = await this.session.cacheOpen(this.domain_id, this.pid, this.next);
this.stat.read_cases = new Date();
this.config = await readCases(
this.folder,
{ detail: this.session.config.detail },
{ next: this.next },
);
this.stat.judge = new Date();
await judge[this.config.type || 'default'].judge(this);
}
async doPretest() {
this.folder = path.resolve(this.tmpdir, 'data');
await this.session.recordPretestData(this.rid, this.folder);
this.config = await readCases(
this.folder,
{ detail: this.session.config.detail },
{ next: this.next },
);
await judge[this.config.type || 'default'].judge(this);
}
next(data, id?: number) {
if (data.message !== undefined) {
data.judge_text = data.message;
delete data.message;
}
if (data.judge_text && typeof data.judge_text !== 'string') {
data.judge_text = data.judge_text.message.format(data.judge_text.params);
}
if (data.case?.message !== undefined) {
data.case.judge_text = data.case.message;
delete data.case.message;
}
if (data.case?.judge_text && typeof data.case?.judge_text !== 'string') {
data.case.judge_text = data.case.judge_text.message.format(data.case.judge_text.params);
}
data.key = 'next';
data.tag = this.tag;
if (id) {
if (id === this.nextId) {
this.ws.send(JSON.stringify(data));
this.nextId++;
let t = true;
while (t) {
t = false;
for (const i in this.nextWaiting) {
if (this.nextId === this.nextWaiting[i].id) {
this.ws.send(JSON.stringify(this.nextWaiting[i].data));
this.nextId++;
this.nextWaiting.splice(+i, 1);
t = true;
}
}
}
} else this.nextWaiting.push({ data, id });
} else this.ws.send(JSON.stringify(data));
}
end(data) {
this.ws.send(JSON.stringify({
key: 'next',
tag: this.tag,
judge_text: '自豪地采用 HydroJudge(https://github.com/hydro-dev/Hydro) 进行评测。',
}));
if (data.message !== undefined) {
data.judge_text = data.message;
delete data.message;
}
data.key = 'end';
data.tag = this.tag;
log.info('Status=%d Score=%s Time=%dms Memory=%dkb', data.status, data.score, data.time_ms, data.memory_kb);
this.ws.send(JSON.stringify(data));
}
}
export default class VJ4 {
config: any;
axios: any;
ws: WebSocket;
constructor(config) {
this.config = config;
this.config.detail = this.config.detail ?? true;
this.config.cookie = this.config.cookie || '';
this.config.last_update_at = this.config.last_update_at || 0;
if (!this.config.server_url.startsWith('http')) this.config.server_url = `http://${this.config.server_url}`;
if (!this.config.server_url.endsWith('/')) this.config.server_url = `${this.config.server_url}/`;
}
async init() {
await this.setCookie(this.config.cookie || '');
await this.ensureLogin();
setInterval(() => { this.axios.get('judge/noop'); }, 30000000);
}
async problemDataVersion(domainId: string, pid: string, retry = 3) {
let location: string;
let err: Error;
await this.ensureLogin();
try {
await this.axios.get(`d/${domainId}/p/${pid}/data`, { maxRedirects: 0 });
} catch (res) {
res.response = res.response || {};
if (res.response.status === 302) {
location = res.response.headers.location;
if (location.includes('/fs/')) return location;
} else if (res.response.status === 404) {
throw new FormatError(`没有找到测试数据 ${domainId}/${pid}`);
} else {
if (retry) return await this.problemDataVersion(domainId, pid, retry - 1);
res.config = res.request = null;
err = res;
log.error('%o', err);
}
}
if (!location) return 'unknown';
try {
await this.axios.get(location, { maxRedirects: 0 });
} catch (res) {
res.response = res.response || {};
if (res.response.status === 302) return res.response.headers.location;
if (res.response.status === 404) throw new FormatError(`没有找到测试数据 ${domainId}/${pid}`);
else {
if (retry) return await this.problemDataVersion(domainId, pid, retry - 1);
res.config = res.request = null;
err = res;
log.error(err);
}
}
return 'unknown';
}
async problemData(domainId: string, pid: string, savePath: string, retry = 3, next?) {
log.info(`Getting problem data: ${this.config.host}/${domainId}/${pid}`);
await this.ensureLogin();
if (next) next({ judge_text: '正在同步测试数据,请稍后' });
const tmpFilePath = path.resolve(getConfig('cache_dir'), `download_${this.config.host}_${domainId}_${pid}`);
try {
const res = await this.axios.get(
`${this.config.server_url}d/${domainId}/p/${pid}/data`,
{ responseType: 'stream' },
);
const w = fs.createWriteStream(tmpFilePath);
res.data.pipe(w);
await new Promise((resolve, reject) => {
w.on('finish', resolve);
w.on('error', reject);
});
fs.ensureDirSync(path.dirname(savePath));
const zip = new AdmZip(tmpFilePath);
const entries = zip.getEntries();
if (entries.length > 512) throw new FormatError('Too many files');
await new Promise((resolve, reject) => {
zip.extractAllToAsync(savePath, true, (e) => {
if (e) reject(e);
else resolve(null);
});
});
await fs.unlink(tmpFilePath);
await this.processData(savePath).catch(noop);
} catch (e) {
if (retry) await this.problemData(domainId, pid, savePath, retry - 1);
else throw e;
}
return savePath;
}
async recordPretestData(rid: string, savePath: string) {
log.info(`Getting pretest data: ${this.config.host}/${rid}`);
const tmpFilePath = path.resolve(getConfig('cache_dir'), `download_${this.config.host}_${rid}`);
await this.ensureLogin();
const res = await this.axios.get(`records/${rid}/data`, { responseType: 'stream' });
const w = fs.createWriteStream(tmpFilePath);
res.data.pipe(w);
await new Promise((resolve, reject) => {
w.on('finish', resolve);
w.on('error', reject);
});
fs.ensureDirSync(path.dirname(savePath));
const zip = new AdmZip(tmpFilePath);
const entries = zip.getEntries();
if (entries.length > 512) throw new FormatError('Too many files');
await new Promise((resolve, reject) => {
zip.extractAllToAsync(savePath, true, (e) => {
if (e) reject(e);
else resolve(null);
});
});
await fs.unlink(tmpFilePath);
await this.processData(savePath);
return savePath;
}
async consume(queue: Queue<any>) {
log.info('正在连接 %sjudge/consume-conn', this.config.server_url);
const res = await this.axios.get('judge/consume-conn/info');
this.ws = new WebSocket(`${this.config.server_url.replace(/^http/i, 'ws')}judge/consume-conn/websocket?t=${res.data.entropy}`, {
headers: { cookie: this.config.cookie },
});
this.ws.on('message', (data) => {
const request = JSON.parse(data.toString());
if (!request.event) queue.push(new JudgeTask(this, request, this.ws));
});
this.ws.on('close', (data, reason) => {
log.warn(`[${this.config.host}] Websocket 断开:`, data, reason);
setTimeout(() => {
this.retry(queue);
}, 30000);
});
this.ws.on('error', (e) => {
log.error(`[${this.config.host}] Websocket 错误:`, e);
setTimeout(() => {
this.retry(queue);
}, 30000);
});
await new Promise((resolve) => {
this.ws.once('open', () => { resolve(null); });
});
log.info(`[${this.config.host}] 已连接`);
}
async setCookie(cookie: string) {
this.config.cookie = cookie;
this.axios = axios.create({
baseURL: this.config.server_url,
timeout: 30000,
headers: {
accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
cookie: this.config.cookie,
},
transformRequest: [
(data) => {
let ret = '';
for (const it in data) {
ret += `${encodeURIComponent(it)}=${encodeURIComponent(data[it])}&`;
}
return ret;
},
],
});
}
async login() {
log.info('[%s] Updating session', this.config.host);
const res = await this.axios.post('login', {
uname: this.config.uname, password: this.config.password, rememberme: 'on',
});
await this.setCookie(res.headers['set-cookie'].join(';'));
}
async ensureLogin() {
try {
await this.axios.get('judge/noop');
} catch (e) {
await this.login();
}
}
async processData(folder: string) { // eslint-disable-line class-methods-use-this
let files = await fs.readdir(folder);
let ini = false;
if (files.length <= 2) {
if (files.length === 2) files.splice(files.indexOf('version'), 1);
const s = fs.statSync(path.resolve(folder, files[0]));
if (s.isDirectory()) folder = path.resolve(folder, files[0]);
}
files = await fs.readdir(folder);
for (const i of files) {
if (i.toLowerCase() === 'config.ini') {
ini = true;
await fs.rename(`${folder}/${i}`, `${folder}/config.ini`);
break;
}
}
if (ini) {
for (const i of files) {
if (i.toLowerCase() === 'input') await fs.rename(`${folder}/${i}`, `${folder}/input`);
else if (i.toLowerCase() === 'output') await fs.rename(`${folder}/${i}`, `${folder}/output`);
}
files = await fs.readdir(`${folder}/input`);
for (const i of files) {
if (fs.statSync(`${folder}/input/${i}`).size > 64 * 1024 * 1024) {
await fs.rename(`${folder}/input/${i}`, `${folder}/input/${i.toLowerCase()}`);
} else {
const buffer = await fs.readFile(`${folder}/input/${i}`);
const data = buffer.toString().replace(/\r/g, '');
await fs.unlink(`${folder}/input/${i}`);
await fs.writeFile(`${folder}/input/${i.toLowerCase()}`, data);
}
}
files = await fs.readdir(`${folder}/output`);
for (const i of files) await fs.rename(`${folder}/output/${i}`, `${folder}/output/${i.toLowerCase()}`);
}
}
async cacheOpen(domainId: string, pid: string, next) {
const domainDir = path.join(getConfig('cache_dir'), this.config.host, domainId);
const filePath = path.join(domainDir, pid);
const version = await this.problemDataVersion(domainId, pid);
if (fs.existsSync(filePath)) {
let ver;
try {
ver = fs.readFileSync(path.join(filePath, 'version')).toString();
} catch (e) { /* ignore */ }
if (version === ver) {
fs.writeFileSync(path.join(filePath, 'lastUsage'), new Date().getTime().toString());
return filePath;
}
fs.removeSync(filePath);
}
fs.ensureDirSync(domainDir);
await this.problemData(domainId, pid, filePath, 3, next);
fs.writeFileSync(path.join(filePath, 'version'), version);
fs.writeFileSync(path.join(filePath, 'lastUsage'), new Date().getTime().toString());
return filePath;
}
async retry(queue: Queue<any>) {
this.consume(queue).catch(() => {
setTimeout(() => {
this.retry(queue);
}, 30000);
});
constructor() {
console.log('新版本不再支持 VJ4。请降级至 hydrojudge@2.5.21 或迁移到 Hydro。');
console.log('详细文档: https://hydro.js.org/ ');
process.exit(1);
}
}
+4 -12
View File
@@ -1,10 +1,9 @@
import Queue from 'p-queue';
import path from 'path';
import yaml from 'js-yaml';
import fs from 'fs-extra';
import { argv } from 'yargs';
import * as STATUS from '../status';
import { CompileError, SystemError } from '../error';
import { SystemError } from '../error';
import { parseFilename } from '../utils';
import { run } from '../sandbox';
import compile from '../compile';
@@ -128,15 +127,7 @@ function judgeSubtask(subtask, sid) {
export const judge = async (ctx) => {
if (!ctx.config.subtasks.length) throw new SystemError('Problem data not found.');
if (ctx.config.template) {
if (ctx.config.template[ctx.lang]) {
const tpl = ctx.config.template[ctx.lang];
ctx.code = tpl[0] + ctx.code + tpl[1];
} else throw new CompileError('Language not supported by provided templates');
}
const LANGS = yaml.load(getConfig('langs'));
if (!LANGS[ctx.lang]) throw new SystemError('Unsupported language {0}.', [ctx.lang]);
const info = LANGS[ctx.lang];
const info = ctx.getLang(ctx.lang);
ctx.time_limit_rate = info.time_limit_rate || 1;
ctx.next({ status: STATUS.STATUS_COMPILING });
[ctx.execute, ctx.checker] = await Promise.all([
@@ -145,7 +136,7 @@ export const judge = async (ctx) => {
for (const file of ctx.config.user_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next);
return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next);
})(),
(async () => {
if (!ctx.config.checker_type || ctx.config.checker_type === 'default') {
@@ -156,6 +147,7 @@ export const judge = async (ctx) => {
copyIn[parseFilename(file)] = { src: file };
}
return await compileChecker(
ctx.getLang,
ctx.config.checker_type,
ctx.config.checker,
copyIn,
+4 -3
View File
@@ -25,7 +25,7 @@ export const judge = async (ctx) => {
for (const file of ctx.config.user_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next);
return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next);
})(),
// Validator
(async () => {
@@ -34,7 +34,7 @@ export const judge = async (ctx) => {
copyIn[parseFilename(file)] = { src: file };
}
const file = await fs.readFile(ctx.config.validator);
return await compile(parseFilename(ctx.config.validator).split('.')[1], file.toString(), 'validator', copyIn);
return await compile(ctx.getLang(parseFilename(ctx.config.validator).split('.')[1]), file.toString(), 'validator', copyIn);
})(),
// Std
(async () => {
@@ -43,7 +43,7 @@ export const judge = async (ctx) => {
copyIn[parseFilename(file)] = { src: file };
}
const file = await fs.readFile(ctx.config.std);
return await compile(parseFilename(ctx.config.std).split('.')[1], file.toString(), 'std', copyIn);
return await compile(ctx.getLang(parseFilename(ctx.config.std).split('.')[1]), file.toString(), 'std', copyIn);
})(),
// Checker
(async () => {
@@ -53,6 +53,7 @@ export const judge = async (ctx) => {
copyIn[parseFilename(file)] = { src: file };
}
return await compileChecker(
ctx.getLang,
ctx.config.checker_type,
ctx.config.checker,
copyIn,
+4 -11
View File
@@ -1,7 +1,6 @@
import Queue from 'p-queue';
import fs from 'fs-extra';
import { argv } from 'yargs';
import yaml from 'js-yaml';
import * as STATUS from '../status';
import { parse } from '../testlib';
import { findFileSync, parseFilename } from '../utils';
@@ -9,7 +8,6 @@ import { run } from '../sandbox';
import compile from '../compile';
import signals from '../signals';
import { getConfig } from '../config';
import { SystemError } from '../error';
const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h');
const Score = {
@@ -93,27 +91,22 @@ function judgeSubtask(subtask) {
export const judge = async (ctx) => {
ctx.next({ status: STATUS.STATUS_COMPILING });
const LANGS = yaml.load(getConfig('langs'));
if (!LANGS[ctx.lang]) throw new SystemError('Unsupported language {0}.', [ctx.lang]);
const info = LANGS[ctx.lang];
ctx.time_limit_rate = info.time_limit_rate || 1;
ctx.time_limit_rate = ctx.getLang(ctx.lang).time_limit_rate;
[ctx.executeUser, ctx.executeInteractor] = await Promise.all([
(async () => {
const copyIn = {};
for (const file of ctx.config.user_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next);
return await compile(ctx.getLang(ctx.lang), ctx.code, 'code', copyIn, ctx.next);
})(),
(async () => {
const copyIn = {
'testlib.h': testlibSrc,
};
const copyIn = { 'testlib.h': testlibSrc };
for (const file of ctx.config.judge_extra_files) {
copyIn[parseFilename(file)] = { src: file };
}
return await compile(
parseFilename(ctx.config.interactor).split('.')[1],
ctx.getLang(parseFilename(ctx.config.interactor).split('.')[1]),
fs.readFileSync(ctx.config.interactor).toString(),
'interactor',
copyIn,
+1 -1
View File
@@ -12,7 +12,7 @@ export const judge = async (ctx) => {
ctx.stat.judge = new Date();
ctx.next({ status: STATUS.STATUS_COMPILING });
try {
ctx.execute = await compile(ctx.lang, ctx.code, 'code', {}, ctx.next);
ctx.execute = await compile(ctx.getLang(ctx.lang), ctx.code, 'code', {}, ctx.next);
} catch (e) {
if (e instanceof CompileError) {
ctx.next({
+7
View File
@@ -153,6 +153,11 @@ async function postInit() {
};
}
function getLang(lang: string) {
if (system.lang[lang]) return system.lang[lang];
throw new SystemError('Unsupported language {0}.', [lang]);
}
class JudgeTask {
stat: any;
request: any;
@@ -170,11 +175,13 @@ async function postInit() {
tmpdir: string;
clean: any[];
folder: string;
getLang: (lang: string) => any;
constructor(request) {
this.stat = {};
this.stat.receive = new Date();
this.request = request;
this.getLang = getLang;
logger.debug('%o', request);
}