package runner import ( "fmt" "os" "os/exec" "strings" "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) (written int, err error) { datalen := len(data) written = datalen l.lock.Lock() defer l.lock.Unlock() for data[datalen-1] == byte('\n') { datalen-- } data = data[:datalen] *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, outfile 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 fmt.Fprintf(logman, "building %s", p.Name) // 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", "exec "+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: %s", err.(*exec.ExitError).ExitCode(), err.Error()) break runcmds } } fmt.Fprintf(logman, "system %.4f, user %.4f", systime.Seconds(), usrtime.Seconds()) // package the files writer, err := os.OpenFile(outfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) defer writer.Close() if err != nil { fmt.Fprintf(logman, "!!!!! error opening output pack: %s", err.Error()) return } packstr := "zip -r - " + strings.Join(p.Output, " ") fmt.Fprintf(logman, "[fin] packing output: \"%s\"", packstr) packcmd := exec.Command("sh", "-c", "exec "+packstr) packcmd.Dir = p.OutputDir packcmd.Stdout = writer packcmd.Stderr = logerr err = packcmd.Run() if err != nil { fmt.Fprintf(logman, "!!!!! pack subprocess return %d: %s", packcmd.ProcessState.ExitCode(), err.Error()) return } fmt.Fprintf(logman, "output is at %s", outfile) return }