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.
package main
import (
"strings"
"unicode/utf8"
"strings"
"unicode/utf8"
)
// Expand a word. This includes substituting variables and handling quotes.
func expand(input string, vars map[string][]string, expandBackticks bool) []string {
parts := make([]string, 0)
expanded := ""
parts := make([]string, 0)
expanded := ""
var i, j int
for i = 0; i < len(input); {
j = i + strings.IndexAny(input[i:], "\"'`$\\")
if j < 0 {
expanded += input[i:]
expanded += input[i:]
break
}
println("-------------------")
println(len(input))
println(i)
println(j)
println("-------------------")
println(len(input))
println(i)
println(j)
expanded += input[i:j]
expanded += input[i:j]
c, w := utf8.DecodeRuneInString(input[j:])
i = j + w
@ -36,41 +34,41 @@ func expand(input string, vars map[string][]string, expandBackticks bool) []stri
switch c {
case '\\':
out, off = expandEscape(input[i:])
expanded += out
expanded += out
case '"':
out, off = expandDoubleQuoted(input[i:], vars, expandBackticks)
expanded += out
expanded += out
case '\'':
out, off = expandSingleQuoted(input[i:])
expanded += out
expanded += out
case '`':
if expandBackticks {
out, off = expandBackQuoted(input[i:], vars)
} else {
out = input
off = len(input)
}
expanded += out
if expandBackticks {
out, off = expandBackQuoted(input[i:], vars)
} else {
out = input
off = len(input)
}
expanded += out
case '$':
var outparts []string
outparts, off = expandSigil(input[i:], vars)
if len(outparts) > 0 {
outparts[0] = expanded + outparts[0]
expanded = outparts[len(outparts)-1]
parts = append(parts, outparts[:len(outparts)-1]...)
}
var outparts []string
outparts, off = expandSigil(input[i:], vars)
if len(outparts) > 0 {
outparts[0] = expanded + outparts[0]
expanded = outparts[len(outparts)-1]
parts = append(parts, outparts[:len(outparts)-1]...)
}
}
i += off
}
if len(expanded) > 0 {
parts = append(parts, expanded)
}
if len(expanded) > 0 {
parts = append(parts, expanded)
}
return parts
}
@ -126,188 +124,177 @@ func expandSingleQuoted(input string) (string, int) {
// Expand something starting with at '$'.
func expandSigil(input string, vars map[string][]string) ([]string, int) {
c, w := utf8.DecodeRuneInString(input)
var offset int
var varname string
if c == '{' {
j := strings.IndexRune(input[w:], '}')
if j < 0 {
return []string{"$" + input}, len(input)
}
c, w := utf8.DecodeRuneInString(input)
var offset int
var varname string
if c == '{' {
j := strings.IndexRune(input[w:], '}')
if j < 0 {
return []string{"$" + input}, len(input)
}
varname = input[w:j]
offset = j + 1
} else {
// try to match a variable name
i := 0
j := i
for j < len(input) {
c, w = utf8.DecodeRuneInString(input[j:])
if !(isalpha(c) || c == '_' || (j > i && isdigit(c))) {
break
}
j += w
}
varname = input[w:j]
offset = j + 1
} else {
// try to match a variable name
i := 0
j := i
for j < len(input) {
c, w = utf8.DecodeRuneInString(input[j:])
if !(isalpha(c) || c == '_' || (j > i && isdigit(c))) {
break
}
j += w
}
if j > i {
varname = input[i:j]
offset = j
} else {
return []string{"$" + input}, len(input)
}
}
if j > i {
varname = input[i:j]
offset = j
} else {
return []string{"$" + input}, len(input)
}
}
if isValidVarName(varname) {
varvals, ok := vars[varname]
if ok {
return varvals, offset
}
}
if isValidVarName(varname) {
varvals, ok := vars[varname]
if ok {
return varvals, offset
}
}
return []string{"$" + input}, len(input)
return []string{"$" + input}, len(input)
}
// Find and expand all sigils.
func expandSigils(input string, vars map[string][]string) []string {
parts := make([]string, 0)
expanded := ""
for i := 0; i < len(input); {
j := strings.IndexRune(input[i:], '$')
if j < 0 {
expanded += input[i:]
break
}
parts := make([]string, 0)
expanded := ""
for i := 0; i < len(input); {
j := strings.IndexRune(input[i:], '$')
if j < 0 {
expanded += input[i:]
break
}
ex, k := expandSigil(input[j+1:], vars)
if len(ex) > 0 {
ex[0] = expanded + ex[0]
expanded = ex[len(ex)-1]
parts = append(parts, ex[:len(ex)-1]...)
}
i = k
}
ex, k := expandSigil(input[j+1:], vars)
if len(ex) > 0 {
ex[0] = expanded + ex[0]
expanded = ex[len(ex)-1]
parts = append(parts, ex[:len(ex)-1]...)
}
i = k
}
if len(expanded) > 0 {
parts = append(parts, expanded)
}
if len(expanded) > 0 {
parts = append(parts, expanded)
}
return parts
return parts
}
// Find and expand all sigils in a recipe, producing a flat string.
func expandRecipeSigils(input string, vars map[string][]string) string {
expanded := ""
for i := 0; i < len(input); {
j := strings.IndexAny(input[i:], "$\\")
if j < 0 {
expanded += input[i:]
break
}
expanded := ""
for i := 0; i < len(input); {
j := strings.IndexAny(input[i:], "$\\")
if j < 0 {
expanded += input[i:]
break
}
expanded += input[i:j]
i = j
c, w := utf8.DecodeRuneInString(input[i:])
if c == '$' {
i += w
ex, k := expandSigil(input[i:], vars)
expanded += strings.Join(ex, " ")
i += k
} else if c == '\\' {
i += w
c, w := utf8.DecodeRuneInString(input[i:])
if c == '$' {
expanded += "$"
} else {
expanded += "\\" + string(c)
}
i += w
}
}
expanded += input[i:j]
i = j
c, w := utf8.DecodeRuneInString(input[i:])
if c == '$' {
i += w
ex, k := expandSigil(input[i:], vars)
expanded += strings.Join(ex, " ")
i += k
} else if c == '\\' {
i += w
c, w := utf8.DecodeRuneInString(input[i:])
if c == '$' {
expanded += "$"
} else {
expanded += "\\" + string(c)
}
i += w
}
}
return expanded
return expanded
}
// Expand all unescaped '%' characters.
func expandSuffixes(input string, stem string) string {
expanded := make([]byte, 0)
for i := 0; i < len(input); {
j := strings.IndexAny(input[i:], "\\%")
if j < 0 {
expanded = append(expanded, input[i:]...)
break
}
expanded := make([]byte, 0)
for i := 0; i < len(input); {
j := strings.IndexAny(input[i:], "\\%")
if j < 0 {
expanded = append(expanded, input[i:]...)
break
}
c, w := utf8.DecodeRuneInString(input[j:])
if c == '%' {
expanded = append(expanded, stem...)
i += w
} else {
j += w
c, w := utf8.DecodeRuneInString(input[j:])
if c == '%' {
expanded = append(expanded, '%')
i = j + w
}
}
}
c, w := utf8.DecodeRuneInString(input[j:])
if c == '%' {
expanded = append(expanded, stem...)
i += w
} else {
j += w
c, w := utf8.DecodeRuneInString(input[j:])
if c == '%' {
expanded = append(expanded, '%')
i = j + w
}
}
}
return string(expanded)
return string(expanded)
}
// TODO: expand RegexpRefs
// Expand a backtick quoted string, by executing the contents.
func expandBackQuoted(input string, vars map[string][]string) (string, int) {
// TODO: expand sigils?
// TODO: expand sigils?
j := strings.Index(input, "`")
if j < 0 {
return input, len(input)
}
// TODO: handle errors
// TODO: handle errors
output, _ := subprocess("sh", nil, input[:j], false, false, true)
return output, (j + 1)
}
// Split a string on whitespace taking into account escaping and quoting.
//func splitQuoted(input string) []string {
//parts := make([]string, 0)
//var i, j int
//i = 0
//for {
//// skip all unescaped whitespace
//for i < len(input) {
//c, w := utf8.DecodeRuneInString(input[i:])
//if strings.IndexRune(" \t", c) < 0 {
//break
//}
//i += w
//}
//if i >= len(input) {
//break
//}
//// Ugh. Will this take into account quoting in variables?
//switch c {
//case '"':
//case '\'':
//default:
//}
//}
//return parts
//parts := make([]string, 0)
//var i, j int
//i = 0
//for {
//// skip all unescaped whitespace
//for i < len(input) {
//c, w := utf8.DecodeRuneInString(input[i:])
//if strings.IndexRune(" \t", c) < 0 {
//break
//}
//i += w
//}
//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
import (
"fmt"
"io"
"os"
"sync"
"time"
"fmt"
"io"
"os"
"sync"
"time"
)
// A dependency graph
type graph struct {
root *node // the intial target's node
nodes map[string]*node // map targets to their nodes
root *node // the intial target's node
nodes map[string]*node // map targets to their nodes
}
// An edge in the graph.
type edge struct {
v *node // node this edge directs to
stem string // stem matched for meta-rule applications
matches []string // regular expression matches
r *rule
v *node // node this edge directs to
stem string // stem matched for meta-rule applications
matches []string // regular expression matches
r *rule
}
// Current status of a node in the build.
type nodeStatus int
const (
nodeStatusReady nodeStatus = iota
nodeStatusStarted
nodeStatusDone
nodeStatusFailed
nodeStatusReady nodeStatus = iota
nodeStatusStarted
nodeStatusDone
nodeStatusFailed
)
// A node in the dependency graph
type node struct {
r *rule // rule to be applied
name string // target name
prog string // custom program to compare times
t time.Time // file modification time
exists bool // does a non-virtual target exist
prereqs []*edge // prerequisite rules
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
r *rule // rule to be applied
name string // target name
prog string // custom program to compare times
t time.Time // file modification time
exists bool // does a non-virtual target exist
prereqs []*edge // prerequisite rules
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
}
// Create a new node
func (g *graph) newnode(name string) *node {
u := &node{name: name}
info, err := os.Stat(name)
if err == nil {
u.t = info.ModTime()
u.exists = true
} else {
_, ok := err.(*os.PathError)
if ok {
u.exists = false
} else {
mkError(err.Error())
}
}
g.nodes[name] = u
return u
u := &node{name: name}
info, err := os.Stat(name)
if err == nil {
u.t = info.ModTime()
u.exists = true
} else {
_, ok := err.(*os.PathError)
if ok {
u.exists = false
} else {
mkError(err.Error())
}
}
g.nodes[name] = u
return u
}
// Print a graph in graphviz format.
func (g *graph) visualize(w io.Writer) {
fmt.Fprintln(w, "digraph mk {")
for t, u := range g.nodes {
for i := range u.prereqs {
if u.prereqs[i].v != nil {
fmt.Fprintf(w, " \"%s\" -> \"%s\";\n", t, u.prereqs[i].v.name)
}
}
}
fmt.Fprintln(w, "}")
fmt.Fprintln(w, "digraph mk {")
for t, u := range g.nodes {
for i := range u.prereqs {
if u.prereqs[i].v != nil {
fmt.Fprintf(w, " \"%s\" -> \"%s\";\n", t, u.prereqs[i].v.name)
}
}
}
fmt.Fprintln(w, "}")
}
// Create a new arc.
func (u *node) newedge(v *node, r *rule) *edge {
e := &edge{v: v, r: r}
u.prereqs = append(u.prereqs, e)
return e
e := &edge{v: v, r: r}
u.prereqs = append(u.prereqs, e)
return e
}
// Create a dependency graph for the given target.
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.
rulecnt := make([]int, len(rs.rules))
g.root = applyrules(rs, g, target, rulecnt)
// 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)
return g
return g
}
// Recursively match the given target to a rule in the rule set to construct the
// full graph.
func applyrules(rs *ruleSet, g *graph, target string, rulecnt []int) *node {
u, ok := g.nodes[target]
if ok {
return u
}
u = g.newnode(target)
u, ok := g.nodes[target]
if ok {
return u
}
u = g.newnode(target)
// does the target match a concrete rule?
// does the target match a concrete rule?
ks, ok := rs.targetrules[target]
if ok {
for ki := range ks {
k := ks[ki]
if rulecnt[k] > max_rule_cnt {
continue
}
ks, ok := rs.targetrules[target]
if ok {
for ki := range ks {
k := ks[ki]
if rulecnt[k] > max_rule_cnt {
continue
}
r := &rs.rules[k]
r := &rs.rules[k]
// skip meta-rules
if r.ismeta {
continue
}
// skip meta-rules
if r.ismeta {
continue
}
// skip rules that have no effect
if r.recipe == "" && len(r.prereqs) == 0 {
continue
}
// skip rules that have no effect
if r.recipe == "" && len(r.prereqs) == 0 {
continue
}
rulecnt[k] += 1
if len(r.prereqs) == 0 {
u.newedge(nil, r)
} else {
for i := range r.prereqs {
u.newedge(applyrules(rs, g, r.prereqs[i], rulecnt), r)
}
}
rulecnt[k] -= 1
}
}
rulecnt[k] += 1
if len(r.prereqs) == 0 {
u.newedge(nil, r)
} else {
for i := range r.prereqs {
u.newedge(applyrules(rs, g, r.prereqs[i], rulecnt), r)
}
}
rulecnt[k] -= 1
}
}
// find applicable metarules
for k := range rs.rules {
if rulecnt[k] > max_rule_cnt {
continue
}
// find applicable metarules
for k := range rs.rules {
if rulecnt[k] > max_rule_cnt {
continue
}
r := &rs.rules[k]
r := &rs.rules[k]
if !r.ismeta {
continue
}
if !r.ismeta {
continue
}
// skip rules that have no effect
if r.recipe == "" && len(r.prereqs) == 0 {
continue
}
// skip rules that have no effect
if r.recipe == "" && len(r.prereqs) == 0 {
continue
}
for j := range r.targets {
mat := r.targets[j].match(target)
if mat == nil {
continue
}
for j := range r.targets {
mat := r.targets[j].match(target)
if mat == nil {
continue
}
var stem string
var matches []string
var stem string
var matches []string
if r.attributes.regex {
matches = mat
} else {
stem = mat[1]
}
if r.attributes.regex {
matches = mat
} else {
stem = mat[1]
}
rulecnt[k] += 1
if len(r.prereqs) == 0 {
e := u.newedge(nil, r)
e.stem = stem
e.matches = matches
} else {
for i := range r.prereqs {
var prereq string
if r.attributes.regex {
// TODO: write substituteRegexpRefs and use that here
prereq = r.prereqs[i]
} else {
prereq = expandSuffixes(r.prereqs[i], stem)
}
rulecnt[k] += 1
if len(r.prereqs) == 0 {
e := u.newedge(nil, r)
e.stem = stem
e.matches = matches
} else {
for i := range r.prereqs {
var prereq string
if r.attributes.regex {
// TODO: write substituteRegexpRefs and use that here
prereq = r.prereqs[i]
} else {
prereq = expandSuffixes(r.prereqs[i], stem)
}
e := u.newedge(applyrules(rs, g, prereq, rulecnt), r)
e.stem = stem
e.matches = matches
}
}
rulecnt[k] -= 1
}
}
e := u.newedge(applyrules(rs, g, prereq, rulecnt), r)
e.stem = stem
e.matches = matches
}
}
rulecnt[k] -= 1
}
}
return u
return u
}

