blob: f927c589301d650d8735b9c59e28142dccb8b37c [file] [log] [blame]
package main
import (
"bytes"
"fmt"
"io"
"path/filepath"
"strings"
"sync"
)
type Executor struct {
rules map[string]*Rule
implicitRules []*Rule
suffixRules map[string][]*Rule
firstRule *Rule
shell string
vars Vars
varsLock sync.Mutex
// target -> Job, nil means the target is currently being processed.
done map[string]*Job
wm *WorkerManager
currentOutput string
currentInputs []string
currentStem string
trace []string
buildCnt int
alreadyDoneCnt int
noRuleCnt int
upToDateCnt int
runCommandCnt int
}
type AutoVar struct{ ex *Executor }
func (v AutoVar) Flavor() string { return "undefined" }
func (v AutoVar) Origin() string { return "automatic" }
func (v AutoVar) IsDefined() bool { panic("not implemented") }
func (v AutoVar) String() string { panic("not implemented") }
func (v AutoVar) Append(*Evaluator, string) Var {
panic("must not be called")
}
func (v AutoVar) AppendVar(*Evaluator, Value) Var {
panic("must not be called")
}
func (v AutoVar) Serialize() SerializableVar {
panic(fmt.Sprintf("cannot serialize auto var: %q", v))
}
func (v AutoVar) Dump(w io.Writer) {
panic(fmt.Sprintf("cannot dump auto var: %q", v))
}
type AutoAtVar struct{ AutoVar }
func (v AutoAtVar) Eval(w io.Writer, ev *Evaluator) {
fmt.Fprint(w, v.ex.currentOutput)
}
type AutoLessVar struct{ AutoVar }
func (v AutoLessVar) Eval(w io.Writer, ev *Evaluator) {
if len(v.ex.currentInputs) > 0 {
fmt.Fprint(w, v.ex.currentInputs[0])
}
}
type AutoHatVar struct{ AutoVar }
func (v AutoHatVar) Eval(w io.Writer, ev *Evaluator) {
var uniqueInputs []string
seen := make(map[string]bool)
for _, input := range v.ex.currentInputs {
if !seen[input] {
seen[input] = true
uniqueInputs = append(uniqueInputs, input)
}
}
fmt.Fprint(w, strings.Join(uniqueInputs, " "))
}
type AutoPlusVar struct{ AutoVar }
func (v AutoPlusVar) Eval(w io.Writer, ev *Evaluator) {
fmt.Fprint(w, strings.Join(v.ex.currentInputs, " "))
}
type AutoStarVar struct{ AutoVar }
func (v AutoStarVar) Eval(w io.Writer, ev *Evaluator) {
// TODO: Use currentStem. See auto_stem_var.mk
fmt.Fprint(w, stripExt(v.ex.currentOutput))
}
type AutoSuffixDVar struct {
AutoVar
v Var
}
func (v AutoSuffixDVar) Eval(w io.Writer, ev *Evaluator) {
var buf bytes.Buffer
v.v.Eval(&buf, ev)
for i, tok := range splitSpaces(buf.String()) {
if i > 0 {
w.Write([]byte{' '})
}
fmt.Fprint(w, filepath.Dir(tok))
}
}
type AutoSuffixFVar struct {
AutoVar
v Var
}
func (v AutoSuffixFVar) Eval(w io.Writer, ev *Evaluator) {
var buf bytes.Buffer
v.v.Eval(&buf, ev)
for i, tok := range splitSpaces(buf.String()) {
if i > 0 {
w.Write([]byte{' '})
}
fmt.Fprint(w, filepath.Base(tok))
}
}
func (ex *Executor) makeJobs(n *DepNode, neededBy *Job) error {
output := n.Output
if neededBy != nil {
Logf("MakeJob: %s for %s", output, neededBy.n.Output)
}
ex.buildCnt++
if ex.buildCnt%100 == 0 {
ex.reportStats()
}
j, present := ex.done[output]
if present {
if j == nil {
if !n.IsPhony {
fmt.Printf("Circular %s <- %s dependency dropped.\n", neededBy.n.Output, n.Output)
}
if neededBy != nil {
neededBy.numDeps--
}
} else {
Logf("%s already done: %d", j.n.Output, j.outputTs)
if neededBy != nil {
ex.wm.ReportNewDep(j, neededBy)
}
}
return nil
}
j = &Job{
n: n,
ex: ex,
numDeps: len(n.Deps),
depsTs: int64(-1),
}
if neededBy != nil {
j.parents = append(j.parents, neededBy)
}
ex.done[output] = nil
// We iterate n.Deps twice. In the first run, we may modify
// numDeps. There will be a race if we do so after the first
// ex.makeJobs(d, j).
var deps []*DepNode
for _, d := range n.Deps {
if d.IsOrderOnly && exists(d.Output) {
j.numDeps--
continue
}
deps = append(deps, d)
}
Logf("new: %s (%d)", j.n.Output, j.numDeps)
for _, d := range deps {
ex.trace = append(ex.trace, d.Output)
err := ex.makeJobs(d, j)
ex.trace = ex.trace[0 : len(ex.trace)-1]
if err != nil {
return err
}
}
ex.done[output] = j
ex.wm.PostJob(j)
return nil
}
func (ex *Executor) reportStats() {
if !katiLogFlag && !katiPeriodicStatsFlag {
return
}
LogStats("build=%d alreadyDone=%d noRule=%d, upToDate=%d runCommand=%d",
ex.buildCnt, ex.alreadyDoneCnt, ex.noRuleCnt, ex.upToDateCnt, ex.runCommandCnt)
if len(ex.trace) > 1 {
LogStats("trace=%q", ex.trace)
}
}
func NewExecutor(vars Vars) *Executor {
ex := &Executor{
rules: make(map[string]*Rule),
suffixRules: make(map[string][]*Rule),
done: make(map[string]*Job),
vars: vars,
wm: NewWorkerManager(),
}
// TODO: We should move this to somewhere around evalCmd so that
// we can handle SHELL in target specific variables.
ev := newEvaluator(ex.vars)
ex.shell = ev.EvaluateVar("SHELL")
for k, v := range map[string]Var{
"@": AutoAtVar{AutoVar: AutoVar{ex: ex}},
"<": AutoLessVar{AutoVar: AutoVar{ex: ex}},
"^": AutoHatVar{AutoVar: AutoVar{ex: ex}},
"+": AutoPlusVar{AutoVar: AutoVar{ex: ex}},
"*": AutoStarVar{AutoVar: AutoVar{ex: ex}},
} {
ex.vars[k] = v
ex.vars[k+"D"] = AutoSuffixDVar{v: v}
ex.vars[k+"F"] = AutoSuffixFVar{v: v}
}
return ex
}
func (ex *Executor) Exec(roots []*DepNode) error {
for _, root := range roots {
ex.makeJobs(root, nil)
}
ex.wm.Wait()
return nil
}
func (ex *Executor) createRunners(n *DepNode, avoidIO bool) ([]runner, bool) {
var runners []runner
if len(n.Cmds) == 0 {
return runners, false
}
var restores []func()
defer func() {
for i := len(restores) - 1; i >= 0; i-- {
restores[i]()
}
}()
ex.varsLock.Lock()
restores = append(restores, func() { ex.varsLock.Unlock() })
// For automatic variables.
ex.currentOutput = n.Output
ex.currentInputs = n.ActualInputs
for k, v := range n.TargetSpecificVars {
restores = append(restores, ex.vars.save(k))
ex.vars[k] = v
}
ev := newEvaluator(ex.vars)
ev.avoidIO = avoidIO
ev.filename = n.Filename
ev.lineno = n.Lineno
Logf("Building: %s cmds:%q", n.Output, n.Cmds)
r := runner{
output: n.Output,
echo: true,
shell: ex.shell,
}
for _, cmd := range n.Cmds {
for _, r := range evalCmd(ev, r, cmd) {
if len(r.cmd) != 0 {
runners = append(runners, r)
}
}
}
return runners, ev.hasIO
}
func EvalCommands(nodes []*DepNode, vars Vars) {
ioCnt := 0
ex := NewExecutor(vars)
for i, n := range nodes {
runners, hasIO := ex.createRunners(n, true)
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 {
cmd := r.cmd
// TODO: Do not preserve the effect of dryRunFlag.
if r.echo {
cmd = "@" + cmd
}
if r.ignoreError {
cmd = "-" + cmd
}
n.Cmds = append(n.Cmds, cmd)
}
}
LogStats("%d/%d rules have IO", ioCnt, len(nodes))
}