aboutsummaryrefslogtreecommitdiffstats
path: root/golang/kati/evalcmd.go
diff options
context:
space:
mode:
authorDan Willemsen <dwillemsen@google.com>2020-06-26 18:46:21 -0700
committerDan Willemsen <dwillemsen@google.com>2020-06-26 18:52:06 -0700
commit979e7ae6e417ae4ee45e835104b66191ae16a14c (patch)
tree6b5075e832cbdf2a7996a25a26659363527b6e4c /golang/kati/evalcmd.go
parent003cf51e9b6da48063c90cf4c6710fde103c9c4a (diff)
downloadplatform_build_kati-979e7ae6e417ae4ee45e835104b66191ae16a14c.tar.gz
platform_build_kati-979e7ae6e417ae4ee45e835104b66191ae16a14c.tar.bz2
platform_build_kati-979e7ae6e417ae4ee45e835104b66191ae16a14c.zip
Refactor source tree into directories
Now instead of almost every file in the top level, move the old go code into its own directory 'golang', and the C++ code into it's own 'src' Also removes a few obsolete scripts that were used to work on Android before Android fully switched to Kati.
Diffstat (limited to 'golang/kati/evalcmd.go')
-rw-r--r--golang/kati/evalcmd.go369
1 files changed, 369 insertions, 0 deletions
diff --git a/golang/kati/evalcmd.go b/golang/kati/evalcmd.go
new file mode 100644
index 0000000..37f94b8
--- /dev/null
+++ b/golang/kati/evalcmd.go
@@ -0,0 +1,369 @@
+// 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
+}