Home | History | Annotate | Download | only in kati
      1 // Copyright 2015 Google Inc. All rights reserved
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //      http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package kati
     16 
     17 import (
     18 	"fmt"
     19 	"os/exec"
     20 	"strings"
     21 	"sync"
     22 
     23 	"github.com/golang/glog"
     24 )
     25 
     26 type execContext struct {
     27 	shell string
     28 
     29 	mu     sync.Mutex
     30 	ev     *Evaluator
     31 	vpaths searchPaths
     32 	output string
     33 	inputs []string
     34 }
     35 
     36 func newExecContext(vars Vars, vpaths searchPaths, avoidIO bool) *execContext {
     37 	ev := NewEvaluator(vars)
     38 	ev.avoidIO = avoidIO
     39 
     40 	ctx := &execContext{
     41 		ev:     ev,
     42 		vpaths: vpaths,
     43 	}
     44 	av := autoVar{ctx: ctx}
     45 	for k, v := range map[string]Var{
     46 		"@": autoAtVar{autoVar: av},
     47 		"<": autoLessVar{autoVar: av},
     48 		"^": autoHatVar{autoVar: av},
     49 		"+": autoPlusVar{autoVar: av},
     50 		"*": autoStarVar{autoVar: av},
     51 	} {
     52 		ev.vars[k] = v
     53 		// $<k>D = $(patsubst %/,%,$(dir $<k>))
     54 		ev.vars[k+"D"] = suffixDVar(k)
     55 		// $<k>F = $(notdir $<k>)
     56 		ev.vars[k+"F"] = suffixFVar(k)
     57 	}
     58 
     59 	// TODO: We should move this to somewhere around evalCmd so that
     60 	// we can handle SHELL in target specific variables.
     61 	shell, err := ev.EvaluateVar("SHELL")
     62 	if err != nil {
     63 		shell = "/bin/sh"
     64 	}
     65 	ctx.shell = shell
     66 	return ctx
     67 }
     68 
     69 func (ec *execContext) uniqueInputs() []string {
     70 	var uniqueInputs []string
     71 	seen := make(map[string]bool)
     72 	for _, input := range ec.inputs {
     73 		if !seen[input] {
     74 			seen[input] = true
     75 			uniqueInputs = append(uniqueInputs, input)
     76 		}
     77 	}
     78 	return uniqueInputs
     79 }
     80 
     81 type autoVar struct{ ctx *execContext }
     82 
     83 func (v autoVar) Flavor() string  { return "undefined" }
     84 func (v autoVar) Origin() string  { return "automatic" }
     85 func (v autoVar) IsDefined() bool { return true }
     86 func (v autoVar) Append(*Evaluator, string) (Var, error) {
     87 	return nil, fmt.Errorf("cannot append to autovar")
     88 }
     89 func (v autoVar) AppendVar(*Evaluator, Value) (Var, error) {
     90 	return nil, fmt.Errorf("cannot append to autovar")
     91 }
     92 func (v autoVar) serialize() serializableVar {
     93 	return serializableVar{Type: ""}
     94 }
     95 func (v autoVar) dump(d *dumpbuf) {
     96 	d.err = fmt.Errorf("cannot dump auto var: %v", v)
     97 }
     98 
     99 type autoAtVar struct{ autoVar }
    100 
    101 func (v autoAtVar) Eval(w evalWriter, ev *Evaluator) error {
    102 	fmt.Fprint(w, v.String())
    103 	return nil
    104 }
    105 func (v autoAtVar) String() string { return v.ctx.output }
    106 
    107 type autoLessVar struct{ autoVar }
    108 
    109 func (v autoLessVar) Eval(w evalWriter, ev *Evaluator) error {
    110 	fmt.Fprint(w, v.String())
    111 	return nil
    112 }
    113 func (v autoLessVar) String() string {
    114 	if len(v.ctx.inputs) > 0 {
    115 		return v.ctx.inputs[0]
    116 	}
    117 	return ""
    118 }
    119 
    120 type autoHatVar struct{ autoVar }
    121 
    122 func (v autoHatVar) Eval(w evalWriter, ev *Evaluator) error {
    123 	fmt.Fprint(w, v.String())
    124 	return nil
    125 }
    126 func (v autoHatVar) String() string {
    127 	return strings.Join(v.ctx.uniqueInputs(), " ")
    128 }
    129 
    130 type autoPlusVar struct{ autoVar }
    131 
    132 func (v autoPlusVar) Eval(w evalWriter, ev *Evaluator) error {
    133 	fmt.Fprint(w, v.String())
    134 	return nil
    135 }
    136 func (v autoPlusVar) String() string { return strings.Join(v.ctx.inputs, " ") }
    137 
    138 type autoStarVar struct{ autoVar }
    139 
    140 func (v autoStarVar) Eval(w evalWriter, ev *Evaluator) error {
    141 	fmt.Fprint(w, v.String())
    142 	return nil
    143 }
    144 
    145 // TODO: Use currentStem. See auto_stem_var.mk
    146 func (v autoStarVar) String() string { return stripExt(v.ctx.output) }
    147 
    148 func suffixDVar(k string) Var {
    149 	return &recursiveVar{
    150 		expr: expr{
    151 			&funcPatsubst{
    152 				fclosure: fclosure{
    153 					args: []Value{
    154 						literal("(patsubst"),
    155 						literal("%/"),
    156 						literal("%"),
    157 						&funcDir{
    158 							fclosure: fclosure{
    159 								args: []Value{
    160 									literal("(dir"),
    161 									&varref{
    162 										varname: literal(k),
    163 									},
    164 								},
    165 							},
    166 						},
    167 					},
    168 				},
    169 			},
    170 		},
    171 		origin: "automatic",
    172 	}
    173 }
    174 
    175 func suffixFVar(k string) Var {
    176 	return &recursiveVar{
    177 		expr: expr{
    178 			&funcNotdir{
    179 				fclosure: fclosure{
    180 					args: []Value{
    181 						literal("(notdir"),
    182 						&varref{varname: literal(k)},
    183 					},
    184 				},
    185 			},
    186 		},
    187 		origin: "automatic",
    188 	}
    189 }
    190 
    191 // runner is a single shell command invocation.
    192 type runner struct {
    193 	output      string
    194 	cmd         string
    195 	echo        bool
    196 	ignoreError bool
    197 	shell       string
    198 }
    199 
    200 func (r runner) String() string {
    201 	cmd := r.cmd
    202 	if !r.echo {
    203 		cmd = "@" + cmd
    204 	}
    205 	if r.ignoreError {
    206 		cmd = "-" + cmd
    207 	}
    208 	return cmd
    209 }
    210 
    211 func (r runner) forCmd(s string) runner {
    212 	for {
    213 		s = trimLeftSpace(s)
    214 		if s == "" {
    215 			return runner{}
    216 		}
    217 		switch s[0] {
    218 		case '@':
    219 			if !DryRunFlag {
    220 				r.echo = false
    221 			}
    222 			s = s[1:]
    223 			continue
    224 		case '-':
    225 			r.ignoreError = true
    226 			s = s[1:]
    227 			continue
    228 		}
    229 		break
    230 	}
    231 	r.cmd = s
    232 	return r
    233 }
    234 
    235 func (r runner) eval(ev *Evaluator, s string) ([]runner, error) {
    236 	r = r.forCmd(s)
    237 	if strings.IndexByte(r.cmd, '$') < 0 {
    238 		// fast path
    239 		return []runner{r}, nil
    240 	}
    241 	// TODO(ukai): parse once more earlier?
    242 	expr, _, err := parseExpr([]byte(r.cmd), nil, parseOp{})
    243 	if err != nil {
    244 		return nil, ev.errorf("parse cmd %q: %v", r.cmd, err)
    245 	}
    246 	buf := newEbuf()
    247 	err = expr.Eval(buf, ev)
    248 	if err != nil {
    249 		return nil, err
    250 	}
    251 	cmds := buf.String()
    252 	buf.release()
    253 	glog.V(1).Infof("evalcmd: %q => %q", r.cmd, cmds)
    254 	var runners []runner
    255 	for _, cmd := range strings.Split(cmds, "\n") {
    256 		if len(runners) > 0 && strings.HasSuffix(runners[len(runners)-1].cmd, "\\") {
    257 			runners[len(runners)-1].cmd += "\n"
    258 			runners[len(runners)-1].cmd += cmd
    259 			continue
    260 		}
    261 		runners = append(runners, r.forCmd(cmd))
    262 	}
    263 	return runners, nil
    264 }
    265 
    266 func (r runner) run(output string) error {
    267 	if r.echo || DryRunFlag {
    268 		fmt.Printf("%s\n", r.cmd)
    269 	}
    270 	s := cmdline(r.cmd)
    271 	glog.Infof("sh:%q", s)
    272 	if DryRunFlag {
    273 		return nil
    274 	}
    275 	args := []string{r.shell, "-c", s}
    276 	cmd := exec.Cmd{
    277 		Path: args[0],
    278 		Args: args,
    279 	}
    280 	out, err := cmd.CombinedOutput()
    281 	fmt.Printf("%s", out)
    282 	exit := exitStatus(err)
    283 	if r.ignoreError && exit != 0 {
    284 		fmt.Printf("[%s] Error %d (ignored)\n", output, exit)
    285 		err = nil
    286 	}
    287 	return err
    288 }
    289 
    290 func createRunners(ctx *execContext, n *DepNode) ([]runner, bool, error) {
    291 	var runners []runner
    292 	if len(n.Cmds) == 0 {
    293 		return runners, false, nil
    294 	}
    295 
    296 	ctx.mu.Lock()
    297 	defer ctx.mu.Unlock()
    298 	// For automatic variables.
    299 	ctx.output = n.Output
    300 	ctx.inputs = n.ActualInputs
    301 	for k, v := range n.TargetSpecificVars {
    302 		restore := ctx.ev.vars.save(k)
    303 		defer restore()
    304 		ctx.ev.vars[k] = v
    305 		if glog.V(1) {
    306 			glog.Infof("set tsv: %s=%s", k, v)
    307 		}
    308 	}
    309 
    310 	ctx.ev.filename = n.Filename
    311 	ctx.ev.lineno = n.Lineno
    312 	glog.Infof("Building: %s cmds:%q", n.Output, n.Cmds)
    313 	r := runner{
    314 		output: n.Output,
    315 		echo:   true,
    316 		shell:  ctx.shell,
    317 	}
    318 	for _, cmd := range n.Cmds {
    319 		rr, err := r.eval(ctx.ev, cmd)
    320 		if err != nil {
    321 			return nil, false, err
    322 		}
    323 		for _, r := range rr {
    324 			if len(r.cmd) != 0 {
    325 				runners = append(runners, r)
    326 			}
    327 		}
    328 	}
    329 	if len(ctx.ev.delayedOutputs) > 0 {
    330 		var nrunners []runner
    331 		r := runner{
    332 			output: n.Output,
    333 			shell:  ctx.shell,
    334 		}
    335 		for _, o := range ctx.ev.delayedOutputs {
    336 			nrunners = append(nrunners, r.forCmd(o))
    337 		}
    338 		nrunners = append(nrunners, runners...)
    339 		runners = nrunners
    340 		ctx.ev.delayedOutputs = nil
    341 	}
    342 	return runners, ctx.ev.hasIO, nil
    343 }
    344 
    345 func evalCommands(nodes []*DepNode, vars Vars) error {
    346 	ioCnt := 0
    347 	ectx := newExecContext(vars, searchPaths{}, true)
    348 	for i, n := range nodes {
    349 		runners, hasIO, err := createRunners(ectx, n)
    350 		if err != nil {
    351 			return err
    352 		}
    353 		if hasIO {
    354 			ioCnt++
    355 			if ioCnt%100 == 0 {
    356 				logStats("%d/%d rules have IO", ioCnt, i+1)
    357 			}
    358 			continue
    359 		}
    360 
    361 		n.Cmds = []string{}
    362 		n.TargetSpecificVars = make(Vars)
    363 		for _, r := range runners {
    364 			n.Cmds = append(n.Cmds, r.String())
    365 		}
    366 	}
    367 	logStats("%d/%d rules have IO", ioCnt, len(nodes))
    368 	return nil
    369 }
    370