diff options
Diffstat (limited to 'golang/kati/evalcmd.go')
| -rw-r--r-- | golang/kati/evalcmd.go | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/golang/kati/evalcmd.go b/golang/kati/evalcmd.go new file mode 100644 index 0000000..37f94b8 --- /dev/null +++ b/golang/kati/evalcmd.go @@ -0,0 +1,369 @@ +// 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 + // $<k>D = $(patsubst %/,%,$(dir $<k>)) + ev.vars[k+"D"] = suffixDVar(k) + // $<k>F = $(notdir $<k>) + 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 +} |
