From 6e0d8979be5e84419e5c77e6cd5ee1605d4bd309 Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Sat, 9 Mar 2013 19:27:28 -0800 Subject: [PATCH] Graph pruning. --- expand.go | 5 -- graph.go | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++- mk.go | 68 ++++++++++++---------- recipe.go | 1 - rules.go | 21 +++++++ 5 files changed, 224 insertions(+), 36 deletions(-) diff --git a/expand.go b/expand.go index 2232d58..38e9bff 100644 --- a/expand.go +++ b/expand.go @@ -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 diff --git a/graph.go b/graph.go index 6011077..eb77dd7 100644 --- a/graph.go +++ b/graph.go @@ -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 + } + } +} diff --git a/mk.go b/mk.go index 122c236..664e8d2 100644 --- a/mk.go +++ b/mk.go @@ -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) + } } diff --git a/recipe.go b/recipe.go index f5cec9a..22c8372 100644 --- a/recipe.go +++ b/recipe.go @@ -32,7 +32,6 @@ func stripIndentation(s string, mincol int) string { break } } - output += line[i:] if err != nil { diff --git a/rules.go b/rules.go index 9a45472..2e49973 100644 --- a/rules.go +++ b/rules.go @@ -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.