woj-server/internal/service/runner/compile.go

125 lines
3.6 KiB
Go

package runner
import (
"fmt"
"git.0x7f.app/WOJ/woj-server/internal/e"
"git.0x7f.app/WOJ/woj-server/pkg/file"
"git.0x7f.app/WOJ/woj-server/pkg/utils"
"github.com/opencontainers/runtime-spec/specs-go"
"go.uber.org/zap"
"io"
"os"
"path/filepath"
"time"
)
func (s *service) Compile(meta *JudgeMeta) (*JudgeStatus, e.Status) {
// 1. ensure problem/user exists
status := s.ValidatePath(meta)
if status != e.Success {
return &JudgeStatus{Message: "check failed"}, status
}
config, cLang, status := s.GetConfig(meta, true)
if status != e.Success {
s.log.Error("[compile] parse config failed", zap.Any("meta", *meta))
return &JudgeStatus{
Message: "parse config failed",
Tasks: []TaskStatus{{Verdict: VerdictSystemError, Message: "parse config failed"}},
}, e.RunnerProblemParseFailed
}
// 2. prepare judge environment
workDir := filepath.Join(UserDir, meta.User)
judgeDir := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Version), "judge")
sourceFile := filepath.Join(workDir, fmt.Sprintf("%s.%s", meta.User, meta.Lang))
targetFile := filepath.Join(workDir, fmt.Sprintf("%s.out", meta.User))
logFile := filepath.Join(workDir, fmt.Sprintf("%s.compile.log", meta.User))
log, err := os.Create(logFile)
if err != nil {
s.log.Error("[compile] create log file failed", zap.Error(err))
return &JudgeStatus{
Message: "create log file failed",
Tasks: []TaskStatus{{Verdict: VerdictSystemError, Message: "create log file failed"}},
}, e.RunnerUserCompileFailed
}
defer func(log *os.File) {
_ = log.Close()
}(log)
// 3. compile
err = utils.NewMust().
DoAny(func() error { return os.Remove(targetFile) }).
Do(func() error { return file.TouchErr(targetFile) }).
Do(func() error {
l, ok := config.FilterLanguage(meta.Lang)
if !ok {
return e.RunnerProblemParseFailed.AsError()
}
script := l.JudgeScript()
args := &RunArgs{
Program: ProgramArgs{
Args: []string{"sh", "-c", fmt.Sprintf("cd /woj/user && make -f %s compile", script)},
Env: []string{fmt.Sprintf("USER_PROG=%s", meta.User), fmt.Sprintf("LANG=%s", meta.Lang)},
},
Runtime: RuntimeArgs{
Image: ContainerImageFull,
Pid: int64(cLang.Runtime.Compile.NProcLimit + 2), // bash + make
Memory: uint64(cLang.Runtime.Compile.MemoryLimit * 1024 * 1024),
Timeout: time.Duration((cLang.Runtime.Compile.TimeLimit+1000)/1000) * time.Second,
},
IO: IOArgs{
Output: log,
Limit: 4 * 1024, // 4 KB
},
}
args.Runtime.Mount = []specs.Mount{
{
Source: judgeDir,
Destination: "/woj/problem/judge",
Type: "bind",
Options: []string{"rbind", "ro"},
},
{
Source: sourceFile,
Destination: fmt.Sprintf("/woj/user/%s.%s", meta.User, meta.Lang),
Type: "bind",
Options: []string{"rbind", "ro"},
},
{
Source: targetFile,
Destination: fmt.Sprintf("/woj/user/%s.out", meta.User),
Type: "bind",
Options: []string{"rbind"},
},
}
id := s.ContainerRunPool(args)
return s.pool.WaitForTask(id)
}).
Done()
if err != nil {
s.log.Info("[compile] compile failed", zap.Error(err), zap.Any("meta", *meta))
status = e.RunnerUserCompileFailed
}
// 4. read log
_, _ = log.Seek(0, io.SeekStart)
msg, err := io.ReadAll(log)
msg = utils.If(err == nil, msg, nil)
msgText := string(msg)
if !file.Exist(targetFile) || file.Empty(targetFile) {
return &JudgeStatus{
Message: "compile failed",
Tasks: []TaskStatus{{Verdict: VerdictCompileError, Message: msgText}}},
utils.If(status == e.Success, e.RunnerUserCompileFailed, status)
}
return &JudgeStatus{}, e.Success
}