You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
369 lines
7.8 KiB
369 lines
7.8 KiB
// 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 |
|
}
|
|
|