1// Copyright 2015 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 kati 16 17import ( 18 "fmt" 19 "os/exec" 20 "strings" 21 "sync" 22 23 "github.com/golang/glog" 24) 25 26type execContext struct { 27 shell string 28 29 mu sync.Mutex 30 ev *Evaluator 31 vpaths searchPaths 32 output string 33 inputs []string 34} 35 36func newExecContext(vars Vars, vpaths searchPaths, avoidIO bool) *execContext { 37 ev := NewEvaluator(vars) 38 ev.avoidIO = avoidIO 39 40 ctx := &execContext{ 41 ev: ev, 42 vpaths: vpaths, 43 } 44 av := autoVar{ctx: ctx} 45 for k, v := range map[string]Var{ 46 "@": autoAtVar{autoVar: av}, 47 "<": autoLessVar{autoVar: av}, 48 "^": autoHatVar{autoVar: av}, 49 "+": autoPlusVar{autoVar: av}, 50 "*": autoStarVar{autoVar: av}, 51 } { 52 ev.vars[k] = v 53 // $<k>D = $(patsubst %/,%,$(dir $<k>)) 54 ev.vars[k+"D"] = suffixDVar(k) 55 // $<k>F = $(notdir $<k>) 56 ev.vars[k+"F"] = suffixFVar(k) 57 } 58 59 // TODO: We should move this to somewhere around evalCmd so that 60 // we can handle SHELL in target specific variables. 61 shell, err := ev.EvaluateVar("SHELL") 62 if err != nil { 63 shell = "/bin/sh" 64 } 65 ctx.shell = shell 66 return ctx 67} 68 69func (ec *execContext) uniqueInputs() []string { 70 var uniqueInputs []string 71 seen := make(map[string]bool) 72 for _, input := range ec.inputs { 73 if !seen[input] { 74 seen[input] = true 75 uniqueInputs = append(uniqueInputs, input) 76 } 77 } 78 return uniqueInputs 79} 80 81type autoVar struct{ ctx *execContext } 82 83func (v autoVar) Flavor() string { return "undefined" } 84func (v autoVar) Origin() string { return "automatic" } 85func (v autoVar) IsDefined() bool { return true } 86func (v autoVar) Append(*Evaluator, string) (Var, error) { 87 return nil, fmt.Errorf("cannot append to autovar") 88} 89func (v autoVar) AppendVar(*Evaluator, Value) (Var, error) { 90 return nil, fmt.Errorf("cannot append to autovar") 91} 92func (v autoVar) serialize() serializableVar { 93 return serializableVar{Type: ""} 94} 95func (v autoVar) dump(d *dumpbuf) { 96 d.err = fmt.Errorf("cannot dump auto var: %v", v) 97} 98 99type autoAtVar struct{ autoVar } 100 101func (v autoAtVar) Eval(w evalWriter, ev *Evaluator) error { 102 fmt.Fprint(w, v.String()) 103 return nil 104} 105func (v autoAtVar) String() string { return v.ctx.output } 106 107type autoLessVar struct{ autoVar } 108 109func (v autoLessVar) Eval(w evalWriter, ev *Evaluator) error { 110 fmt.Fprint(w, v.String()) 111 return nil 112} 113func (v autoLessVar) String() string { 114 if len(v.ctx.inputs) > 0 { 115 return v.ctx.inputs[0] 116 } 117 return "" 118} 119 120type autoHatVar struct{ autoVar } 121 122func (v autoHatVar) Eval(w evalWriter, ev *Evaluator) error { 123 fmt.Fprint(w, v.String()) 124 return nil 125} 126func (v autoHatVar) String() string { 127 return strings.Join(v.ctx.uniqueInputs(), " ") 128} 129 130type autoPlusVar struct{ autoVar } 131 132func (v autoPlusVar) Eval(w evalWriter, ev *Evaluator) error { 133 fmt.Fprint(w, v.String()) 134 return nil 135} 136func (v autoPlusVar) String() string { return strings.Join(v.ctx.inputs, " ") } 137 138type autoStarVar struct{ autoVar } 139 140func (v autoStarVar) Eval(w evalWriter, ev *Evaluator) error { 141 fmt.Fprint(w, v.String()) 142 return nil 143} 144 145// TODO: Use currentStem. See auto_stem_var.mk 146func (v autoStarVar) String() string { return stripExt(v.ctx.output) } 147 148func suffixDVar(k string) Var { 149 return &recursiveVar{ 150 expr: expr{ 151 &funcPatsubst{ 152 fclosure: fclosure{ 153 args: []Value{ 154 literal("(patsubst"), 155 literal("%/"), 156 literal("%"), 157 &funcDir{ 158 fclosure: fclosure{ 159 args: []Value{ 160 literal("(dir"), 161 &varref{ 162 varname: literal(k), 163 }, 164 }, 165 }, 166 }, 167 }, 168 }, 169 }, 170 }, 171 origin: "automatic", 172 } 173} 174 175func suffixFVar(k string) Var { 176 return &recursiveVar{ 177 expr: expr{ 178 &funcNotdir{ 179 fclosure: fclosure{ 180 args: []Value{ 181 literal("(notdir"), 182 &varref{varname: literal(k)}, 183 }, 184 }, 185 }, 186 }, 187 origin: "automatic", 188 } 189} 190 191// runner is a single shell command invocation. 192type runner struct { 193 output string 194 cmd string 195 echo bool 196 ignoreError bool 197 shell string 198} 199 200func (r runner) String() string { 201 cmd := r.cmd 202 if !r.echo { 203 cmd = "@" + cmd 204 } 205 if r.ignoreError { 206 cmd = "-" + cmd 207 } 208 return cmd 209} 210 211func (r runner) forCmd(s string) runner { 212 for { 213 s = trimLeftSpace(s) 214 if s == "" { 215 return runner{} 216 } 217 switch s[0] { 218 case '@': 219 if !DryRunFlag { 220 r.echo = false 221 } 222 s = s[1:] 223 continue 224 case '-': 225 r.ignoreError = true 226 s = s[1:] 227 continue 228 } 229 break 230 } 231 r.cmd = s 232 return r 233} 234 235func (r runner) eval(ev *Evaluator, s string) ([]runner, error) { 236 r = r.forCmd(s) 237 if strings.IndexByte(r.cmd, '$') < 0 { 238 // fast path 239 return []runner{r}, nil 240 } 241 // TODO(ukai): parse once more earlier? 242 expr, _, err := parseExpr([]byte(r.cmd), nil, parseOp{}) 243 if err != nil { 244 return nil, ev.errorf("parse cmd %q: %v", r.cmd, err) 245 } 246 buf := newEbuf() 247 err = expr.Eval(buf, ev) 248 if err != nil { 249 return nil, err 250 } 251 cmds := buf.String() 252 buf.release() 253 glog.V(1).Infof("evalcmd: %q => %q", r.cmd, cmds) 254 var runners []runner 255 for _, cmd := range strings.Split(cmds, "\n") { 256 if len(runners) > 0 && strings.HasSuffix(runners[len(runners)-1].cmd, "\\") { 257 runners[len(runners)-1].cmd += "\n" 258 runners[len(runners)-1].cmd += cmd 259 continue 260 } 261 runners = append(runners, r.forCmd(cmd)) 262 } 263 return runners, nil 264} 265 266func (r runner) run(output string) error { 267 if r.echo || DryRunFlag { 268 fmt.Printf("%s\n", r.cmd) 269 } 270 s := cmdline(r.cmd) 271 glog.Infof("sh:%q", s) 272 if DryRunFlag { 273 return nil 274 } 275 args := []string{r.shell, "-c", s} 276 cmd := exec.Cmd{ 277 Path: args[0], 278 Args: args, 279 } 280 out, err := cmd.CombinedOutput() 281 fmt.Printf("%s", out) 282 exit := exitStatus(err) 283 if r.ignoreError && exit != 0 { 284 fmt.Printf("[%s] Error %d (ignored)\n", output, exit) 285 err = nil 286 } 287 return err 288} 289 290func createRunners(ctx *execContext, n *DepNode) ([]runner, bool, error) { 291 var runners []runner 292 if len(n.Cmds) == 0 { 293 return runners, false, nil 294 } 295 296 ctx.mu.Lock() 297 defer ctx.mu.Unlock() 298 // For automatic variables. 299 ctx.output = n.Output 300 ctx.inputs = n.ActualInputs 301 for k, v := range n.TargetSpecificVars { 302 restore := ctx.ev.vars.save(k) 303 defer restore() 304 ctx.ev.vars[k] = v 305 if glog.V(1) { 306 glog.Infof("set tsv: %s=%s", k, v) 307 } 308 } 309 310 ctx.ev.filename = n.Filename 311 ctx.ev.lineno = n.Lineno 312 glog.Infof("Building: %s cmds:%q", n.Output, n.Cmds) 313 r := runner{ 314 output: n.Output, 315 echo: true, 316 shell: ctx.shell, 317 } 318 for _, cmd := range n.Cmds { 319 rr, err := r.eval(ctx.ev, cmd) 320 if err != nil { 321 return nil, false, err 322 } 323 for _, r := range rr { 324 if len(r.cmd) != 0 { 325 runners = append(runners, r) 326 } 327 } 328 } 329 if len(ctx.ev.delayedOutputs) > 0 { 330 var nrunners []runner 331 r := runner{ 332 output: n.Output, 333 shell: ctx.shell, 334 } 335 for _, o := range ctx.ev.delayedOutputs { 336 nrunners = append(nrunners, r.forCmd(o)) 337 } 338 nrunners = append(nrunners, runners...) 339 runners = nrunners 340 ctx.ev.delayedOutputs = nil 341 } 342 return runners, ctx.ev.hasIO, nil 343} 344 345func evalCommands(nodes []*DepNode, vars Vars) error { 346 ioCnt := 0 347 ectx := newExecContext(vars, searchPaths{}, true) 348 for i, n := range nodes { 349 runners, hasIO, err := createRunners(ectx, n) 350 if err != nil { 351 return err 352 } 353 if hasIO { 354 ioCnt++ 355 if ioCnt%100 == 0 { 356 logStats("%d/%d rules have IO", ioCnt, i+1) 357 } 358 continue 359 } 360 361 n.Cmds = []string{} 362 n.TargetSpecificVars = make(Vars) 363 for _, r := range runners { 364 n.Cmds = append(n.Cmds, r.String()) 365 } 366 } 367 logStats("%d/%d rules have IO", ioCnt, len(nodes)) 368 return nil 369} 370