Graph pruning.
This commit is contained in:
parent
ceac4466a5
commit
6e0d8979be
5 changed files with 224 additions and 36 deletions
|
|
@ -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
165
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
mk.go
68
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ func stripIndentation(s string, mincol int) string {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
output += line[i:]
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
21
rules.go
21
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue