2013-02-25 21:25:25 -08:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2013-04-06 11:35:21 -07:00
|
|
|
"bufio"
|
2013-03-03 17:51:00 -08:00
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"os"
|
2014-08-05 14:16:59 -07:00
|
|
|
"path/filepath"
|
2013-04-06 11:35:21 -07:00
|
|
|
"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
|
|
|
|
|
|
2014-03-24 23:19:33 -07:00
|
|
|
// 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
|
|
|
|
|
|
2013-03-02 00:34:31 -08:00
|
|
|
// 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
|
2013-07-09 23:13:37 -07:00
|
|
|
|
|
|
|
|
// 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() {
|
2013-07-09 23:13:37 -07:00
|
|
|
subprocsRunningCond.L.Lock()
|
|
|
|
|
for subprocsRunning >= subprocsAllowed {
|
|
|
|
|
subprocsRunningCond.Wait()
|
2013-03-04 00:06:24 -08:00
|
|
|
}
|
2013-07-09 23:13:37 -07:00
|
|
|
subprocsRunning++
|
|
|
|
|
subprocsRunningCond.L.Unlock()
|
2013-03-04 00:06:24 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Free up another subprocess to run.
|
|
|
|
|
func finishSubproc() {
|
2013-07-09 23:13:37 -07:00
|
|
|
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-02 00:34:31 -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
|
|
|
)
|
|
|
|
|
|
2013-07-28 19:01:58 -07: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:
|
2013-07-28 19:01:58 -07:00
|
|
|
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.
|
|
|
|
|
//
|
2013-07-28 19:01:58 -07:00
|
|
|
// 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()
|
2013-03-18 20:37:01 -07:00
|
|
|
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))
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-28 19:01:58 -07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-07-28 19:01:58 -07:00
|
|
|
} else if required {
|
2013-04-24 11:36:24 -07:00
|
|
|
uptodate = false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
uptodate = false
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-24 23:19:33 -07:00
|
|
|
_, isrebuildtarget := rebuildtargets[u.name]
|
|
|
|
|
if isrebuildtarget || rebuildall {
|
2013-04-24 11:36:24 -07:00
|
|
|
uptodate = false
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-28 19:01:58 -07:00
|
|
|
// 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 {
|
2013-07-09 23:13:37 -07:00
|
|
|
if e.r.attributes.exclusive {
|
|
|
|
|
reserveExclusiveSubproc()
|
|
|
|
|
} else {
|
|
|
|
|
reserveSubproc()
|
|
|
|
|
}
|
|
|
|
|
|
2013-04-06 11:35:21 -07:00
|
|
|
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()
|
2013-07-09 23:13:37 -07:00
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2013-03-02 00:34:31 -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-02 00:34:31 -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
|
|
|
}
|
|
|
|
|
|
2015-03-24 17:47:02 -07: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 {
|
2013-03-18 20:37:01 -07:00
|
|
|
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
|
|
|
}
|
2015-03-24 17:47:02 -07: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
|
2013-04-06 11:35:21 -07:00
|
|
|
var interactive bool
|
|
|
|
|
var dryrun bool
|
2014-03-24 23:19:33 -07:00
|
|
|
var shallowrebuild bool
|
2015-03-24 17:47:02 -07:00
|
|
|
var quiet bool
|
2013-04-06 11:35:21 -07:00
|
|
|
|
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")
|
2014-03-24 23:19:33 -07:00
|
|
|
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")
|
2013-04-06 11:35:21 -07:00
|
|
|
flag.BoolVar(&interactive, "i", false, "prompt before executing rules")
|
2015-03-24 17:47:02 -07:00
|
|
|
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()
|
|
|
|
|
|
2014-08-05 14:16:59 -07:00
|
|
|
abspath, err := filepath.Abs(mkfilepath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
mkError("unable to find mkfile's absolute path")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rs := parse(string(input), mkfilepath, abspath)
|
2015-03-24 17:47:02 -07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-24 23:19:33 -07:00
|
|
|
if shallowrebuild {
|
|
|
|
|
for i := range targets {
|
|
|
|
|
rebuildtargets[targets[i]] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a dummy virtual rule that depends on every target
|
2013-04-06 11:35:21 -07:00
|
|
|
root := rule{}
|
|
|
|
|
root.targets = []pattern{pattern{false, "", nil}}
|
2013-07-09 23:13:37 -07:00
|
|
|
root.attributes = attribSet{false, false, false, false, false, false, false, true, false}
|
2013-04-06 11:35:21 -07:00
|
|
|
root.prereqs = targets
|
|
|
|
|
rs.add(root)
|
|
|
|
|
|
|
|
|
|
if interactive {
|
|
|
|
|
g := buildgraph(rs, "")
|
2013-07-28 19:01:58 -07:00
|
|
|
mkNode(g, g.root, true, true)
|
2013-04-06 11:35:21 -07:00
|
|
|
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
|
|
|
}
|
2013-04-06 11:35:21 -07:00
|
|
|
|
|
|
|
|
g := buildgraph(rs, "")
|
2013-07-28 19:01:58 -07:00
|
|
|
mkNode(g, g.root, dryrun, true)
|
2013-02-25 21:25:25 -08:00
|
|
|
}
|