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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
println("-------------------")
|
|
||||||
println(len(input))
|
|
||||||
println(i)
|
|
||||||
println(j)
|
|
||||||
|
|
||||||
expanded += input[i:j]
|
expanded += input[i:j]
|
||||||
c, w := utf8.DecodeRuneInString(input[j:])
|
c, w := utf8.DecodeRuneInString(input[j:])
|
||||||
i = j + w
|
i = j + w
|
||||||
|
|
|
||||||
165
graph.go
165
graph.go
|
|
@ -19,6 +19,7 @@ type edge struct {
|
||||||
v *node // node this edge directs to
|
v *node // node this edge directs to
|
||||||
stem string // stem matched for meta-rule applications
|
stem string // stem matched for meta-rule applications
|
||||||
matches []string // regular expression matches
|
matches []string // regular expression matches
|
||||||
|
togo bool // this edge is going to be pruned
|
||||||
r *rule
|
r *rule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,6 +33,15 @@ const (
|
||||||
nodeStatusFailed
|
nodeStatusFailed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type nodeFlag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
nodeFlagCycle nodeFlag = 0x0002
|
||||||
|
nodeFlagReady = 0x0004
|
||||||
|
nodeFlagProbable = 0x0100
|
||||||
|
nodeFlagVacuous = 0x0200
|
||||||
|
)
|
||||||
|
|
||||||
// A node in the dependency graph
|
// A node in the dependency graph
|
||||||
type node struct {
|
type node struct {
|
||||||
r *rule // rule to be applied
|
r *rule // rule to be applied
|
||||||
|
|
@ -43,6 +53,7 @@ type node struct {
|
||||||
status nodeStatus // current state of the node in the build
|
status nodeStatus // current state of the node in the build
|
||||||
mutex sync.Mutex // exclusivity for the status variable
|
mutex sync.Mutex // exclusivity for the status variable
|
||||||
listeners []chan nodeStatus // channels to notify of completion
|
listeners []chan nodeStatus // channels to notify of completion
|
||||||
|
flags nodeFlag // bitwise combination of node flags
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new node
|
// Create a new node
|
||||||
|
|
@ -52,6 +63,7 @@ func (g *graph) newnode(name string) *node {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
u.t = info.ModTime()
|
u.t = info.ModTime()
|
||||||
u.exists = true
|
u.exists = true
|
||||||
|
u.flags |= nodeFlagProbable
|
||||||
} else {
|
} else {
|
||||||
_, ok := err.(*os.PathError)
|
_, ok := err.(*os.PathError)
|
||||||
if ok {
|
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.
|
// keep track of how many times each rule is visited, to avoid cycles.
|
||||||
rulecnt := make([]int, len(rs.rules))
|
rulecnt := make([]int, len(rs.rules))
|
||||||
g.root = applyrules(rs, g, target, rulecnt)
|
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
|
return g
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +146,7 @@ func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.flags |= nodeFlagProbable
|
||||||
rulecnt[k] += 1
|
rulecnt[k] += 1
|
||||||
if len(r.prereqs) == 0 {
|
if len(r.prereqs) == 0 {
|
||||||
u.newedge(nil, r)
|
u.newedge(nil, r)
|
||||||
|
|
@ -140,7 +161,7 @@ func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
|
||||||
|
|
||||||
// find applicable metarules
|
// find applicable metarules
|
||||||
for k := range rs.rules {
|
for k := range rs.rules {
|
||||||
if rulecnt[k] > maxRuleCnt {
|
if rulecnt[k] >= maxRuleCnt {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,6 +192,7 @@ func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
|
||||||
}
|
}
|
||||||
|
|
||||||
rulecnt[k] += 1
|
rulecnt[k] += 1
|
||||||
|
fmt.Println(rulecnt)
|
||||||
if len(r.prereqs) == 0 {
|
if len(r.prereqs) == 0 {
|
||||||
e := u.newedge(nil, r)
|
e := u.newedge(nil, r)
|
||||||
e.stem = stem
|
e.stem = stem
|
||||||
|
|
@ -196,3 +218,144 @@ func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
|
||||||
|
|
||||||
return u
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
44
mk.go
44
mk.go
|
|
@ -21,7 +21,7 @@ var rebuildall bool = false
|
||||||
var mkMsgMutex sync.Mutex
|
var mkMsgMutex sync.Mutex
|
||||||
|
|
||||||
// The maximum number of times an rule may be applied.
|
// The maximum number of times an rule may be applied.
|
||||||
const maxRuleCnt = 3
|
const maxRuleCnt = 1
|
||||||
|
|
||||||
// Limit the number of recipes executed simultaneously.
|
// Limit the number of recipes executed simultaneously.
|
||||||
var subprocsAllowed int
|
var subprocsAllowed int
|
||||||
|
|
@ -47,13 +47,15 @@ func finishSubproc() {
|
||||||
|
|
||||||
// Ansi color codes.
|
// Ansi color codes.
|
||||||
const (
|
const (
|
||||||
colorDefault string = "\033[0m"
|
ansiTermDefault = "\033[0m"
|
||||||
colorBlack string = "\033[30m"
|
ansiTermBlack = "\033[30m"
|
||||||
colorRed string = "\033[31m"
|
ansiTermRed = "\033[31m"
|
||||||
colorGreen string = "\033[32m"
|
ansiTermGreen = "\033[32m"
|
||||||
colorYellow string = "\033[33m"
|
ansiTermYellow = "\033[33m"
|
||||||
colorBlue string = "\033[34m"
|
ansiTermBlue = "\033[34m"
|
||||||
colorMagenta string = "\033[35m"
|
ansiTermMagenta = "\033[35m"
|
||||||
|
ansiTermBright = "\033[1m"
|
||||||
|
ansiTermUnderline = "\033[4m"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mk(rs *ruleSet, target string, dryrun bool) {
|
func mk(rs *ruleSet, target string, dryrun bool) {
|
||||||
|
|
@ -167,21 +169,25 @@ func mkNode(g *graph, u *node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkError(msg string) {
|
func mkError(msg string) {
|
||||||
|
mkPrintError(msg)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkPrintError(msg string) {
|
||||||
if !nocolor {
|
if !nocolor {
|
||||||
os.Stderr.WriteString(colorRed)
|
os.Stderr.WriteString(ansiTermRed)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "mk: %s\n", msg)
|
fmt.Fprintf(os.Stderr, "mk: %s\n", msg)
|
||||||
if !nocolor {
|
if !nocolor {
|
||||||
os.Stderr.WriteString(colorDefault)
|
os.Stderr.WriteString(ansiTermDefault)
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkPrintSuccess(msg string) {
|
func mkPrintSuccess(msg string) {
|
||||||
if nocolor {
|
if nocolor {
|
||||||
fmt.Println(msg)
|
fmt.Println(msg)
|
||||||
} else {
|
} 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 {
|
if nocolor {
|
||||||
fmt.Println(msg)
|
fmt.Println(msg)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s%s%s\n", colorBlue, msg, colorDefault)
|
fmt.Printf("%s%s%s\n", ansiTermBlue, msg, ansiTermDefault)
|
||||||
}
|
}
|
||||||
mkMsgMutex.Unlock()
|
mkMsgMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
@ -200,14 +206,16 @@ func mkPrintRecipe(target string, recipe string) {
|
||||||
if nocolor {
|
if nocolor {
|
||||||
fmt.Printf("%s: ", target)
|
fmt.Printf("%s: ", target)
|
||||||
} else {
|
} 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 {
|
if len(recipe) == 0 {
|
||||||
os.Stdout.WriteString("\n")
|
os.Stdout.WriteString("\n")
|
||||||
}
|
}
|
||||||
if !nocolor {
|
if !nocolor {
|
||||||
os.Stdout.WriteString(colorDefault)
|
os.Stdout.WriteString(ansiTermDefault)
|
||||||
}
|
}
|
||||||
mkMsgMutex.Unlock()
|
mkMsgMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +225,7 @@ func main() {
|
||||||
flag.StringVar(&mkfilepath, "f", "mkfile", "use the given file as mkfile")
|
flag.StringVar(&mkfilepath, "f", "mkfile", "use the given file as mkfile")
|
||||||
flag.BoolVar(&dryrun, "n", false, "print commands without actually executing")
|
flag.BoolVar(&dryrun, "n", false, "print commands without actually executing")
|
||||||
flag.BoolVar(&rebuildall, "a", false, "force building of all dependencies")
|
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()
|
flag.Parse()
|
||||||
|
|
||||||
mkfile, err := os.Open(mkfilepath)
|
mkfile, err := os.Open(mkfilepath)
|
||||||
|
|
@ -231,6 +239,7 @@ func main() {
|
||||||
targets := flag.Args()
|
targets := flag.Args()
|
||||||
|
|
||||||
// build the first non-meta rule in the makefile, if none are given explicitly
|
// build the first non-meta rule in the makefile, if none are given explicitly
|
||||||
|
if len(targets) == 0 {
|
||||||
for i := range rs.rules {
|
for i := range rs.rules {
|
||||||
if !rs.rules[i].ismeta {
|
if !rs.rules[i].ismeta {
|
||||||
for j := range rs.rules[i].targets {
|
for j := range rs.rules[i].targets {
|
||||||
|
|
@ -239,6 +248,7 @@ func main() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(targets) == 0 {
|
if len(targets) == 0 {
|
||||||
fmt.Println("mk: nothing to mk")
|
fmt.Println("mk: nothing to mk")
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ func stripIndentation(s string, mincol int) string {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output += line[i:]
|
output += line[i:]
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
21
rules.go
21
rules.go
|
|
@ -58,6 +58,27 @@ type rule struct {
|
||||||
command []string // command attribute
|
command []string // command attribute
|
||||||
ismeta bool // is this a meta rule
|
ismeta bool // is this a meta rule
|
||||||
mutex sync.Mutex // prevent the rule from being executed multiple times
|
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.
|
// A set of rules.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue