From 57fc43ff9c44013d3cfba569ddb3050763deca4f Mon Sep 17 00:00:00 2001 From: Edgaru089 Date: Tue, 15 Aug 2023 18:41:11 +0800 Subject: [PATCH] Initial commit --- .gitignore | 1 + cmd/pbuild/config.go | 11 +++++ cmd/pbuild/main.go | 27 +++++++++++++ cmd/pbuild/profile.json | 8 ++++ go.mod | 3 ++ internal/runner/profile.go | 41 +++++++++++++++++++ internal/runner/run.go | 82 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 173 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/pbuild/config.go create mode 100644 cmd/pbuild/main.go create mode 100644 cmd/pbuild/profile.json create mode 100644 go.mod create mode 100644 internal/runner/profile.go create mode 100644 internal/runner/run.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf85505 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cmd/pbuild/pbuild diff --git a/cmd/pbuild/config.go b/cmd/pbuild/config.go new file mode 100644 index 0000000..621accd --- /dev/null +++ b/cmd/pbuild/config.go @@ -0,0 +1,11 @@ +package main + +// Config file for the daemon. +type Config struct { + WorkDir string // Working directory. + ListenAddr string // Listen address for net.Dial. +} + +// Post message in Gitea format, only taking what we're interested in. +type GiteaPost struct { +} diff --git a/cmd/pbuild/main.go b/cmd/pbuild/main.go new file mode 100644 index 0000000..290d8af --- /dev/null +++ b/cmd/pbuild/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "edgaru089.ink/go/pbuild/internal/runner" +) + +func main() { + + var profile runner.Profile + + file, err := os.ReadFile("profile.json") + if err != nil { + panic(err) + } + err = json.Unmarshal(file, &profile) + if err != nil { + panic(err) + } + + logs, _, _ := profile.Run(".") + + fmt.Print(logs) +} diff --git a/cmd/pbuild/profile.json b/cmd/pbuild/profile.json new file mode 100644 index 0000000..8cc531d --- /dev/null +++ b/cmd/pbuild/profile.json @@ -0,0 +1,8 @@ +{ + "Name": "pbuild", + "ID": "pbuild", + "Commands": [ + "go clean", + "go build -v" + ] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..912d7f7 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module edgaru089.ink/go/pbuild + +go 1.21.0 diff --git a/internal/runner/profile.go b/internal/runner/profile.go new file mode 100644 index 0000000..88fa8bc --- /dev/null +++ b/internal/runner/profile.go @@ -0,0 +1,41 @@ +package runner + +import ( + "fmt" + "strings" + "time" +) + +// A Profile defines what to run when invoked on the target directory. +type Profile struct { + Name string // Name of the profile. + ID string // ID of the profile. + Commands []string // Commands to run on the profile. + Environ map[string]string // Environment variables + + Output []string // List of globs to match output files. + OutputDir string // The globs will be matched in this relative directory. +} + +type LogEntry struct { + Time time.Time + Text string + OutFd int // 0(manual) 1(stdout) 2(stderr) +} + +type Logs []LogEntry + +func (logs Logs) String() string { + var buf strings.Builder + + for _, log := range logs { + switch log.OutFd { + case 0: + fmt.Fprintf(&buf, "%s %s\n", log.Time.Format("15:04:05"), log.Text) + case 1, 2: + fmt.Fprintf(&buf, "%s %s\n", log.Time.Format("15:04:05"), log.Text) + } + } + + return buf.String() +} diff --git a/internal/runner/run.go b/internal/runner/run.go new file mode 100644 index 0000000..d42fe05 --- /dev/null +++ b/internal/runner/run.go @@ -0,0 +1,82 @@ +package runner + +import ( + "fmt" + "os" + "os/exec" + "sync" + "time" +) + +type logsWriter struct { + lock *sync.Mutex + logs *Logs + outfd int // 0(manual message) 1(stdout) or 2(stderr) +} + +func (l *logsWriter) Write(data []byte) (len int, err error) { + l.lock.Lock() + defer l.lock.Unlock() + + *l.logs = append(*l.logs, LogEntry{ + Time: time.Now(), + Text: string(data), + OutFd: l.outfd, + }) + return +} + +// Run runs the profile on the target directory, preserving output. +func (p *Profile) Run(dir string) (logs Logs, exitcode int, err error) { + + // Try if the directory exists + _, err = os.ReadDir(dir) + if err != nil { + return + } + + // Construct the env slice + env := make([]string, len(p.Environ)) + { + i := 0 + for key, val := range p.Environ { + env[i] = key + "=" + val + i++ + } + } + + loglock := &sync.Mutex{} + logman := &logsWriter{logs: &logs, lock: loglock, outfd: 0} + logout := &logsWriter{logs: &logs, lock: loglock, outfd: 1} + logerr := &logsWriter{logs: &logs, lock: loglock, outfd: 2} + + var systime, usrtime time.Duration + + // Run commands +runcmds: + for i, str := range p.Commands { + + fmt.Fprintf(logman, "[%d/%d] running \"%s\"", i, len(p.Commands), str) + + cmd := exec.Command("sh", "-c", str) + cmd.Env = append(cmd.Env, env...) + cmd.Stdout = logout + cmd.Stderr = logerr + cmd.Dir = dir + + err := cmd.Run() + + systime += cmd.ProcessState.SystemTime() + usrtime += cmd.ProcessState.UserTime() + + switch err.(type) { + case *exec.ExitError: + fmt.Fprintf(logman, "!!!!! command exit with code %d !!!!!", err.(*exec.ExitError).ExitCode()) + break runcmds + } + } + + fmt.Fprintf(logman, "system %.4f, user %.4f", systime.Seconds(), usrtime.Seconds()) + + return +}