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 state := c.Cmd.ProcessState; state != nil { 70 if c.ctx.Metrics != nil { 71 c.ctx.Metrics.EventTracer.AddProcResInfo(c.name, state) 72 } 73 rusage := state.SysUsage().(*syscall.Rusage) 74 c.ctx.Verbosef("%q finished with exit code %d (%s real, %s user, %s system, %dMB maxrss)", 75 c.name, c.Cmd.ProcessState.ExitCode(), 76 time.Since(c.started).Round(time.Millisecond), 77 c.Cmd.ProcessState.UserTime().Round(time.Millisecond), 78 c.Cmd.ProcessState.SystemTime().Round(time.Millisecond), 79 rusage.Maxrss/1024) 80 } 81} 82 83func (c *Cmd) Start() error { 84 c.prepare() 85 return c.Cmd.Start() 86} 87 88func (c *Cmd) Run() error { 89 c.prepare() 90 err := c.Cmd.Run() 91 c.report() 92 return err 93} 94 95func (c *Cmd) Output() ([]byte, error) { 96 c.prepare() 97 bytes, err := c.Cmd.Output() 98 c.report() 99 return bytes, err 100} 101 102func (c *Cmd) CombinedOutput() ([]byte, error) { 103 c.prepare() 104 bytes, err := c.Cmd.CombinedOutput() 105 c.report() 106 return bytes, err 107} 108 109func (c *Cmd) Wait() error { 110 err := c.Cmd.Wait() 111 c.report() 112 return err 113} 114 115// StartOrFatal is equivalent to Start, but handles the error with a call to ctx.Fatal 116func (c *Cmd) StartOrFatal() { 117 if err := c.Start(); err != nil { 118 c.ctx.Fatalf("Failed to run %s: %v", c.name, err) 119 } 120} 121 122func (c *Cmd) reportError(err error) { 123 if err == nil { 124 return 125 } 126 if e, ok := err.(*exec.ExitError); ok { 127 c.ctx.Fatalf("%s failed with: %v", c.name, e.ProcessState.String()) 128 } else { 129 c.ctx.Fatalf("Failed to run %s: %v", c.name, err) 130 } 131} 132 133// RunOrFatal is equivalent to Run, but handles the error with a call to ctx.Fatal 134func (c *Cmd) RunOrFatal() { 135 c.reportError(c.Run()) 136} 137 138// WaitOrFatal is equivalent to Wait, but handles the error with a call to ctx.Fatal 139func (c *Cmd) WaitOrFatal() { 140 c.reportError(c.Wait()) 141} 142 143// OutputOrFatal is equivalent to Output, but handles the error with a call to ctx.Fatal 144func (c *Cmd) OutputOrFatal() []byte { 145 ret, err := c.Output() 146 c.reportError(err) 147 return ret 148} 149 150// CombinedOutputOrFatal is equivalent to CombinedOutput, but handles the error with 151// a call to ctx.Fatal 152func (c *Cmd) CombinedOutputOrFatal() []byte { 153 ret, err := c.CombinedOutput() 154 c.reportError(err) 155 return ret 156} 157 158// RunAndPrintOrFatal will run the command, then after finishing 159// print any output, then handling any errors with a call to 160// ctx.Fatal 161func (c *Cmd) RunAndPrintOrFatal() { 162 ret, err := c.CombinedOutput() 163 st := c.ctx.Status.StartTool() 164 if len(ret) > 0 { 165 if err != nil { 166 st.Error(string(ret)) 167 } else { 168 st.Print(string(ret)) 169 } 170 } 171 st.Finish() 172 c.reportError(err) 173} 174 175// RunAndStreamOrFatal will run the command, while running print 176// any output, then handle any errors with a call to ctx.Fatal 177func (c *Cmd) RunAndStreamOrFatal() { 178 out, err := c.StdoutPipe() 179 if err != nil { 180 c.ctx.Fatal(err) 181 } 182 c.Stderr = c.Stdout 183 184 st := c.ctx.Status.StartTool() 185 186 c.StartOrFatal() 187 188 buf := bufio.NewReaderSize(out, 2*1024*1024) 189 for { 190 // Attempt to read whole lines, but write partial lines that are too long to fit in the buffer or hit EOF 191 line, err := buf.ReadString('\n') 192 if line != "" { 193 st.Print(strings.TrimSuffix(line, "\n")) 194 } else if err == io.EOF { 195 break 196 } else if err != nil { 197 c.ctx.Fatal(err) 198 } 199 } 200 201 err = c.Wait() 202 st.Finish() 203 c.reportError(err) 204} 205