mk/mk.go

392 lines
8.9 KiB
Go
Raw Normal View History

2013-02-25 21:25:25 -08:00
package main
import (
"bufio"
2013-03-03 17:51:00 -08:00
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
2013-03-03 18:57:14 -08:00
"sync"
2013-02-25 21:25:25 -08:00
)
2013-03-03 17:50:00 -08:00
// True if messages should be printed without fancy colors.
var nocolor bool = false
2013-03-04 00:06:24 -08:00
// True if we are ignoring timestamps and rebuilding everything.
var rebuildall bool = false
// Set of targets for which we are forcing rebuild
var rebuildtargets map[string]bool = make(map[string]bool)
2013-03-03 18:57:14 -08:00
// Lock on standard out, messages don't get interleaved too much.
var mkMsgMutex sync.Mutex
// The maximum number of times an rule may be applied.
2013-03-09 19:27:28 -08:00
const maxRuleCnt = 1
2013-03-04 00:06:24 -08:00
// Limit the number of recipes executed simultaneously.
var subprocsAllowed int
// Current subprocesses being executed
var subprocsRunning int
// Wakeup on a free subprocess slot.
var subprocsRunningCond *sync.Cond = sync.NewCond(&sync.Mutex{})
// Prevent more than one recipe at a time from trying to take over
var exclusiveSubproc = sync.Mutex{}
2013-03-04 00:06:24 -08:00
// Wait until there is an available subprocess slot.
func reserveSubproc() {
subprocsRunningCond.L.Lock()
for subprocsRunning >= subprocsAllowed {
subprocsRunningCond.Wait()
2013-03-04 00:06:24 -08:00
}
subprocsRunning++
subprocsRunningCond.L.Unlock()
2013-03-04 00:06:24 -08:00
}
// Free up another subprocess to run.
func finishSubproc() {
subprocsRunningCond.L.Lock()
subprocsRunning--
subprocsRunningCond.Signal()
subprocsRunningCond.L.Unlock()
}
// Make everyone wait while we
func reserveExclusiveSubproc() {
exclusiveSubproc.Lock()
// Wait until everything is done running
stolen_subprocs := 0
subprocsRunningCond.L.Lock()
stolen_subprocs = subprocsAllowed - subprocsRunning
subprocsRunning = subprocsAllowed
for stolen_subprocs < subprocsAllowed {
subprocsRunningCond.Wait()
stolen_subprocs += subprocsAllowed - subprocsRunning
subprocsRunning = subprocsAllowed
}
}
func finishExclusiveSubproc() {
subprocsRunning = 0
subprocsRunningCond.Broadcast()
subprocsRunningCond.L.Unlock()
exclusiveSubproc.Unlock()
2013-03-04 00:06:24 -08:00
}
2013-03-03 17:50:00 -08:00
// Ansi color codes.
const (
2013-03-09 19:27:28 -08:00
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"
2013-03-03 17:50:00 -08:00
)
// Build a node's prereqs. Block until completed.
//
func mkNodePrereqs(g *graph, u *node, e *edge, prereqs []*node, dryrun bool,
required bool) nodeStatus {
prereqstat := make(chan nodeStatus)
pending := 0
// build prereqs that need building
for i := range prereqs {
prereqs[i].mutex.Lock()
switch prereqs[i].status {
2013-07-28 21:16:50 -07:00
case nodeStatusReady, nodeStatusNop:
go mkNode(g, prereqs[i], dryrun, required)
fallthrough
case nodeStatusStarted:
prereqs[i].listeners = append(prereqs[i].listeners, prereqstat)
pending++
}
prereqs[i].mutex.Unlock()
}
// wait until all the prereqs are built
status := nodeStatusDone
for pending > 0 {
s := <-prereqstat
pending--
if s == nodeStatusFailed {
status = nodeStatusFailed
}
}
return status
}
2013-03-03 17:50:00 -08:00
// Build a target in the graph.
//
// This selects an appropriate rule (edge) and builds all prerequisites
// concurrently.
//
// Args:
// g: Graph in which the node lives.
// u: Node to (possibly) build.
// dryrun: Don't actually build anything, just pretend.
// required: Avoid building this node, unless its prereqs are out of date.
//
func mkNode(g *graph, u *node, dryrun bool, required bool) {
2013-03-03 17:51:00 -08:00
// try to claim on this node
u.mutex.Lock()
2013-07-28 21:16:50 -07:00
if u.status != nodeStatusReady && u.status != nodeStatusNop {
2013-03-03 17:51:00 -08:00
u.mutex.Unlock()
return
} else {
u.status = nodeStatusStarted
}
u.mutex.Unlock()
// when finished, notify the listeners
finalstatus := nodeStatusDone
defer func() {
u.mutex.Lock()
u.status = finalstatus
for i := range u.listeners {
u.listeners[i] <- u.status
}
2013-07-28 21:16:50 -07:00
u.listeners = u.listeners[0:0]
2013-08-05 07:32:21 -07:00
u.mutex.Unlock()
2013-03-03 17:51:00 -08:00
}()
// there's no fucking rules, dude
if len(u.prereqs) == 0 {
2013-03-03 23:32:36 -08:00
if !(u.r != nil && u.r.attributes.virtual) && !u.exists {
2013-03-03 17:51:00 -08:00
wd, _ := os.Getwd()
mkError(fmt.Sprintf("don't know how to make %s in %s\n", u.name, wd))
2013-03-03 17:51:00 -08:00
}
2013-04-24 11:36:24 -07:00
finalstatus = nodeStatusNop
2013-03-03 17:51:00 -08:00
return
}
// there should otherwise be exactly one edge with an associated rule
prereqs := make([]*node, 0)
var e *edge = nil
for i := range u.prereqs {
if u.prereqs[i].r != nil {
e = u.prereqs[i]
}
if u.prereqs[i].v != nil {
prereqs = append(prereqs, u.prereqs[i].v)
}
}
// this should have been caught during graph building
if e == nil {
wd, _ := os.Getwd()
mkError(fmt.Sprintf("don't know how to make %s in %s", u.name, wd))
}
prereqs_required := required && (e.r.attributes.virtual || !u.exists)
mkNodePrereqs(g, u, e, prereqs, dryrun, prereqs_required)
2013-03-03 17:51:00 -08:00
2013-04-24 11:36:24 -07:00
uptodate := true
if !e.r.attributes.virtual {
u.updateTimestamp()
2013-07-28 21:16:50 -07:00
if !u.exists && required {
uptodate = false
} else if u.exists || required {
2013-04-24 11:36:24 -07:00
for i := range prereqs {
if u.t.Before(prereqs[i].t) || prereqs[i].status == nodeStatusDone {
uptodate = false
}
}
} else if required {
2013-04-24 11:36:24 -07:00
uptodate = false
}
} else {
uptodate = false
}
_, isrebuildtarget := rebuildtargets[u.name]
if isrebuildtarget || rebuildall {
2013-04-24 11:36:24 -07:00
uptodate = false
}
// make another pass on the prereqs, since we know we need them now
if !uptodate {
mkNodePrereqs(g, u, e, prereqs, dryrun, true)
}
2013-03-03 17:51:00 -08:00
// execute the recipe, unless the prereqs failed
2013-04-24 11:36:24 -07:00
if !uptodate && finalstatus != nodeStatusFailed && len(e.r.recipe) > 0 {
if e.r.attributes.exclusive {
reserveExclusiveSubproc()
} else {
reserveSubproc()
}
if !dorecipe(u.name, u, e, dryrun) {
2013-03-03 17:51:00 -08:00
finalstatus = nodeStatusFailed
}
2013-04-24 11:36:24 -07:00
u.updateTimestamp()
if e.r.attributes.exclusive {
finishExclusiveSubproc()
} else {
finishSubproc()
}
2013-04-24 11:36:24 -07:00
} else if finalstatus != nodeStatusFailed {
finalstatus = nodeStatusNop
2013-03-03 17:51:00 -08:00
}
2013-03-03 17:50:00 -08:00
}
func mkError(msg string) {
2013-03-09 19:27:28 -08:00
mkPrintError(msg)
os.Exit(1)
}
func mkPrintError(msg string) {
2013-03-03 17:51:00 -08:00
if !nocolor {
2013-03-09 19:27:28 -08:00
os.Stderr.WriteString(ansiTermRed)
2013-03-03 17:51:00 -08:00
}
2014-08-04 13:57:52 -07:00
fmt.Fprintf(os.Stderr, "%s\n", msg)
2013-03-03 17:51:00 -08:00
if !nocolor {
2013-03-09 19:27:28 -08:00
os.Stderr.WriteString(ansiTermDefault)
2013-03-03 17:51:00 -08:00
}
}
2013-03-03 17:50:00 -08:00
func mkPrintSuccess(msg string) {
2013-03-03 17:51:00 -08:00
if nocolor {
fmt.Println(msg)
} else {
2013-03-09 19:27:28 -08:00
fmt.Printf("%s%s%s\n", ansiTermGreen, msg, ansiTermDefault)
2013-03-03 17:51:00 -08:00
}
2013-03-03 17:50:00 -08:00
}
func mkPrintMessage(msg string) {
2013-03-03 18:57:14 -08:00
mkMsgMutex.Lock()
2013-03-03 17:51:00 -08:00
if nocolor {
fmt.Println(msg)
} else {
2013-03-09 19:27:28 -08:00
fmt.Printf("%s%s%s\n", ansiTermBlue, msg, ansiTermDefault)
2013-03-03 17:51:00 -08:00
}
2013-03-03 18:57:14 -08:00
mkMsgMutex.Unlock()
2013-03-03 17:50:00 -08:00
}
func mkPrintRecipe(target string, recipe string, quiet bool) {
2013-03-03 18:57:14 -08:00
mkMsgMutex.Lock()
if nocolor {
fmt.Printf("%s: ", target)
} else {
fmt.Printf("%s%s%s → %s",
2013-03-09 19:27:28 -08:00
ansiTermBlue+ansiTermBright+ansiTermUnderline, target,
ansiTermDefault, ansiTermBlue)
2013-03-03 18:57:14 -08:00
}
if quiet {
if nocolor {
fmt.Println("...")
} else {
fmt.Println("…")
}
} else {
printIndented(os.Stdout, recipe, len(target)+3)
if len(recipe) == 0 {
os.Stdout.WriteString("\n")
}
2013-03-03 17:51:00 -08:00
}
if !nocolor {
2013-03-09 19:27:28 -08:00
os.Stdout.WriteString(ansiTermDefault)
2013-03-03 17:51:00 -08:00
}
2013-03-03 18:57:14 -08:00
mkMsgMutex.Unlock()
2013-03-03 17:50:00 -08:00
}
2013-02-25 21:25:25 -08:00
func main() {
2013-03-03 17:51:00 -08:00
var mkfilepath string
var interactive bool
var dryrun bool
var shallowrebuild bool
var quiet bool
2013-03-03 17:51:00 -08:00
flag.StringVar(&mkfilepath, "f", "mkfile", "use the given file as mkfile")
flag.BoolVar(&dryrun, "n", false, "print commands without actually executing")
flag.BoolVar(&shallowrebuild, "r", false, "force building of just targets")
2013-03-04 00:06:24 -08:00
flag.BoolVar(&rebuildall, "a", false, "force building of all dependencies")
2014-08-04 13:42:57 -07:00
flag.IntVar(&subprocsAllowed, "p", 4, "maximum number of jobs to execute in parallel")
flag.BoolVar(&interactive, "i", false, "prompt before executing rules")
flag.BoolVar(&quiet, "q", false, "don't print recipes before executing them")
2013-03-03 17:51:00 -08:00
flag.Parse()
mkfile, err := os.Open(mkfilepath)
if err != nil {
mkError("no mkfile found")
}
input, _ := ioutil.ReadAll(mkfile)
mkfile.Close()
abspath, err := filepath.Abs(mkfilepath)
if err != nil {
mkError("unable to find mkfile's absolute path")
}
rs := parse(string(input), mkfilepath, abspath)
if quiet {
for i := range rs.rules {
rs.rules[i].attributes.quiet = true
}
}
2013-03-03 17:51:00 -08:00
targets := flag.Args()
// build the first non-meta rule in the makefile, if none are given explicitly
2013-03-10 00:34:42 -08:00
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
}
}
}
2013-03-03 17:51:00 -08:00
if len(targets) == 0 {
fmt.Println("mk: nothing to mk")
return
}
if shallowrebuild {
for i := range targets {
rebuildtargets[targets[i]] = true
}
}
// Create a dummy virtual rule that depends on every target
root := rule{}
root.targets = []pattern{pattern{false, "", nil}}
root.attributes = attribSet{false, false, false, false, false, false, false, true, false}
root.prereqs = targets
rs.add(root)
if interactive {
g := buildgraph(rs, "")
mkNode(g, g.root, true, true)
fmt.Print("Proceed? ")
in := bufio.NewReader(os.Stdin)
for {
c, _, err := in.ReadRune()
if err != nil {
return
} else if strings.IndexRune(" \n\t\r", c) >= 0 {
continue
} else if c == 'y' {
break
} else {
return
}
}
2013-03-10 00:34:42 -08:00
}
g := buildgraph(rs, "")
mkNode(g, g.root, dryrun, true)
2013-02-25 21:25:25 -08:00
}