feat: run stages and teapot in joj3 #47
							
								
								
									
										169
									
								
								cmd/joj3/main.go
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								cmd/joj3/main.go
									
									
									
									
									
								
							|  | @ -1,170 +1,19 @@ | |||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"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/go-git/go-git/v5" | ||||
| 	"github.com/jinzhu/copier" | ||||
| 	stagerunner "github.com/joint-online-judge/JOJ3/cmd/joj3/stage-runner" | ||||
| 	teapotcaller "github.com/joint-online-judge/JOJ3/cmd/joj3/teapot-caller" | ||||
| ) | ||||
| 
 | ||||
| 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 ( | ||||
| 	confRoot    string | ||||
| 	confName    string | ||||
| 	msg         string | ||||
| 	showVersion *bool | ||||
| 	Version     string = "debug" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	flag.StringVar(&confRoot, "conf-root", ".", "root path for all config files") | ||||
| 	flag.StringVar(&confName, "conf-name", "conf.json", "filename for config files") | ||||
| 	flag.StringVar(&msg, "msg", "", "message to trigger the running, leave empty to use git commit message on HEAD") | ||||
| 	showVersion = flag.Bool("version", false, "print current version") | ||||
| } | ||||
| 
 | ||||
| func mainImpl() error { | ||||
| 	for _, e := range os.Environ() { | ||||
| 		fmt.Println(e) | ||||
| 	} | ||||
| 	if err := setupSlog(""); err != nil { // before conf is loaded
 | ||||
| 		return err | ||||
| 	} | ||||
| 	flag.Parse() | ||||
| 	if *showVersion { | ||||
| 		fmt.Println(Version) | ||||
| 		return nil | ||||
| 	} | ||||
| 	slog.Info("start joj3", "version", Version) | ||||
| 	if msg == "" { | ||||
| 		var err error | ||||
| 		msg, err = getCommitMsg() | ||||
| 		if err != nil { | ||||
| 			slog.Error("get commit msg", "error", err) | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	conf, group, err := parseMsg(confRoot, confName, msg) | ||||
| 	if err != nil { | ||||
| 		slog.Error("parse msg", "error", err) | ||||
| 		validScopes, scopeErr := listValidScopes( | ||||
| 			confRoot, confName, msg) | ||||
| 		if scopeErr != nil { | ||||
| 			slog.Error("list valid scopes", "error", scopeErr) | ||||
| 			return scopeErr | ||||
| 		} | ||||
| 		slog.Info("hint: valid scopes in commit message", "scopes", validScopes) | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := setupSlog(conf.LogPath); err != nil { // after conf is loaded
 | ||||
| 		return err | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| 	if err := mainImpl(); err != nil { | ||||
| 		slog.Error("main exit", "error", err) | ||||
| 		os.Exit(1) | ||||
| 	// TODO: call stage-runner
 | ||||
| 	if err := stagerunner.Run(); err != nil { | ||||
| 		slog.Error("stage runner error", "error", err) | ||||
| 	} | ||||
| 	// TODO: call joint-teapot
 | ||||
| 	if err := teapotcaller.Run(); err != nil { | ||||
| 		slog.Error("teapot caller error", "error", err) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| package main | ||||
| package stagerunner | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | @ -1,4 +1,4 @@ | |||
| package main | ||||
| package stagerunner | ||||
| 
 | ||||
| import ( | ||||
| 	"reflect" | ||||
|  | @ -1,4 +1,4 @@ | |||
| package main | ||||
| package stagerunner | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
							
								
								
									
										160
									
								
								cmd/joj3/stage-runner/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								cmd/joj3/stage-runner/main.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,160 @@ | |||
| package stagerunner | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"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/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 ( | ||||
| 	confRoot    string | ||||
| 	confName    string | ||||
| 	msg         string | ||||
| 	showVersion *bool | ||||
| 	Version     string = "debug" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	flag.StringVar(&confRoot, "conf-root", ".", "root path for all config files") | ||||
| 	flag.StringVar(&confName, "conf-name", "conf.json", "filename for config files") | ||||
| 	flag.StringVar(&msg, "msg", "", "message to trigger the running, leave empty to use git commit message on HEAD") | ||||
| 	showVersion = flag.Bool("version", false, "print current version") | ||||
| } | ||||
| 
 | ||||
| func Run() error { | ||||
| 	if err := setupSlog(""); err != nil { // before conf is loaded
 | ||||
| 		return err | ||||
| 	} | ||||
| 	flag.Parse() | ||||
| 	if *showVersion { | ||||
| 		fmt.Println(Version) | ||||
| 		return nil | ||||
| 	} | ||||
| 	slog.Info("start joj3", "version", Version) | ||||
| 	if msg == "" { | ||||
| 		var err error | ||||
| 		msg, err = getCommitMsg() | ||||
| 		if err != nil { | ||||
| 			slog.Error("get commit msg", "error", err) | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	conf, group, err := parseMsg(confRoot, confName, msg) | ||||
| 	if err != nil { | ||||
| 		slog.Error("parse msg", "error", err) | ||||
| 		validScopes, scopeErr := listValidScopes( | ||||
| 			confRoot, confName, msg) | ||||
| 		if scopeErr != nil { | ||||
| 			slog.Error("list valid scopes", "error", scopeErr) | ||||
| 			return scopeErr | ||||
| 		} | ||||
| 		slog.Info("hint: valid scopes in commit message", "scopes", validScopes) | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := setupSlog(conf.LogPath); err != nil { // after conf is loaded
 | ||||
| 		return err | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
|  | @ -1,4 +1,4 @@ | |||
| package main | ||||
| package stagerunner | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
|  | @ -61,9 +61,9 @@ func readStageResults(t *testing.T, path string) []stage.StageResult { | |||
| 	return results | ||||
| } | ||||
| 
 | ||||
| func TestMain(t *testing.T) { | ||||
| func TestRun(t *testing.T) { | ||||
| 	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 { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
|  | @ -105,7 +105,9 @@ func TestMain(t *testing.T) { | |||
| 			os.Args = []string{"./joj3"} | ||||
| 			outputFile := "joj3_result.json" | ||||
| 			defer os.Remove(outputFile) | ||||
| 			main() | ||||
| 			if err := Run(); err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 			stageResults := readStageResults(t, outputFile) | ||||
| 			regex := true | ||||
| 			expectedFile := "expected_regex.json" | ||||
							
								
								
									
										18
									
								
								cmd/joj3/teapot-caller/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								cmd/joj3/teapot-caller/main.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| package teapotcaller | ||||
| 
 | ||||
| import ( | ||||
| 	"log/slog" | ||||
| 	"os/exec" | ||||
| ) | ||||
| 
 | ||||
| func Run() error { | ||||
| 	// TODO: call teapot
 | ||||
| 	cmd := exec.Command("joint-teapot", "--help") | ||||
| 	output, err := cmd.CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		slog.Error("running git command:", "err", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	slog.Info("joint-teapot run", "output", string(output)) | ||||
| 	return nil | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user