// Copyright 2015 Google Inc. All rights reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kati import ( "fmt" "os/exec" "strings" "sync" "github.com/golang/glog" ) type execContext struct { shell string mu sync.Mutex ev *Evaluator vpaths searchPaths output string inputs []string } func newExecContext(vars Vars, vpaths searchPaths, avoidIO bool) *execContext { ev := NewEvaluator(vars) ev.avoidIO = avoidIO ctx := &execContext{ ev: ev, vpaths: vpaths, } av := autoVar{ctx: ctx} for k, v := range map[string]Var{ "@": autoAtVar{autoVar: av}, "<": autoLessVar{autoVar: av}, "^": autoHatVar{autoVar: av}, "+": autoPlusVar{autoVar: av}, "*": autoStarVar{autoVar: av}, } { ev.vars[k] = v // $D = $(patsubst %/,%,$(dir $)) ev.vars[k+"D"] = suffixDVar(k) // $F = $(notdir $) ev.vars[k+"F"] = suffixFVar(k) } // TODO: We should move this to somewhere around evalCmd so that // we can handle SHELL in target specific variables. shell, err := ev.EvaluateVar("SHELL") if err != nil { shell = "/bin/sh" } ctx.shell = shell return ctx } func (ec *execContext) uniqueInputs() []string { var uniqueInputs []string seen := make(map[string]bool) for _, input := range ec.inputs { if !seen[input] { seen[input] = true uniqueInputs = append(uniqueInputs, input) } } return uniqueInputs } type autoVar struct{ ctx *execContext } func (v autoVar) Flavor() string { return "undefined" } func (v autoVar) Origin() string { return "automatic" } func (v autoVar) IsDefined() bool { return true } func (v autoVar) Append(*Evaluator, string) (Var, error) { return nil, fmt.Errorf("cannot append to autovar") } func (v autoVar) AppendVar(*Evaluator, Value) (Var, error) { return nil, fmt.Errorf("cannot append to autovar") } func (v autoVar) serialize() serializableVar { return serializableVar{Type: ""} } func (v autoVar) dump(d *dumpbuf) { d.err = fmt.Errorf("cannot dump auto var: %v", v) } type autoAtVar struct{ autoVar } func (v autoAtVar) Eval(w evalWriter, ev *Evaluator) error { fmt.Fprint(w, v.String()) return nil } func (v autoAtVar) String() string { return v.ctx.output } type autoLessVar struct{ autoVar } func (v autoLessVar) Eval(w evalWriter, ev *Evaluator) error { fmt.Fprint(w, v.String()) return nil } func (v autoLessVar) String() string { if len(v.ctx.inputs) > 0 { return v.ctx.inputs[0] } return "" } type autoHatVar struct{ autoVar } func (v autoHatVar) Eval(w evalWriter, ev *Evaluator) error { fmt.Fprint(w, v.String()) return nil } func (v autoHatVar) String() string { return strings.Join(v.ctx.uniqueInputs(), " ") } type autoPlusVar struct{ autoVar } func (v autoPlusVar) Eval(w evalWriter, ev *Evaluator) error { fmt.Fprint(w, v.String()) return nil } func (v autoPlusVar) String() string { return strings.Join(v.ctx.inputs, " ") } type autoStarVar struct{ autoVar } func (v autoStarVar) Eval(w evalWriter, ev *Evaluator) error { fmt.Fprint(w, v.String()) return nil } // TODO: Use currentStem. See auto_stem_var.mk func (v autoStarVar) String() string { return stripExt(v.ctx.output) } func suffixDVar(k string) Var { return &recursiveVar{ expr: expr{ &funcPatsubst{ fclosure: fclosure{ args: []Value{ literal("(patsubst"), literal("%/"), literal("%"), &funcDir{ fclosure: fclosure{ args: []Value{ literal("(dir"), &varref{ varname: literal(k), }, }, }, }, }, }, }, }, origin: "automatic", } } func suffixFVar(k string) Var { return &recursiveVar{ expr: expr{ &funcNotdir{ fclosure: fclosure{ args: []Value{ literal("(notdir"), &varref{varname: literal(k)}, }, }, }, }, origin: "automatic", } } // runner is a single shell command invocation. type runner struct { output string cmd string echo bool ignoreError bool shell string } func (r runner) String() string { cmd := r.cmd if !r.echo { cmd = "@" + cmd } if r.ignoreError { cmd = "-" + cmd } return cmd } func (r runner) forCmd(s string) runner { for { s = trimLeftSpace(s) if s == "" { return runner{} } switch s[0] { case '@': if !DryRunFlag { r.echo = false } s = s[1:] continue case '-': r.ignoreError = true s = s[1:] continue } break } r.cmd = s return r } func (r runner) eval(ev *Evaluator, s string) ([]runner, error) { r = r.forCmd(s) if strings.IndexByte(r.cmd, '$') < 0 { // fast path return []runner{r}, nil } // TODO(ukai): parse once more earlier? expr, _, err := parseExpr([]byte(r.cmd), nil, parseOp{}) if err != nil { return nil, ev.errorf("parse cmd %q: %v", r.cmd, err) } buf := newEbuf() err = expr.Eval(buf, ev) if err != nil { return nil, err } cmds := buf.String() buf.release() glog.V(1).Infof("evalcmd: %q => %q", r.cmd, cmds) var runners []runner for _, cmd := range strings.Split(cmds, "\n") { if len(runners) > 0 && strings.HasSuffix(runners[len(runners)-1].cmd, "\\") { runners[len(runners)-1].cmd += "\n" runners[len(runners)-1].cmd += cmd continue } runners = append(runners, r.forCmd(cmd)) } return runners, nil } func (r runner) run(output string) error { if r.echo || DryRunFlag { fmt.Printf("%s\n", r.cmd) } s := cmdline(r.cmd) glog.Infof("sh:%q", s) if DryRunFlag { return nil } args := []string{r.shell, "-c", s} cmd := exec.Cmd{ Path: args[0], Args: args, } out, err := cmd.CombinedOutput() fmt.Printf("%s", out) exit := exitStatus(err) if r.ignoreError && exit != 0 { fmt.Printf("[%s] Error %d (ignored)\n", output, exit) err = nil } return err } func createRunners(ctx *execContext, n *DepNode) ([]runner, bool, error) { var runners []runner if len(n.Cmds) == 0 { return runners, false, nil } ctx.mu.Lock() defer ctx.mu.Unlock() // For automatic variables. ctx.output = n.Output ctx.inputs = n.ActualInputs for k, v := range n.TargetSpecificVars { restore := ctx.ev.vars.save(k) defer restore() ctx.ev.vars[k] = v if glog.V(1) { glog.Infof("set tsv: %s=%s", k, v) } } ctx.ev.filename = n.Filename ctx.ev.lineno = n.Lineno glog.Infof("Building: %s cmds:%q", n.Output, n.Cmds) r := runner{ output: n.Output, echo: true, shell: ctx.shell, } for _, cmd := range n.Cmds { rr, err := r.eval(ctx.ev, cmd) if err != nil { return nil, false, err } for _, r := range rr { if len(r.cmd) != 0 { runners = append(runners, r) } } } if len(ctx.ev.delayedOutputs) > 0 { var nrunners []runner r := runner{ output: n.Output, shell: ctx.shell, } for _, o := range ctx.ev.delayedOutputs { nrunners = append(nrunners, r.forCmd(o)) } nrunners = append(nrunners, runners...) runners = nrunners ctx.ev.delayedOutputs = nil } return runners, ctx.ev.hasIO, nil } func evalCommands(nodes []*DepNode, vars Vars) error { ioCnt := 0 ectx := newExecContext(vars, searchPaths{}, true) for i, n := range nodes { runners, hasIO, err := createRunners(ectx, n) if err != nil { return err } if hasIO { ioCnt++ if ioCnt%100 == 0 { logStats("%d/%d rules have IO", ioCnt, i+1) } continue } n.Cmds = []string{} n.TargetSpecificVars = make(Vars) for _, r := range runners { n.Cmds = append(n.Cmds, r.String()) } } logStats("%d/%d rules have IO", ioCnt, len(nodes)) return nil }