Better handling of recipes and subprocesses.

This commit is contained in:
Daniel Jones 2013-07-09 12:33:25 -07:00
parent bd5c30c30a
commit fc1d2ebfb8
3 changed files with 82 additions and 49 deletions

View file

@ -259,7 +259,7 @@ func expandBackQuoted(input string, vars map[string][]string) (string, int) {
}
// TODO: handle errors
output, _ := subprocess("sh", nil, input[:j], false, false, true)
output, _ := subprocess("sh", nil, input[:j], true)
return output, (j + 1)
}

View file

@ -112,7 +112,7 @@ func parsePipeInclude(p *parser, t token) parserStateFun {
args[i-1] = p.tokenbuf[i].val
}
output, success := subprocess("sh", args, "", false, false, true)
output, success := subprocess("sh", args, "", true)
if !success {
p.basicErrorAtToken("subprocess include failed", t)
}

127
recipe.go
View file

@ -109,65 +109,98 @@ func dorecipe(target string, u *node, e *edge, dryrun bool) bool {
sh,
args,
input,
true,
true,
false)
return success
}
// A monolithic function for executing subprocesses
// Execute a subprocess (typically a recipe).
//
// Args:
// program: Program path or name located in PATH
// input: String piped into the program's stdin
// capture_out: If true, capture and return the program's stdout rather than echoing it.
//
// Returns
// (output, success)
// output is an empty string of catputer_out is false, or the collected output from the profram is true.
//
// success is true if the exit code was 0 and false otherwise
//
func subprocess(program string,
args []string,
input string,
echo_out bool,
echo_err bool,
capture_out bool) (string, bool) {
cmd := exec.Command(program, args...)
if echo_out {
cmdout, err := cmd.StdoutPipe()
if err == nil {
go io.Copy(os.Stdout, cmdout)
}
}
if echo_err {
cmderr, err := cmd.StderrPipe()
if err == nil {
go io.Copy(os.Stderr, cmderr)
}
}
if len(input) > 0 {
cmdin, err := cmd.StdinPipe()
if err == nil {
go func() { cmdin.Write([]byte(input)); cmdin.Close() }()
}
}
output := ""
var err error
if capture_out {
var outbytes []byte
outbytes, err = cmd.Output()
output = string(outbytes)
if len(output) > 0 && output[len(output)-1] == '\n' {
output = output[:len(output)-1]
}
} else {
err = cmd.Run()
}
success := true
program_path, err := exec.LookPath(program)
if err != nil {
exiterr, ok := err.(*exec.ExitError)
if ok {
success = exiterr.ProcessState.Success()
} else {
log.Fatal(err)
}
proc_args := []string{program}
proc_args = append(proc_args, args...)
stdin_pipe_read, stdin_pipe_write, err := os.Pipe()
if err != nil {
log.Fatal(err)
}
attr := os.ProcAttr{Files: []*os.File{stdin_pipe_read, os.Stdout, os.Stderr}}
output := make([]byte, 0)
capture_done := make(chan bool)
if capture_out {
stdout_pipe_read, stdout_pipe_write, err := os.Pipe()
if err != nil {
log.Fatal(err)
}
attr.Files[1] = stdout_pipe_write
go func() {
buf := make([]byte, 1024)
for {
n, err := stdout_pipe_read.Read(buf)
if err != nil {
log.Fatal(err)
}
if n == 0 {
break
}
output = append(output, buf[:n]...)
}
capture_done <- true
}()
}
return output, success
proc, err := os.StartProcess(program_path, proc_args, &attr)
if err != nil {
log.Fatal(err)
}
go func() {
_, err := stdin_pipe_write.WriteString(input)
if err != nil {
log.Fatal(err)
}
err = stdin_pipe_write.Close()
if err != nil {
log.Fatal(err)
}
}()
state, err := proc.Wait()
if err != nil {
log.Fatal(err)
}
// wait until stdout copying in finished
if capture_out {
<-capture_done
}
return string(output), state.Success()
}