Skip to content

Commit

Permalink
release/dist: add a helper to run commands
Browse files Browse the repository at this point in the history
The helper suppresses output if the command runs successfully. If the
command fails, it dumps the buffered output to stdout before returning
the error. This means the happy path isn't swamped by debug noise or
xcode being intensely verbose about what kind of day it's having,
but you still get debug output when something goes wrong.

Updates tailscale/corp#9045

Signed-off-by: David Anderson <danderson@tailscale.com>
  • Loading branch information
danderson committed Mar 2, 2023
1 parent f18beaa commit 0df1125
Showing 1 changed file with 46 additions and 10 deletions.
56 changes: 46 additions & 10 deletions release/dist/dist.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package dist

import (
"bytes"
"errors"
"fmt"
"log"
Expand Down Expand Up @@ -146,11 +147,11 @@ func (b *Build) Extra(key any, constructor func() any) any {
// GoPkg returns the path on disk of pkg.
// The module of pkg must be imported in b.Repo's go.mod.
func (b *Build) GoPkg(pkg string) (string, error) {
bs, err := exec.Command(b.Go, "list", "-f", "{{.Dir}}", pkg).Output()
out, err := b.Command(b.Repo, b.Go, "list", "-f", "{{.Dir}}", pkg).CombinedOutput()
if err != nil {
return "", fmt.Errorf("finding package %q: %w", pkg, err)
}
return strings.TrimSpace(string(bs)), nil
return strings.TrimSpace(out), nil
}

// TmpDir creates and returns a new empty temporary directory.
Expand Down Expand Up @@ -178,7 +179,7 @@ func (b *Build) BuildGoBinary(path string, env map[string]string) (string, error
// and do other initialization. Running `go version` once takes care of
// all of that and avoids that initialization happening concurrently
// later on in builds.
_, err := exec.Command(b.Go, "version").Output()
_, err := b.Command(b.Repo, b.Go, "version").CombinedOutput()
return err
})
if err != nil {
Expand All @@ -197,15 +198,10 @@ func (b *Build) BuildGoBinary(path string, env map[string]string) (string, error
sort.Strings(envStrs)
log.Printf("Building %s (with env %s)", path, strings.Join(envStrs, " "))
buildDir := b.TmpDir()
cmd := exec.Command(b.Go, "build", "-o", buildDir, path)
cmd.Dir = b.Repo
cmd.Env = os.Environ()
cmd := b.Command(b.Repo, b.Go, "build", "-o", buildDir, path)
for k, v := range env {
cmd.Env = append(cmd.Env, k+"="+v)
cmd.Cmd.Env = append(cmd.Cmd.Env, k+"="+v)
}
cmd.Env = append(cmd.Env, "TS_USE_GOCROSS=1")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return "", err
}
Expand All @@ -217,6 +213,46 @@ func (b *Build) BuildGoBinary(path string, env map[string]string) (string, error
})
}

// Command prepares an exec.Cmd to run [cmd, args...] in dir.
func (b *Build) Command(dir, cmd string, args ...string) *Command {
ret := &Command{
Cmd: exec.Command(cmd, args...),
}
ret.Cmd.Stdout = &ret.Output
ret.Cmd.Stderr = &ret.Output
// dist always wants to use gocross if any Go is involved.
ret.Cmd.Env = append(os.Environ(), "TS_USE_GOCROSS=1")
ret.Cmd.Dir = dir
return ret
}

// Command runs an exec.Cmd and returns its exit status. If the command fails,
// its output is printed to os.Stdout, otherwise it's suppressed.
type Command struct {
Cmd *exec.Cmd
Output bytes.Buffer
}

// Run is like c.Cmd.Run, but if the command fails, its output is printed to
// os.Stdout before returning the error.
func (c *Command) Run() error {
err := c.Cmd.Run()
if err != nil {
// Command failed, dump its output.
os.Stdout.Write(c.Output.Bytes())
}
return err
}

// CombinedOutput is like c.Cmd.CombinedOutput, but returns the output as a
// string instead of a byte slice.
func (c *Command) CombinedOutput() (string, error) {
c.Cmd.Stdout = nil
c.Cmd.Stderr = nil
bs, err := c.Cmd.CombinedOutput()
return string(bs), err
}

func findModRoot(path string) (string, error) {
for {
modpath := filepath.Join(path, "go.mod")
Expand Down

0 comments on commit 0df1125

Please sign in to comment.