factor out execContext from executor.
diff --git a/evalcmd.go b/evalcmd.go
new file mode 100644
index 0000000..1218efb
--- /dev/null
+++ b/evalcmd.go
@@ -0,0 +1,344 @@
+// 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 (
+ "bytes"
+ "fmt"
+ "io"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "sync"
+)
+
+type execContext struct {
+ shell string
+
+ mu sync.Mutex
+ ev *Evaluator
+ output string
+ inputs []string
+}
+
+func newExecContext(vars Vars, avoidIO bool) *execContext {
+ ev := NewEvaluator(vars)
+ ev.avoidIO = avoidIO
+
+ ctx := &execContext{
+ ev: ev,
+ }
+ 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
+ // TODO(ukai): define recursive vars
+ // $<k>D = $(patsubst %/,%,$(dir $<k>))
+ // $<k>F = $(notdir $<k>)
+ ev.vars[k+"D"] = autoSuffixDVar{v: v}
+ ev.vars[k+"F"] = autoSuffixFVar{v: v}
+ }
+
+ // 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 io.Writer, ev *Evaluator) error {
+ fmt.Fprint(w, v.ctx.output)
+ return nil
+}
+func (v autoAtVar) String() string { return "$*" }
+
+type autoLessVar struct{ autoVar }
+
+func (v autoLessVar) Eval(w io.Writer, ev *Evaluator) error {
+ if len(v.ctx.inputs) > 0 {
+ fmt.Fprint(w, v.ctx.inputs[0])
+ }
+ return nil
+}
+func (v autoLessVar) String() string { return "$<" }
+
+type autoHatVar struct{ autoVar }
+
+func (v autoHatVar) Eval(w io.Writer, ev *Evaluator) error {
+ fmt.Fprint(w, strings.Join(v.ctx.uniqueInputs(), " "))
+ return nil
+}
+func (v autoHatVar) String() string { return "$^" }
+
+type autoPlusVar struct{ autoVar }
+
+func (v autoPlusVar) Eval(w io.Writer, ev *Evaluator) error {
+ fmt.Fprint(w, strings.Join(v.ctx.inputs, " "))
+ return nil
+}
+func (v autoPlusVar) String() string { return "$+" }
+
+type autoStarVar struct{ autoVar }
+
+func (v autoStarVar) Eval(w io.Writer, ev *Evaluator) error {
+ // TODO: Use currentStem. See auto_stem_var.mk
+ fmt.Fprint(w, stripExt(v.ctx.output))
+ return nil
+}
+func (v autoStarVar) String() string { return "$*" }
+
+type autoSuffixDVar struct {
+ autoVar
+ v Var
+}
+
+func (v autoSuffixDVar) Eval(w io.Writer, ev *Evaluator) error {
+ var buf bytes.Buffer
+ err := v.v.Eval(&buf, ev)
+ if err != nil {
+ return err
+ }
+ ws := newWordScanner(buf.Bytes())
+ sw := ssvWriter{w: w}
+ for ws.Scan() {
+ sw.WriteString(filepath.Dir(string(ws.Bytes())))
+ }
+ return nil
+}
+
+func (v autoSuffixDVar) String() string { return v.v.String() + "D" }
+
+type autoSuffixFVar struct {
+ autoVar
+ v Var
+}
+
+func (v autoSuffixFVar) Eval(w io.Writer, ev *Evaluator) error {
+ var buf bytes.Buffer
+ err := v.v.Eval(&buf, ev)
+ if err != nil {
+ return err
+ }
+ ws := newWordScanner(buf.Bytes())
+ sw := ssvWriter{w: w}
+ for ws.Scan() {
+ sw.WriteString(filepath.Base(string(ws.Bytes())))
+ }
+ return nil
+}
+
+func (v autoSuffixFVar) String() string { return v.v.String() + "F" }
+
+// 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, false)
+ if err != nil {
+ return nil, ev.errorf("parse cmd %q: %v", r.cmd, err)
+ }
+ buf := newBuf()
+ err = expr.Eval(buf, ev)
+ if err != nil {
+ return nil, err
+ }
+ cmds := buf.String()
+ freeBuf(buf)
+ 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)
+ }
+ if DryRunFlag {
+ return nil
+ }
+ args := []string{r.shell, "-c", r.cmd}
+ 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
+ logf("tsv: %s=%s", k, v)
+ }
+
+ ctx.ev.filename = n.Filename
+ ctx.ev.lineno = n.Lineno
+ logf("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)
+ }
+ }
+ }
+ return runners, ctx.ev.hasIO, nil
+}
+
+func evalCommands(nodes []*DepNode, vars Vars) error {
+ ioCnt := 0
+ ectx := newExecContext(vars, 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
+}