github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/helper/exec/exec.go (about)

     1  package exec
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/otto/ui"
    13  )
    14  
    15  // Run runs the given command and streams all the output to the
    16  // given UI. It also connects stdin properly so that input works as
    17  // expected.
    18  func Run(uiVal ui.Ui, cmd *exec.Cmd) error {
    19  	out_r, out_w := io.Pipe()
    20  	cmd.Stdin = os.Stdin
    21  	cmd.Stdout = out_w
    22  	cmd.Stderr = out_w
    23  
    24  	// Copy output to the UI until we can't.
    25  	output := false
    26  	uiDone := make(chan struct{})
    27  	go func() {
    28  		defer close(uiDone)
    29  		var buf [1024]byte
    30  		for {
    31  			n, err := out_r.Read(buf[:])
    32  			if n > 0 {
    33  				output = true
    34  				uiVal.Raw(string(buf[:n]))
    35  			}
    36  
    37  			// We just break on any error. io.EOF is not an error and
    38  			// is our true exit case, but any other error we don't really
    39  			// handle here. It probably means something went wrong
    40  			// somewhere else anyways.
    41  			if err != nil {
    42  				break
    43  			}
    44  		}
    45  	}()
    46  
    47  	// Run the command
    48  	log.Printf("[DEBUG] execDir: %s", cmd.Dir)
    49  	log.Printf("[DEBUG] exec: %s %s", cmd.Path, strings.Join(cmd.Args[1:], " "))
    50  
    51  	// Build a runnable command that we can log out to make things easier
    52  	// for debugging. This lets debuging devs just copy and paste the command.
    53  	logRunnableCommand(cmd)
    54  
    55  	// Run
    56  	err := Runner(cmd)
    57  
    58  	// Wait for all the output to finish
    59  	out_w.Close()
    60  	<-uiDone
    61  
    62  	if output {
    63  		// Output one extra newline to separate output from Otto. We only
    64  		// do this if there was any output to begin with.
    65  		uiVal.Message("")
    66  	}
    67  
    68  	// Return the output from the command
    69  	return err
    70  }
    71  
    72  // OttoSkipCleanupEnvVar, when set, tells Otto to avoid cleaning up its
    73  // temporary workspace files, which can be helpful for debugging.
    74  const OttoSkipCleanupEnvVar = "OTTO_SKIP_CLEANUP"
    75  
    76  // ShouldCleanup returns true for normal operation. It returns false if the
    77  // user requested that Otto avoid cleaning up its temporary files for
    78  // debugging purposes.
    79  func ShouldCleanup() bool {
    80  	return os.Getenv(OttoSkipCleanupEnvVar) == ""
    81  }
    82  
    83  func logRunnableCommand(cmd *exec.Cmd) {
    84  	// Create the struct of the global available environment variables
    85  	//
    86  	// We could probably cache this, but I'm not sure this really matters.
    87  	// The bottleneck should always be subprocess execution and not this.
    88  	global := make(map[string]struct{})
    89  	for _, env := range os.Environ() {
    90  		idx := strings.Index(env, "=")
    91  		if idx == -1 {
    92  			panic("env var doesn't contain = " + env)
    93  		}
    94  
    95  		global[env[:idx]] = struct{}{}
    96  	}
    97  
    98  	// Build the env vars
    99  	var debugBuf bytes.Buffer
   100  	for _, env := range cmd.Env {
   101  		// Split the env var, if we have it globally, then don't put it
   102  		// in the output in order to save some sanity.
   103  		parts := strings.SplitN(env, "=", 2)
   104  		if _, ok := global[parts[0]]; ok {
   105  			continue
   106  		}
   107  
   108  		debugBuf.WriteString(fmt.Sprintf("%s=%q ", parts[0], parts[1]))
   109  	}
   110  
   111  	// Write the command and the arguments
   112  	debugBuf.WriteString(cmd.Path + " ")
   113  	for _, arg := range cmd.Args[1:] {
   114  		// If the argument contains a space, then we want to quote it
   115  		if strings.Contains(arg, " ") {
   116  			debugBuf.WriteString(fmt.Sprintf("'%s' ", arg))
   117  		} else {
   118  			debugBuf.WriteString(fmt.Sprintf("%s ", arg))
   119  		}
   120  	}
   121  
   122  	log.Printf("[DEBUG] exec runnable: %s", debugBuf.String())
   123  }