You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
4.4 KiB
203 lines
4.4 KiB
// 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" |
|
"time" |
|
|
|
"github.com/golang/glog" |
|
) |
|
|
|
// Executor manages execution of makefile rules. |
|
type Executor struct { |
|
rules map[string]*rule |
|
implicitRules []*rule |
|
suffixRules map[string][]*rule |
|
firstRule *rule |
|
// target -> Job, nil means the target is currently being processed. |
|
done map[string]*job |
|
|
|
wm *workerManager |
|
|
|
ctx *execContext |
|
|
|
trace []string |
|
buildCnt int |
|
alreadyDoneCnt int |
|
noRuleCnt int |
|
upToDateCnt int |
|
runCommandCnt int |
|
} |
|
|
|
func (ex *Executor) makeJobs(n *DepNode, neededBy *job) error { |
|
output, _ := ex.ctx.vpaths.exists(n.Output) |
|
if neededBy != nil { |
|
glog.V(1).Infof("MakeJob: %s for %s", output, neededBy.n.Output) |
|
} |
|
n.Output = 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 { |
|
glog.Infof("%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) + len(n.OrderOnlys), |
|
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 { |
|
deps = append(deps, d) |
|
} |
|
for _, d := range n.OrderOnlys { |
|
if _, ok := ex.ctx.vpaths.exists(d.Output); ok { |
|
j.numDeps-- |
|
continue |
|
} |
|
deps = append(deps, d) |
|
} |
|
glog.V(1).Infof("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 |
|
return ex.wm.PostJob(j) |
|
} |
|
|
|
func (ex *Executor) reportStats() { |
|
if !PeriodicStatsFlag { |
|
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) |
|
} |
|
} |
|
|
|
// ExecutorOpt is an option for Executor. |
|
type ExecutorOpt struct { |
|
NumJobs int |
|
} |
|
|
|
// NewExecutor creates new Executor. |
|
func NewExecutor(opt *ExecutorOpt) (*Executor, error) { |
|
if opt == nil { |
|
opt = &ExecutorOpt{NumJobs: 1} |
|
} |
|
if opt.NumJobs < 1 { |
|
opt.NumJobs = 1 |
|
} |
|
wm, err := newWorkerManager(opt.NumJobs) |
|
if err != nil { |
|
return nil, err |
|
} |
|
ex := &Executor{ |
|
rules: make(map[string]*rule), |
|
suffixRules: make(map[string][]*rule), |
|
done: make(map[string]*job), |
|
wm: wm, |
|
} |
|
return ex, nil |
|
} |
|
|
|
// Exec executes to build targets, or first target in DepGraph. |
|
func (ex *Executor) Exec(g *DepGraph, targets []string) error { |
|
ex.ctx = newExecContext(g.vars, g.vpaths, false) |
|
|
|
// TODO: Handle target specific variables. |
|
for name, export := range g.exports { |
|
if export { |
|
v, err := ex.ctx.ev.EvaluateVar(name) |
|
if err != nil { |
|
return err |
|
} |
|
os.Setenv(name, v) |
|
} else { |
|
os.Unsetenv(name) |
|
} |
|
} |
|
|
|
startTime := time.Now() |
|
var nodes []*DepNode |
|
if len(targets) == 0 { |
|
if len(g.nodes) > 0 { |
|
nodes = append(nodes, g.nodes[0]) |
|
} |
|
} else { |
|
m := make(map[string]*DepNode) |
|
for _, n := range g.nodes { |
|
m[n.Output] = n |
|
} |
|
for _, t := range targets { |
|
n := m[t] |
|
if n != nil { |
|
nodes = append(nodes, n) |
|
} |
|
} |
|
} |
|
for _, root := range nodes { |
|
err := ex.makeJobs(root, nil) |
|
if err != nil { |
|
break |
|
} |
|
} |
|
n, err := ex.wm.Wait() |
|
logStats("exec time: %q", time.Since(startTime)) |
|
if n == 0 { |
|
for _, root := range nodes { |
|
fmt.Printf("kati: Nothing to be done for `%s'.\n", root.Output) |
|
} |
|
} |
|
return err |
|
}
|
|
|