• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15package kati
16
17import (
18	"fmt"
19	"os/exec"
20	"strings"
21	"sync"
22
23	"github.com/golang/glog"
24)
25
26type execContext struct {
27	shell string
28
29	mu     sync.Mutex
30	ev     *Evaluator
31	vpaths searchPaths
32	output string
33	inputs []string
34}
35
36func 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
69func (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
81type autoVar struct{ ctx *execContext }
82
83func (v autoVar) Flavor() string  { return "undefined" }
84func (v autoVar) Origin() string  { return "automatic" }
85func (v autoVar) IsDefined() bool { return true }
86func (v autoVar) Append(*Evaluator, string) (Var, error) {
87	return nil, fmt.Errorf("cannot append to autovar")
88}
89func (v autoVar) AppendVar(*Evaluator, Value) (Var, error) {
90	return nil, fmt.Errorf("cannot append to autovar")
91}
92func (v autoVar) serialize() serializableVar {
93	return serializableVar{Type: ""}
94}
95func (v autoVar) dump(d *dumpbuf) {
96	d.err = fmt.Errorf("cannot dump auto var: %v", v)
97}
98
99type autoAtVar struct{ autoVar }
100
101func (v autoAtVar) Eval(w evalWriter, ev *Evaluator) error {
102	fmt.Fprint(w, v.String())
103	return nil
104}
105func (v autoAtVar) String() string { return v.ctx.output }
106
107type autoLessVar struct{ autoVar }
108
109func (v autoLessVar) Eval(w evalWriter, ev *Evaluator) error {
110	fmt.Fprint(w, v.String())
111	return nil
112}
113func (v autoLessVar) String() string {
114	if len(v.ctx.inputs) > 0 {
115		return v.ctx.inputs[0]
116	}
117	return ""
118}
119
120type autoHatVar struct{ autoVar }
121
122func (v autoHatVar) Eval(w evalWriter, ev *Evaluator) error {
123	fmt.Fprint(w, v.String())
124	return nil
125}
126func (v autoHatVar) String() string {
127	return strings.Join(v.ctx.uniqueInputs(), " ")
128}
129
130type autoPlusVar struct{ autoVar }
131
132func (v autoPlusVar) Eval(w evalWriter, ev *Evaluator) error {
133	fmt.Fprint(w, v.String())
134	return nil
135}
136func (v autoPlusVar) String() string { return strings.Join(v.ctx.inputs, " ") }
137
138type autoStarVar struct{ autoVar }
139
140func (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
146func (v autoStarVar) String() string { return stripExt(v.ctx.output) }
147
148func 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
175func 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.
192type runner struct {
193	output      string
194	cmd         string
195	echo        bool
196	ignoreError bool
197	shell       string
198}
199
200func (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
211func (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
235func (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
266func (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
290func 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
345func 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