220 lines
6.6 KiB
JavaScript
220 lines
6.6 KiB
JavaScript
|
|
/**
|
|||
|
|
* C 代码编译运行后端服务
|
|||
|
|
* 接收 C 代码 → 编译 → 运行 → 返回输出
|
|||
|
|
*/
|
|||
|
|
const express = require('express')
|
|||
|
|
const cors = require('cors')
|
|||
|
|
const { execSync } = require('child_process')
|
|||
|
|
const path = require('path')
|
|||
|
|
const fs = require('fs')
|
|||
|
|
|
|||
|
|
const app = express()
|
|||
|
|
const PORT = 3001
|
|||
|
|
|
|||
|
|
app.use(cors())
|
|||
|
|
app.use(express.json({ limit: '1mb' }))
|
|||
|
|
|
|||
|
|
// 查找可用的 C 编译器
|
|||
|
|
// w64devkit GCC 路径
|
|||
|
|
const W64DEVKIT_DIR = path.join(__dirname, 'bin', 'w64devkit', 'w64devkit')
|
|||
|
|
const W64DEVKIT_GCC = path.join(W64DEVKIT_DIR, 'bin', 'gcc.exe')
|
|||
|
|
const W64DEVKIT_PREFIX = path.join(W64DEVKIT_DIR, 'lib', 'gcc')
|
|||
|
|
|
|||
|
|
function findCompiler() {
|
|||
|
|
const candidates = [
|
|||
|
|
// MinGW / MSYS2 (PATH)
|
|||
|
|
'gcc.exe',
|
|||
|
|
'x86_64-w64-mingw32-gcc.exe',
|
|||
|
|
'i686-w64-mingw32-gcc.exe',
|
|||
|
|
// TCC (Tiny C Compiler)
|
|||
|
|
'tcc.exe',
|
|||
|
|
// Clang
|
|||
|
|
'clang.exe',
|
|||
|
|
// MSVC
|
|||
|
|
'cl.exe',
|
|||
|
|
// 本项目的便携编译器
|
|||
|
|
path.join(__dirname, 'bin', 'tcc.exe'),
|
|||
|
|
path.join(__dirname, 'bin', 'gcc.exe'),
|
|||
|
|
path.join(__dirname, '..', 'tools', 'tcc', 'tcc.exe'),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
// 先在 PATH 中查找
|
|||
|
|
for (const name of ['gcc.exe', 'tcc.exe', 'clang.exe', 'cl.exe']) {
|
|||
|
|
try {
|
|||
|
|
execSync(`where ${name}`, { stdio: 'pipe', timeout: 3000 })
|
|||
|
|
return { path: name, name }
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查 w64devkit GCC(需设置 GCC_EXEC_PREFIX)
|
|||
|
|
if (fs.existsSync(W64DEVKIT_GCC)) {
|
|||
|
|
return { path: W64DEVKIT_GCC, name: 'gcc.exe', execPrefix: W64DEVKIT_PREFIX }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 再检查其他具体路径
|
|||
|
|
for (const candidate of candidates) {
|
|||
|
|
try {
|
|||
|
|
const absPath = path.resolve(candidate)
|
|||
|
|
if (fs.existsSync(absPath)) {
|
|||
|
|
return { path: absPath, name: path.basename(absPath) }
|
|||
|
|
}
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 搜索 Visual Studio MSVC 安装目录
|
|||
|
|
try {
|
|||
|
|
const vsPaths = [
|
|||
|
|
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC',
|
|||
|
|
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\MSVC',
|
|||
|
|
'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC',
|
|||
|
|
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC',
|
|||
|
|
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC',
|
|||
|
|
]
|
|||
|
|
for (const vsPath of vsPaths) {
|
|||
|
|
if (fs.existsSync(vsPath)) {
|
|||
|
|
const versions = fs.readdirSync(vsPath).sort().reverse()
|
|||
|
|
for (const ver of versions) {
|
|||
|
|
const clPath = path.join(vsPath, ver, 'bin', 'Hostx64', 'x64', 'cl.exe')
|
|||
|
|
if (fs.existsSync(clPath)) {
|
|||
|
|
return { path: clPath, name: 'cl.exe' }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 编译并运行 C 代码
|
|||
|
|
app.post('/api/run-c', (req, res) => {
|
|||
|
|
const { code } = req.body
|
|||
|
|
if (!code) {
|
|||
|
|
return res.status(400).json({ error: '请提供 C 代码' })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const compiler = findCompiler()
|
|||
|
|
if (!compiler) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
error: '未找到 C 编译器',
|
|||
|
|
hint: '请安装 MinGW-w64 GCC 编译器',
|
|||
|
|
installGuide: [
|
|||
|
|
'--- 安装 MinGW-w64 ---',
|
|||
|
|
'方法一(推荐):下载 MinGW-w64 GCC',
|
|||
|
|
' 1. 访问 https://winlibs.com/ 下载 GCC (MinGW-w64)',
|
|||
|
|
' 2. 解压并将 bin/ 目录添加到系统 PATH 环境变量',
|
|||
|
|
' 3. 重启终端后即可使用',
|
|||
|
|
'',
|
|||
|
|
'方法二(轻量):下载 TCC (Tiny C Compiler)',
|
|||
|
|
' 将 tcc.exe 放入 server/bin/ 目录',
|
|||
|
|
'',
|
|||
|
|
'安装后重新启动: npm run server',
|
|||
|
|
].join('\n')
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const tmpDir = path.join(__dirname, '..', 'tmp')
|
|||
|
|
if (!fs.existsSync(tmpDir)) {
|
|||
|
|
fs.mkdirSync(tmpDir, { recursive: true })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const srcFile = path.join(tmpDir, `code_${Date.now()}.c`)
|
|||
|
|
const outFile = path.join(tmpDir, `code_${Date.now()}.exe`)
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 写入源码
|
|||
|
|
fs.writeFileSync(srcFile, code, 'utf-8')
|
|||
|
|
|
|||
|
|
let compileCmd
|
|||
|
|
const execOptions = {
|
|||
|
|
timeout: 15000,
|
|||
|
|
cwd: tmpDir,
|
|||
|
|
encoding: 'utf-8',
|
|||
|
|
env: { ...process.env }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (compiler.name.includes('tcc')) {
|
|||
|
|
// TCC: 编译并运行一步到位(支持 -run)
|
|||
|
|
compileCmd = `"${compiler.path}" -run "${srcFile}"`
|
|||
|
|
} else if (compiler.execPrefix) {
|
|||
|
|
// w64devkit GCC: 需要设置 GCC_EXEC_PREFIX 并加入 PATH
|
|||
|
|
const w64binDir = path.dirname(compiler.path)
|
|||
|
|
execOptions.env.GCC_EXEC_PREFIX = compiler.execPrefix + path.sep
|
|||
|
|
execOptions.env.PATH = w64binDir + path.delimiter + (execOptions.env.PATH || '')
|
|||
|
|
compileCmd = `"${compiler.path}" "${srcFile}" -o "${outFile}" -Wall -std=c99`
|
|||
|
|
} else {
|
|||
|
|
// GCC/Clang: 先编译再运行
|
|||
|
|
compileCmd = `"${compiler.path}" "${srcFile}" -o "${outFile}" -Wall -std=c99 2>&1`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 编译
|
|||
|
|
const compileOutput = execSync(compileCmd, execOptions)
|
|||
|
|
|
|||
|
|
let runOutput = compileOutput
|
|||
|
|
|
|||
|
|
// GCC/Clang: 编译后还需运行 exe
|
|||
|
|
if (!compiler.name.includes('tcc') && fs.existsSync(outFile)) {
|
|||
|
|
try {
|
|||
|
|
runOutput = execSync(`"${outFile}"`, {
|
|||
|
|
timeout: 10000,
|
|||
|
|
cwd: tmpDir,
|
|||
|
|
encoding: 'utf-8',
|
|||
|
|
env: execOptions.env
|
|||
|
|
})
|
|||
|
|
} catch (runErr) {
|
|||
|
|
runOutput = runErr.stdout || runErr.message
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理临时文件
|
|||
|
|
try {
|
|||
|
|
if (fs.existsSync(srcFile)) fs.unlinkSync(srcFile)
|
|||
|
|
if (fs.existsSync(outFile)) fs.unlinkSync(outFile)
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
res.json({ output: runOutput })
|
|||
|
|
} catch (compileErr) {
|
|||
|
|
// 编译错误
|
|||
|
|
res.json({
|
|||
|
|
output: compileErr.stdout || compileErr.message,
|
|||
|
|
isError: true
|
|||
|
|
})
|
|||
|
|
} finally {
|
|||
|
|
// 确保清理
|
|||
|
|
try {
|
|||
|
|
if (fs.existsSync(srcFile)) fs.unlinkSync(srcFile)
|
|||
|
|
if (fs.existsSync(outFile)) fs.unlinkSync(outFile)
|
|||
|
|
} catch {}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 健康检查
|
|||
|
|
app.get('/api/status', (req, res) => {
|
|||
|
|
const compiler = findCompiler()
|
|||
|
|
res.json({
|
|||
|
|
status: 'ok',
|
|||
|
|
compiler: compiler ? `${compiler.name} (${compiler.path})` : '未安装'
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 下载 TCC 脚本
|
|||
|
|
app.get('/api/setup-compiler', (req, res) => {
|
|||
|
|
res.json({
|
|||
|
|
message: '请手动下载 TCC:',
|
|||
|
|
url: 'https://raw.githubusercontent.com/FooBarWidget/tinycc/master/tcc.exe',
|
|||
|
|
instructions: `将 tcc.exe 放入 ${path.join(__dirname, 'bin')} 目录`
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
app.listen(PORT, () => {
|
|||
|
|
console.log(`✅ C 代码运行服务已启动: http://localhost:${PORT}`)
|
|||
|
|
const compiler = findCompiler()
|
|||
|
|
if (compiler) {
|
|||
|
|
console.log(` 检测到编译器: ${compiler.name}`)
|
|||
|
|
} else {
|
|||
|
|
console.log(` ⚠️ 未检测到 C 编译器,请安装后使用`)
|
|||
|
|
console.log(` 下载 TCC: https://raw.githubusercontent.com/FooBarWidget/tinycc/master/tcc.exe`)
|
|||
|
|
console.log(` 放置路径: ${path.join(__dirname, 'bin', 'tcc.exe')}`)
|
|||
|
|
}
|
|||
|
|
})
|