This commit is contained in:
Daniel Jones 2013-03-03 17:51:00 -08:00
parent ee70f46012
commit 8a218f35c0
6 changed files with 586 additions and 623 deletions

329
expand.go
View file

@ -1,33 +1,31 @@
// String substitution and expansion. // String substitution and expansion.
package main package main
import ( import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
// Expand a word. This includes substituting variables and handling quotes. // Expand a word. This includes substituting variables and handling quotes.
func expand(input string, vars map[string][]string, expandBackticks bool) []string { func expand(input string, vars map[string][]string, expandBackticks bool) []string {
parts := make([]string, 0) parts := make([]string, 0)
expanded := "" expanded := ""
var i, j int var i, j int
for i = 0; i < len(input); { for i = 0; i < len(input); {
j = i + strings.IndexAny(input[i:], "\"'`$\\") j = i + strings.IndexAny(input[i:], "\"'`$\\")
if j < 0 { if j < 0 {
expanded += input[i:] expanded += input[i:]
break break
} }
println("-------------------") println("-------------------")
println(len(input)) println(len(input))
println(i) println(i)
println(j) 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
@ -36,41 +34,41 @@ func expand(input string, vars map[string][]string, expandBackticks bool) []stri
switch c { switch c {
case '\\': case '\\':
out, off = expandEscape(input[i:]) out, off = expandEscape(input[i:])
expanded += out expanded += out
case '"': case '"':
out, off = expandDoubleQuoted(input[i:], vars, expandBackticks) out, off = expandDoubleQuoted(input[i:], vars, expandBackticks)
expanded += out expanded += out
case '\'': case '\'':
out, off = expandSingleQuoted(input[i:]) out, off = expandSingleQuoted(input[i:])
expanded += out expanded += out
case '`': case '`':
if expandBackticks { if expandBackticks {
out, off = expandBackQuoted(input[i:], vars) out, off = expandBackQuoted(input[i:], vars)
} else { } else {
out = input out = input
off = len(input) off = len(input)
} }
expanded += out expanded += out
case '$': case '$':
var outparts []string var outparts []string
outparts, off = expandSigil(input[i:], vars) outparts, off = expandSigil(input[i:], vars)
if len(outparts) > 0 { if len(outparts) > 0 {
outparts[0] = expanded + outparts[0] outparts[0] = expanded + outparts[0]
expanded = outparts[len(outparts)-1] expanded = outparts[len(outparts)-1]
parts = append(parts, outparts[:len(outparts)-1]...) parts = append(parts, outparts[:len(outparts)-1]...)
} }
} }
i += off i += off
} }
if len(expanded) > 0 { if len(expanded) > 0 {
parts = append(parts, expanded) parts = append(parts, expanded)
} }
return parts return parts
} }
@ -126,188 +124,177 @@ func expandSingleQuoted(input string) (string, int) {
// Expand something starting with at '$'. // Expand something starting with at '$'.
func expandSigil(input string, vars map[string][]string) ([]string, int) { func expandSigil(input string, vars map[string][]string) ([]string, int) {
c, w := utf8.DecodeRuneInString(input) c, w := utf8.DecodeRuneInString(input)
var offset int var offset int
var varname string var varname string
if c == '{' { if c == '{' {
j := strings.IndexRune(input[w:], '}') j := strings.IndexRune(input[w:], '}')
if j < 0 { if j < 0 {
return []string{"$" + input}, len(input) return []string{"$" + input}, len(input)
} }
varname = input[w:j] varname = input[w:j]
offset = j + 1 offset = j + 1
} else { } else {
// try to match a variable name // try to match a variable name
i := 0 i := 0
j := i j := i
for j < len(input) { for j < len(input) {
c, w = utf8.DecodeRuneInString(input[j:]) c, w = utf8.DecodeRuneInString(input[j:])
if !(isalpha(c) || c == '_' || (j > i && isdigit(c))) { if !(isalpha(c) || c == '_' || (j > i && isdigit(c))) {
break break
} }
j += w j += w
} }
if j > i { if j > i {
varname = input[i:j] varname = input[i:j]
offset = j offset = j
} else { } else {
return []string{"$" + input}, len(input) return []string{"$" + input}, len(input)
} }
} }
if isValidVarName(varname) { if isValidVarName(varname) {
varvals, ok := vars[varname] varvals, ok := vars[varname]
if ok { if ok {
return varvals, offset return varvals, offset
} }
} }
return []string{"$" + input}, len(input) return []string{"$" + input}, len(input)
} }
// Find and expand all sigils. // Find and expand all sigils.
func expandSigils(input string, vars map[string][]string) []string { func expandSigils(input string, vars map[string][]string) []string {
parts := make([]string, 0) parts := make([]string, 0)
expanded := "" expanded := ""
for i := 0; i < len(input); { for i := 0; i < len(input); {
j := strings.IndexRune(input[i:], '$') j := strings.IndexRune(input[i:], '$')
if j < 0 { if j < 0 {
expanded += input[i:] expanded += input[i:]
break break
} }
ex, k := expandSigil(input[j+1:], vars) ex, k := expandSigil(input[j+1:], vars)
if len(ex) > 0 { if len(ex) > 0 {
ex[0] = expanded + ex[0] ex[0] = expanded + ex[0]
expanded = ex[len(ex)-1] expanded = ex[len(ex)-1]
parts = append(parts, ex[:len(ex)-1]...) parts = append(parts, ex[:len(ex)-1]...)
} }
i = k i = k
} }
if len(expanded) > 0 { if len(expanded) > 0 {
parts = append(parts, expanded) parts = append(parts, expanded)
} }
return parts return parts
} }
// Find and expand all sigils in a recipe, producing a flat string. // Find and expand all sigils in a recipe, producing a flat string.
func expandRecipeSigils(input string, vars map[string][]string) string { func expandRecipeSigils(input string, vars map[string][]string) string {
expanded := "" expanded := ""
for i := 0; i < len(input); { for i := 0; i < len(input); {
j := strings.IndexAny(input[i:], "$\\") j := strings.IndexAny(input[i:], "$\\")
if j < 0 { if j < 0 {
expanded += input[i:] expanded += input[i:]
break break
} }
expanded += input[i:j] expanded += input[i:j]
i = j i = j
c, w := utf8.DecodeRuneInString(input[i:]) c, w := utf8.DecodeRuneInString(input[i:])
if c == '$' { if c == '$' {
i += w i += w
ex, k := expandSigil(input[i:], vars) ex, k := expandSigil(input[i:], vars)
expanded += strings.Join(ex, " ") expanded += strings.Join(ex, " ")
i += k i += k
} else if c == '\\' { } else if c == '\\' {
i += w i += w
c, w := utf8.DecodeRuneInString(input[i:]) c, w := utf8.DecodeRuneInString(input[i:])
if c == '$' { if c == '$' {
expanded += "$" expanded += "$"
} else { } else {
expanded += "\\" + string(c) expanded += "\\" + string(c)
} }
i += w i += w
} }
} }
return expanded return expanded
} }
// Expand all unescaped '%' characters. // Expand all unescaped '%' characters.
func expandSuffixes(input string, stem string) string { func expandSuffixes(input string, stem string) string {
expanded := make([]byte, 0) expanded := make([]byte, 0)
for i := 0; i < len(input); { for i := 0; i < len(input); {
j := strings.IndexAny(input[i:], "\\%") j := strings.IndexAny(input[i:], "\\%")
if j < 0 { if j < 0 {
expanded = append(expanded, input[i:]...) expanded = append(expanded, input[i:]...)
break break
} }
c, w := utf8.DecodeRuneInString(input[j:]) c, w := utf8.DecodeRuneInString(input[j:])
if c == '%' { if c == '%' {
expanded = append(expanded, stem...) expanded = append(expanded, stem...)
i += w i += w
} else { } else {
j += w j += w
c, w := utf8.DecodeRuneInString(input[j:]) c, w := utf8.DecodeRuneInString(input[j:])
if c == '%' { if c == '%' {
expanded = append(expanded, '%') expanded = append(expanded, '%')
i = j + w i = j + w
} }
} }
} }
return string(expanded) return string(expanded)
} }
// TODO: expand RegexpRefs // TODO: expand RegexpRefs
// Expand a backtick quoted string, by executing the contents. // Expand a backtick quoted string, by executing the contents.
func expandBackQuoted(input string, vars map[string][]string) (string, int) { func expandBackQuoted(input string, vars map[string][]string) (string, int) {
// TODO: expand sigils? // TODO: expand sigils?
j := strings.Index(input, "`") j := strings.Index(input, "`")
if j < 0 { if j < 0 {
return input, len(input) return input, len(input)
} }
// TODO: handle errors // TODO: handle errors
output, _ := subprocess("sh", nil, input[:j], false, false, true) output, _ := subprocess("sh", nil, input[:j], false, false, true)
return output, (j + 1) return output, (j + 1)
} }
// Split a string on whitespace taking into account escaping and quoting. // Split a string on whitespace taking into account escaping and quoting.
//func splitQuoted(input string) []string { //func splitQuoted(input string) []string {
//parts := make([]string, 0) //parts := make([]string, 0)
//var i, j int //var i, j int
//i = 0 //i = 0
//for { //for {
//// skip all unescaped whitespace //// skip all unescaped whitespace
//for i < len(input) { //for i < len(input) {
//c, w := utf8.DecodeRuneInString(input[i:]) //c, w := utf8.DecodeRuneInString(input[i:])
//if strings.IndexRune(" \t", c) < 0 { //if strings.IndexRune(" \t", c) < 0 {
//break //break
//} //}
//i += w //i += w
//}
//if i >= len(input) {
//break
//}
//// Ugh. Will this take into account quoting in variables?
//switch c {
//case '"':
//case '\'':
//default:
//}
//}
//return parts
//} //}
//if i >= len(input) {
//break
//}
//// Ugh. Will this take into account quoting in variables?
//switch c {
//case '"':
//case '\'':
//default:
//}
//}
//return parts
//}

282
graph.go
View file

@ -1,206 +1,198 @@
package main package main
import ( import (
"fmt" "fmt"
"io" "io"
"os" "os"
"sync" "sync"
"time" "time"
) )
// A dependency graph // A dependency graph
type graph struct { type graph struct {
root *node // the intial target's node root *node // the intial target's node
nodes map[string]*node // map targets to their nodes nodes map[string]*node // map targets to their nodes
} }
// An edge in the graph. // An edge in the graph.
type edge struct { 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
r *rule r *rule
} }
// Current status of a node in the build. // Current status of a node in the build.
type nodeStatus int type nodeStatus int
const ( const (
nodeStatusReady nodeStatus = iota nodeStatusReady nodeStatus = iota
nodeStatusStarted nodeStatusStarted
nodeStatusDone nodeStatusDone
nodeStatusFailed nodeStatusFailed
) )
// 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
name string // target name name string // target name
prog string // custom program to compare times prog string // custom program to compare times
t time.Time // file modification time t time.Time // file modification time
exists bool // does a non-virtual target exist exists bool // does a non-virtual target exist
prereqs []*edge // prerequisite rules prereqs []*edge // prerequisite rules
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
} }
// Create a new node // Create a new node
func (g *graph) newnode(name string) *node { func (g *graph) newnode(name string) *node {
u := &node{name: name} u := &node{name: name}
info, err := os.Stat(name) info, err := os.Stat(name)
if err == nil { if err == nil {
u.t = info.ModTime() u.t = info.ModTime()
u.exists = true u.exists = true
} else { } else {
_, ok := err.(*os.PathError) _, ok := err.(*os.PathError)
if ok { if ok {
u.exists = false u.exists = false
} else { } else {
mkError(err.Error()) mkError(err.Error())
} }
} }
g.nodes[name] = u g.nodes[name] = u
return u return u
} }
// Print a graph in graphviz format. // Print a graph in graphviz format.
func (g *graph) visualize(w io.Writer) { func (g *graph) visualize(w io.Writer) {
fmt.Fprintln(w, "digraph mk {") fmt.Fprintln(w, "digraph mk {")
for t, u := range g.nodes { for t, u := range g.nodes {
for i := range u.prereqs { for i := range u.prereqs {
if u.prereqs[i].v != nil { if u.prereqs[i].v != nil {
fmt.Fprintf(w, " \"%s\" -> \"%s\";\n", t, u.prereqs[i].v.name) fmt.Fprintf(w, " \"%s\" -> \"%s\";\n", t, u.prereqs[i].v.name)
} }
} }
} }
fmt.Fprintln(w, "}") fmt.Fprintln(w, "}")
} }
// Create a new arc. // Create a new arc.
func (u *node) newedge(v *node, r *rule) *edge { func (u *node) newedge(v *node, r *rule) *edge {
e := &edge{v: v, r: r} e := &edge{v: v, r: r}
u.prereqs = append(u.prereqs, e) u.prereqs = append(u.prereqs, e)
return e return e
} }
// Create a dependency graph for the given target. // Create a dependency graph for the given target.
func buildgraph(rs *ruleSet, target string) *graph { func buildgraph(rs *ruleSet, target string) *graph {
g := &graph{nil, make(map[string]*node)} g := &graph{nil, make(map[string]*node)}
// 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)
return g return g
} }
// Recursively match the given target to a rule in the rule set to construct the // Recursively match the given target to a rule in the rule set to construct the
// full graph. // full graph.
func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node { func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
u, ok := g.nodes[target] u, ok := g.nodes[target]
if ok { if ok {
return u return u
} }
u = g.newnode(target) u = g.newnode(target)
// does the target match a concrete rule? // does the target match a concrete rule?
ks, ok := rs.targetrules[target] ks, ok := rs.targetrules[target]
if ok { if ok {
for ki := range ks { for ki := range ks {
k := ks[ki] k := ks[ki]
if rulecnt[k] > max_rule_cnt { if rulecnt[k] > max_rule_cnt {
continue continue
} }
r := &rs.rules[k] r := &rs.rules[k]
// skip meta-rules // skip meta-rules
if r.ismeta { if r.ismeta {
continue continue
} }
// skip rules that have no effect // skip rules that have no effect
if r.recipe == "" && len(r.prereqs) == 0 { if r.recipe == "" && len(r.prereqs) == 0 {
continue continue
} }
rulecnt[k] += 1 rulecnt[k] += 1
if len(r.prereqs) == 0 { if len(r.prereqs) == 0 {
u.newedge(nil, r) u.newedge(nil, r)
} else { } else {
for i := range r.prereqs { for i := range r.prereqs {
u.newedge(applyrules(rs, g, r.prereqs[i], rulecnt), r) u.newedge(applyrules(rs, g, r.prereqs[i], rulecnt), r)
} }
} }
rulecnt[k] -= 1 rulecnt[k] -= 1
} }
} }
// find applicable metarules // find applicable metarules
for k := range rs.rules { for k := range rs.rules {
if rulecnt[k] > max_rule_cnt { if rulecnt[k] > max_rule_cnt {
continue continue
} }
r := &rs.rules[k] r := &rs.rules[k]
if !r.ismeta { if !r.ismeta {
continue continue
} }
// skip rules that have no effect // skip rules that have no effect
if r.recipe == "" && len(r.prereqs) == 0 { if r.recipe == "" && len(r.prereqs) == 0 {
continue continue
} }
for j := range r.targets { for j := range r.targets {
mat := r.targets[j].match(target) mat := r.targets[j].match(target)
if mat == nil { if mat == nil {
continue continue
} }
var stem string var stem string
var matches []string var matches []string
if r.attributes.regex { if r.attributes.regex {
matches = mat matches = mat
} else { } else {
stem = mat[1] stem = mat[1]
} }
rulecnt[k] += 1 rulecnt[k] += 1
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
e.matches = matches e.matches = matches
} else { } else {
for i := range r.prereqs { for i := range r.prereqs {
var prereq string var prereq string
if r.attributes.regex { if r.attributes.regex {
// TODO: write substituteRegexpRefs and use that here // TODO: write substituteRegexpRefs and use that here
prereq = r.prereqs[i] prereq = r.prereqs[i]
} else { } else {
prereq = expandSuffixes(r.prereqs[i], stem) prereq = expandSuffixes(r.prereqs[i], stem)
} }
e := u.newedge(applyrules(rs, g, prereq, rulecnt), r) e := u.newedge(applyrules(rs, g, prereq, rulecnt), r)
e.stem = stem e.stem = stem
e.matches = matches e.matches = matches
} }
} }
rulecnt[k] -= 1 rulecnt[k] -= 1
} }
} }
return u return u
} }

