blob: 725291837fe7fa9d8c371293593f08e2bf11318f [file] [log] [blame]
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +09001// 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
15package kati
16
17import (
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +090018 "fmt"
19 "io"
20 "os/exec"
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +090021 "strings"
22 "sync"
23)
24
25type execContext struct {
26 shell string
27
28 mu sync.Mutex
29 ev *Evaluator
30 output string
31 inputs []string
32}
33
34func newExecContext(vars Vars, avoidIO bool) *execContext {
35 ev := NewEvaluator(vars)
36 ev.avoidIO = avoidIO
37
38 ctx := &execContext{
39 ev: ev,
40 }
41 av := autoVar{ctx: ctx}
42 for k, v := range map[string]Var{
43 "@": autoAtVar{autoVar: av},
44 "<": autoLessVar{autoVar: av},
45 "^": autoHatVar{autoVar: av},
46 "+": autoPlusVar{autoVar: av},
47 "*": autoStarVar{autoVar: av},
48 } {
49 ev.vars[k] = v
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +090050 // $<k>D = $(patsubst %/,%,$(dir $<k>))
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +090051 ev.vars[k+"D"] = suffixDVar(k)
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +090052 // $<k>F = $(notdir $<k>)
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +090053 ev.vars[k+"F"] = suffixFVar(k)
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +090054 }
55
56 // TODO: We should move this to somewhere around evalCmd so that
57 // we can handle SHELL in target specific variables.
58 shell, err := ev.EvaluateVar("SHELL")
59 if err != nil {
60 shell = "/bin/sh"
61 }
62 ctx.shell = shell
63 return ctx
64}
65
66func (ec *execContext) uniqueInputs() []string {
67 var uniqueInputs []string
68 seen := make(map[string]bool)
69 for _, input := range ec.inputs {
70 if !seen[input] {
71 seen[input] = true
72 uniqueInputs = append(uniqueInputs, input)
73 }
74 }
75 return uniqueInputs
76}
77
78type autoVar struct{ ctx *execContext }
79
80func (v autoVar) Flavor() string { return "undefined" }
81func (v autoVar) Origin() string { return "automatic" }
82func (v autoVar) IsDefined() bool { return true }
83func (v autoVar) Append(*Evaluator, string) (Var, error) {
84 return nil, fmt.Errorf("cannot append to autovar")
85}
86func (v autoVar) AppendVar(*Evaluator, Value) (Var, error) {
87 return nil, fmt.Errorf("cannot append to autovar")
88}
89func (v autoVar) serialize() serializableVar {
90 return serializableVar{Type: ""}
91}
92func (v autoVar) dump(d *dumpbuf) {
93 d.err = fmt.Errorf("cannot dump auto var: %v", v)
94}
95
96type autoAtVar struct{ autoVar }
97
98func (v autoAtVar) Eval(w io.Writer, ev *Evaluator) error {
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +090099 fmt.Fprint(w, v.String())
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900100 return nil
101}
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900102func (v autoAtVar) String() string { return v.ctx.output }
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900103
104type autoLessVar struct{ autoVar }
105
106func (v autoLessVar) Eval(w io.Writer, ev *Evaluator) error {
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900107 fmt.Fprint(w, v.String())
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900108 return nil
109}
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900110func (v autoLessVar) String() string {
111 if len(v.ctx.inputs) > 0 {
112 return v.ctx.inputs[0]
113 }
114 return ""
115}
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900116
117type autoHatVar struct{ autoVar }
118
119func (v autoHatVar) Eval(w io.Writer, ev *Evaluator) error {
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900120 fmt.Fprint(w, v.String())
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900121 return nil
122}
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900123func (v autoHatVar) String() string {
124 return strings.Join(v.ctx.uniqueInputs(), " ")
125}
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900126
127type autoPlusVar struct{ autoVar }
128
129func (v autoPlusVar) Eval(w io.Writer, ev *Evaluator) error {
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900130 fmt.Fprint(w, v.String())
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900131 return nil
132}
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900133func (v autoPlusVar) String() string { return strings.Join(v.ctx.inputs, " ") }
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900134
135type autoStarVar struct{ autoVar }
136
137func (v autoStarVar) Eval(w io.Writer, ev *Evaluator) error {
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900138 fmt.Fprint(w, v.String())
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900139 return nil
140}
141
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900142// TODO: Use currentStem. See auto_stem_var.mk
143func (v autoStarVar) String() string { return stripExt(v.ctx.output) }
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900144
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900145func suffixDVar(k string) Var {
146 return &recursiveVar{
147 expr: expr{
148 &funcPatsubst{
149 fclosure: fclosure{
150 args: []Value{
151 literal("(patsubst"),
152 literal("%/"),
153 literal("%"),
154 &funcDir{
155 fclosure: fclosure{
156 args: []Value{
157 literal("(dir"),
158 &varref{
159 varname: literal(k),
160 },
161 },
162 },
163 },
164 },
165 },
166 },
167 },
168 origin: "automatic",
169 }
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900170}
171
Fumitoshi Ukaibbb0db52015-06-30 16:51:27 +0900172func suffixFVar(k string) Var {
173 return &recursiveVar{
174 expr: expr{
175 &funcNotdir{
176 fclosure: fclosure{
177 args: []Value{
178 literal("(notdir"),
179 &varref{varname: literal(k)},
180 },
181 },
182 },
183 },
184 origin: "automatic",
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900185 }
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900186}
187
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900188// runner is a single shell command invocation.
189type runner struct {
190 output string
191 cmd string
192 echo bool
193 ignoreError bool
194 shell string
195}
196
197func (r runner) String() string {
198 cmd := r.cmd
199 if !r.echo {
200 cmd = "@" + cmd
201 }
202 if r.ignoreError {
203 cmd = "-" + cmd
204 }
205 return cmd
206}
207
208func (r runner) forCmd(s string) runner {
209 for {
210 s = trimLeftSpace(s)
211 if s == "" {
212 return runner{}
213 }
214 switch s[0] {
215 case '@':
216 if !DryRunFlag {
217 r.echo = false
218 }
219 s = s[1:]
220 continue
221 case '-':
222 r.ignoreError = true
223 s = s[1:]
224 continue
225 }
226 break
227 }
228 r.cmd = s
229 return r
230}
231
232func (r runner) eval(ev *Evaluator, s string) ([]runner, error) {
233 r = r.forCmd(s)
234 if strings.IndexByte(r.cmd, '$') < 0 {
235 // fast path
236 return []runner{r}, nil
237 }
238 // TODO(ukai): parse once more earlier?
Fumitoshi Ukaie9aa3802015-07-03 11:33:23 +0900239 expr, _, err := parseExpr([]byte(r.cmd), nil, parseOp{})
Fumitoshi Ukaia70b4ea2015-06-30 15:31:49 +0900240 if err != nil {
241 return nil, ev.errorf("parse cmd %q: %v", r.cmd, err)
242 }
243 buf := newBuf()
244 err = expr.Eval(buf, ev)
245 if err != nil {
246 return nil, err
247 }
248 cmds := buf.String()
249 freeBuf(buf)
250 var runners []runner
251 for _, cmd := range strings.Split(cmds, "\n") {
252 if len(runners) > 0 && strings.HasSuffix(runners[len(runners)-1].cmd, "\\") {
253 runners[len(runners)-1].cmd += "\n"
254 runners[len(runners)-1].cmd += cmd
255 continue
256 }
257 runners = append(runners, r.forCmd(cmd))
258 }
259 return runners, nil
260}
261
262func (r runner) run(output string) error {
263 if r.echo || DryRunFlag {
264 fmt.Printf("%s\n", r.cmd)
265 }
266 if DryRunFlag {
267 return nil
268 }
269 args := []string{r.shell, "-c", r.cmd}
270 cmd := exec.Cmd{
271 Path: args[0],
272 Args: args,
273 }
274 out, err := cmd.CombinedOutput()
275 fmt.Printf("%s", out)
276 exit := exitStatus(err)
277 if r.ignoreError && exit != 0 {
278 fmt.Printf("[%s] Error %d (ignored)\n", output, exit)
279 err = nil
280 }
281 return err
282}
283
284func createRunners(ctx *execContext, n *DepNode) ([]runner, bool, error) {
285 var runners []runner
286 if len(n.Cmds) == 0 {
287 return runners, false, nil
288 }
289
290 ctx.mu.Lock()
291 defer ctx.mu.Unlock()
292 // For automatic variables.
293 ctx.output = n.Output
294 ctx.inputs = n.ActualInputs
295 for k, v := range n.TargetSpecificVars {
296 restore := ctx.ev.vars.save(k)
297 defer restore()
298 ctx.ev.vars[k] = v
299 logf("tsv: %s=%s", k, v)
300 }
301
302 ctx.ev.filename = n.Filename
303 ctx.ev.lineno = n.Lineno
304 logf("Building: %s cmds:%q", n.Output, n.Cmds)
305 r := runner{
306 output: n.Output,
307 echo: true,
308 shell: ctx.shell,
309 }
310 for _, cmd := range n.Cmds {
311 rr, err := r.eval(ctx.ev, cmd)
312 if err != nil {
313 return nil, false, err
314 }
315 for _, r := range rr {
316 if len(r.cmd) != 0 {
317 runners = append(runners, r)
318 }
319 }
320 }
321 return runners, ctx.ev.hasIO, nil
322}
323
324func evalCommands(nodes []*DepNode, vars Vars) error {
325 ioCnt := 0
326 ectx := newExecContext(vars, true)
327 for i, n := range nodes {
328 runners, hasIO, err := createRunners(ectx, n)
329 if err != nil {
330 return err
331 }
332 if hasIO {
333 ioCnt++
334 if ioCnt%100 == 0 {
335 logStats("%d/%d rules have IO", ioCnt, i+1)
336 }
337 continue
338 }
339
340 n.Cmds = []string{}
341 n.TargetSpecificVars = make(Vars)
342 for _, r := range runners {
343 n.Cmds = append(n.Cmds, r.String())
344 }
345 }
346 logStats("%d/%d rules have IO", ioCnt, len(nodes))
347 return nil
348}