206 lines
4.9 KiB
Go
206 lines
4.9 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
|
|
r *rule
|
|
}
|
|
|
|
|
|
// Current status of a node in the build.
|
|
type nodeStatus int
|
|
|
|
const (
|
|
nodeStatusReady nodeStatus = iota
|
|
nodeStatusStarted
|
|
nodeStatusDone
|
|
nodeStatusFailed
|
|
)
|
|
|
|
|
|
// 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
|
|
}
|
|
|
|
// Create a new node
|
|
func (g *graph) newnode(name string) *node {
|
|
u := &node{name: name}
|
|
info, err := os.Stat(name)
|
|
if err == nil {
|
|
u.t = info.ModTime()
|
|
u.exists = true
|
|
} else {
|
|
_, ok := err.(*os.PathError)
|
|
if ok {
|
|
u.exists = false
|
|
} else {
|
|
mkError(err.Error())
|
|
}
|
|
}
|
|
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)
|
|
|
|
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] > max_rule_cnt {
|
|
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
|
|
}
|
|
|
|
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] > max_rule_cnt {
|
|
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
|
|
|
|
if r.attributes.regex {
|
|
matches = mat
|
|
} 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 {
|
|
// TODO: write substituteRegexpRefs and use that here
|
|
prereq = r.prereqs[i]
|
|
} 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
|
|
}
|
|
|
|
|
|
|