309
mk.go
View file

@ -1,13 +1,12 @@
package main package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
) )
// True if messages should be printed without fancy colors. // True if messages should be printed without fancy colors.
var nocolor bool = false var nocolor bool = false
@ -19,20 +18,19 @@ const max_rule_cnt = 3
// Ansi color codes. // Ansi color codes.
const ( const (
colorDefault string = "\033[0m" colorDefault string = "\033[0m"
colorBlack string = "\033[30m" colorBlack string = "\033[30m"
colorRed string = "\033[31m" colorRed string = "\033[31m"
colorGreen string = "\033[32m" colorGreen string = "\033[32m"
colorYellow string = "\033[33m" colorYellow string = "\033[33m"
colorBlue string = "\033[34m" colorBlue string = "\033[34m"
colorMagenta string = "\033[35m" colorMagenta string = "\033[35m"
) )
func mk(rs *ruleSet, target string, dryrun bool) { func mk(rs *ruleSet, target string, dryrun bool) {
g := buildgraph(rs, target) g := buildgraph(rs, target)
//g.visualize(os.Stdout) //g.visualize(os.Stdout)
mkNode(g, g.root) mkNode(g, g.root)
} }
// Build a target in the graph. // Build a target in the graph.
@ -45,174 +43,173 @@ func mk(rs *ruleSet, target string, dryrun bool) {
// execute our rule. // execute our rule.
// //
func mkNode(g *graph, u *node) { func mkNode(g *graph, u *node) {
// try to claim on this node // try to claim on this node
u.mutex.Lock() u.mutex.Lock()
if u.status != nodeStatusReady { if u.status != nodeStatusReady {
u.mutex.Unlock() u.mutex.Unlock()
return return
} else { } else {
u.status = nodeStatusStarted u.status = nodeStatusStarted
} }
u.mutex.Unlock() u.mutex.Unlock()
// when finished, notify the listeners // when finished, notify the listeners
finalstatus := nodeStatusDone finalstatus := nodeStatusDone
defer func () { defer func() {
u.mutex.Lock() u.mutex.Lock()
u.status = finalstatus u.status = finalstatus
u.mutex.Unlock() u.mutex.Unlock()
for i := range u.listeners { for i := range u.listeners {
u.listeners[i] <- u.status u.listeners[i] <- u.status
} }
}() }()
// there's no fucking rules, dude // there's no fucking rules, dude
if len(u.prereqs) == 0 { if len(u.prereqs) == 0 {
if !u.r.attributes.virtual && !u.exists { if !u.r.attributes.virtual && !u.exists {
wd, _ := os.Getwd() wd, _ := os.Getwd()
mkError(fmt.Sprintf("don't know how to make %s in %s", u.name, wd)) mkError(fmt.Sprintf("don't know how to make %s in %s", u.name, wd))
} }
return return
} }
// there should otherwise be exactly one edge with an associated rule // there should otherwise be exactly one edge with an associated rule
prereqs := make([]*node, 0) prereqs := make([]*node, 0)
var e *edge = nil var e *edge = nil
for i := range u.prereqs { for i := range u.prereqs {
if u.prereqs[i].r != nil { if u.prereqs[i].r != nil {
e = u.prereqs[i] e = u.prereqs[i]
} }
if u.prereqs[i].v != nil { if u.prereqs[i].v != nil {
prereqs = append(prereqs, u.prereqs[i].v) prereqs = append(prereqs, u.prereqs[i].v)
} }
} }
// this should have been caught during graph building // this should have been caught during graph building
if e == nil { if e == nil {
wd, _ := os.Getwd() wd, _ := os.Getwd()
mkError(fmt.Sprintf("don't know how to make %s in %s", u.name, wd)) mkError(fmt.Sprintf("don't know how to make %s in %s", u.name, wd))
} }
prereqstat := make(chan nodeStatus) prereqstat := make(chan nodeStatus)
pending := 0 pending := 0
// build prereqs that need building // build prereqs that need building
e.r.mutex.Lock() e.r.mutex.Lock()
for i := range prereqs { for i := range prereqs {
prereqs[i].mutex.Lock() prereqs[i].mutex.Lock()
// needs to be built? // needs to be built?
if !prereqs[i].exists || e.r.attributes.virtual || (u.exists && u.t.Before(prereqs[i].t)) { if !prereqs[i].exists || e.r.attributes.virtual || (u.exists && u.t.Before(prereqs[i].t)) {
switch prereqs[i].status { switch prereqs[i].status {
case nodeStatusReady: case nodeStatusReady:
go mkNode(g, prereqs[i]) go mkNode(g, prereqs[i])
fallthrough fallthrough
case nodeStatusStarted: case nodeStatusStarted:
prereqs[i].listeners = append(prereqs[i].listeners, prereqstat) prereqs[i].listeners = append(prereqs[i].listeners, prereqstat)
pending++ pending++
} }
} }
prereqs[i].mutex.Unlock() prereqs[i].mutex.Unlock()
} }
e.r.mutex.Unlock() e.r.mutex.Unlock()
// wait until all the prereqs are built // wait until all the prereqs are built
//fmt.Printf("%s: %d\n", u.name, pending) //fmt.Printf("%s: %d\n", u.name, pending)
for pending > 0 { for pending > 0 {
//for i := range prereqs { //for i := range prereqs {
//fmt.Println(prereqs[i].name) //fmt.Println(prereqs[i].name)
//} //}
s := <-prereqstat s := <-prereqstat
pending-- pending--
if s == nodeStatusFailed { if s == nodeStatusFailed {
finalstatus = nodeStatusFailed finalstatus = nodeStatusFailed
} }
} }
// execute the recipe, unless the prereqs failed // execute the recipe, unless the prereqs failed
if finalstatus != nodeStatusFailed { if finalstatus != nodeStatusFailed {
mkPrintMessage("mking " + u.name) mkPrintMessage("mking " + u.name)
if !dorecipe(u.name, u, e) { if !dorecipe(u.name, u, e) {
finalstatus = nodeStatusFailed finalstatus = nodeStatusFailed
} }
} }
mkPrintSuccess("finished mking " + u.name) mkPrintSuccess("finished mking " + u.name)
} }
func mkError(msg string) { func mkError(msg string) {
if !nocolor { if !nocolor {
os.Stderr.WriteString(colorRed) os.Stderr.WriteString(colorRed)
} }
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(colorDefault)
} }
os.Exit(1) 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", colorGreen, msg, colorDefault)
} }
} }
func mkPrintMessage(msg string) { 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", colorBlue, msg, colorDefault)
} }
} }
func mkPrintRecipe(msg string) { func mkPrintRecipe(msg string) {
if !nocolor { if !nocolor {
os.Stdout.WriteString(colorYellow) os.Stdout.WriteString(colorYellow)
} }
printIndented(os.Stdout, msg) printIndented(os.Stdout, msg)
if !nocolor { if !nocolor {
os.Stdout.WriteString(colorDefault) os.Stdout.WriteString(colorDefault)
} }
} }
func main() { func main() {
var mkfilepath string var mkfilepath string
var dryrun bool var dryrun bool
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.Parse() flag.Parse()
mkfile, err := os.Open(mkfilepath) mkfile, err := os.Open(mkfilepath)
if err != nil { if err != nil {
mkError("no mkfile found") mkError("no mkfile found")
} }
input, _ := ioutil.ReadAll(mkfile) input, _ := ioutil.ReadAll(mkfile)
mkfile.Close() mkfile.Close()
rs := parse(string(input), mkfilepath) rs := parse(string(input), mkfilepath)
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
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 {
targets = append(targets, rs.rules[i].targets[j].spat) targets = append(targets, rs.rules[i].targets[j].spat)
} }
break break
} }
} }
if len(targets) == 0 { if len(targets) == 0 {
fmt.Println("mk: nothing to mk") fmt.Println("mk: nothing to mk")
return return
} }
// TODO: For multiple targets, we should add a dummy rule that depends on // TODO: For multiple targets, we should add a dummy rule that depends on
// all let mk handle executing each. // all let mk handle executing each.
for _, target := range targets { for _, target := range targets {
mk(rs, target, dryrun) mk(rs, target, dryrun)
} }
} }

