blob: adf1c102fffdb1e2af89d5130db59c5a2df73d48 [file] [log] [blame]
// 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
}
}
err := ex.wm.Wait()
logStats("exec time: %q", time.Since(startTime))
return err
}