Files
OJ2/packages/contract/src/language.ts
T
xuyueandClaude Opus 5 8b4d8899f9
Deploy / deploy (push) Canceled after 0s
feat(判题机): 自建镜像升级工具链,语言收敛到 C / C++ / Python
上游 QingdaoU/JudgeServer 停更在 2024-04(registry 上的 latest 和 1.6.1 是同一份
镜像,编译器停在 gcc-13),没有新版可拉,所以自己重编。docker/judge/ 是只改工具链
的 Dockerfile 分叉,server/ 和 Judger/ 从上游固定 commit b28aa56 拉,一行没动。

镜像 oj2-judge-2(不在任何 registry 上:本机 build.sh --save → scp → docker load)
- gcc/g++ 13 → 14.2,Python 3.12 → 3.13.5,都是 trixie 默认
- Go / JDK / Node 整套删掉:前端的题目语言复选框从来只给 C / C++ / Python / SQL,
  12 万条提交里 Java 44 条、Golang 15、JavaScript 3,全是很早以前的
- 体积 1.1GB → 433MB;默认走清华源,构建 12 分钟 → 40 秒(--no-mirror 换回官方)
- deploy.sh 加一道自检:镜像不在本机就中止,并打印该跑的三条命令

C 的编译参数加三个 -Wno-error(implicit-function-declaration / int-conversion /
incompatible-pointer-types):gcc-14 把它们从 warning 提成了 error,而 -w 压不住。

语言值统一成 Python(迁移 0019 / 0020)
- 0019:Python3(104527 条提交)与 Python2(3 条)并成 Python,一并改掉 937 道题的
  languages、75 个 template 键、15 个 ast_rules 键、257 条 answers、1235 个用户的
  成就指标 _languages(languages_used 重算,总和 1928 → 1925,少的 3 个是同时用过
  两种 Python 的人)
- 0020:把 Java / JavaScript / Golang 从 84 道题的可选语言里摘掉 —— 不摘的话那些题
  的语言下拉还能选 Java,提交必 SYSTEM_ERROR
- 契约新增 normalizeLanguage() 别名表,判题侧一律走 judgeConfigFor():旧客户端
  localStorage 里的 Python3、迁移前排进队列的任务都还能判;协作的语言归一也走它,
  否则上线那一刻学生页面里的 Python3 会静默落到 C
- 回滚要连数据一起回,只滚代码会让所有 Python 提交变 SYSTEM_ERROR

实跑
- 判题冒烟 docker/judge/smoke.ts 13 条全过:三种语言、六种状态码、gcc 宽松度
- 拿备份里的真实代码逐文件比对新旧镜像的编译结果,0 差异:C 提交 1951 份
  (1725 过 / 226 CE)、C++ 882 份、Python 2000 份、20 篇 C 教程的 93 个代码块。
  不加那三个 -Wno-error 的话,C 有 26 份会从能过变成 CE
- 迁移在灌了 12.4 万行真实数据的一次性库里跑过:0 残留、没有题目被清空;
  dev 库用真正的执行器跑通
- check:ast 56 个 target 全过,前后端 typecheck 均 0

顺带记下一个升级之前就有的坑(现在随 Go 一起消失,写在 README 里):GOCACHE 指向
容器的 tmpfs,判题机重启后第一次 Go 提交是冷构建,Go 1.22 要 5.6 秒 CPU、超过 3 秒
的编译预算,于是重启后第一个交 Go 的学生必吃一次 CE,后面的人缓存热了又都正常。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 06:24:05 -06:00

76 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod"
/**
* 判题沙箱认得的语言。**这是全仓唯一的语言集合定义处。**
*
* 键名必须与 `apps/api/src/judge/languages.ts` 的 `languageConfigs` 一致 ——
* 沙箱的编译/运行命令按这些键查表,查不到就是 `Unsupported judge language`。
* 后端那边是 `Record<string, …>`(判题机要按名字取配置,不能窄化成联合),
* 所以这个联合是**手写**的,改 languages.ts 时两边一起改。
*
* **这里列的是历史上出现过的值,不等于现在能提交的语言。** 现在判题沙箱只认
* `C` / `C++` / `Python``languages.ts` 里就这三块),前端的语言复选框也只给这三个
* 加 SQL。`Java`(44 条) / `Golang`(15) / `JavaScript`(3) 是历史值:判题机没有它们的
* 编译配置、镜像里也没有工具链了,但生产库里有这些提交记录,提交列表要能渲染出来,
* 所以键必须留着。
*
* Java / JavaScript / Golang 是 2026-09 随判题镜像升级一起下掉的,理由见
* `apps/api/src/judge/languages.ts` 顶部。
*
* `Python2` / `Python3` 这两个旧值**已经不在这里了** —— 0019 迁移把库里的
* 104530 条提交、937 道题、1235 个用户的成就指标全并成了 `Python`。万一还有旧值
* 从别处冒出来(旧客户端的 localStorage、迁移之前排进队列的任务),走下面的
* `normalizeLanguage()`,别直接 parse。
*/
export const judgeLanguageSchema = z.enum([
"C",
"C++",
"Python",
"Java",
"JavaScript",
"Golang",
])
/**
* 题目可以挂的语言 = 沙箱语言 + 两种非沙箱题型。
*
* - `SQL` 走 `judge/sql/` 那条独立链路(子进程 + sql.js),不经过沙箱;
* - `Flowchart` 走 AI 评分(flowchart/run.ts),也不经过沙箱。
*
* **两者都是真实可选、真实有历史数据的**,不是预留值:生产库 961 道题里有
* 9 道 SQL 题、124191 条提交里有 91 条 SQL。前端原来手抄的语言联合漏了 SQL,
* 于是那 91 条提交的 `language` 在类型上是 `undefined` —— 这就是两份真相
* 各自演进的代价,现在并成一份。
*/
export const problemLanguageSchema = z.enum([
...judgeLanguageSchema.options,
"SQL",
"Flowchart",
])
export type JudgeLanguage = z.infer<typeof judgeLanguageSchema>
export type ProblemLanguage = z.infer<typeof problemLanguageSchema>
/**
* 旧语言名 → 现在的值。**库里已经没有旧值了**(0019 迁移清干净并核对过),
* 这张表挡的是数据之外的三条路:
*
* 1. 学生浏览器 localStorage 里存着上次选的 `Python3`(上线那一刻还在那儿);
* 2. 迁移之前就排进 BullMQ 的判题任务;
* 3. 万一要回滚 —— 旧版后端读 `Python` 会查不到配置,所以**回滚必须连数据一起回**,
* 别只滚代码。
*/
const LANGUAGE_ALIASES: Record<string, ProblemLanguage> = {
Python2: "Python",
Python3: "Python",
}
/** 把可能是旧值的语言名归一化;认不出来返回 null,由调用方决定怎么兜底。 */
export function normalizeLanguage(value: unknown): ProblemLanguage | null {
if (typeof value !== "string") return null
const parsed = problemLanguageSchema.safeParse(
LANGUAGE_ALIASES[value] ?? value,
)
return parsed.success ? parsed.data : null
}