309
mk.go
View file

@ -1,13 +1,12 @@
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"flag"
"fmt"
"io/ioutil"
"os"
)
// True if messages should be printed without fancy colors.
var nocolor bool = false
@ -19,20 +18,19 @@ const max_rule_cnt = 3
// 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"
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"
)
func mk(rs *ruleSet, target string, dryrun bool) {
g := buildgraph(rs, target)
//g.visualize(os.Stdout)
mkNode(g, g.root)
g := buildgraph(rs, target)
//g.visualize(os.Stdout)
mkNode(g, g.root)
}
// Build a target in the graph.
@ -45,174 +43,173 @@ func mk(rs *ruleSet, target string, dryrun bool) {
// execute our rule.
//
func mkNode(g *graph, u *node) {
// try to claim on this node
u.mutex.Lock()
if u.status != nodeStatusReady {
u.mutex.Unlock()
return
} else {
u.status = nodeStatusStarted
}
u.mutex.Unlock()
// try to claim on this node
u.mutex.Lock()
if u.status != nodeStatusReady {
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
u.mutex.Unlock()
for i := range u.listeners {
u.listeners[i] <- u.status
}
}()
// when finished, notify the listeners
finalstatus := nodeStatusDone
defer func() {
u.mutex.Lock()
u.status = finalstatus
u.mutex.Unlock()
for i := range u.listeners {
u.listeners[i] <- u.status
}
}()
// there's no fucking rules, dude
if len(u.prereqs) == 0 {
if !u.r.attributes.virtual && !u.exists {
wd, _ := os.Getwd()
mkError(fmt.Sprintf("don't know how to make %s in %s", u.name, wd))
}
return
}
// there's no fucking rules, dude
if len(u.prereqs) == 0 {
if !u.r.attributes.virtual && !u.exists {
wd, _ := os.Getwd()
mkError(fmt.Sprintf("don't know how to make %s in %s", u.name, wd))
}
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)
}
}
// 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))
}
// 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))
}
prereqstat := make(chan nodeStatus)
pending := 0
prereqstat := make(chan nodeStatus)
pending := 0
// build prereqs that need building
e.r.mutex.Lock()
for i := range prereqs {
prereqs[i].mutex.Lock()
// needs to be built?
if !prereqs[i].exists || e.r.attributes.virtual || (u.exists && u.t.Before(prereqs[i].t)) {
switch prereqs[i].status {
case nodeStatusReady:
go mkNode(g, prereqs[i])
fallthrough
case nodeStatusStarted:
prereqs[i].listeners = append(prereqs[i].listeners, prereqstat)
pending++
}
}
prereqs[i].mutex.Unlock()
}
e.r.mutex.Unlock()
// build prereqs that need building
e.r.mutex.Lock()
for i := range prereqs {
prereqs[i].mutex.Lock()
// needs to be built?
if !prereqs[i].exists || e.r.attributes.virtual || (u.exists && u.t.Before(prereqs[i].t)) {
switch prereqs[i].status {
case nodeStatusReady:
go mkNode(g, prereqs[i])
fallthrough
case nodeStatusStarted:
prereqs[i].listeners = append(prereqs[i].listeners, prereqstat)
pending++
}
}
prereqs[i].mutex.Unlock()
}
e.r.mutex.Unlock()
// wait until all the prereqs are built
//fmt.Printf("%s: %d\n", u.name, pending)
for pending > 0 {
//for i := range prereqs {
//fmt.Println(prereqs[i].name)
//}
// wait until all the prereqs are built
//fmt.Printf("%s: %d\n", u.name, pending)
for pending > 0 {
//for i := range prereqs {
//fmt.Println(prereqs[i].name)
//}
s := <-prereqstat
pending--
if s == nodeStatusFailed {
finalstatus = nodeStatusFailed
}
}
s := <-prereqstat
pending--
if s == nodeStatusFailed {
finalstatus = nodeStatusFailed
}
}
// execute the recipe, unless the prereqs failed
if finalstatus != nodeStatusFailed {
mkPrintMessage("mking " + u.name)
if !dorecipe(u.name, u, e) {
finalstatus = nodeStatusFailed
}
}
// execute the recipe, unless the prereqs failed
if finalstatus != nodeStatusFailed {
mkPrintMessage("mking " + u.name)
if !dorecipe(u.name, u, e) {
finalstatus = nodeStatusFailed
}
}
mkPrintSuccess("finished mking " + u.name)
mkPrintSuccess("finished mking " + u.name)
}
func mkError(msg string) {
if !nocolor {
os.Stderr.WriteString(colorRed)
}
fmt.Fprintf(os.Stderr, "mk: %s\n", msg)
if !nocolor {
os.Stderr.WriteString(colorDefault)
}
os.Exit(1)
if !nocolor {
os.Stderr.WriteString(colorRed)
}
fmt.Fprintf(os.Stderr, "mk: %s\n", msg)
if !nocolor {
os.Stderr.WriteString(colorDefault)
}
os.Exit(1)
}
func mkPrintSuccess(msg string) {
if nocolor {
fmt.Println(msg)
} else {
fmt.Printf("%s%s%s\n", colorGreen, msg, colorDefault)
}
if nocolor {
fmt.Println(msg)
} else {
fmt.Printf("%s%s%s\n", colorGreen, msg, colorDefault)
}
}
func mkPrintMessage(msg string) {
if nocolor {
fmt.Println(msg)
} else {
fmt.Printf("%s%s%s\n", colorBlue, msg, colorDefault)
}
if nocolor {
fmt.Println(msg)
} else {
fmt.Printf("%s%s%s\n", colorBlue, msg, colorDefault)
}
}
func mkPrintRecipe(msg string) {
if !nocolor {
os.Stdout.WriteString(colorYellow)
}
printIndented(os.Stdout, msg)
if !nocolor {
os.Stdout.WriteString(colorDefault)
}
if !nocolor {
os.Stdout.WriteString(colorYellow)
}
printIndented(os.Stdout, msg)
if !nocolor {
os.Stdout.WriteString(colorDefault)
}
}
func main() {
var mkfilepath string
var dryrun bool
flag.StringVar(&mkfilepath, "f", "mkfile", "use the given file as mkfile")
flag.BoolVar(&dryrun, "n", false, "print commands without actually executing")
flag.Parse()
var mkfilepath string
var dryrun bool
flag.StringVar(&mkfilepath, "f", "mkfile", "use the given file as mkfile")
flag.BoolVar(&dryrun, "n", false, "print commands without actually executing")
flag.Parse()
mkfile, err := os.Open(mkfilepath)
if err != nil {
mkError("no mkfile found")
}
input, _ := ioutil.ReadAll(mkfile)
mkfile.Close()
mkfile, err := os.Open(mkfilepath)
if err != nil {
mkError("no mkfile found")
}
input, _ := ioutil.ReadAll(mkfile)
mkfile.Close()
rs := parse(string(input), mkfilepath)
targets := flag.Args()
rs := parse(string(input), mkfilepath)
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
}
}
// 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 {
fmt.Println("mk: nothing to mk")
return
}
if len(targets) == 0 {
fmt.Println("mk: nothing to mk")
return
}
// 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)
}
// 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)
}
}

View file

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

132
recipe.go
View file

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

View file

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