1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package build 16 17import ( 18 "bufio" 19 "io" 20 "os/exec" 21 "strings" 22 "syscall" 23 "time" 24) 25 26// Cmd is a wrapper of os/exec.Cmd that integrates with the build context for 27// logging, the config's Environment for simpler environment modification, and 28// implements hooks for sandboxing 29type Cmd struct { 30 *exec.Cmd 31 32 Environment *Environment 33 Sandbox Sandbox 34 35 ctx Context 36 config Config 37 name string 38 39 started time.Time 40} 41 42func Command(ctx Context, config Config, name string, executable string, args ...string) *Cmd { 43 ret := &Cmd{ 44 Cmd: exec.CommandContext(ctx.Context, executable, args...), 45 Environment: config.Environment().Copy(), 46 Sandbox: noSandbox, 47 48 ctx: ctx, 49 config: config, 50 name: name, 51 } 52 53 return ret 54} 55 56func (c *Cmd) prepare() { 57 if c.Env == nil { 58 c.Env = c.Environment.Environ() 59 } 60 if c.sandboxSupported() { 61 c.wrapSandbox() 62 } 63 64 c.ctx.Verbosef("%q executing %q %v\n", c.name, c.Path, c.Args) 65 c.started = time.Now() 66} 67 68func (c *Cmd) report() { 69 if c.Cmd.ProcessState != nil { 70 rusage := c.Cmd.ProcessState.SysUsage().(*syscall.Rusage) 71 c.ctx.Verbosef("%q finished with exit code %d (%s real, %s user, %s system, %dMB maxrss)", 72 c.name, c.Cmd.ProcessState.ExitCode(), 73 time.Since(c.started).Round(time.Millisecond), 74 c.Cmd.ProcessState.UserTime().Round(time.Millisecond), 75 c.Cmd.ProcessState.SystemTime().Round(time.Millisecond), 76 rusage.Maxrss/1024) 77 } 78} 79 80func (c *Cmd) Start() error { 81 c.prepare() 82 return c.Cmd.Start() 83} 84 85func (c *Cmd) Run() error { 86 c.prepare() 87 err := c.Cmd.Run() 88 c.report() 89 return err 90} 91 92func (c *Cmd) Output() ([]byte, error) { 93 c.prepare() 94 bytes, err := c.Cmd.Output() 95 c.report() 96 return bytes, err 97} 98 99func (c *Cmd) CombinedOutput() ([]byte, error) { 100 c.prepare() 101 bytes, err := c.Cmd.CombinedOutput() 102 c.report() 103 return bytes, err 104} 105 106func (c *Cmd) Wait() error { 107 err := c.Cmd.Wait() 108 c.report() 109 return err 110} 111 112// StartOrFatal is equivalent to Start, but handles the error with a call to ctx.Fatal 113func (c *Cmd) StartOrFatal() { 114 if err := c.Start(); err != nil { 115 c.ctx.Fatalf("Failed to run %s: %v", c.name, err) 116 } 117} 118 119func (c *Cmd) reportError(err error) { 120 if err == nil { 121 return 122 } 123 if e, ok := err.(*exec.ExitError); ok { 124 c.ctx.Fatalf("%s failed with: %v", c.name, e.ProcessState.String()) 125 } else { 126 c.ctx.Fatalf("Failed to run %s: %v", c.name, err) 127 } 128} 129 130// RunOrFatal is equivalent to Run, but handles the error with a call to ctx.Fatal 131func (c *Cmd) RunOrFatal() { 132 c.reportError(c.Run()) 133} 134 135// WaitOrFatal is equivalent to Wait, but handles the error with a call to ctx.Fatal 136func (c *Cmd) WaitOrFatal() { 137 c.reportError(c.Wait()) 138} 139 140// OutputOrFatal is equivalent to Output, but handles the error with a call to ctx.Fatal 141func (c *Cmd) OutputOrFatal() []byte { 142 ret, err := c.Output() 143 c.reportError(err) 144 return ret 145} 146 147// CombinedOutputOrFatal is equivalent to CombinedOutput, but handles the error with 148// a call to ctx.Fatal 149func (c *Cmd) CombinedOutputOrFatal() []byte { 150 ret, err := c.CombinedOutput() 151 c.reportError(err) 152 return ret 153} 154 155// RunAndPrintOrFatal will run the command, then after finishing 156// print any output, then handling any errors with a call to 157// ctx.Fatal 158func (c *Cmd) RunAndPrintOrFatal() { 159 ret, err := c.CombinedOutput() 160 st := c.ctx.Status.StartTool() 161 if len(ret) > 0 { 162 if err != nil { 163 st.Error(string(ret)) 164 } else { 165 st.Print(string(ret)) 166 } 167 } 168 st.Finish() 169 c.reportError(err) 170} 171 172// RunAndStreamOrFatal will run the command, while running print 173// any output, then handle any errors with a call to ctx.Fatal 174func (c *Cmd) RunAndStreamOrFatal() { 175 out, err := c.StdoutPipe() 176 if err != nil { 177 c.ctx.Fatal(err) 178 } 179 c.Stderr = c.Stdout 180 181 st := c.ctx.Status.StartTool() 182 183 c.StartOrFatal() 184 185 buf := bufio.NewReaderSize(out, 2*1024*1024) 186 for { 187 // Attempt to read whole lines, but write partial lines that are too long to fit in the buffer or hit EOF 188 line, err := buf.ReadString('\n') 189 if line != "" { 190 st.Print(strings.TrimSuffix(line, "\n")) 191 } else if err == io.EOF { 192 break 193 } else if err != nil { 194 c.ctx.Fatal(err) 195 } 196 } 197 198 err = c.Wait() 199 st.Finish() 200 c.reportError(err) 201} 202