mk/graph.go

371 lines
7.3 KiB
Go

package main
import (
"fmt"
"io"
"os"
"sync"
"time"
)
// A dependency graph
type graph struct {
root *node // the intial target's node
nodes map[string]*node // map targets to their nodes
}
// An edge in the graph.
type edge struct {
v *node // node this edge directs to
stem string // stem matched for meta-rule applications
matches []string // regular expression matches
togo bool // this edge is going to be pruned
r *rule
}
// Current status of a node in the build.
type nodeStatus int
const (
nodeStatusReady nodeStatus = iota
nodeStatusStarted
nodeStatusNop
nodeStatusDone
nodeStatusFailed
)
type nodeFlag int
const (
nodeFlagCycle nodeFlag = 0x0002
nodeFlagReady = 0x0004
nodeFlagProbable = 0x0100
nodeFlagVacuous = 0x0200
)
// A node in the dependency graph
type node struct {
r *rule // rule to be applied
name string // target name
prog string // custom program to compare times
t time.Time // file modification time
exists bool // does a non-virtual target exist
prereqs []*edge // prerequisite rules
status nodeStatus // current state of the node in the build
mutex sync.Mutex // exclusivity for the status variable
listeners []chan nodeStatus // channels to notify of completion
flags nodeFlag // bitwise combination of node flags
}
// Update a node's timestamp and 'exists' flag.
func (u *node) updateTimestamp() {
info, err := os.Stat(u.name)
if err == nil {
u.t = info.ModTime()
u.exists = true
u.flags |= nodeFlagProbable
} else {
_, ok := err.(*os.PathError)
if ok {
u.t = time.Unix(0, 0)
u.exists = false
} else {
mkError(err.Error())
}
}
if rebuildall {
u.flags |= nodeFlagProbable
}
}
// Create a new node
func (g *graph) newnode(name string) *node {
u := &node{name: name}
u.updateTimestamp()
g.nodes[name] = u
return u
}
// Print a graph in graphviz format.
func (g *graph) visualize(w io.Writer) {
fmt.Fprintln(w, "digraph mk {")
for t, u := range g.nodes {
for i := range u.prereqs {
if u.prereqs[i].v != nil {
fmt.Fprintf(w, " \"%s\" -> \"%s\";\n", t, u.prereqs[i].v.name)
}
}
}
fmt.Fprintln(w, "}")
}
// Create a new arc.
func (u *node) newedge(v *node, r *rule) *edge {
e := &edge{v: v, r: r}
u.prereqs = append(u.prereqs, e)
return e
}
// Create a dependency graph for the given target.
func buildgraph(rs *ruleSet, target string) *graph {
g := &graph{nil, make(map[string]*node)}
// keep track of how many times each rule is visited, to avoid cycles.
rulecnt := make([]int, len(rs.rules))
g.root = applyrules(rs, g, target, rulecnt)
g.cyclecheck(g.root)
g.root.flags |= nodeFlagProbable
g.vacuous(g.root)
g.ambiguous(g.root)
return g
}
// Recursively match the given target to a rule in the rule set to construct the
// full graph.
func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
u, ok := g.nodes[target]
if ok {
return u
}
u = g.newnode(target)
// does the target match a concrete rule?
ks, ok := rs.targetrules[target]
if ok {
for ki := range ks {
k := ks[ki]
if rulecnt[k] > maxRuleCnt {
continue
}
r := &rs.rules[k]
// skip meta-rules
if r.ismeta {
continue
}
// skip rules that have no effect
if r.recipe == "" && len(r.prereqs) == 0 {
continue
}
u.flags |= nodeFlagProbable
rulecnt[k] += 1
if len(r.prereqs) == 0 {
u.newedge(nil, r)
} else {
for i := range r.prereqs {
u.newedge(applyrules(rs, g, r.prereqs[i], rulecnt), r)
}
}
rulecnt[k] -= 1
}
}
// find applicable metarules
for k := range rs.rules {
if rulecnt[k] >= maxRuleCnt {
continue
}
r := &rs.rules[k]
if !r.ismeta {
continue
}
// skip rules that have no effect
if r.recipe == "" && len(r.prereqs) == 0 {
continue
}
for j := range r.targets {
mat := r.targets[j].match(target)
if mat == nil {
continue
}
var stem string
var matches []string
var match_vars = make(map[string][]string)
if r.attributes.regex {
matches = mat
for i := range matches {
key := fmt.Sprintf("stem%d", i)
match_vars[key] = matches[i : i+1]
}
} else {
stem = mat[1]
}
rulecnt[k] += 1
if len(r.prereqs) == 0 {
e := u.newedge(nil, r)
e.stem = stem
e.matches = matches
} else {
for i := range r.prereqs {
var prereq string
if r.attributes.regex {
prereq = expandRecipeSigils(r.prereqs[i], match_vars)
} else {
prereq = expandSuffixes(r.prereqs[i], stem)
}
e := u.newedge(applyrules(rs, g, prereq, rulecnt), r)
e.stem = stem
e.matches = matches
}
}
rulecnt[k] -= 1
}
}
return u
}
// Remove edges marked as togo.
func (g *graph) togo(u *node) {
n := 0
for i := range u.prereqs {
if !u.prereqs[i].togo {
n++
}
}
prereqs := make([]*edge, n)
j := 0
for i := range u.prereqs {
if !u.prereqs[i].togo {
prereqs[j] = u.prereqs[i]
j++
}
}
// TODO: We may have to delete nodes from g.nodes, right?
u.prereqs = prereqs
}
// Remove vacous children of n.
func (g *graph) vacuous(u *node) bool {
vac := u.flags&nodeFlagProbable == 0
if u.flags&nodeFlagReady != 0 {
return vac
}
u.flags |= nodeFlagReady
for i := range u.prereqs {
e := u.prereqs[i]
if e.v != nil && g.vacuous(e.v) && e.r.ismeta {
e.togo = true
} else {
vac = false
}
}
// if a rule generated edges that are not togo, keep all of its edges
for i := range u.prereqs {
e := u.prereqs[i]
if !e.togo {
for j := range u.prereqs {
f := u.prereqs[j]
if e.r == f.r {
f.togo = false
}
}
}
}
g.togo(u)
if vac {
u.flags |= nodeFlagVacuous
}
return vac
}
// Check for cycles
func (g *graph) cyclecheck(u *node) {
if u.flags&nodeFlagCycle != 0 && len(u.prereqs) > 0 {
mkError(fmt.Sprintf("cycle in the graph detected at target %s", u.name))
}
u.flags |= nodeFlagCycle
for i := range u.prereqs {
if u.prereqs[i].v != nil {
g.cyclecheck(u.prereqs[i].v)
}
}
u.flags &= ^nodeFlagCycle
}
// Deal with ambiguous rules.
func (g *graph) ambiguous(u *node) {
bad := 0
var le *edge
for i := range u.prereqs {
e := u.prereqs[i]
if e.v != nil {
g.ambiguous(e.v)
}
if e.r.recipe == "" {
continue
}
if le == nil || le.r == nil {
le = e
} else {
if !le.r.equivRecipe(e.r) {
if le.r.ismeta && !e.r.ismeta {
mkPrintRecipe(u.name, le.r.recipe, false)
le.togo = true
le = e
} else if !le.r.ismeta && e.r.ismeta {
mkPrintRecipe(u.name, e.r.recipe, false)
e.togo = true
continue
}
}
if !le.r.equivRecipe(e.r) {
if bad == 0 {
mkPrintError(fmt.Sprintf("mk: ambiguous recipes for %s\n", u.name))
bad = 1
g.trace(u.name, le)
}
g.trace(u.name, e)
}
}
}
if bad > 0 {
mkError("")
}
g.togo(u)
}
// Print a trace of rules, k
func (g *graph) trace(name string, e *edge) {
fmt.Fprintf(os.Stderr, "\t%s", name)
for true {
prereqname := ""
if e.v != nil {
prereqname = e.v.name
}
fmt.Fprintf(os.Stderr, " <-(%s:%d)- %s", e.r.file, e.r.line, prereqname)
if e.v != nil {
for i := range e.v.prereqs {
if e.v.prereqs[i].r.recipe != "" {
e = e.v.prereqs[i]
continue
}
}
break
} else {
break
}
}
}