View file

@ -6,8 +6,8 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
"strings" "strings"
) )
type parser struct { type parser struct {
@ -53,8 +53,8 @@ type parserStateFun func(*parser, token) parserStateFun
// Parse a mkfile, returning a new ruleSet. // Parse a mkfile, returning a new ruleSet.
func parse(input string, name string) *ruleSet { func parse(input string, name string) *ruleSet {
rules := &ruleSet{make(map[string][]string), rules := &ruleSet{make(map[string][]string),
make([]rule, 0), make([]rule, 0),
make(map[string][]int)} make(map[string][]int)}
parseInto(input, name, rules) parseInto(input, name, rules)
return rules return rules
} }
@ -115,9 +115,9 @@ func parsePipeInclude(p *parser, t token) parserStateFun {
} }
output, success := subprocess("sh", args, "", false, false, true) output, success := subprocess("sh", args, "", false, false, true)
if !success { if !success {
p.basicErrorAtToken("subprocess include failed", t) p.basicErrorAtToken("subprocess include failed", t)
} }
parseInto(output, fmt.Sprintf("%s:sh", p.name), p.rules) parseInto(output, fmt.Sprintf("%s:sh", p.name), p.rules)
@ -275,8 +275,8 @@ func parseRecipe(p *parser, t token) parserStateFun {
if j < len(p.tokenbuf) { if j < len(p.tokenbuf) {
attribs := make([]string, 0) attribs := make([]string, 0)
for k := i + 1; k < j; k++ { for k := i + 1; k < j; k++ {
exparts := expand(p.tokenbuf[k].val, p.rules.vars, true) exparts := expand(p.tokenbuf[k].val, p.rules.vars, true)
attribs = append(attribs, exparts...) attribs = append(attribs, exparts...)
} }
err := r.parseAttribs(attribs) err := r.parseAttribs(attribs)
if err != nil { if err != nil {
@ -284,9 +284,9 @@ func parseRecipe(p *parser, t token) parserStateFun {
p.basicErrorAtToken(msg, p.tokenbuf[i+1]) p.basicErrorAtToken(msg, p.tokenbuf[i+1])
} }
if r.attributes.regex { if r.attributes.regex {
r.ismeta = true r.ismeta = true
} }
} else { } else {
j = i j = i
} }
@ -294,48 +294,48 @@ func parseRecipe(p *parser, t token) parserStateFun {
// targets // targets
r.targets = make([]pattern, 0) r.targets = make([]pattern, 0)
for k := 0; k < i; k++ { for k := 0; k < i; k++ {
exparts := expand(p.tokenbuf[k].val, p.rules.vars, true) exparts := expand(p.tokenbuf[k].val, p.rules.vars, true)
for i := range exparts { for i := range exparts {
targetstr := exparts[i] targetstr := exparts[i]
r.targets = append(r.targets, pattern{spat: targetstr}) r.targets = append(r.targets, pattern{spat: targetstr})
if r.attributes.regex { if r.attributes.regex {
rpat, err := regexp.Compile(targetstr) rpat, err := regexp.Compile(targetstr)
if err != nil { if err != nil {
msg := fmt.Sprintf("invalid regular expression: %q", err) msg := fmt.Sprintf("invalid regular expression: %q", err)
p.basicErrorAtToken(msg, p.tokenbuf[k]) p.basicErrorAtToken(msg, p.tokenbuf[k])
} }
r.targets[len(r.targets)-1].rpat = rpat r.targets[len(r.targets)-1].rpat = rpat
} else { } else {
idx := strings.IndexRune(targetstr, '%') idx := strings.IndexRune(targetstr, '%')
if idx >= 0 { if idx >= 0 {
var left, right string var left, right string
if idx > 0 { if idx > 0 {
left = regexp.QuoteMeta(targetstr[:idx]) left = regexp.QuoteMeta(targetstr[:idx])
} }
if idx < len(targetstr) - 1 { if idx < len(targetstr)-1 {
right = regexp.QuoteMeta(targetstr[idx+1:]) right = regexp.QuoteMeta(targetstr[idx+1:])
} }
patstr := fmt.Sprintf("^%s(.*)%s$", left, right) patstr := fmt.Sprintf("^%s(.*)%s$", left, right)
rpat, err := regexp.Compile(patstr) rpat, err := regexp.Compile(patstr)
if err != nil { if err != nil {
msg := fmt.Sprintf("error compiling suffix rule. This is a bug.", err) msg := fmt.Sprintf("error compiling suffix rule. This is a bug.", err)
p.basicErrorAtToken(msg, p.tokenbuf[k]) p.basicErrorAtToken(msg, p.tokenbuf[k])
} }
r.targets[len(r.targets)-1].rpat = rpat r.targets[len(r.targets)-1].rpat = rpat
r.targets[len(r.targets)-1].issuffix = true r.targets[len(r.targets)-1].issuffix = true
r.ismeta = true r.ismeta = true
} }
} }
} }
} }
// prereqs // prereqs
r.prereqs = make([]string, 0) r.prereqs = make([]string, 0)
for k := j + 1; k < len(p.tokenbuf); k++ { for k := j + 1; k < len(p.tokenbuf); k++ {
exparts := expand(p.tokenbuf[k].val, p.rules.vars, true) exparts := expand(p.tokenbuf[k].val, p.rules.vars, true)
r.prereqs = append(r.prereqs, exparts...) r.prereqs = append(r.prereqs, exparts...)
} }
if t.typ == tokenRecipe { if t.typ == tokenRecipe {

132
recipe.go
View file

@ -1,19 +1,17 @@
// Various function for dealing with recipes. // Various function for dealing with recipes.
package main package main
import ( import (
"bufio"
"fmt"
"io" "io"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"bufio" "strings"
"fmt"
"strings"
) )
// Try to unindent a recipe, so that it begins an column 0. (This is mainly for // Try to unindent a recipe, so that it begins an column 0. (This is mainly for
// recipes in python, or other indentation-significant languages.) // recipes in python, or other indentation-significant languages.)
//func stripIndentation(s string) string { //func stripIndentation(s string) string {
@ -22,78 +20,76 @@ import (
// Indent each line of a recipe. // Indent each line of a recipe.
func printIndented(out io.Writer, s string) { func printIndented(out io.Writer, s string) {
reader := bufio.NewReader(strings.NewReader(s)) reader := bufio.NewReader(strings.NewReader(s))
for { for {
line, err := reader.ReadString('\n') line, err := reader.ReadString('\n')
if len(line) > 0 { if len(line) > 0 {
io.WriteString(out, " ") io.WriteString(out, " ")
io.WriteString(out, line) io.WriteString(out, line)
} }
if (err != nil) { if err != nil {
break break
} }
} }
} }
// Execute a recipe. // Execute a recipe.
func dorecipe(target string, u *node, e *edge) bool { func dorecipe(target string, u *node, e *edge) bool {
vars := make(map[string][]string) vars := make(map[string][]string)
vars["target"] = []string{target} vars["target"] = []string{target}
if e.r.ismeta { if e.r.ismeta {
if e.r.attributes.regex { if e.r.attributes.regex {
for i := range e.matches { for i := range e.matches {
vars[fmt.Sprintf("stem%d", i)] = e.matches[i:i+1] vars[fmt.Sprintf("stem%d", i)] = e.matches[i : i+1]
} }
} else { } else {
vars["stem"] = []string{e.stem} vars["stem"] = []string{e.stem}
} }
} }
// TODO: other variables to set // TODO: other variables to set
// alltargets // alltargets
// newprereq // newprereq
prereqs := make([]string, 0) prereqs := make([]string, 0)
for i := range u.prereqs { for i := range u.prereqs {
if u.prereqs[i].r == e.r && u.prereqs[i].v != nil { if u.prereqs[i].r == e.r && u.prereqs[i].v != nil {
prereqs = append(prereqs, u.prereqs[i].v.name) prereqs = append(prereqs, u.prereqs[i].v.name)
} }
} }
vars["prereqs"] = prereqs vars["prereqs"] = prereqs
input := expandRecipeSigils(e.r.recipe, vars) input := expandRecipeSigils(e.r.recipe, vars)
sh := "sh" sh := "sh"
args := []string{} args := []string{}
if len(e.r.shell) > 0 { if len(e.r.shell) > 0 {
sh = e.r.shell[0] sh = e.r.shell[0]
args = e.r.shell[1:] args = e.r.shell[1:]
} }
if !e.r.attributes.quiet { if !e.r.attributes.quiet {
mkPrintRecipe(input) mkPrintRecipe(input)
} }
if dryrun { if dryrun {
return true return true
} }
_, success := subprocess( _, success := subprocess(
sh, sh,
args, args,
input, input,
true, true,
true, true,
false) false)
// TODO: update the timestamps of each target
// TODO: update the timestamps of each target return success
return success
} }
// A monolithic function for executing subprocesses // A monolithic function for executing subprocesses
func subprocess(program string, func subprocess(program string,
args []string, args []string,
@ -136,15 +132,15 @@ func subprocess(program string,
} else { } else {
err = cmd.Run() err = cmd.Run()
} }
success := true success := true
if err != nil { if err != nil {
exiterr, ok := err.(*exec.ExitError) exiterr, ok := err.(*exec.ExitError)
if ok { if ok {
success = exiterr.ProcessState.Success() success = exiterr.ProcessState.Success()
} else { } else {
log.Fatal(err) log.Fatal(err)
} }
} }
return output, success return output, success

View file

@ -6,9 +6,9 @@ package main
import ( import (
"fmt" "fmt"
"regexp"
"sync"
"unicode/utf8" "unicode/utf8"
"regexp"
"sync"
) )
type attribSet struct { type attribSet struct {
@ -27,30 +27,27 @@ type attribError struct {
found rune found rune
} }
// target and rereq patterns // target and rereq patterns
type pattern struct { type pattern struct {
issuffix bool // is a suffix '%' rule, so we should define $stem. issuffix bool // is a suffix '%' rule, so we should define $stem.
spat string // simple string pattern spat string // simple string pattern
rpat *regexp.Regexp // non-nil if this is a regexp pattern rpat *regexp.Regexp // non-nil if this is a regexp pattern
} }
// Match a pattern, returning an array of submatches, or nil if it doesn'm // Match a pattern, returning an array of submatches, or nil if it doesn'm
// match. // match.
func (p *pattern) match(target string) []string { func (p *pattern) match(target string) []string {
if p.rpat != nil { if p.rpat != nil {
return p.rpat.FindStringSubmatch(target) return p.rpat.FindStringSubmatch(target)
} }
if target == p.spat { if target == p.spat {
return make([]string, 0) return make([]string, 0)
} }
return nil return nil
} }
// A single rule. // A single rule.
type rule struct { type rule struct {
targets []pattern // non-empty array of targets targets []pattern // non-empty array of targets
@ -59,20 +56,18 @@ type rule struct {
shell []string // command used to execute the recipe shell []string // command used to execute the recipe
recipe string // recipe source recipe string // recipe source
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
} }
// A set of rules. // A set of rules.
type ruleSet struct { type ruleSet struct {
vars map[string][]string vars map[string][]string
rules []rule rules []rule
// map a target to an array of indexes into rules // map a target to an array of indexes into rules
targetrules map[string][]int targetrules map[string][]int
} }
// Read attributes for an array of strings, updating the rule. // Read attributes for an array of strings, updating the rule.
func (r *rule) parseAttribs(inputs []string) *attribError { func (r *rule) parseAttribs(inputs []string) *attribError {
for i := 0; i < len(inputs); i++ { for i := 0; i < len(inputs); i++ {
@ -124,17 +119,16 @@ func (r *rule) parseAttribs(inputs []string) *attribError {
// Add a rule to the rule set. // Add a rule to the rule set.
func (rs *ruleSet) add(r rule) { func (rs *ruleSet) add(r rule) {
rs.rules = append(rs.rules, r) rs.rules = append(rs.rules, r)
k := len(rs.rules) - 1 k := len(rs.rules) - 1
for i := range r.targets { for i := range r.targets {
if r.targets[i].rpat == nil { if r.targets[i].rpat == nil {
rs.targetrules[r.targets[i].spat] = rs.targetrules[r.targets[i].spat] =
append(rs.targetrules[r.targets[i].spat], k) append(rs.targetrules[r.targets[i].spat], k)
} }
} }
} }
func isValidVarName(v string) bool { func isValidVarName(v string) bool {
for i := 0; i < len(v); { for i := 0; i < len(v); {
c, w := utf8.DecodeRuneInString(v[i:]) c, w := utf8.DecodeRuneInString(v[i:])
@ -149,16 +143,15 @@ func isValidVarName(v string) bool {
} }
func isdigit(c rune) bool { func isdigit(c rune) bool {
return '0' <= c && c <= '9' return '0' <= c && c <= '9'
} }
func isalpha(c rune) bool { func isalpha(c rune) bool {
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')
} }
func isalnum(c rune) bool { func isalnum(c rune) bool {
return isalpha(c) || isdigit(c) return isalpha(c) || isdigit(c)
} }
type assignmentError struct { type assignmentError struct {
@ -175,14 +168,12 @@ func (rs *ruleSet) executeAssignment(ts []token) *assignmentError {
ts[0]} ts[0]}
} }
// expanded variables // expanded variables
vals := make([]string, 0) vals := make([]string, 0)
for i := 1; i < len(ts); i++ { for i := 1; i < len(ts); i++ {
vals = append(vals, expand(ts[i].val, rs.vars, true)...) vals = append(vals, expand(ts[i].val, rs.vars, true)...)
} }
rs.vars[assignee] = vals rs.vars[assignee] = vals
return nil return nil
} }