369 lines
7.8 KiB
Go
369 lines
7.8 KiB
Go
// 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
|
|
}
|