Merge branch 'master' into fix/release
This commit is contained in:
commit
5911ec6adc
|
@ -34,6 +34,9 @@ jobs:
|
||||||
rm -rf golangci-lint-1.61.0-linux-amd64.tar.gz
|
rm -rf golangci-lint-1.61.0-linux-amd64.tar.gz
|
||||||
mkdir -p /root/go/bin
|
mkdir -p /root/go/bin
|
||||||
mv /tmp/golangci-lint-1.61.0-linux-amd64/golangci-lint /root/go/bin
|
mv /tmp/golangci-lint-1.61.0-linux-amd64/golangci-lint /root/go/bin
|
||||||
|
- name: Setup Joint-Teapot
|
||||||
|
run: |
|
||||||
|
pip install git+https://ghp.ci/https://github.com/BoYanZh/Joint-Teapot
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: make lint
|
run: make lint
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|
1
Makefile
1
Makefile
|
@ -10,6 +10,7 @@ all: build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
$(foreach APP,$(APPS), go build -ldflags=$(FLAGS) -o $(BUILD_DIR)/$(APP) ./cmd/$(APP);)
|
$(foreach APP,$(APPS), go build -ldflags=$(FLAGS) -o $(BUILD_DIR)/$(APP) ./cmd/$(APP);)
|
||||||
|
cp ./build/repo-health-checker ./build/healthcheck
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BUILD_DIR)/*
|
rm -rf $(BUILD_DIR)/*
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -8,6 +8,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/joint-online-judge/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
"github.com/koding/multiconfig"
|
"github.com/koding/multiconfig"
|
||||||
)
|
)
|
||||||
|
@ -17,6 +18,10 @@ type Conf struct {
|
||||||
SandboxToken string `default:""`
|
SandboxToken string `default:""`
|
||||||
LogPath string `default:""`
|
LogPath string `default:""`
|
||||||
OutputPath string `default:"joj3_result.json"`
|
OutputPath string `default:"joj3_result.json"`
|
||||||
|
GradingRepoName string `default:""`
|
||||||
|
SkipTeapot bool `default:"true"`
|
||||||
|
ScoreboardPath string `default:"scoreboard.csv"`
|
||||||
|
FailedTablePath string `default:"failed-table.md"`
|
||||||
Stages []struct {
|
Stages []struct {
|
||||||
Name string
|
Name string
|
||||||
Group string
|
Group string
|
||||||
|
@ -73,6 +78,23 @@ type ConventionalCommit struct {
|
||||||
Footer string
|
Footer string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCommitMsg() (msg string, err error) {
|
||||||
|
r, err := git.PlainOpen(".")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ref, err := r.Head()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
commit, err := r.CommitObject(ref.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg = commit.Message
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func parseConventionalCommit(commit string) (*ConventionalCommit, error) {
|
func parseConventionalCommit(commit string) (*ConventionalCommit, error) {
|
||||||
re := regexp.MustCompile(`(?s)^(\w+)(\(([^)]+)\))?!?: (.+?)(\n\n(.+?))?(\n\n(.+))?$`)
|
re := regexp.MustCompile(`(?s)^(\w+)(\(([^)]+)\))?!?: (.+?)(\n\n(.+?))?(\n\n(.+))?$`)
|
||||||
matches := re.FindStringSubmatch(strings.TrimSpace(commit))
|
matches := re.FindStringSubmatch(strings.TrimSpace(commit))
|
||||||
|
@ -107,7 +129,7 @@ func parseConfFile(path string) (conf Conf, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMsg(confRoot, confName, msg string) (conf Conf, group string, err error) {
|
func ParseMsg(confRoot, confName, msg string) (conf Conf, group string, err error) {
|
||||||
slog.Info("parse msg", "msg", msg)
|
slog.Info("parse msg", "msg", msg)
|
||||||
conventionalCommit, err := parseConventionalCommit(msg)
|
conventionalCommit, err := parseConventionalCommit(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -142,7 +164,7 @@ func parseMsg(confRoot, confName, msg string) (conf Conf, group string, err erro
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func listValidScopes(confRoot, confName, msg string) ([]string, error) {
|
func ListValidScopes(confRoot, confName, msg string) ([]string, error) {
|
||||||
conventionalCommit, err := parseConventionalCommit(msg)
|
conventionalCommit, err := parseConventionalCommit(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}, err
|
return []string{}, err
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
140
cmd/joj3/main.go
140
cmd/joj3/main.go
|
@ -1,97 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/joint-online-judge/JOJ3/internal/executors"
|
"github.com/joint-online-judge/JOJ3/cmd/joj3/conf"
|
||||||
_ "github.com/joint-online-judge/JOJ3/internal/parsers"
|
"github.com/joint-online-judge/JOJ3/cmd/joj3/stage"
|
||||||
"github.com/joint-online-judge/JOJ3/internal/stage"
|
"github.com/joint-online-judge/JOJ3/cmd/joj3/teapot"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
"github.com/jinzhu/copier"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCommitMsg() (msg string, err error) {
|
|
||||||
r, err := git.PlainOpen(".")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ref, err := r.Head()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
commit, err := r.CommitObject(ref.Hash())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msg = commit.Message
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateStages(conf Conf, group string) ([]stage.Stage, error) {
|
|
||||||
stages := []stage.Stage{}
|
|
||||||
existNames := map[string]bool{}
|
|
||||||
for _, s := range conf.Stages {
|
|
||||||
if s.Group != "" && group != s.Group {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, ok := existNames[s.Name] // check for existence
|
|
||||||
if ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
existNames[s.Name] = true
|
|
||||||
var cmds []stage.Cmd
|
|
||||||
defaultCmd := s.Executor.With.Default
|
|
||||||
for _, optionalCmd := range s.Executor.With.Cases {
|
|
||||||
cmd := s.Executor.With.Default
|
|
||||||
err := copier.Copy(&cmd, &optionalCmd)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("generate stages", "error", err)
|
|
||||||
return stages, err
|
|
||||||
}
|
|
||||||
// since these 3 values are pointers, copier will always copy
|
|
||||||
// them, so we need to check them manually
|
|
||||||
if defaultCmd.Stdin != nil && optionalCmd.Stdin == nil {
|
|
||||||
cmd.Stdin = defaultCmd.Stdin
|
|
||||||
}
|
|
||||||
if defaultCmd.Stdout != nil && optionalCmd.Stdout == nil {
|
|
||||||
cmd.Stdout = defaultCmd.Stdout
|
|
||||||
}
|
|
||||||
if defaultCmd.Stderr != nil && optionalCmd.Stderr == nil {
|
|
||||||
cmd.Stderr = defaultCmd.Stderr
|
|
||||||
}
|
|
||||||
cmds = append(cmds, cmd)
|
|
||||||
}
|
|
||||||
if len(s.Executor.With.Cases) == 0 {
|
|
||||||
cmds = []stage.Cmd{defaultCmd}
|
|
||||||
}
|
|
||||||
stages = append(stages, stage.Stage{
|
|
||||||
Name: s.Name,
|
|
||||||
ExecutorName: s.Executor.Name,
|
|
||||||
ExecutorCmds: cmds,
|
|
||||||
ParserName: s.Parser.Name,
|
|
||||||
ParserConf: s.Parser.With,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
slog.Debug("stages generated", "stages", stages)
|
|
||||||
return stages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func outputResult(outputPath string, results []stage.StageResult) error {
|
|
||||||
slog.Info("output result start", "path", outputPath)
|
|
||||||
slog.Debug("output result start", "path", outputPath, "results", results)
|
|
||||||
content, err := json.Marshal(results)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(outputPath,
|
|
||||||
append(content, []byte("\n")...), 0o600)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
confRoot string
|
confRoot string
|
||||||
confName string
|
confName string
|
||||||
|
@ -107,61 +25,47 @@ func init() {
|
||||||
showVersion = flag.Bool("version", false, "print current version")
|
showVersion = flag.Bool("version", false, "print current version")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainImpl() error {
|
func main() {
|
||||||
if err := setupSlog(""); err != nil { // before conf is loaded
|
if err := setupSlog(""); err != nil { // before conf is loaded
|
||||||
return err
|
slog.Error("setup slog", "error", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *showVersion {
|
if *showVersion {
|
||||||
fmt.Println(Version)
|
fmt.Println(Version)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
slog.Info("start joj3", "version", Version)
|
slog.Info("start joj3", "version", Version)
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
var err error
|
var err error
|
||||||
msg, err = getCommitMsg()
|
msg, err = conf.GetCommitMsg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("get commit msg", "error", err)
|
slog.Error("get commit msg", "error", err)
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conf, group, err := parseMsg(confRoot, confName, msg)
|
confObj, group, err := conf.ParseMsg(confRoot, confName, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("parse msg", "error", err)
|
slog.Error("parse msg", "error", err)
|
||||||
validScopes, scopeErr := listValidScopes(
|
validScopes, scopeErr := conf.ListValidScopes(
|
||||||
confRoot, confName, msg)
|
confRoot, confName, msg)
|
||||||
if scopeErr != nil {
|
if scopeErr != nil {
|
||||||
slog.Error("list valid scopes", "error", scopeErr)
|
slog.Error("list valid scopes", "error", scopeErr)
|
||||||
return scopeErr
|
return
|
||||||
}
|
}
|
||||||
slog.Info("hint: valid scopes in commit message", "scopes", validScopes)
|
slog.Info("hint: valid scopes in commit message", "scopes", validScopes)
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
if err := setupSlog(conf.LogPath); err != nil { // after conf is loaded
|
if err := setupSlog(confObj.LogPath); err != nil { // after conf is loaded
|
||||||
return err
|
slog.Error("setup slog", "error", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
executors.InitWithConf(conf.SandboxExecServer, conf.SandboxToken)
|
if err := stage.Run(confObj, group); err != nil {
|
||||||
stages, err := generateStages(conf, group)
|
slog.Error("stage run", "error", err)
|
||||||
if err != nil {
|
return
|
||||||
slog.Error("generate stages", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
defer stage.Cleanup()
|
if err := teapot.Run(confObj); err != nil {
|
||||||
results, err := stage.Run(stages)
|
slog.Error("teapot run", "error", err)
|
||||||
if err != nil {
|
return
|
||||||
slog.Error("run stages", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := outputResult(conf.OutputPath, results); err != nil {
|
|
||||||
slog.Error("output result", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := mainImpl(); err != nil {
|
|
||||||
slog.Error("main exit", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func readStageResults(t *testing.T, path string) []stage.StageResult {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestRun(t *testing.T) {
|
||||||
var tests []string
|
var tests []string
|
||||||
root := "../../tmp/submodules/JOJ3-examples/examples/"
|
root := "../../tmp/submodules/JOJ3-examples/examples/"
|
||||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
|
94
cmd/joj3/stage/main.go
Normal file
94
cmd/joj3/stage/main.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package stage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/joint-online-judge/JOJ3/cmd/joj3/conf"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/executors"
|
||||||
|
_ "github.com/joint-online-judge/JOJ3/internal/parsers"
|
||||||
|
"github.com/joint-online-judge/JOJ3/internal/stage"
|
||||||
|
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateStages(conf conf.Conf, group string) ([]stage.Stage, error) {
|
||||||
|
stages := []stage.Stage{}
|
||||||
|
existNames := map[string]bool{}
|
||||||
|
for _, s := range conf.Stages {
|
||||||
|
if s.Group != "" && group != s.Group {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, ok := existNames[s.Name] // check for existence
|
||||||
|
if ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existNames[s.Name] = true
|
||||||
|
var cmds []stage.Cmd
|
||||||
|
defaultCmd := s.Executor.With.Default
|
||||||
|
for _, optionalCmd := range s.Executor.With.Cases {
|
||||||
|
cmd := s.Executor.With.Default
|
||||||
|
err := copier.Copy(&cmd, &optionalCmd)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("generate stages", "error", err)
|
||||||
|
return stages, err
|
||||||
|
}
|
||||||
|
// since these 3 values are pointers, copier will always copy
|
||||||
|
// them, so we need to check them manually
|
||||||
|
if defaultCmd.Stdin != nil && optionalCmd.Stdin == nil {
|
||||||
|
cmd.Stdin = defaultCmd.Stdin
|
||||||
|
}
|
||||||
|
if defaultCmd.Stdout != nil && optionalCmd.Stdout == nil {
|
||||||
|
cmd.Stdout = defaultCmd.Stdout
|
||||||
|
}
|
||||||
|
if defaultCmd.Stderr != nil && optionalCmd.Stderr == nil {
|
||||||
|
cmd.Stderr = defaultCmd.Stderr
|
||||||
|
}
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
|
if len(s.Executor.With.Cases) == 0 {
|
||||||
|
cmds = []stage.Cmd{defaultCmd}
|
||||||
|
}
|
||||||
|
stages = append(stages, stage.Stage{
|
||||||
|
Name: s.Name,
|
||||||
|
ExecutorName: s.Executor.Name,
|
||||||
|
ExecutorCmds: cmds,
|
||||||
|
ParserName: s.Parser.Name,
|
||||||
|
ParserConf: s.Parser.With,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
slog.Debug("stages generated", "stages", stages)
|
||||||
|
return stages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputResult(outputPath string, results []stage.StageResult) error {
|
||||||
|
slog.Info("output result start", "path", outputPath)
|
||||||
|
slog.Debug("output result start", "path", outputPath, "results", results)
|
||||||
|
content, err := json.Marshal(results)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(outputPath,
|
||||||
|
append(content, []byte("\n")...), 0o600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(conf conf.Conf, group string) error {
|
||||||
|
executors.InitWithConf(conf.SandboxExecServer, conf.SandboxToken)
|
||||||
|
stages, err := generateStages(conf, group)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("generate stages", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stage.Cleanup()
|
||||||
|
results, err := stage.Run(stages)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("run stages", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := outputResult(conf.OutputPath, results); err != nil {
|
||||||
|
slog.Error("output result", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
77
cmd/joj3/teapot/main.go
Normal file
77
cmd/joj3/teapot/main.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package teapot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/joint-online-judge/JOJ3/cmd/joj3/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(conf conf.Conf) error {
|
||||||
|
if conf.SkipTeapot {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
os.Setenv("LOG_FILE_PATH", "/home/tt/.cache/joint-teapot-debug.log")
|
||||||
|
os.Setenv("_TYPER_STANDARD_TRACEBACK", "1")
|
||||||
|
envFilePath := "/home/tt/.config/teapot/teapot.env"
|
||||||
|
actor := os.Getenv("GITHUB_ACTOR")
|
||||||
|
repository := os.Getenv("GITHUB_REPOSITORY")
|
||||||
|
runNumber := os.Getenv("GITHUB_RUN_NUMBER")
|
||||||
|
if actor == "" || repository == "" || strings.Count(repository, "/") != 1 ||
|
||||||
|
runNumber == "" {
|
||||||
|
slog.Error("teapot env not set")
|
||||||
|
return fmt.Errorf("teapot env not set")
|
||||||
|
}
|
||||||
|
repoParts := strings.Split(repository, "/")
|
||||||
|
repoName := repoParts[1]
|
||||||
|
re := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
|
||||||
|
cmd := exec.Command("joint-teapot", "joj3-scoreboard",
|
||||||
|
envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName,
|
||||||
|
runNumber, conf.ScoreboardPath) // #nosec G204
|
||||||
|
outputBytes, err := cmd.CombinedOutput()
|
||||||
|
output := re.ReplaceAllString(string(outputBytes), "")
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slog.Info("joint-teapot joj3-scoreboard", "output", line)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("joint-teapot joj3-scoreboard", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd = exec.Command("joint-teapot", "joj3-failed-table",
|
||||||
|
envFilePath, conf.OutputPath, actor, conf.GradingRepoName, repoName,
|
||||||
|
runNumber, conf.FailedTablePath) // #nosec G204
|
||||||
|
outputBytes, err = cmd.CombinedOutput()
|
||||||
|
output = re.ReplaceAllString(string(outputBytes), "")
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slog.Info("joint-teapot joj3-scoreboard", "output", line)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("joint-teapot joj3-failed-table", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd = exec.Command("joint-teapot", "joj3-create-result-issue",
|
||||||
|
envFilePath, conf.OutputPath, repoName, runNumber) // #nosec G204
|
||||||
|
outputBytes, err = cmd.CombinedOutput()
|
||||||
|
output = re.ReplaceAllString(string(outputBytes), "")
|
||||||
|
for _, line := range strings.Split(output, "\n") {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slog.Info("joint-teapot joj3-scoreboard", "output", line)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("joint-teapot joj3-create-result-issue", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user