Graph pruning.

This commit is contained in:
Daniel Jones 2013-03-09 19:27:28 -08:00
parent ceac4466a5
commit 6e0d8979be
5 changed files with 224 additions and 36 deletions

View file

@ -20,11 +20,6 @@ func expand(input string, vars map[string][]string, expandBackticks bool) []stri
break
}
println("-------------------")
println(len(input))
println(i)
println(j)
expanded += input[i:j]
c, w := utf8.DecodeRuneInString(input[j:])
i = j + w

165
graph.go
View file

@ -19,6 +19,7 @@ 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
}
@ -32,6 +33,15 @@ const (
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
@ -43,6 +53,7 @@ type node struct {
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
}
// Create a new node
@ -52,6 +63,7 @@ func (g *graph) newnode(name string) *node {
if err == nil {
u.t = info.ModTime()
u.exists = true
u.flags |= nodeFlagProbable
} else {
_, ok := err.(*os.PathError)
if ok {
@ -91,6 +103,14 @@ func buildgraph(rs *ruleSet, target string) *graph {
// 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)
println("cyclecheck")
g.cyclecheck(g.root)
g.root.flags |= nodeFlagProbable
println("vacuous")
g.vacuous(g.root)
println("ambiguous")
g.ambiguous(g.root)
println("done")
return g
}
@ -126,6 +146,7 @@ func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
continue
}
u.flags |= nodeFlagProbable
rulecnt[k] += 1
if len(r.prereqs) == 0 {
u.newedge(nil, r)
@ -140,7 +161,7 @@ func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
// find applicable metarules
for k := range rs.rules {
if rulecnt[k] > maxRuleCnt {
if rulecnt[k] >= maxRuleCnt {
continue
}
@ -171,6 +192,7 @@ func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
}
rulecnt[k] += 1
fmt.Println(rulecnt)
if len(r.prereqs) == 0 {
e := u.newedge(nil, r)
e.stem = stem
@ -196,3 +218,144 @@ func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
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)
le.togo = true
le = e
} else if !le.r.ismeta && e.r.ismeta {
mkPrintRecipe(u.name, e.r.recipe)
e.togo = true
continue
}
}
if le.r.equivRecipe(e.r) {
if bad == 0 {
mkPrintError(fmt.Sprintf("mk: ambiguous recipes for %sn", 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
}
}
}

68
mk.go
View file

@ -21,7 +21,7 @@ var rebuildall bool = false
var mkMsgMutex sync.Mutex
// The maximum number of times an rule may be applied.
const maxRuleCnt = 3
const maxRuleCnt = 1
// Limit the number of recipes executed simultaneously.
var subprocsAllowed int
@ -47,13 +47,15 @@ func finishSubproc() {
// Ansi color codes.
const (
colorDefault string = "\033[0m"
colorBlack string = "\033[30m"
colorRed string = "\033[31m"
colorGreen string = "\033[32m"
colorYellow string = "\033[33m"
colorBlue string = "\033[34m"
colorMagenta string = "\033[35m"
ansiTermDefault = "\033[0m"
ansiTermBlack = "\033[30m"
ansiTermRed = "\033[31m"
ansiTermGreen = "\033[32m"
ansiTermYellow = "\033[33m"
ansiTermBlue = "\033[34m"
ansiTermMagenta = "\033[35m"
ansiTermBright = "\033[1m"
ansiTermUnderline = "\033[4m"
)
func mk(rs *ruleSet, target string, dryrun bool) {
@ -61,7 +63,7 @@ func mk(rs *ruleSet, target string, dryrun bool) {
if g.root.exists && !rebuildall {
return
}
mkNode(g, g.root)
mkNode(g, g.root)
}
// Build a target in the graph.
@ -167,21 +169,25 @@ func mkNode(g *graph, u *node) {
}
func mkError(msg string) {
mkPrintError(msg)
os.Exit(1)
}
func mkPrintError(msg string) {
if !nocolor {
os.Stderr.WriteString(colorRed)
os.Stderr.WriteString(ansiTermRed)
}
fmt.Fprintf(os.Stderr, "mk: %s\n", msg)
if !nocolor {
os.Stderr.WriteString(colorDefault)
os.Stderr.WriteString(ansiTermDefault)
}
os.Exit(1)
}
func mkPrintSuccess(msg string) {
if nocolor {
fmt.Println(msg)
} else {
fmt.Printf("%s%s%s\n", colorGreen, msg, colorDefault)
fmt.Printf("%s%s%s\n", ansiTermGreen, msg, ansiTermDefault)
}
}
@ -190,7 +196,7 @@ func mkPrintMessage(msg string) {
if nocolor {
fmt.Println(msg)
} else {
fmt.Printf("%s%s%s\n", colorBlue, msg, colorDefault)
fmt.Printf("%s%s%s\n", ansiTermBlue, msg, ansiTermDefault)
}
mkMsgMutex.Unlock()
}
@ -200,14 +206,16 @@ func mkPrintRecipe(target string, recipe string) {
if nocolor {
fmt.Printf("%s: ", target)
} else {
fmt.Printf("%s%s%s => %s", colorBlue, target, colorDefault, colorMagenta)
fmt.Printf("%s%s%s → %s",
ansiTermBlue+ansiTermBright+ansiTermUnderline, target,
ansiTermDefault, ansiTermBlue)
}
printIndented(os.Stdout, recipe, len(target)+4)
printIndented(os.Stdout, recipe, len(target)+3)
if len(recipe) == 0 {
os.Stdout.WriteString("\n")
}
if !nocolor {
os.Stdout.WriteString(colorDefault)
os.Stdout.WriteString(ansiTermDefault)
}
mkMsgMutex.Unlock()
}
@ -217,7 +225,7 @@ func main() {
flag.StringVar(&mkfilepath, "f", "mkfile", "use the given file as mkfile")
flag.BoolVar(&dryrun, "n", false, "print commands without actually executing")
flag.BoolVar(&rebuildall, "a", false, "force building of all dependencies")
flag.IntVar(&subprocsAllowed, "p", 64, "maximum number of jobs to execute in parallel")
flag.IntVar(&subprocsAllowed, "p", 8, "maximum number of jobs to execute in parallel")
flag.Parse()
mkfile, err := os.Open(mkfilepath)
@ -231,14 +239,16 @@ func main() {
targets := flag.Args()
// build the first non-meta rule in the makefile, if none are given explicitly
for i := range rs.rules {
if !rs.rules[i].ismeta {
for j := range rs.rules[i].targets {
targets = append(targets, rs.rules[i].targets[j].spat)
}
break
}
}
if len(targets) == 0 {
for i := range rs.rules {
if !rs.rules[i].ismeta {
for j := range rs.rules[i].targets {
targets = append(targets, rs.rules[i].targets[j].spat)
}
break
}
}
}
if len(targets) == 0 {
fmt.Println("mk: nothing to mk")
@ -247,7 +257,7 @@ func main() {
// TODO: For multiple targets, we should add a dummy rule that depends on
// all let mk handle executing each.
for _, target := range targets {
mk(rs, target, dryrun)
}
for _, target := range targets {
mk(rs, target, dryrun)
}
}

View file

@ -32,7 +32,6 @@ func stripIndentation(s string, mincol int) string {
break
}
}
output += line[i:]
if err != nil {

View file

@ -58,6 +58,27 @@ type rule struct {
command []string // command attribute
ismeta bool // is this a meta rule
mutex sync.Mutex // prevent the rule from being executed multiple times
file string // file where the rule is defined
line int // line number on which the rule is defined
}
// Equivalent recipes.
func (r1 *rule) equivRecipe(r2 *rule) bool {
if r1.recipe != r2.recipe {
return false
}
if len(r1.shell) != len(r2.shell) {
return false
}
for i := range r1.shell {
if r1.shell[i] != r2.shell[i] {
return false
}
}
return true
}
// A set of rules.