diff options
author | Fumitoshi Ukai <fumitoshi.ukai@gmail.com> | 2015-06-26 21:32:50 +0900 |
---|---|---|
committer | Fumitoshi Ukai <fumitoshi.ukai@gmail.com> | 2015-06-26 23:08:01 +0900 |
commit | 65c7233c02ba0f62c38ef96e4bb59dcb273542e9 (patch) | |
tree | 9b02ced4ebe2cff26d772431cc81b4ecfc2425ff | |
parent | 480bca50f6f74c0021910540a6e0ea8761eb5f61 (diff) | |
download | kati-65c7233c02ba0f62c38ef96e4bb59dcb273542e9.tar.gz |
fix panic based error reporting
-rw-r--r-- | ast.go | 68 | ||||
-rw-r--r-- | bootstrap.go | 10 | ||||
-rw-r--r-- | cmd/kati/main.go | 44 | ||||
-rw-r--r-- | dep.go | 56 | ||||
-rw-r--r-- | depgraph.go | 51 | ||||
-rw-r--r-- | doc.go | 2 | ||||
-rw-r--r-- | eval.go | 254 | ||||
-rw-r--r-- | exec.go | 106 | ||||
-rw-r--r-- | expr.go | 136 | ||||
-rw-r--r-- | flags.go | 1 | ||||
-rw-r--r-- | func.go | 564 | ||||
-rw-r--r-- | log.go | 29 | ||||
-rw-r--r-- | ninja.go | 93 | ||||
-rw-r--r-- | para.go | 145 | ||||
-rw-r--r-- | para_test.go | 12 | ||||
-rw-r--r-- | parser.go | 222 | ||||
-rw-r--r-- | pathutil.go | 31 | ||||
-rw-r--r-- | query.go | 1 | ||||
-rw-r--r-- | rule_parser.go | 27 | ||||
-rw-r--r-- | serialize.go | 279 | ||||
-rw-r--r-- | shellutil.go | 71 | ||||
-rw-r--r-- | stats.go | 3 | ||||
-rw-r--r-- | testcase/gen_testcase_parse_benchmark.go | 5 | ||||
-rw-r--r-- | var.go | 140 | ||||
-rw-r--r-- | worker.go | 103 |
25 files changed, 1589 insertions, 864 deletions
@@ -16,33 +16,27 @@ package kati import ( "bytes" - "fmt" "strings" ) type ast interface { - eval(*Evaluator) + eval(*Evaluator) error show() } -type astBase struct { - filename string - lineno int -} - type assignAST struct { - astBase + srcpos lhs Value rhs Value op string opt string // "override", "export" } -func (ast *assignAST) eval(ev *Evaluator) { - ev.evalAssign(ast) +func (ast *assignAST) eval(ev *Evaluator) error { + return ev.evalAssign(ast) } -func (ast *assignAST) evalRHS(ev *Evaluator, lhs string) Var { +func (ast *assignAST) evalRHS(ev *Evaluator, lhs string) (Var, error) { origin := "file" if ast.filename == bootstrapMakefileName { origin = "default" @@ -55,31 +49,33 @@ func (ast *assignAST) evalRHS(ev *Evaluator, lhs string) Var { case ":=": switch v := ast.rhs.(type) { case literal: - return &simpleVar{value: v.String(), origin: origin} + return &simpleVar{value: v.String(), origin: origin}, nil case tmpval: - return &simpleVar{value: v.String(), origin: origin} + return &simpleVar{value: v.String(), origin: origin}, nil default: var buf bytes.Buffer - v.Eval(&buf, ev) - return &simpleVar{value: buf.String(), origin: origin} + err := v.Eval(&buf, ev) + if err != nil { + return nil, err + } + return &simpleVar{value: buf.String(), origin: origin}, nil } case "=": - return &recursiveVar{expr: ast.rhs, origin: origin} + return &recursiveVar{expr: ast.rhs, origin: origin}, nil case "+=": prev := ev.lookupVarInCurrentScope(lhs) if !prev.IsDefined() { - return &recursiveVar{expr: ast.rhs, origin: origin} + return &recursiveVar{expr: ast.rhs, origin: origin}, nil } return prev.AppendVar(ev, ast.rhs) case "?=": prev := ev.lookupVarInCurrentScope(lhs) if prev.IsDefined() { - return prev + return prev, nil } - return &recursiveVar{expr: ast.rhs, origin: origin} - default: - panic(fmt.Sprintf("unknown assign op: %q", ast.op)) + return &recursiveVar{expr: ast.rhs, origin: origin}, nil } + return nil, ast.errorf("unknown assign op: %q", ast.op) } func (ast *assignAST) show() { @@ -90,14 +86,14 @@ func (ast *assignAST) show() { // Note we cannot be sure what this is, until all variables in |expr| // are expanded. type maybeRuleAST struct { - astBase + srcpos expr Value term byte // Either ':', '=', or 0 afterTerm []byte } -func (ast *maybeRuleAST) eval(ev *Evaluator) { - ev.evalMaybeRule(ast) +func (ast *maybeRuleAST) eval(ev *Evaluator) error { + return ev.evalMaybeRule(ast) } func (ast *maybeRuleAST) show() { @@ -105,12 +101,12 @@ func (ast *maybeRuleAST) show() { } type commandAST struct { - astBase + srcpos cmd string } -func (ast *commandAST) eval(ev *Evaluator) { - ev.evalCommand(ast) +func (ast *commandAST) eval(ev *Evaluator) error { + return ev.evalCommand(ast) } func (ast *commandAST) show() { @@ -118,13 +114,13 @@ func (ast *commandAST) show() { } type includeAST struct { - astBase + srcpos expr string op string } -func (ast *includeAST) eval(ev *Evaluator) { - ev.evalInclude(ast) +func (ast *includeAST) eval(ev *Evaluator) error { + return ev.evalInclude(ast) } func (ast *includeAST) show() { @@ -132,7 +128,7 @@ func (ast *includeAST) show() { } type ifAST struct { - astBase + srcpos op string lhs Value rhs Value // Empty if |op| is ifdef or ifndef. @@ -140,8 +136,8 @@ type ifAST struct { falseStmts []ast } -func (ast *ifAST) eval(ev *Evaluator) { - ev.evalIf(ast) +func (ast *ifAST) eval(ev *Evaluator) error { + return ev.evalIf(ast) } func (ast *ifAST) show() { @@ -150,13 +146,13 @@ func (ast *ifAST) show() { } type exportAST struct { - astBase + srcpos expr []byte export bool } -func (ast *exportAST) eval(ev *Evaluator) { - ev.evalExport(ast) +func (ast *exportAST) eval(ev *Evaluator) error { + return ev.evalExport(ast) } func (ast *exportAST) show() { diff --git a/bootstrap.go b/bootstrap.go index 30ca77d..91cac40 100644 --- a/bootstrap.go +++ b/bootstrap.go @@ -22,7 +22,7 @@ import ( const bootstrapMakefileName = "*bootstrap*" -func bootstrapMakefile(targets []string) makefile { +func bootstrapMakefile(targets []string) (makefile, error) { bootstrap := ` CC:=cc CXX:=g++ @@ -45,12 +45,8 @@ SHELL:=/bin/sh bootstrap += fmt.Sprintf("MAKECMDGOALS:=%s\n", strings.Join(targets, " ")) cwd, err := filepath.Abs(".") if err != nil { - panic(err) + return makefile{}, err } bootstrap += fmt.Sprintf("CURDIR:=%s\n", cwd) - mk, err := parseMakefileString(bootstrap, bootstrapMakefileName, 0) - if err != nil { - panic(err) - } - return mk + return parseMakefileString(bootstrap, srcpos{bootstrapMakefileName, 0}) } diff --git a/cmd/kati/main.go b/cmd/kati/main.go index f45dd28..1732e18 100644 --- a/cmd/kati/main.go +++ b/cmd/kati/main.go @@ -162,28 +162,33 @@ func save(g *kati.DepGraph, targets []string) error { func main() { runtime.GOMAXPROCS(runtime.NumCPU()) flag.Parse() + err := katiMain() + if err != nil { + fmt.Println(err) + // http://www.gnu.org/software/make/manual/html_node/Running.html + os.Exit(2) + } +} + +func katiMain() error { if cpuprofile != "" { f, err := os.Create(cpuprofile) if err != nil { - panic(err) + return err } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() - kati.AtError(pprof.StopCPUProfile) } if heapprofile != "" { defer writeHeapProfile() - kati.AtError(writeHeapProfile) } defer kati.DumpStats() - kati.AtError(kati.DumpStats) if memstats != "" { ms := memStatsDumper{ Template: template.Must(template.New("memstats").Parse(memstats)), } ms.dump() defer ms.dump() - kati.AtError(ms.dump) } if traceEventFile != "" { f, err := os.Create(traceEventFile) @@ -192,7 +197,6 @@ func main() { } kati.TraceEventStart(f) defer kati.TraceEventStop() - kati.AtError(kati.TraceEventStop) } if shellDate != "" { @@ -225,35 +229,41 @@ func main() { g, err := load(req) if err != nil { - panic(err) + return err } nodes := g.Nodes() vars := g.Vars() err = save(g, req.Targets) if err != nil { - panic(err) + return err } if generateNinja { - kati.GenerateNinja(g, gomaDir) - return + err = kati.GenerateNinja(g, gomaDir) + if err != nil { + return err + } } if syntaxCheckOnlyFlag { - return + return nil } if queryFlag != "" { kati.Query(os.Stdout, queryFlag, g) - return + return nil } // TODO: Handle target specific variables. ev := kati.NewEvaluator(vars) for name, export := range g.Exports() { if export { - os.Setenv(name, ev.EvaluateVar(name)) + v, err := ev.EvaluateVar(name) + if err != nil { + return err + } + os.Setenv(name, v) } else { os.Unsetenv(name) } @@ -265,9 +275,13 @@ func main() { if usePara { execOpt.ParaPath = findPara() } - ex := kati.NewExecutor(vars, execOpt) + ex, err := kati.NewExecutor(vars, execOpt) + if err != nil { + return err + } err = ex.Exec(nodes) if err != nil { - panic(err) + return err } + return nil } @@ -21,6 +21,7 @@ import ( "strings" ) +// DepNode represents a makefile rule for an output. type DepNode struct { Output string Cmds []string @@ -145,7 +146,8 @@ func (db *depBuilder) canPickImplicitRule(r *rule, output string) bool { func (db *depBuilder) mergeImplicitRuleVars(outputs []string, vars Vars) Vars { if len(outputs) != 1 { - panic(fmt.Sprintf("Implicit rule should have only one output but %q", outputs)) + // TODO(ukai): should return error? + panic(fmt.Sprintf("FIXME: Implicit rule should have only one output but %q", outputs)) } logf("merge? %q", db.ruleVars) logf("merge? %q", outputs[0]) @@ -211,7 +213,8 @@ func (db *depBuilder) pickRule(output string) (*rule, Vars, bool) { } for _, irule := range rules { if len(irule.inputs) != 1 { - panic(fmt.Sprintf("unexpected number of input for a suffix rule (%d)", len(irule.inputs))) + // TODO(ukai): should return error? + panic(fmt.Sprintf("FIXME: unexpected number of input for a suffix rule (%d)", len(irule.inputs))) } if !db.exists(replaceSuffix(output, irule.inputs[0])) { continue @@ -272,7 +275,11 @@ func (db *depBuilder) buildPlan(output string, neededBy string, tsvs Vars) (*Dep if !present || oldVar.String() == "" { db.vars[name] = tsv } else { - v = oldVar.AppendVar(NewEvaluator(db.vars), tsv) + var err error + v, err = oldVar.AppendVar(NewEvaluator(db.vars), tsv) + if err != nil { + return nil, err + } db.vars[name] = v } tsvs[name] = v @@ -296,7 +303,7 @@ func (db *depBuilder) buildPlan(output string, neededBy string, tsvs Vars) (*Dep for _, input := range rule.inputs { if len(rule.outputPatterns) > 0 { if len(rule.outputPatterns) > 1 { - panic("TODO: multiple output pattern is not supported yet") + panic(fmt.Sprintf("FIXME: multiple output pattern is not supported yet")) } input = intern(rule.outputPatterns[0].subst(input, output)) } else if rule.isSuffixRule { @@ -337,6 +344,7 @@ func (db *depBuilder) buildPlan(output string, neededBy string, tsvs Vars) (*Dep n.ActualInputs = actualInputs n.TargetSpecificVars = make(Vars) for k, v := range tsvs { + logf("output=%s tsv %s=%s", output, k, v) n.TargetSpecificVars[k] = v } n.Filename = rule.filename @@ -373,13 +381,13 @@ func (db *depBuilder) populateSuffixRule(r *rule, output string) bool { return true } -func mergeRules(oldRule, r *rule, output string, isSuffixRule bool) *rule { +func mergeRules(oldRule, r *rule, output string, isSuffixRule bool) (*rule, error) { if oldRule.isDoubleColon != r.isDoubleColon { - errorExit(r.filename, r.lineno, "*** target file %q has both : and :: entries.", output) + return nil, r.errorf("*** target file %q has both : and :: entries.", output) } if len(oldRule.cmds) > 0 && len(r.cmds) > 0 && !isSuffixRule && !r.isDoubleColon { - warn(r.filename, r.cmdLineno, "overriding commands for target %q", output) - warn(oldRule.filename, oldRule.cmdLineno, "ignoring old commands for target %q", output) + warn(r.cmdpos(), "overriding commands for target %q", output) + warn(oldRule.cmdpos(), "ignoring old commands for target %q", output) } mr := &rule{} @@ -400,13 +408,13 @@ func mergeRules(oldRule, r *rule, output string, isSuffixRule bool) *rule { mr.orderOnlyInputs = append(oldRule.orderOnlyInputs, mr.orderOnlyInputs...) } mr.outputPatterns = append(mr.outputPatterns, oldRule.outputPatterns...) - return mr + return mr, nil } -func (db *depBuilder) populateExplicitRule(r *rule) { +func (db *depBuilder) populateExplicitRule(r *rule) error { // It seems rules with no outputs are siliently ignored. if len(r.outputs) == 0 { - return + return nil } for _, output := range r.outputs { output = trimLeadingCurdir(output) @@ -414,7 +422,10 @@ func (db *depBuilder) populateExplicitRule(r *rule) { isSuffixRule := db.populateSuffixRule(r, output) if oldRule, present := db.rules[output]; present { - mr := mergeRules(oldRule, r, output, isSuffixRule) + mr, err := mergeRules(oldRule, r, output, isSuffixRule) + if err != nil { + return err + } db.rules[output] = mr } else { db.rules[output] = r @@ -423,6 +434,7 @@ func (db *depBuilder) populateExplicitRule(r *rule) { } } } + return nil } func (db *depBuilder) populateImplicitRule(r *rule) { @@ -440,7 +452,7 @@ func (db *depBuilder) populateImplicitRule(r *rule) { } } -func (db *depBuilder) populateRules(er *evalResult) { +func (db *depBuilder) populateRules(er *evalResult) error { for _, r := range er.rules { for i, input := range r.inputs { r.inputs[i] = trimLeadingCurdir(input) @@ -448,8 +460,10 @@ func (db *depBuilder) populateRules(er *evalResult) { for i, orderOnlyInput := range r.orderOnlyInputs { r.orderOnlyInputs[i] = trimLeadingCurdir(orderOnlyInput) } - db.populateExplicitRule(r) - + err := db.populateExplicitRule(r) + if err != nil { + return err + } if len(r.outputs) == 0 { db.populateImplicitRule(r) } @@ -463,6 +477,7 @@ func (db *depBuilder) populateRules(er *evalResult) { sort.Stable(byPrefix(db.iprefixRules)) sort.Stable(bySuffix(db.isuffixRules)) + return nil } func reverseImplicitRules(rules []*rule) { @@ -500,7 +515,7 @@ func (db *depBuilder) reportStats() { } } -func newDepBuilder(er *evalResult, vars Vars) *depBuilder { +func newDepBuilder(er *evalResult, vars Vars) (*depBuilder, error) { db := &depBuilder{ rules: make(map[string]*rule), ruleVars: er.ruleVars, @@ -510,20 +525,23 @@ func newDepBuilder(er *evalResult, vars Vars) *depBuilder { phony: make(map[string]bool), } - db.populateRules(er) + err := db.populateRules(er) + if err != nil { + return nil, err + } rule, present := db.rules[".PHONY"] if present { for _, input := range rule.inputs { db.phony[input] = true } } - return db + return db, nil } func (db *depBuilder) Eval(targets []string) ([]*DepNode, error) { if len(targets) == 0 { if db.firstRule == nil { - errorNoLocationExit("*** No targets.") + return nil, fmt.Errorf("*** No targets.") } targets = append(targets, db.firstRule.outputs[0]) } diff --git a/depgraph.go b/depgraph.go index 2f243e4..a844aaa 100644 --- a/depgraph.go +++ b/depgraph.go @@ -22,6 +22,7 @@ import ( "time" ) +// DepGraph represents rules defined in makefiles. type DepGraph struct { nodes []*DepNode vars Vars @@ -30,11 +31,19 @@ type DepGraph struct { isCached bool } -func (g *DepGraph) Nodes() []*DepNode { return g.nodes } -func (g *DepGraph) Vars() Vars { return g.vars } +// Nodes returns all rules. +func (g *DepGraph) Nodes() []*DepNode { return g.nodes } + +// Vars returns all variables. +func (g *DepGraph) Vars() Vars { return g.vars } + +// Exports returns map for export variables. func (g *DepGraph) Exports() map[string]bool { return g.exports } -func (g *DepGraph) IsCached() bool { return g.isCached } +// IsCached indicates the DepGraph is loaded from cache. +func (g *DepGraph) IsCached() bool { return g.isCached } + +// LoadReq is a request to load makefile. type LoadReq struct { Makefile string Targets []string @@ -44,6 +53,7 @@ type LoadReq struct { EagerEvalCommand bool } +// FromCommandLine creates LoadReq from given command line. func FromCommandLine(cmdline []string) LoadReq { var vars []string var targets []string @@ -54,8 +64,12 @@ func FromCommandLine(cmdline []string) LoadReq { } targets = append(targets, arg) } + mk, err := defaultMakefile() + if err != nil { + logf("default makefile: %v", err) + } return LoadReq{ - Makefile: defaultMakefile(), + Makefile: mk, Targets: targets, CommandLineVars: vars, } @@ -76,20 +90,28 @@ func initVars(vars Vars, kvlist []string, origin string) error { return nil } +// Load loads makefile. func Load(req LoadReq) (*DepGraph, error) { startTime := time.Now() + var err error if req.Makefile == "" { - req.Makefile = defaultMakefile() + req.Makefile, err = defaultMakefile() + if err != nil { + return nil, err + } } if req.UseCache { - g := loadCache(req.Makefile, req.Targets) - if g != nil { + g, err := loadCache(req.Makefile, req.Targets) + if err == nil { return g, nil } } - bmk := bootstrapMakefile(req.Targets) + bmk, err := bootstrapMakefile(req.Targets) + if err != nil { + return nil, err + } content, err := ioutil.ReadFile(req.Makefile) if err != nil { @@ -125,7 +147,10 @@ func Load(req LoadReq) (*DepGraph, error) { logStats("shell func time: %q %d", shellStats.Duration(), shellStats.Count()) startTime = time.Now() - db := newDepBuilder(er, vars) + db, err := newDepBuilder(er, vars) + if err != nil { + return nil, err + } logStats("dep build prepare time: %q", time.Since(startTime)) startTime = time.Now() @@ -150,7 +175,10 @@ func Load(req LoadReq) (*DepGraph, error) { } if req.EagerEvalCommand { startTime := time.Now() - evalCommands(nodes, vars) + err = evalCommands(nodes, vars) + if err != nil { + return nil, err + } logStats("eager eval command time: %q", time.Since(startTime)) } if req.UseCache { @@ -161,14 +189,17 @@ func Load(req LoadReq) (*DepGraph, error) { return gd, nil } +// Loader is the interface that loads DepGraph. type Loader interface { Load(string) (*DepGraph, error) } +// Saver is the interface that saves DepGraph. type Saver interface { Save(*DepGraph, string, []string) error } +// LoadSaver is the interface that groups Load and Save methods. type LoadSaver interface { Loader Saver @@ -5,4 +5,4 @@ to speed up the continuous build of Android. */ package kati -// TODO(ukai): cleanup API. make more symbol unexported. +// TODO(ukai): add more doc comments. @@ -105,6 +105,46 @@ type evalResult struct { exports map[string]bool } +type srcpos struct { + filename string + lineno int +} + +func (p srcpos) String() string { + return fmt.Sprintf("%s:%d", p.filename, p.lineno) +} + +// EvalError is an error in kati evaluation. +type EvalError struct { + Filename string + Lineno int + Err error +} + +func (e EvalError) Error() string { + return fmt.Sprintf("%s:%d: %v", e.Filename, e.Lineno, e.Err) +} + +func (p srcpos) errorf(f string, args ...interface{}) error { + return EvalError{ + Filename: p.filename, + Lineno: p.lineno, + Err: fmt.Errorf(f, args...), + } +} + +func (p srcpos) error(err error) error { + if _, ok := err.(EvalError); ok { + return err + } + return EvalError{ + Filename: p.filename, + Lineno: p.lineno, + Err: err, + } +} + +// Evaluator manages makefile evaluation. type Evaluator struct { paramVars []tmpval // $1 => paramVars[1] outVars Vars @@ -118,10 +158,10 @@ type Evaluator struct { cache *accessCache exports map[string]bool - filename string - lineno int + srcpos } +// NewEvaluator creates new Evaluator. func NewEvaluator(vars map[string]Var) *Evaluator { return &Evaluator{ outVars: make(Vars), @@ -131,10 +171,13 @@ func NewEvaluator(vars map[string]Var) *Evaluator { } } -func (ev *Evaluator) args(buf *buffer, args ...Value) [][]byte { +func (ev *Evaluator) args(buf *buffer, args ...Value) ([][]byte, error) { pos := make([]int, 0, len(args)) for _, arg := range args { - arg.Eval(buf, ev) + err := arg.Eval(buf, ev) + if err != nil { + return nil, err + } pos = append(pos, buf.Len()) } v := buf.Bytes() @@ -144,24 +187,27 @@ func (ev *Evaluator) args(buf *buffer, args ...Value) [][]byte { buf.args = append(buf.args, v[s:p]) s = p } - return buf.args + return buf.args, nil } -func (ev *Evaluator) evalAssign(ast *assignAST) { +func (ev *Evaluator) evalAssign(ast *assignAST) error { ev.lastRule = nil - lhs, rhs := ev.evalAssignAST(ast) + lhs, rhs, err := ev.evalAssignAST(ast) + if err != nil { + return err + } if LogFlag { logf("ASSIGN: %s=%q (flavor:%q)", lhs, rhs, rhs.Flavor()) } if lhs == "" { - errorExit(ast.filename, ast.lineno, "*** empty variable name.") + return ast.errorf("*** empty variable name.") } ev.outVars.Assign(lhs, rhs) + return nil } -func (ev *Evaluator) evalAssignAST(ast *assignAST) (string, Var) { - ev.filename = ast.filename - ev.lineno = ast.lineno +func (ev *Evaluator) evalAssignAST(ast *assignAST) (string, Var, error) { + ev.srcpos = ast.srcpos var lhs string switch v := ast.lhs.(type) { @@ -171,37 +217,49 @@ func (ev *Evaluator) evalAssignAST(ast *assignAST) (string, Var) { lhs = string(v) default: buf := newBuf() - v.Eval(buf, ev) + err := v.Eval(buf, ev) + if err != nil { + return "", nil, err + } lhs = string(trimSpaceBytes(buf.Bytes())) freeBuf(buf) } - rhs := ast.evalRHS(ev, lhs) - return lhs, rhs + rhs, err := ast.evalRHS(ev, lhs) + if err != nil { + return "", nil, err + } + return lhs, rhs, nil } -func (ev *Evaluator) setTargetSpecificVar(assign *assignAST, output string) { +func (ev *Evaluator) setTargetSpecificVar(assign *assignAST, output string) error { vars, present := ev.outRuleVars[output] if !present { vars = make(Vars) ev.outRuleVars[output] = vars } ev.currentScope = vars - lhs, rhs := ev.evalAssignAST(assign) + lhs, rhs, err := ev.evalAssignAST(assign) + if err != nil { + return err + } if LogFlag { - logf("rule outputs:%q assign:%q=%q (flavor:%q)", output, lhs, rhs, rhs.Flavor()) + logf("rule outputs:%q assign:%q%s%q (flavor:%q)", output, lhs, assign.op, rhs, rhs.Flavor()) } vars.Assign(lhs, &targetSpecificVar{v: rhs, op: assign.op}) ev.currentScope = nil + return nil } -func (ev *Evaluator) evalMaybeRule(ast *maybeRuleAST) { +func (ev *Evaluator) evalMaybeRule(ast *maybeRuleAST) error { ev.lastRule = nil - ev.filename = ast.filename - ev.lineno = ast.lineno + ev.srcpos = ast.srcpos lexpr := ast.expr buf := newBuf() - lexpr.Eval(buf, ev) + err := lexpr.Eval(buf, ev) + if err != nil { + return err + } line := buf.Bytes() if ast.term == '=' { line = append(line, ast.afterTerm...) @@ -213,16 +271,13 @@ func (ev *Evaluator) evalMaybeRule(ast *maybeRuleAST) { // See semicolon.mk. if len(bytes.TrimRight(line, " \t\n;")) == 0 { freeBuf(buf) - return + return nil } - r := &rule{ - filename: ast.filename, - lineno: ast.lineno, - } + r := &rule{srcpos: ast.srcpos} assign, err := r.parse(line) if err != nil { - errorExit(ast.filename, ast.lineno, "%v", err) + return ast.error(err) } freeBuf(buf) if LogFlag { @@ -236,15 +291,18 @@ func (ev *Evaluator) evalMaybeRule(ast *maybeRuleAST) { if ast.term == ';' { nexpr, _, err := parseExpr(ast.afterTerm, nil, false) if err != nil { - panic(fmt.Errorf("parse %s:%d %v", ev.filename, ev.lineno, err)) + return ast.errorf("parse error: %q: %v", string(ast.afterTerm), err) } lexpr = expr{lexpr, nexpr} buf = newBuf() - lexpr.Eval(buf, ev) + err = lexpr.Eval(buf, ev) + if err != nil { + return err + } assign, err = r.parse(buf.Bytes()) if err != nil { - errorExit(ast.filename, ast.lineno, "%v", err) + return ast.error(err) } freeBuf(buf) } @@ -254,7 +312,7 @@ func (ev *Evaluator) evalMaybeRule(ast *maybeRuleAST) { for _, output := range r.outputPatterns { ev.setTargetSpecificVar(assign, output.String()) } - return + return nil } if ast.term == ';' { @@ -265,37 +323,42 @@ func (ev *Evaluator) evalMaybeRule(ast *maybeRuleAST) { } ev.lastRule = r ev.outRules = append(ev.outRules, r) + return nil } -func (ev *Evaluator) evalCommand(ast *commandAST) { - ev.filename = ast.filename - ev.lineno = ast.lineno +func (ev *Evaluator) evalCommand(ast *commandAST) error { + ev.srcpos = ast.srcpos if ev.lastRule == nil { // This could still be an assignment statement. See // assign_after_tab.mk. if strings.IndexByte(ast.cmd, '=') >= 0 { line := trimLeftSpace(ast.cmd) - mk, err := parseMakefileString(line, ast.filename, ast.lineno) + mk, err := parseMakefileString(line, ast.srcpos) if err != nil { - panic(err) + return ast.errorf("parse failed: %q: %v", line, err) } if len(mk.stmts) == 1 && mk.stmts[0].(*assignAST) != nil { - ev.eval(mk.stmts[0]) + err = ev.eval(mk.stmts[0]) + if err != nil { + return err + } } - return + return nil } // Or, a comment is OK. if strings.TrimSpace(ast.cmd)[0] == '#' { - return + return nil } - errorExit(ast.filename, ast.lineno, "*** commands commence before first target.") + return ast.errorf("*** commands commence before first target.") } ev.lastRule.cmds = append(ev.lastRule.cmds, ast.cmd) if ev.lastRule.cmdLineno == 0 { ev.lastRule.cmdLineno = ast.lineno } + return nil } +// LookupVar looks up named variable. func (ev *Evaluator) LookupVar(name string) Var { if ev.currentScope != nil { v := ev.currentScope.Lookup(name) @@ -325,10 +388,13 @@ func (ev *Evaluator) lookupVarInCurrentScope(name string) Var { // EvaluateVar evaluates variable named name. // Only for a few special uses such as getting SHELL and handling // export/unexport. -func (ev *Evaluator) EvaluateVar(name string) string { +func (ev *Evaluator) EvaluateVar(name string) (string, error) { var buf bytes.Buffer - ev.LookupVar(name).Eval(&buf, ev) - return buf.String() + err := ev.LookupVar(name).Eval(&buf, ev) + if err != nil { + return "", err + } + return buf.String(), nil } func (ev *Evaluator) evalIncludeFile(fname string, mk makefile) error { @@ -336,28 +402,37 @@ func (ev *Evaluator) evalIncludeFile(fname string, mk makefile) error { defer func() { traceEvent.end(te) }() + var err error makefileList := ev.outVars.Lookup("MAKEFILE_LIST") - makefileList = makefileList.Append(ev, mk.filename) + makefileList, err = makefileList.Append(ev, mk.filename) + if err != nil { + return err + } ev.outVars.Assign("MAKEFILE_LIST", makefileList) for _, stmt := range mk.stmts { - ev.eval(stmt) + err = ev.eval(stmt) + if err != nil { + return err + } } return nil } -func (ev *Evaluator) evalInclude(ast *includeAST) { +func (ev *Evaluator) evalInclude(ast *includeAST) error { ev.lastRule = nil - ev.filename = ast.filename - ev.lineno = ast.lineno + ev.srcpos = ast.srcpos - logf("%s:%d include %q", ev.filename, ev.lineno, ast.expr) + logf("%s include %q", ev.srcpos, ast.expr) v, _, err := parseExpr([]byte(ast.expr), nil, false) if err != nil { - panic(err) + return ast.errorf("parse failed: %q: %v", ast.expr, err) } var buf bytes.Buffer - v.Eval(&buf, ev) + err = v.Eval(&buf, ev) + if err != nil { + return ast.errorf("%v", err) + } pats := splitSpaces(buf.String()) buf.Reset() @@ -366,7 +441,7 @@ func (ev *Evaluator) evalInclude(ast *includeAST) { if strings.Contains(pat, "*") || strings.Contains(pat, "?") { matched, err := filepath.Glob(pat) if err != nil { - panic(err) + return ast.errorf("glob error: %s: %v", pat, err) } files = append(files, matched...) } else { @@ -381,36 +456,42 @@ func (ev *Evaluator) evalInclude(ast *includeAST) { mk, hash, err := makefileCache.parse(fn) if os.IsNotExist(err) { if ast.op == "include" { - errorExit(ev.filename, ev.lineno, "%v\nNOTE: kati does not support generating missing makefiles", err) - } else { - msg := ev.cache.update(fn, hash, fileNotExists) - if msg != "" { - warn(ev.filename, ev.lineno, "%s", msg) - } - continue + return ev.errorf("%v\nNOTE: kati does not support generating missing makefiles", err) } + msg := ev.cache.update(fn, hash, fileNotExists) + if msg != "" { + warn(ev.srcpos, "%s", msg) + } + continue } msg := ev.cache.update(fn, hash, fileExists) if msg != "" { - warn(ev.filename, ev.lineno, "%s", msg) + warn(ev.srcpos, "%s", msg) } err = ev.evalIncludeFile(fn, mk) if err != nil { - panic(err) + return err } } + return nil } -func (ev *Evaluator) evalIf(iast *ifAST) { +func (ev *Evaluator) evalIf(iast *ifAST) error { var isTrue bool switch iast.op { case "ifdef", "ifndef": expr := iast.lhs buf := newBuf() - expr.Eval(buf, ev) + err := expr.Eval(buf, ev) + if err != nil { + return iast.errorf("%v\n expr:%s", err, expr) + } v := ev.LookupVar(buf.String()) buf.Reset() - v.Eval(buf, ev) + err = v.Eval(buf, ev) + if err != nil { + return iast.errorf("%v\n expr:%s=>%s", err, expr, v) + } value := buf.String() val := buf.Len() freeBuf(buf) @@ -422,7 +503,10 @@ func (ev *Evaluator) evalIf(iast *ifAST) { lexpr := iast.lhs rexpr := iast.rhs buf := newBuf() - params := ev.args(buf, lexpr, rexpr) + params, err := ev.args(buf, lexpr, rexpr) + if err != nil { + return iast.errorf("%v\n (%s,%s)", err, lexpr, rexpr) + } lhs := string(params[0]) rhs := string(params[1]) freeBuf(buf) @@ -431,7 +515,7 @@ func (ev *Evaluator) evalIf(iast *ifAST) { logf("%s lhs=%q %q rhs=%q %q => %t", iast.op, iast.lhs, lhs, iast.rhs, rhs, isTrue) } default: - panic(fmt.Sprintf("unknown if statement: %q", iast.op)) + return iast.errorf("unknown if statement: %q", iast.op) } var stmts []ast @@ -441,28 +525,35 @@ func (ev *Evaluator) evalIf(iast *ifAST) { stmts = iast.falseStmts } for _, stmt := range stmts { - ev.eval(stmt) + err := ev.eval(stmt) + if err != nil { + return err + } } + return nil } -func (ev *Evaluator) evalExport(ast *exportAST) { +func (ev *Evaluator) evalExport(ast *exportAST) error { ev.lastRule = nil - ev.filename = ast.filename - ev.lineno = ast.lineno + ev.srcpos = ast.srcpos v, _, err := parseExpr(ast.expr, nil, false) if err != nil { - panic(err) + return ast.errorf("failed to parse: %q: %v", string(ast.expr), err) } var buf bytes.Buffer - v.Eval(&buf, ev) + err = v.Eval(&buf, ev) + if err != nil { + return ast.errorf("%v\n expr:%s", err, v) + } for _, n := range splitSpacesBytes(buf.Bytes()) { ev.exports[string(n)] = ast.export } + return nil } -func (ev *Evaluator) eval(stmt ast) { - stmt.eval(ev) +func (ev *Evaluator) eval(stmt ast) error { + return stmt.eval(ev) } func eval(mk makefile, vars Vars, useCache bool) (er *evalResult, err error) { @@ -470,21 +561,22 @@ func eval(mk makefile, vars Vars, useCache bool) (er *evalResult, err error) { if useCache { ev.cache = newAccessCache() } - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("panic in eval %s: %v", mk.filename, r) - } - }() makefileList := vars.Lookup("MAKEFILE_LIST") if !makefileList.IsDefined() { makefileList = &simpleVar{value: "", origin: "file"} } - makefileList = makefileList.Append(ev, mk.filename) + makefileList, err = makefileList.Append(ev, mk.filename) + if err != nil { + return nil, err + } ev.outVars.Assign("MAKEFILE_LIST", makefileList) for _, stmt := range mk.stmts { - ev.eval(stmt) + err = ev.eval(stmt) + if err != nil { + return nil, err + } } return &evalResult{ @@ -24,6 +24,7 @@ import ( "time" ) +// Executor manages execution of makefile rules. type Executor struct { rules map[string]*rule implicitRules []*rule @@ -53,38 +54,41 @@ type autoVar struct{ ex *Executor } func (v autoVar) Flavor() string { return "undefined" } func (v autoVar) Origin() string { return "automatic" } -func (v autoVar) IsDefined() bool { panic("not implemented") } -func (v autoVar) String() string { panic("not implemented") } -func (v autoVar) Append(*Evaluator, string) Var { - panic("must not be called") +func (v autoVar) IsDefined() bool { return true } +func (v autoVar) Append(*Evaluator, string) (Var, error) { + return nil, fmt.Errorf("cannot append to autovar") } -func (v autoVar) AppendVar(*Evaluator, Value) Var { - panic("must not be called") +func (v autoVar) AppendVar(*Evaluator, Value) (Var, error) { + return nil, fmt.Errorf("cannot append to autovar") } func (v autoVar) serialize() serializableVar { - panic(fmt.Sprintf("cannot serialize auto var: %q", v)) + return serializableVar{Type: ""} } -func (v autoVar) dump(w io.Writer) { - panic(fmt.Sprintf("cannot dump auto var: %q", v)) +func (v autoVar) dump(d *dumpbuf) { + d.err = fmt.Errorf("cannot dump auto var: %v", v) } type autoAtVar struct{ autoVar } -func (v autoAtVar) Eval(w io.Writer, ev *Evaluator) { +func (v autoAtVar) Eval(w io.Writer, ev *Evaluator) error { fmt.Fprint(w, v.ex.currentOutput) + return nil } +func (v autoAtVar) String() string { return "$*" } type autoLessVar struct{ autoVar } -func (v autoLessVar) Eval(w io.Writer, ev *Evaluator) { +func (v autoLessVar) Eval(w io.Writer, ev *Evaluator) error { if len(v.ex.currentInputs) > 0 { fmt.Fprint(w, v.ex.currentInputs[0]) } + return nil } +func (v autoLessVar) String() string { return "$<" } type autoHatVar struct{ autoVar } -func (v autoHatVar) Eval(w io.Writer, ev *Evaluator) { +func (v autoHatVar) Eval(w io.Writer, ev *Evaluator) error { var uniqueInputs []string seen := make(map[string]bool) for _, input := range v.ex.currentInputs { @@ -94,51 +98,69 @@ func (v autoHatVar) Eval(w io.Writer, ev *Evaluator) { } } fmt.Fprint(w, strings.Join(uniqueInputs, " ")) + return nil } +func (v autoHatVar) String() string { return "$^" } type autoPlusVar struct{ autoVar } -func (v autoPlusVar) Eval(w io.Writer, ev *Evaluator) { +func (v autoPlusVar) Eval(w io.Writer, ev *Evaluator) error { fmt.Fprint(w, strings.Join(v.ex.currentInputs, " ")) + return nil } +func (v autoPlusVar) String() string { return "$+" } type autoStarVar struct{ autoVar } -func (v autoStarVar) Eval(w io.Writer, ev *Evaluator) { +func (v autoStarVar) Eval(w io.Writer, ev *Evaluator) error { // TODO: Use currentStem. See auto_stem_var.mk fmt.Fprint(w, stripExt(v.ex.currentOutput)) + return nil } +func (v autoStarVar) String() string { return "$*" } type autoSuffixDVar struct { autoVar v Var } -func (v autoSuffixDVar) Eval(w io.Writer, ev *Evaluator) { +func (v autoSuffixDVar) Eval(w io.Writer, ev *Evaluator) error { var buf bytes.Buffer - v.v.Eval(&buf, ev) + err := v.v.Eval(&buf, ev) + if err != nil { + return err + } ws := newWordScanner(buf.Bytes()) sw := ssvWriter{w: w} for ws.Scan() { sw.WriteString(filepath.Dir(string(ws.Bytes()))) } + return nil } +func (v autoSuffixDVar) String() string { return v.v.String() + "D" } + type autoSuffixFVar struct { autoVar v Var } -func (v autoSuffixFVar) Eval(w io.Writer, ev *Evaluator) { +func (v autoSuffixFVar) Eval(w io.Writer, ev *Evaluator) error { var buf bytes.Buffer - v.v.Eval(&buf, ev) + err := v.v.Eval(&buf, ev) + if err != nil { + return err + } ws := newWordScanner(buf.Bytes()) sw := ssvWriter{w: w} for ws.Scan() { sw.WriteString(filepath.Base(string(ws.Bytes()))) } + return nil } +func (v autoSuffixFVar) String() string { return v.v.String() + "F" } + func (ex *Executor) makeJobs(n *DepNode, neededBy *job) error { output := n.Output if neededBy != nil { @@ -219,29 +241,38 @@ func (ex *Executor) reportStats() { } } +// ExecutorOpt is an option for Executor. type ExecutorOpt struct { NumJobs int ParaPath string } -func NewExecutor(vars Vars, opt *ExecutorOpt) *Executor { +// NewExecutor creates new Executor. +func NewExecutor(vars Vars, opt *ExecutorOpt) (*Executor, error) { if opt == nil { opt = &ExecutorOpt{NumJobs: 1} } if opt.NumJobs < 1 { opt.NumJobs = 1 } + wm, err := newWorkerManager(opt.NumJobs, opt.ParaPath) + if err != nil { + return nil, err + } ex := &Executor{ rules: make(map[string]*rule), suffixRules: make(map[string][]*rule), done: make(map[string]*job), vars: vars, - wm: newWorkerManager(opt.NumJobs, opt.ParaPath), + wm: wm, } // TODO: We should move this to somewhere around evalCmd so that // we can handle SHELL in target specific variables. ev := NewEvaluator(ex.vars) - ex.shell = ev.EvaluateVar("SHELL") + ex.shell, err = ev.EvaluateVar("SHELL") + if err != nil { + ex.shell = "/bin/sh" + } for k, v := range map[string]Var{ "@": autoAtVar{autoVar: autoVar{ex: ex}}, "<": autoLessVar{autoVar: autoVar{ex: ex}}, @@ -253,23 +284,24 @@ func NewExecutor(vars Vars, opt *ExecutorOpt) *Executor { ex.vars[k+"D"] = autoSuffixDVar{v: v} ex.vars[k+"F"] = autoSuffixFVar{v: v} } - return ex + return ex, nil } +// Exec executes to build roots. func (ex *Executor) Exec(roots []*DepNode) error { startTime := time.Now() for _, root := range roots { ex.makeJobs(root, nil) } - ex.wm.Wait() + err := ex.wm.Wait() logStats("exec time: %q", time.Since(startTime)) - return nil + return err } -func (ex *Executor) createRunners(n *DepNode, avoidIO bool) ([]runner, bool) { +func (ex *Executor) createRunners(n *DepNode, avoidIO bool) ([]runner, bool, error) { var runners []runner if len(n.Cmds) == 0 { - return runners, false + return runners, false, nil } var restores []func() @@ -287,6 +319,7 @@ func (ex *Executor) createRunners(n *DepNode, avoidIO bool) ([]runner, bool) { for k, v := range n.TargetSpecificVars { restores = append(restores, ex.vars.save(k)) ex.vars[k] = v + logf("tsv: %s=%s", k, v) } ev := NewEvaluator(ex.vars) @@ -300,20 +333,30 @@ func (ex *Executor) createRunners(n *DepNode, avoidIO bool) ([]runner, bool) { shell: ex.shell, } for _, cmd := range n.Cmds { - for _, r := range evalCmd(ev, r, cmd) { + rr, err := evalCmd(ev, r, cmd) + if err != nil { + return nil, false, err + } + for _, r := range rr { if len(r.cmd) != 0 { runners = append(runners, r) } } } - return runners, ev.hasIO + return runners, ev.hasIO, nil } -func evalCommands(nodes []*DepNode, vars Vars) { +func evalCommands(nodes []*DepNode, vars Vars) error { ioCnt := 0 - ex := NewExecutor(vars, nil) + ex, err := NewExecutor(vars, nil) + if err != nil { + return err + } for i, n := range nodes { - runners, hasIO := ex.createRunners(n, true) + runners, hasIO, err := ex.createRunners(n, true) + if err != nil { + return err + } if hasIO { ioCnt++ if ioCnt%100 == 0 { @@ -338,4 +381,5 @@ func evalCommands(nodes []*DepNode, vars Vars) { } logStats("%d/%d rules have IO", ioCnt, len(nodes)) + return nil } @@ -53,42 +53,45 @@ func freeBuf(buf *buffer) { bufFree.Put(buf) } +// Value is an interface for value. type Value interface { String() string - Eval(w io.Writer, ev *Evaluator) + Eval(w io.Writer, ev *Evaluator) error serialize() serializableVar - dump(w io.Writer) + dump(d *dumpbuf) } // literal is literal value. type literal string func (s literal) String() string { return string(s) } -func (s literal) Eval(w io.Writer, ev *Evaluator) { +func (s literal) Eval(w io.Writer, ev *Evaluator) error { io.WriteString(w, string(s)) + return nil } func (s literal) serialize() serializableVar { return serializableVar{Type: "literal", V: string(s)} } -func (s literal) dump(w io.Writer) { - dumpByte(w, valueTypeLiteral) - dumpBytes(w, []byte(s)) +func (s literal) dump(d *dumpbuf) { + d.Byte(valueTypeLiteral) + d.Bytes([]byte(s)) } // tmpval is temporary value. type tmpval []byte func (t tmpval) String() string { return string(t) } -func (t tmpval) Eval(w io.Writer, ev *Evaluator) { +func (t tmpval) Eval(w io.Writer, ev *Evaluator) error { w.Write(t) + return nil } func (t tmpval) Value() []byte { return []byte(t) } func (t tmpval) serialize() serializableVar { return serializableVar{Type: "tmpval", V: string(t)} } -func (t tmpval) dump(w io.Writer) { - dumpByte(w, valueTypeTmpval) - dumpBytes(w, t) +func (t tmpval) dump(d *dumpbuf) { + d.Byte(valueTypeTmpval) + d.Bytes(t) } // expr is a list of values. @@ -102,10 +105,14 @@ func (e expr) String() string { return strings.Join(s, "") } -func (e expr) Eval(w io.Writer, ev *Evaluator) { +func (e expr) Eval(w io.Writer, ev *Evaluator) error { for _, v := range e { - v.Eval(w, ev) + err := v.Eval(w, ev) + if err != nil { + return err + } } + return nil } func (e expr) serialize() serializableVar { @@ -115,11 +122,11 @@ func (e expr) serialize() serializableVar { } return r } -func (e expr) dump(w io.Writer) { - dumpByte(w, valueTypeExpr) - dumpInt(w, len(e)) +func (e expr) dump(d *dumpbuf) { + d.Byte(valueTypeExpr) + d.Int(len(e)) for _, v := range e { - v.dump(w) + v.dump(d) } } @@ -144,14 +151,21 @@ func (v *varref) String() string { return fmt.Sprintf("${%s}", varname) } -func (v *varref) Eval(w io.Writer, ev *Evaluator) { +func (v *varref) Eval(w io.Writer, ev *Evaluator) error { te := traceEvent.begin("var", v, traceEventMain) buf := newBuf() - v.varname.Eval(buf, ev) + err := v.varname.Eval(buf, ev) + if err != nil { + return err + } vv := ev.LookupVar(buf.String()) freeBuf(buf) - vv.Eval(w, ev) + err = vv.Eval(w, ev) + if err != nil { + return err + } traceEvent.end(te) + return nil } func (v *varref) serialize() serializableVar { @@ -160,9 +174,9 @@ func (v *varref) serialize() serializableVar { Children: []serializableVar{v.varname.serialize()}, } } -func (v *varref) dump(w io.Writer) { - dumpByte(w, valueTypeVarref) - v.varname.dump(w) +func (v *varref) dump(d *dumpbuf) { + d.Byte(valueTypeVarref) + v.varname.dump(d) } // paramref is parameter reference e.g. $1. @@ -172,25 +186,32 @@ func (p paramref) String() string { return fmt.Sprintf("$%d", int(p)) } -func (p paramref) Eval(w io.Writer, ev *Evaluator) { +func (p paramref) Eval(w io.Writer, ev *Evaluator) error { te := traceEvent.begin("param", p, traceEventMain) n := int(p) if n < len(ev.paramVars) { - ev.paramVars[n].Eval(w, ev) + err := ev.paramVars[n].Eval(w, ev) + if err != nil { + return err + } } else { vv := ev.LookupVar(fmt.Sprintf("%d", n)) - vv.Eval(w, ev) + err := vv.Eval(w, ev) + if err != nil { + return err + } } traceEvent.end(te) + return nil } func (p paramref) serialize() serializableVar { return serializableVar{Type: "paramref", V: strconv.Itoa(int(p))} } -func (p paramref) dump(w io.Writer) { - dumpByte(w, valueTypeParamref) - dumpInt(w, int(p)) +func (p paramref) dump(d *dumpbuf) { + d.Byte(valueTypeParamref) + d.Int(int(p)) } // varsubst is variable substitutaion. e.g. ${var:pat=subst}. @@ -204,16 +225,22 @@ func (v varsubst) String() string { return fmt.Sprintf("${%s:%s=%s}", v.varname, v.pat, v.subst) } -func (v varsubst) Eval(w io.Writer, ev *Evaluator) { +func (v varsubst) Eval(w io.Writer, ev *Evaluator) error { te := traceEvent.begin("varsubst", v, traceEventMain) buf := newBuf() - params := ev.args(buf, v.varname, v.pat, v.subst) + params, err := ev.args(buf, v.varname, v.pat, v.subst) + if err != nil { + return err + } vname := string(params[0]) pat := string(params[1]) subst := string(params[2]) buf.Reset() vv := ev.LookupVar(vname) - vv.Eval(buf, ev) + err = vv.Eval(buf, ev) + if err != nil { + return err + } vals := splitSpaces(buf.String()) freeBuf(buf) space := false @@ -225,6 +252,7 @@ func (v varsubst) Eval(w io.Writer, ev *Evaluator) { space = true } traceEvent.end(te) + return nil } func (v varsubst) serialize() serializableVar { @@ -238,11 +266,11 @@ func (v varsubst) serialize() serializableVar { } } -func (v varsubst) dump(w io.Writer) { - dumpByte(w, valueTypeVarsubst) - v.varname.dump(w) - v.pat.dump(w) - v.subst.dump(w) +func (v varsubst) dump(d *dumpbuf) { + d.Byte(valueTypeVarsubst) + v.varname.dump(d) + v.pat.dump(d) + v.subst.dump(d) } func str(buf []byte, alloc bool) Value { @@ -447,7 +475,7 @@ Again: subst: subst, }, i + 1, nil default: - panic(fmt.Sprintf("unexpected char")) + return nil, 0, fmt.Errorf("unexpected char %c at %d in %q", in[i], i, string(in)) } } } @@ -606,21 +634,36 @@ type funcstats struct { str string } -func (f funcstats) Eval(w io.Writer, ev *Evaluator) { +func (f funcstats) Eval(w io.Writer, ev *Evaluator) error { te := traceEvent.begin("func", literal(f.str), traceEventMain) - f.Value.Eval(w, ev) + err := f.Value.Eval(w, ev) + if err != nil { + return err + } // TODO(ukai): per functype? traceEvent.end(te) + return nil +} + +type matcherValue struct{} + +func (m matcherValue) Eval(w io.Writer, ev *Evaluator) error { + return fmt.Errorf("couldn't eval matcher") +} +func (m matcherValue) serialize() serializableVar { + return serializableVar{Type: ""} +} + +func (m matcherValue) dump(d *dumpbuf) { + d.err = fmt.Errorf("couldn't dump matcher") } -type matchVarref struct{} +type matchVarref struct{ matcherValue } -func (m matchVarref) String() string { return "$(match-any)" } -func (m matchVarref) Eval(w io.Writer, ev *Evaluator) { panic("not implemented") } -func (m matchVarref) serialize() serializableVar { panic("not implemented") } -func (m matchVarref) dump(w io.Writer) { panic("not implemented") } +func (m matchVarref) String() string { return "$(match-any)" } type literalRE struct { + matcherValue *regexp.Regexp } @@ -630,10 +673,7 @@ func mustLiteralRE(s string) literalRE { } } -func (r literalRE) String() string { return r.Regexp.String() } -func (r literalRE) Eval(w io.Writer, ev *Evaluator) { panic("not implemented") } -func (r literalRE) serialize() serializableVar { panic("not implemented") } -func (r literalRE) dump(w io.Writer) { panic("not implemented") } +func (r literalRE) String() string { return r.Regexp.String() } func matchValue(exp, pat Value) bool { switch pat := pat.(type) { @@ -14,6 +14,7 @@ package kati +// Flags to control kati. var ( LogFlag bool StatsFlag bool @@ -90,10 +90,20 @@ var ( } ) -func assertArity(name string, req, n int) { +type arityError struct { + narg int + name string +} + +func (e arityError) Error() string { + return fmt.Sprintf("*** insufficient number of arguments (%d) to function `%s'.", e.narg, e.name) +} + +func assertArity(name string, req, n int) error { if n-1 < req { - panic(fmt.Sprintf("*** insufficient number of arguments (%d) to function `%s'.", n-1, name)) + return arityError{narg: n - 1, name: name} } + return nil } func numericValueForFunc(v string) (int, bool) { @@ -121,15 +131,15 @@ func (c *fclosure) AddArg(v Value) { func (c *fclosure) String() string { if len(c.args) == 0 { - panic("no args in func") + return "$(func)" } arg0 := c.args[0].String() if arg0 == "" { - panic(fmt.Errorf("wrong format of arg0: %q", arg0)) + return "$(func )" } cp := closeParen(arg0[0]) if cp == 0 { - panic(fmt.Errorf("wrong format of arg0: %q", arg0)) + return "${func }" } var args []string for _, arg := range c.args[1:] { @@ -146,10 +156,10 @@ func (c *fclosure) serialize() serializableVar { return r } -func (c *fclosure) dump(w io.Writer) { - dumpByte(w, valueTypeFunc) +func (c *fclosure) dump(d *dumpbuf) { + d.Byte(valueTypeFunc) for _, a := range c.args { - a.dump(w) + a.dump(d) } } @@ -157,10 +167,16 @@ func (c *fclosure) dump(w io.Writer) { type funcSubst struct{ fclosure } func (f *funcSubst) Arity() int { return 3 } -func (f *funcSubst) Eval(w io.Writer, ev *Evaluator) { - assertArity("subst", 3, len(f.args)) +func (f *funcSubst) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("subst", 3, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() from := fargs[0] to := fargs[1] @@ -169,15 +185,22 @@ func (f *funcSubst) Eval(w io.Writer, ev *Evaluator) { w.Write(bytes.Replace(text, from, to, -1)) freeBuf(abuf) stats.add("funcbody", "subst", t) + return nil } type funcPatsubst struct{ fclosure } func (f *funcPatsubst) Arity() int { return 3 } -func (f *funcPatsubst) Eval(w io.Writer, ev *Evaluator) { - assertArity("patsubst", 3, len(f.args)) +func (f *funcPatsubst) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("patsubst", 3, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() pat := fargs[0] repl := fargs[1] @@ -197,15 +220,22 @@ func (f *funcPatsubst) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "patsubst", t) + return nil } type funcStrip struct{ fclosure } func (f *funcStrip) Arity() int { return 1 } -func (f *funcStrip) Eval(w io.Writer, ev *Evaluator) { - assertArity("strip", 1, len(f.args)) +func (f *funcStrip) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("strip", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) space := false @@ -218,15 +248,22 @@ func (f *funcStrip) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "strip", t) + return nil } type funcFindstring struct{ fclosure } func (f *funcFindstring) Arity() int { return 2 } -func (f *funcFindstring) Eval(w io.Writer, ev *Evaluator) { - assertArity("findstring", 2, len(f.args)) +func (f *funcFindstring) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("findstring", 2, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() find := fargs[0] text := fargs[1] @@ -235,15 +272,22 @@ func (f *funcFindstring) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "findstring", t) + return nil } type funcFilter struct{ fclosure } func (f *funcFilter) Arity() int { return 2 } -func (f *funcFilter) Eval(w io.Writer, ev *Evaluator) { - assertArity("filter", 2, len(f.args)) +func (f *funcFilter) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("filter", 2, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() var patterns [][]byte ws := newWordScanner(fargs[0]) @@ -262,15 +306,22 @@ func (f *funcFilter) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "filter", t) + return nil } type funcFilterOut struct{ fclosure } func (f *funcFilterOut) Arity() int { return 2 } -func (f *funcFilterOut) Eval(w io.Writer, ev *Evaluator) { - assertArity("filter-out", 2, len(f.args)) +func (f *funcFilterOut) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("filter-out", 2, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() var patterns [][]byte ws := newWordScanner(fargs[0]) @@ -291,15 +342,22 @@ Loop: } freeBuf(abuf) stats.add("funcbody", "filter-out", t) + return err } type funcSort struct{ fclosure } func (f *funcSort) Arity() int { return 1 } -func (f *funcSort) Eval(w io.Writer, ev *Evaluator) { - assertArity("sort", 1, len(f.args)) +func (f *funcSort) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("sort", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) var toks []string @@ -322,23 +380,30 @@ func (f *funcSort) Eval(w io.Writer, ev *Evaluator) { prev = tok } stats.add("funcbody", "sort", t) + return nil } type funcWord struct{ fclosure } func (f *funcWord) Arity() int { return 2 } -func (f *funcWord) Eval(w io.Writer, ev *Evaluator) { - assertArity("word", 2, len(f.args)) +func (f *funcWord) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("word", 2, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() v := string(trimSpaceBytes(fargs[0])) index, ok := numericValueForFunc(v) if !ok { - errorExit(ev.filename, ev.lineno, `*** non-numeric first argument to "word" function: %q.`, v) + return ev.errorf(`*** non-numeric first argument to "word" function: %q.`, v) } if index == 0 { - errorExit(ev.filename, ev.lineno, `*** first argument to "word" function must be greater than 0.`) + return ev.errorf(`*** first argument to "word" function must be greater than 0.`) } ws := newWordScanner(fargs[1]) for ws.Scan() { @@ -350,28 +415,35 @@ func (f *funcWord) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "word", t) + return err } type funcWordlist struct{ fclosure } func (f *funcWordlist) Arity() int { return 3 } -func (f *funcWordlist) Eval(w io.Writer, ev *Evaluator) { - assertArity("wordlist", 3, len(f.args)) +func (f *funcWordlist) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("wordlist", 3, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() v := string(trimSpaceBytes(fargs[0])) si, ok := numericValueForFunc(v) if !ok { - errorExit(ev.filename, ev.lineno, `*** non-numeric first argument to "wordlist" function: %q.`, v) + return ev.errorf(`*** non-numeric first argument to "wordlist" function: %q.`, v) } if si == 0 { - errorExit(ev.filename, ev.lineno, `*** invalid first argument to "wordlist" function: %s`, f.args[1]) + return ev.errorf(`*** invalid first argument to "wordlist" function: %s`, f.args[1]) } v = string(trimSpaceBytes(fargs[1])) ei, ok := numericValueForFunc(v) if !ok { - errorExit(ev.filename, ev.lineno, `*** non-numeric second argument to "wordlist" function: %q.`, v) + return ev.errorf(`*** non-numeric second argument to "wordlist" function: %q.`, v) } ws := newWordScanner(fargs[2]) @@ -385,15 +457,22 @@ func (f *funcWordlist) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "wordlist", t) + return nil } type funcWords struct{ fclosure } func (f *funcWords) Arity() int { return 1 } -func (f *funcWords) Eval(w io.Writer, ev *Evaluator) { - assertArity("words", 1, len(f.args)) +func (f *funcWords) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("words", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) n := 0 @@ -403,15 +482,22 @@ func (f *funcWords) Eval(w io.Writer, ev *Evaluator) { freeBuf(abuf) io.WriteString(w, strconv.Itoa(n)) stats.add("funcbody", "words", t) + return nil } type funcFirstword struct{ fclosure } func (f *funcFirstword) Arity() int { return 1 } -func (f *funcFirstword) Eval(w io.Writer, ev *Evaluator) { - assertArity("firstword", 1, len(f.args)) +func (f *funcFirstword) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("firstword", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) if ws.Scan() { @@ -419,15 +505,22 @@ func (f *funcFirstword) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "firstword", t) + return nil } type funcLastword struct{ fclosure } func (f *funcLastword) Arity() int { return 1 } -func (f *funcLastword) Eval(w io.Writer, ev *Evaluator) { - assertArity("lastword", 1, len(f.args)) +func (f *funcLastword) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("lastword", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) var lw []byte @@ -439,6 +532,7 @@ func (f *funcLastword) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "lastword", t) + return err } // https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html#File-Name-Functions @@ -446,10 +540,16 @@ func (f *funcLastword) Eval(w io.Writer, ev *Evaluator) { type funcJoin struct{ fclosure } func (f *funcJoin) Arity() int { return 2 } -func (f *funcJoin) Eval(w io.Writer, ev *Evaluator) { - assertArity("join", 2, len(f.args)) +func (f *funcJoin) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("join", 2, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() ws1 := newWordScanner(fargs[0]) ws2 := newWordScanner(fargs[1]) @@ -464,15 +564,22 @@ func (f *funcJoin) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "join", t) + return nil } type funcWildcard struct{ fclosure } func (f *funcWildcard) Arity() int { return 1 } -func (f *funcWildcard) Eval(w io.Writer, ev *Evaluator) { - assertArity("wildcard", 1, len(f.args)) +func (f *funcWildcard) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("wildcard", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } te := traceEvent.begin("wildcard", tmpval(abuf.Bytes()), traceEventMain) if ev.avoidIO && !UseWildcardCache { ev.hasIO = true @@ -481,27 +588,37 @@ func (f *funcWildcard) Eval(w io.Writer, ev *Evaluator) { io.WriteString(w, " 2> /dev/null)") traceEvent.end(te) freeBuf(abuf) - return + return nil } t := time.Now() ws := newWordScanner(abuf.Bytes()) sw := ssvWriter{w: w} for ws.Scan() { pat := string(ws.Bytes()) - wildcard(&sw, pat) + err = wildcard(&sw, pat) + if err != nil { + return err + } } traceEvent.end(te) freeBuf(abuf) stats.add("funcbody", "wildcard", t) + return nil } type funcDir struct{ fclosure } func (f *funcDir) Arity() int { return 1 } -func (f *funcDir) Eval(w io.Writer, ev *Evaluator) { - assertArity("dir", 1, len(f.args)) +func (f *funcDir) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("dir", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) sw := ssvWriter{w: w} @@ -515,15 +632,22 @@ func (f *funcDir) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "dir", t) + return nil } type funcNotdir struct{ fclosure } func (f *funcNotdir) Arity() int { return 1 } -func (f *funcNotdir) Eval(w io.Writer, ev *Evaluator) { - assertArity("notdir", 1, len(f.args)) +func (f *funcNotdir) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("notdir", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) sw := ssvWriter{w: w} @@ -537,15 +661,22 @@ func (f *funcNotdir) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "notdir", t) + return nil } type funcSuffix struct{ fclosure } func (f *funcSuffix) Arity() int { return 1 } -func (f *funcSuffix) Eval(w io.Writer, ev *Evaluator) { - assertArity("suffix", 1, len(f.args)) +func (f *funcSuffix) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("suffix", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) sw := ssvWriter{w: w} @@ -558,15 +689,22 @@ func (f *funcSuffix) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "suffix", t) + return err } type funcBasename struct{ fclosure } func (f *funcBasename) Arity() int { return 1 } -func (f *funcBasename) Eval(w io.Writer, ev *Evaluator) { - assertArity("basename", 1, len(f.args)) +func (f *funcBasename) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("basename", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) sw := ssvWriter{w: w} @@ -577,15 +715,22 @@ func (f *funcBasename) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "basename", t) + return nil } type funcAddsuffix struct{ fclosure } func (f *funcAddsuffix) Arity() int { return 2 } -func (f *funcAddsuffix) Eval(w io.Writer, ev *Evaluator) { - assertArity("addsuffix", 2, len(f.args)) +func (f *funcAddsuffix) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("addsuffix", 2, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() suf := fargs[0] ws := newWordScanner(fargs[1]) @@ -597,15 +742,22 @@ func (f *funcAddsuffix) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "addsuffix", t) + return err } type funcAddprefix struct{ fclosure } func (f *funcAddprefix) Arity() int { return 2 } -func (f *funcAddprefix) Eval(w io.Writer, ev *Evaluator) { - assertArity("addprefix", 2, len(f.args)) +func (f *funcAddprefix) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("addprefix", 2, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } t := time.Now() pre := fargs[0] ws := newWordScanner(fargs[1]) @@ -617,20 +769,27 @@ func (f *funcAddprefix) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "addprefix", t) + return err } type funcRealpath struct{ fclosure } func (f *funcRealpath) Arity() int { return 1 } -func (f *funcRealpath) Eval(w io.Writer, ev *Evaluator) { - assertArity("realpath", 1, len(f.args)) +func (f *funcRealpath) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("realpath", 1, len(f.args)) + if err != nil { + return err + } if ev.avoidIO { io.WriteString(w, "KATI_TODO(realpath)") ev.hasIO = true - return + return nil } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) sw := ssvWriter{w: w} @@ -650,15 +809,22 @@ func (f *funcRealpath) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "realpath", t) + return err } type funcAbspath struct{ fclosure } func (f *funcAbspath) Arity() int { return 1 } -func (f *funcAbspath) Eval(w io.Writer, ev *Evaluator) { - assertArity("abspath", 1, len(f.args)) +func (f *funcAbspath) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("abspath", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } t := time.Now() ws := newWordScanner(abuf.Bytes()) sw := ssvWriter{w: w} @@ -673,64 +839,85 @@ func (f *funcAbspath) Eval(w io.Writer, ev *Evaluator) { } freeBuf(abuf) stats.add("funcbody", "abspath", t) + return nil } // http://www.gnu.org/software/make/manual/make.html#Conditional-Functions type funcIf struct{ fclosure } func (f *funcIf) Arity() int { return 3 } -func (f *funcIf) Eval(w io.Writer, ev *Evaluator) { - assertArity("if", 2, len(f.args)) +func (f *funcIf) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("if", 2, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } if len(abuf.Bytes()) != 0 { freeBuf(abuf) - f.args[2].Eval(w, ev) - return + return f.args[2].Eval(w, ev) } freeBuf(abuf) if len(f.args) > 3 { - f.args[3].Eval(w, ev) + return f.args[3].Eval(w, ev) } + return nil } type funcAnd struct{ fclosure } func (f *funcAnd) Arity() int { return 0 } -func (f *funcAnd) Eval(w io.Writer, ev *Evaluator) { - assertArity("and", 0, len(f.args)) +func (f *funcAnd) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("and", 0, len(f.args)) + if err != nil { + return nil + } abuf := newBuf() var cond []byte for _, arg := range f.args[1:] { abuf.Reset() - arg.Eval(abuf, ev) + err = arg.Eval(abuf, ev) + if err != nil { + return err + } cond = abuf.Bytes() if len(cond) == 0 { freeBuf(abuf) - return + return nil } } w.Write(cond) freeBuf(abuf) + return nil } type funcOr struct{ fclosure } func (f *funcOr) Arity() int { return 0 } -func (f *funcOr) Eval(w io.Writer, ev *Evaluator) { - assertArity("or", 0, len(f.args)) +func (f *funcOr) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("or", 0, len(f.args)) + if err != nil { + return err + } abuf := newBuf() for _, arg := range f.args[1:] { abuf.Reset() - arg.Eval(abuf, ev) + err = arg.Eval(abuf, ev) + if err != nil { + return err + } cond := abuf.Bytes() if len(cond) != 0 { w.Write(cond) freeBuf(abuf) - return + return nil } } freeBuf(abuf) + return nil } // http://www.gnu.org/software/make/manual/make.html#Shell-Function @@ -755,10 +942,16 @@ func hasNoIoInShellScript(s []byte) bool { return true } -func (f *funcShell) Eval(w io.Writer, ev *Evaluator) { - assertArity("shell", 1, len(f.args)) +func (f *funcShell) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("shell", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } if ev.avoidIO && !hasNoIoInShellScript(abuf.Bytes()) { te := traceEvent.begin("shell", tmpval(abuf.Bytes()), traceEventMain) ev.hasIO = true @@ -767,7 +960,7 @@ func (f *funcShell) Eval(w io.Writer, ev *Evaluator) { writeByte(w, ')') traceEvent.end(te) freeBuf(abuf) - return + return nil } arg := abuf.String() freeBuf(abuf) @@ -790,6 +983,7 @@ func (f *funcShell) Eval(w io.Writer, ev *Evaluator) { } w.Write(formatCommandOutput(out)) traceEvent.end(te) + return nil } func (f *funcShell) Compact() Value { @@ -825,9 +1019,12 @@ type funcCall struct{ fclosure } func (f *funcCall) Arity() int { return 0 } -func (f *funcCall) Eval(w io.Writer, ev *Evaluator) { +func (f *funcCall) Eval(w io.Writer, ev *Evaluator) error { abuf := newBuf() - fargs := ev.args(abuf, f.args[1:]...) + fargs, err := ev.args(abuf, f.args[1:]...) + if err != nil { + return err + } varname := fargs[0] variable := string(varname) te := traceEvent.begin("call", literal(variable), traceEventMain) @@ -857,44 +1054,62 @@ func (f *funcCall) Eval(w io.Writer, ev *Evaluator) { if LogFlag { w = io.MultiWriter(w, &buf) } - v.Eval(w, ev) + err = v.Eval(w, ev) + if err != nil { + return err + } ev.paramVars = oldParams traceEvent.end(te) if LogFlag { logf("call %q variable %q return %q", f.args[1], variable, buf.Bytes()) } freeBuf(abuf) + return nil } // http://www.gnu.org/software/make/manual/make.html#Value-Function type funcValue struct{ fclosure } func (f *funcValue) Arity() int { return 1 } -func (f *funcValue) Eval(w io.Writer, ev *Evaluator) { - assertArity("value", 1, len(f.args)) +func (f *funcValue) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("value", 1, len(f.args)) + if err != nil { + return err + } v := ev.LookupVar(f.args[1].String()) io.WriteString(w, v.String()) + return nil } // http://www.gnu.org/software/make/manual/make.html#Eval-Function type funcEval struct{ fclosure } func (f *funcEval) Arity() int { return 1 } -func (f *funcEval) Eval(w io.Writer, ev *Evaluator) { - assertArity("eval", 1, len(f.args)) +func (f *funcEval) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("eval", 1, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } s := abuf.Bytes() - logf("eval %q at %s:%d", s, ev.filename, ev.lineno) - mk, err := parseMakefileBytes(s, ev.filename, ev.lineno) + logf("eval %q at %s", s, ev.srcpos) + mk, err := parseMakefileBytes(s, ev.srcpos) if err != nil { - panic(err) + return ev.errorf("%v", err) } for _, stmt := range mk.stmts { - ev.eval(stmt) + err = ev.eval(stmt) + if err != nil { + return err + } } freeBuf(abuf) + return nil } func (f *funcEval) Compact() Value { @@ -963,16 +1178,16 @@ func stripComment(arg string) string { type funcNop struct{ expr string } -func (f *funcNop) String() string { return f.expr } -func (f *funcNop) Eval(io.Writer, *Evaluator) {} +func (f *funcNop) String() string { return f.expr } +func (f *funcNop) Eval(io.Writer, *Evaluator) error { return nil } func (f *funcNop) serialize() serializableVar { return serializableVar{ Type: "funcNop", V: f.expr, } } -func (f *funcNop) dump(w io.Writer) { - dumpByte(w, valueTypeNop) +func (f *funcNop) dump(d *dumpbuf) { + d.Byte(valueTypeNop) } func parseAssignLiteral(s string) (lhs, op string, rhs Value, ok bool) { @@ -1007,9 +1222,12 @@ func (f *funcEvalAssign) String() string { return fmt.Sprintf("$(eval %s %s %s)", f.lhs, f.op, f.rhs) } -func (f *funcEvalAssign) Eval(w io.Writer, ev *Evaluator) { +func (f *funcEvalAssign) Eval(w io.Writer, ev *Evaluator) error { var abuf bytes.Buffer - f.rhs.Eval(&abuf, ev) + err := f.rhs.Eval(&abuf, ev) + if err != nil { + return err + } rhs := trimLeftSpaceBytes(abuf.Bytes()) var rvalue Var switch f.op { @@ -1018,10 +1236,13 @@ func (f *funcEvalAssign) Eval(w io.Writer, ev *Evaluator) { // literal? e.g. literal("$(foo)") => varref{literal("foo")}. exp, _, err := parseExpr(rhs, nil, false) if err != nil { - panic(fmt.Sprintf("eval assign error: %q: %v", f.String(), err)) + return ev.errorf("eval assign error: %q: %v", f.String(), err) } vbuf := newBuf() - exp.Eval(vbuf, ev) + err = exp.Eval(vbuf, ev) + if err != nil { + return err + } rvalue = &simpleVar{value: vbuf.String(), origin: "file"} freeBuf(vbuf) case "=": @@ -1029,14 +1250,17 @@ func (f *funcEvalAssign) Eval(w io.Writer, ev *Evaluator) { case "+=": prev := ev.LookupVar(f.lhs) if prev.IsDefined() { - rvalue = prev.Append(ev, string(rhs)) + rvalue, err = prev.Append(ev, string(rhs)) + if err != nil { + return err + } } else { rvalue = &recursiveVar{expr: tmpval(rhs), origin: "file"} } case "?=": prev := ev.LookupVar(f.lhs) if prev.IsDefined() { - return + return nil } rvalue = &recursiveVar{expr: tmpval(rhs), origin: "file"} } @@ -1044,6 +1268,7 @@ func (f *funcEvalAssign) Eval(w io.Writer, ev *Evaluator) { logf("Eval ASSIGN: %s=%q (flavor:%q)", f.lhs, rvalue, rvalue.Flavor()) } ev.outVars.Assign(f.lhs, rvalue) + return nil } func (f *funcEvalAssign) serialize() serializableVar { @@ -1057,80 +1282,107 @@ func (f *funcEvalAssign) serialize() serializableVar { } } -func (f *funcEvalAssign) dump(w io.Writer) { - dumpByte(w, valueTypeAssign) - dumpString(w, f.lhs) - dumpString(w, f.op) - f.rhs.dump(w) +func (f *funcEvalAssign) dump(d *dumpbuf) { + d.Byte(valueTypeAssign) + d.Str(f.lhs) + d.Str(f.op) + f.rhs.dump(d) } // http://www.gnu.org/software/make/manual/make.html#Origin-Function type funcOrigin struct{ fclosure } func (f *funcOrigin) Arity() int { return 1 } -func (f *funcOrigin) Eval(w io.Writer, ev *Evaluator) { - assertArity("origin", 1, len(f.args)) +func (f *funcOrigin) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("origin", 1, len(f.args)) + if err != nil { + return err + } v := ev.LookupVar(f.args[1].String()) io.WriteString(w, v.Origin()) + return nil } // https://www.gnu.org/software/make/manual/html_node/Flavor-Function.html#Flavor-Function type funcFlavor struct{ fclosure } func (f *funcFlavor) Arity() int { return 1 } -func (f *funcFlavor) Eval(w io.Writer, ev *Evaluator) { - assertArity("flavor", 1, len(f.args)) +func (f *funcFlavor) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("flavor", 1, len(f.args)) + if err != nil { + return err + } v := ev.LookupVar(f.args[1].String()) io.WriteString(w, v.Flavor()) + return nil } // http://www.gnu.org/software/make/manual/make.html#Make-Control-Functions type funcInfo struct{ fclosure } func (f *funcInfo) Arity() int { return 1 } -func (f *funcInfo) Eval(w io.Writer, ev *Evaluator) { - assertArity("info", 1, len(f.args)) +func (f *funcInfo) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("info", 1, len(f.args)) + if err != nil { + return err + } if ev.avoidIO { io.WriteString(w, "KATI_TODO(info)") ev.hasIO = true - return + return nil } abuf := newBuf() - f.args[1].Eval(abuf, ev) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } fmt.Printf("%s\n", abuf.String()) freeBuf(abuf) + return nil } type funcWarning struct{ fclosure } func (f *funcWarning) Arity() int { return 1 } -func (f *funcWarning) Eval(w io.Writer, ev *Evaluator) { - assertArity("warning", 1, len(f.args)) +func (f *funcWarning) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("warning", 1, len(f.args)) + if err != nil { + return err + } if ev.avoidIO { io.WriteString(w, "KATI_TODO(warning)") ev.hasIO = true - return + return nil } abuf := newBuf() - f.args[1].Eval(abuf, ev) - fmt.Printf("%s:%d: %s\n", ev.filename, ev.lineno, abuf.String()) + err = f.args[1].Eval(abuf, ev) + if err != nil { + return err + } + fmt.Printf("%s: %s\n", ev.srcpos, abuf.String()) freeBuf(abuf) + return nil } type funcError struct{ fclosure } func (f *funcError) Arity() int { return 1 } -func (f *funcError) Eval(w io.Writer, ev *Evaluator) { - assertArity("error", 1, len(f.args)) +func (f *funcError) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("error", 1, len(f.args)) + if err != nil { + return err + } if ev.avoidIO { io.WriteString(w, "KATI_TODO(error)") ev.hasIO = true - return + return nil } - abuf := newBuf() - f.args[1].Eval(abuf, ev) - errorExit(ev.filename, ev.lineno, "*** %s.", abuf.String()) - freeBuf(abuf) + var abuf buffer + err = f.args[1].Eval(&abuf, ev) + if err != nil { + return err + } + return ev.errorf("*** %s.", abuf.String()) } // http://www.gnu.org/software/make/manual/make.html#Foreach-Function @@ -1138,10 +1390,16 @@ type funcForeach struct{ fclosure } func (f *funcForeach) Arity() int { return 3 } -func (f *funcForeach) Eval(w io.Writer, ev *Evaluator) { - assertArity("foreach", 3, len(f.args)) +func (f *funcForeach) Eval(w io.Writer, ev *Evaluator) error { + err := assertArity("foreach", 3, len(f.args)) + if err != nil { + return err + } abuf := newBuf() - fargs := ev.args(abuf, f.args[1], f.args[2]) + fargs, err := ev.args(abuf, f.args[1], f.args[2]) + if err != nil { + return err + } varname := string(fargs[0]) ws := newWordScanner(fargs[1]) text := f.args[3] @@ -1154,8 +1412,12 @@ func (f *funcForeach) Eval(w io.Writer, ev *Evaluator) { if space { writeByte(w, ' ') } - text.Eval(w, ev) + err = text.Eval(w, ev) + if err != nil { + return err + } space = true } freeBuf(abuf) + return nil } @@ -17,7 +17,6 @@ package kati import ( "bytes" "fmt" - "os" "sync" ) @@ -47,32 +46,12 @@ func logf(f string, a ...interface{}) { logAlways(f, a...) } -func warn(filename string, lineno int, f string, a ...interface{}) { - f = fmt.Sprintf("%s:%d: warning: %s\n", filename, lineno, f) +func warn(loc srcpos, f string, a ...interface{}) { + f = fmt.Sprintf("%s: warning: %s\n", loc, f) fmt.Printf(f, a...) } -func warnNoPrefix(filename string, lineno int, f string, a ...interface{}) { - f = fmt.Sprintf("%s:%d: %s\n", filename, lineno, f) +func warnNoPrefix(loc srcpos, f string, a ...interface{}) { + f = fmt.Sprintf("%s: %s\n", loc, f) fmt.Printf(f, a...) } - -var atErrors []func() - -func AtError(f func()) { - atErrors = append(atErrors, f) -} - -func errorExit(filename string, lineno int, f string, a ...interface{}) { - f = fmt.Sprintf("%s:%d: %s", filename, lineno, f) - errorNoLocationExit(f, a...) -} - -func errorNoLocationExit(f string, a ...interface{}) { - f = fmt.Sprintf("%s\n", f) - fmt.Printf(f, a...) - for i := len(atErrors) - 1; i >= 0; i-- { - atErrors[i]() - } - os.Exit(2) -} @@ -33,21 +33,17 @@ type ninjaGenerator struct { ex *Executor ruleID int done map[string]bool - ccRe *regexp.Regexp gomaDir string } +var ccRE = regexp.MustCompile(`^prebuilts/(gcc|clang)/.*(gcc|g\+\+|clang|clang\+\+) .* -c `) + func newNinjaGenerator(g *DepGraph, gomaDir string) *ninjaGenerator { - ccRe, err := regexp.Compile(`^prebuilts/(gcc|clang)/.*(gcc|g\+\+|clang|clang\+\+) .* -c `) - if err != nil { - panic(err) - } return &ninjaGenerator{ nodes: g.nodes, vars: g.vars, exports: g.exports, done: make(map[string]bool), - ccRe: ccRe, gomaDir: gomaDir, } } @@ -104,7 +100,7 @@ func getDepfile(ss string) (string, error) { rest := ss[i+len(mvCmd):] ei := strings.IndexByte(rest, ')') if ei < 0 { - panic(ss) + return "", fmt.Errorf("unbalanced parenthes? %s", ss) } return rest[:ei], nil } @@ -175,7 +171,7 @@ func (n *ninjaGenerator) genShellScript(runners []runner) (string, bool) { if cmd == "" { cmd = "true" } - if n.gomaDir != "" && n.ccRe.MatchString(cmd) { + if n.gomaDir != "" && ccRE.MatchString(cmd) { cmd = fmt.Sprintf("%s/gomacc %s", n.gomaDir, cmd) useGomacc = true } @@ -229,17 +225,20 @@ func getDepString(node *DepNode) string { return dep } -func (n *ninjaGenerator) emitNode(node *DepNode) { +func (n *ninjaGenerator) emitNode(node *DepNode) error { if n.done[node.Output] { - return + return nil } n.done[node.Output] = true if len(node.Cmds) == 0 && len(node.Deps) == 0 && !node.IsPhony { - return + return nil } - runners, _ := n.ex.createRunners(node, true) + runners, _, err := n.ex.createRunners(node, true) + if err != nil { + return err + } ruleName := "phony" useLocalPool := false if len(runners) > 0 { @@ -253,7 +252,7 @@ func (n *ninjaGenerator) emitNode(node *DepNode) { } depfile, err := getDepfile(ss) if err != nil { - panic(err) + return err } if depfile != "" { fmt.Fprintf(n.f, " depfile = %s\n", depfile) @@ -275,26 +274,39 @@ func (n *ninjaGenerator) emitNode(node *DepNode) { } for _, d := range node.Deps { - n.emitNode(d) + err := n.emitNode(d) + if err != nil { + return err + } } + return nil } -func (n *ninjaGenerator) generateShell() { +func (n *ninjaGenerator) generateShell() (err error) { f, err := os.Create("ninja.sh") if err != nil { - panic(err) + return err } - defer f.Close() + defer func() { + cerr := f.Close() + if err == nil { + err = cerr + } + }() ev := NewEvaluator(n.vars) - shell := ev.EvaluateVar("SHELL") - if shell == "" { + shell, err := ev.EvaluateVar("SHELL") + if err != nil { shell = "/bin/sh" } fmt.Fprintf(f, "#!%s\n", shell) for name, export := range n.exports { if export { - fmt.Fprintf(f, "export %s=%s\n", name, ev.EvaluateVar(name)) + v, err := ev.EvaluateVar(name) + if err != nil { + return err + } + fmt.Fprintf(f, "export %s=%s\n", name, v) } else { fmt.Fprintf(f, "unset %s\n", name) } @@ -305,18 +317,20 @@ func (n *ninjaGenerator) generateShell() { fmt.Fprintln(f, `exec ninja -j300 "$@"`) } - err = f.Chmod(0755) - if err != nil { - panic(err) - } + return f.Chmod(0755) } -func (n *ninjaGenerator) generateNinja() { +func (n *ninjaGenerator) generateNinja() (err error) { f, err := os.Create("build.ninja") if err != nil { - panic(err) + return err } - defer f.Close() + defer func() { + cerr := f.Close() + if err == nil { + err = cerr + } + }() n.f = f fmt.Fprintf(n.f, "# Generated by kati\n") @@ -327,16 +341,31 @@ func (n *ninjaGenerator) generateNinja() { fmt.Fprintf(n.f, " depth = %d\n", runtime.NumCPU()) } - n.ex = NewExecutor(n.vars, nil) + n.ex, err = NewExecutor(n.vars, nil) + if err != nil { + return err + } for _, node := range n.nodes { - n.emitNode(node) + err := n.emitNode(node) + if err != nil { + return err + } } + return nil } -func GenerateNinja(g *DepGraph, gomaDir string) { +// GenerateNinja generates build.ninja from DepGraph. +func GenerateNinja(g *DepGraph, gomaDir string) error { startTime := time.Now() n := newNinjaGenerator(g, gomaDir) - n.generateShell() - n.generateNinja() + err := n.generateShell() + if err != nil { + return err + } + err = n.generateNinja() + if err != nil { + return err + } logStats("generate ninja time: %q", time.Since(startTime)) + return nil } @@ -29,38 +29,46 @@ func btoi(b bool) int { return 0 } -func sendMsg(w io.Writer, data []byte) { - for len(data) != 0 { - written, err := w.Write(data) - if err == io.EOF { - return - } - if err != nil { - panic(err) - } - data = data[written:] +type paraConn struct { + w io.WriteCloser + r *bufio.Reader + err error +} + +func (c *paraConn) sendMsg(data []byte) error { + if c.err != nil { + return c.err } + _, err := c.w.Write(data) + c.err = err + return err } -func sendInt(w io.Writer, i int) { +func (c *paraConn) sendInt(i int) error { + if c.err != nil { + return c.err + } v := int32(i) - binary.Write(w, binary.LittleEndian, &v) + c.err = binary.Write(c.w, binary.LittleEndian, &v) + return c.err } -func sendString(w io.Writer, s string) { - sendInt(w, len(s)) - sendMsg(w, []byte(s)) +func (c *paraConn) sendString(s string) error { + c.sendInt(len(s)) + c.sendMsg([]byte(s)) + return c.err } -func sendRunners(w io.Writer, runners []runner) { - sendInt(w, len(runners)) +func (c *paraConn) sendRunners(runners []runner) error { + c.sendInt(len(runners)) for _, r := range runners { - sendString(w, r.output) - sendString(w, r.cmd) - sendString(w, r.shell) - sendInt(w, btoi(r.echo)) - sendInt(w, btoi(r.ignoreError)) + c.sendString(r.output) + c.sendString(r.cmd) + c.sendString(r.shell) + c.sendInt(btoi(r.echo)) + c.sendInt(btoi(r.ignoreError)) } + return c.err } type paraResult struct { @@ -71,49 +79,37 @@ type paraResult struct { signal int } -func recvInt(r *bufio.Reader) (int, error) { +func (c *paraConn) recvInt() (int, error) { + if c.err != nil { + return 0, c.err + } var v int32 - err := binary.Read(r, binary.LittleEndian, &v) - return int(v), err + c.err = binary.Read(c.r, binary.LittleEndian, &v) + return int(v), c.err } -func recvString(r *bufio.Reader) (string, error) { - l, err := recvInt(r) +func (c *paraConn) recvString() (string, error) { + l, err := c.recvInt() if err != nil { + c.err = err return "", err } buf := make([]byte, l) - read := 0 - for read < len(buf) { - r, err := r.Read(buf[read:]) - if err != nil { - return "", err - } - read += r + _, c.err = io.ReadFull(c.r, buf) + if c.err != nil { + return "", c.err } return string(buf), nil } -func recvResult(r *bufio.Reader) (*paraResult, error) { - output, err := recvString(r) - if err != nil { - return nil, err - } - stdout, err := recvString(r) - if err != nil { - return nil, err - } - stderr, err := recvString(r) - if err != nil { - return nil, err - } - status, err := recvInt(r) - if err != nil { - return nil, err - } - signal, err := recvInt(r) - if err != nil { - return nil, err +func (c *paraConn) recvResult() (*paraResult, error) { + output, _ := c.recvString() + stdout, _ := c.recvString() + stderr, _ := c.recvString() + status, _ := c.recvInt() + signal, _ := c.recvInt() + if c.err != nil { + return nil, c.err } return ¶Result{ output: output, @@ -127,55 +123,58 @@ func recvResult(r *bufio.Reader) (*paraResult, error) { type paraWorker struct { para *exec.Cmd paraChan chan *paraResult - stdin io.WriteCloser - stdout *bufio.Reader + c *paraConn doneChan chan bool } -func newParaWorker(paraChan chan *paraResult, numJobs int, paraPath string) *paraWorker { +func newParaWorker(paraChan chan *paraResult, numJobs int, paraPath string) (*paraWorker, error) { para := exec.Command(paraPath, fmt.Sprintf("-j%d", numJobs), "--kati") stdin, err := para.StdinPipe() if err != nil { - panic(err) + return nil, err } stdout, err := para.StdoutPipe() if err != nil { - panic(err) + return nil, err } err = para.Start() if err != nil { - panic(err) + return nil, err } return ¶Worker{ para: para, paraChan: paraChan, - stdin: stdin, - stdout: bufio.NewReader(stdout), + c: ¶Conn{ + w: stdin, + r: bufio.NewReader(stdout), + }, doneChan: make(chan bool), - } + }, nil } -func (para *paraWorker) Run() { +func (para *paraWorker) Run() error { for { - r, err := recvResult(para.stdout) - if err == io.EOF { - break - } + r, err := para.c.recvResult() if err != nil { - panic(err) + break } para.paraChan <- r } para.para.Process.Kill() para.para.Process.Wait() para.doneChan <- true + return para.c.err } -func (para *paraWorker) Wait() { - para.stdin.Close() +func (para *paraWorker) Wait() error { + para.c.w.Close() <-para.doneChan + if para.c.err == io.EOF { + return nil + } + return para.c.err } -func (para *paraWorker) RunCommand(runners []runner) { - sendRunners(para.stdin, runners) +func (para *paraWorker) RunCommand(runners []runner) error { + return para.c.sendRunners(runners) } diff --git a/para_test.go b/para_test.go index c0a78e2..73e6350 100644 --- a/para_test.go +++ b/para_test.go @@ -23,13 +23,16 @@ import ( func TestPara(t *testing.T) { cwd, err := filepath.Abs(".") if err != nil { - panic(err) + t.Fatal(err) } paraPath := filepath.Join(cwd, "para") numJobs := 4 paraChan := make(chan *paraResult) - para := newParaWorker(paraChan, numJobs, paraPath) + para, err := newParaWorker(paraChan, numJobs, paraPath) + if err != nil { + t.Fatal(err) + } go para.Run() numTasks := 100 @@ -61,5 +64,8 @@ func TestPara(t *testing.T) { } } - para.Wait() + err = para.Wait() + if err != nil { + t.Errorf("para.Wait()=%v; want=<nil>", err) + } } @@ -23,6 +23,7 @@ import ( "bufio" "bytes" "crypto/sha1" + "errors" "fmt" "io" "io/ioutil" @@ -48,14 +49,13 @@ type parser struct { lineno int elineno int // lineno == elineno unless there is trailing '\'. linenoFixed bool - unBuf []byte - hasUnBuf bool done bool outStmts *[]ast ifStack []ifState inDef []string defOpt string numIfNest int + err error } func newParser(rd io.Reader, filename string) *parser { @@ -67,16 +67,18 @@ func newParser(rd io.Reader, filename string) *parser { return p } +func (p *parser) srcpos() srcpos { + return srcpos{ + filename: p.mk.filename, + lineno: p.lineno, + } +} + func (p *parser) addStatement(stmt ast) { *p.outStmts = append(*p.outStmts, stmt) } func (p *parser) readLine() []byte { - if p.hasUnBuf { - p.hasUnBuf = false - return p.unBuf - } - if !p.linenoFixed { p.lineno = p.elineno } @@ -88,7 +90,8 @@ func (p *parser) readLine() []byte { if err == io.EOF { p.done = true } else if err != nil { - panic(fmt.Errorf("readline %s:%d: %v", p.mk.filename, p.lineno, err)) + p.err = fmt.Errorf("readline %s: %v", p.srcpos(), err) + p.done = true } line = bytes.TrimRight(line, "\r\n") @@ -163,22 +166,14 @@ func (p *parser) processRecipeLine(line []byte) []byte { return line } -func (p *parser) unreadLine(line []byte) { - if p.hasUnBuf { - panic("unreadLine twice!") - } - p.unBuf = line - p.hasUnBuf = true -} - -func newAssignAST(p *parser, lhsBytes []byte, rhsBytes []byte, op string) *assignAST { +func newAssignAST(p *parser, lhsBytes []byte, rhsBytes []byte, op string) (*assignAST, error) { lhs, _, err := parseExpr(lhsBytes, nil, true) if err != nil { - panic(err) + return nil, err } rhs, _, err := parseExpr(rhsBytes, nil, true) if err != nil { - panic(err) + return nil, err } opt := "" if p != nil { @@ -189,20 +184,22 @@ func newAssignAST(p *parser, lhsBytes []byte, rhsBytes []byte, op string) *assig rhs: rhs, op: op, opt: opt, - } + }, nil } -func (p *parser) parseAssign(line []byte, sep, esep int) ast { +func (p *parser) parseAssign(line []byte, sep, esep int) (ast, error) { logf("parseAssign %q op:%q", line, line[sep:esep]) - aast := newAssignAST(p, bytes.TrimSpace(line[:sep]), trimLeftSpaceBytes(line[esep:]), string(line[sep:esep])) - aast.filename = p.mk.filename - aast.lineno = p.lineno - return aast + aast, err := newAssignAST(p, bytes.TrimSpace(line[:sep]), trimLeftSpaceBytes(line[esep:]), string(line[sep:esep])) + if err != nil { + return nil, err + } + aast.srcpos = p.srcpos() + return aast, nil } -func (p *parser) parseMaybeRule(line []byte, equalIndex, semicolonIndex int) ast { +func (p *parser) parseMaybeRule(line []byte, equalIndex, semicolonIndex int) (ast, error) { if len(trimSpaceBytes(line)) == 0 { - return nil + return nil, nil } expr := line @@ -229,7 +226,7 @@ func (p *parser) parseMaybeRule(line []byte, equalIndex, semicolonIndex int) ast v, _, err := parseExpr(expr, nil, true) if err != nil { - panic(fmt.Errorf("parse %s:%d %v", p.mk.filename, p.lineno, err)) + return nil, p.srcpos().error(err) } rast := &maybeRuleAST{ @@ -237,106 +234,110 @@ func (p *parser) parseMaybeRule(line []byte, equalIndex, semicolonIndex int) ast term: term, afterTerm: afterTerm, } - rast.filename = p.mk.filename - rast.lineno = p.lineno - return rast + rast.srcpos = p.srcpos() + return rast, nil } -func (p *parser) parseInclude(line string, oplen int) ast { +func (p *parser) parseInclude(line string, oplen int) { // TODO(ukai): parse expr here iast := &includeAST{ expr: line[oplen+1:], op: line[:oplen], } - iast.filename = p.mk.filename - iast.lineno = p.lineno - return iast + iast.srcpos = p.srcpos() + p.addStatement(iast) } -func (p *parser) parseIfdef(line []byte, oplen int) ast { +func (p *parser) parseIfdef(line []byte, oplen int) { lhs, _, err := parseExpr(trimLeftSpaceBytes(line[oplen+1:]), nil, true) if err != nil { - panic(fmt.Errorf("ifdef parse %s:%d %v", p.mk.filename, p.lineno, err)) + p.err = p.srcpos().error(err) + return } iast := &ifAST{ op: string(line[:oplen]), lhs: lhs, } - iast.filename = p.mk.filename - iast.lineno = p.lineno + iast.srcpos = p.srcpos() p.addStatement(iast) p.ifStack = append(p.ifStack, ifState{ast: iast, numNest: p.numIfNest}) p.outStmts = &iast.trueStmts - return iast } -func (p *parser) parseTwoQuotes(s string, op string) ([]string, bool) { +func (p *parser) parseTwoQuotes(s string, op string) ([]string, bool, error) { var args []string for i := 0; i < 2; i++ { s = strings.TrimSpace(s) if s == "" { - return nil, false + return nil, false, nil } quote := s[0] if quote != '\'' && quote != '"' { - return nil, false + return nil, false, nil } end := strings.IndexByte(s[1:], quote) + 1 if end < 0 { - return nil, false + return nil, false, nil } args = append(args, s[1:end]) s = s[end+1:] } if len(s) > 0 { - errorExit(p.mk.filename, p.lineno, `extraneous text after %q directive`, op) + return nil, false, p.srcpos().errorf(`extraneous text after %q directive`, op) } - return args, true + return args, true, nil } // parse // "(lhs, rhs)" // "lhs, rhs" -func (p *parser) parseEq(s string, op string) (string, string, bool) { +func (p *parser) parseEq(s string, op string) (string, string, bool, error) { if s[0] == '(' && s[len(s)-1] == ')' { s = s[1 : len(s)-1] term := []byte{','} in := []byte(s) v, n, err := parseExpr(in, term, false) if err != nil { - return "", "", false + return "", "", false, err } lhs := v.String() n++ n += skipSpaces(in[n:], nil) v, n, err = parseExpr(in[n:], nil, false) if err != nil { - return "", "", false + return "", "", false, err } rhs := v.String() - return lhs, rhs, true + return lhs, rhs, true, nil } - args, ok := p.parseTwoQuotes(s, op) + args, ok, err := p.parseTwoQuotes(s, op) if !ok { - return "", "", false + return "", "", false, err } - return args[0], args[1], true + return args[0], args[1], true, nil } -func (p *parser) parseIfeq(line string, oplen int) ast { +func (p *parser) parseIfeq(line string, oplen int) { op := line[:oplen] - lhsBytes, rhsBytes, ok := p.parseEq(strings.TrimSpace(line[oplen+1:]), op) + lhsBytes, rhsBytes, ok, err := p.parseEq(strings.TrimSpace(line[oplen+1:]), op) + if err != nil { + p.err = err + return + } if !ok { - errorExit(p.mk.filename, p.lineno, `*** invalid syntax in conditional.`) + p.err = p.srcpos().errorf(`*** invalid syntax in conditional.`) + return } lhs, _, err := parseExpr([]byte(lhsBytes), nil, true) if err != nil { - panic(fmt.Errorf("parse ifeq lhs %s:%d %v", p.mk.filename, p.lineno, err)) + p.err = p.srcpos().error(err) + return } rhs, _, err := parseExpr([]byte(rhsBytes), nil, true) if err != nil { - panic(fmt.Errorf("parse ifeq rhs %s:%d %v", p.mk.filename, p.lineno, err)) + p.err = p.srcpos().error(err) + return } iast := &ifAST{ @@ -344,25 +345,30 @@ func (p *parser) parseIfeq(line string, oplen int) ast { lhs: lhs, rhs: rhs, } - iast.filename = p.mk.filename - iast.lineno = p.lineno + iast.srcpos = p.srcpos() p.addStatement(iast) p.ifStack = append(p.ifStack, ifState{ast: iast, numNest: p.numIfNest}) p.outStmts = &iast.trueStmts - return iast + return } -func (p *parser) checkIfStack(curKeyword string) { +func (p *parser) checkIfStack(curKeyword string) error { if len(p.ifStack) == 0 { - errorExit(p.mk.filename, p.lineno, `*** extraneous %q.`, curKeyword) + return p.srcpos().errorf(`*** extraneous %q.`, curKeyword) } + return nil } func (p *parser) parseElse(line []byte) { - p.checkIfStack("else") + err := p.checkIfStack("else") + if err != nil { + p.err = err + return + } state := &p.ifStack[len(p.ifStack)-1] if state.inElse { - errorExit(p.mk.filename, p.lineno, `*** only one "else" per conditional.`) + p.err = p.srcpos().errorf(`*** only one "else" per conditional.`) + return } state.inElse = true p.outStmts = &state.ast.falseStmts @@ -384,11 +390,16 @@ func (p *parser) parseElse(line []byte) { return } p.numIfNest = 0 - warnNoPrefix(p.mk.filename, p.lineno, "extraneous text after `else` directive") + warnNoPrefix(p.srcpos(), "extraneous text after `else` directive") + return } func (p *parser) parseEndif(line string) { - p.checkIfStack("endif") + err := p.checkIfStack("endif") + if err != nil { + p.err = err + return + } state := p.ifStack[len(p.ifStack)-1] for t := 0; t <= state.numNest; t++ { p.ifStack = p.ifStack[0 : len(p.ifStack)-1] @@ -403,6 +414,7 @@ func (p *parser) parseEndif(line string) { } } } + return } type directiveFunc func(*parser, []byte) []byte @@ -447,12 +459,12 @@ func (p *parser) isDirective(line []byte, directives map[string]directiveFunc) ( } func includeDirective(p *parser, line []byte) []byte { - p.addStatement(p.parseInclude(string(line), len("include"))) + p.parseInclude(string(line), len("include")) return nil } func sincludeDirective(p *parser, line []byte) []byte { - p.addStatement(p.parseInclude(string(line), len("-include"))) + p.parseInclude(string(line), len("-include")) return nil } @@ -522,8 +534,7 @@ func handleExport(p *parser, line []byte, export bool) (hasEqual bool) { expr: line, export: export, } - east.filename = p.mk.filename - east.lineno = p.lineno + east.srcpos = p.srcpos() p.addStatement(east) return hasEqual } @@ -561,7 +572,7 @@ func (p *parser) isEndef(s string) bool { if found >= 0 && s[:found] == "endef" { rest := strings.TrimSpace(s[found+1:]) if rest != "" && rest[0] != '#' { - warnNoPrefix(p.mk.filename, p.lineno, "extraneous text after \"endef\" directive") + warnNoPrefix(p.srcpos(), "extraneous text after \"endef\" directive") } return true } @@ -569,11 +580,6 @@ func (p *parser) isEndef(s string) bool { } func (p *parser) parse() (mk makefile, err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("panic in parse %s: %v", mk.filename, r) - } - }() for !p.done { line := p.readLine() @@ -581,9 +587,12 @@ func (p *parser) parse() (mk makefile, err error) { lineStr := string(p.processDefineLine(line)) if p.isEndef(lineStr) { logf("multilineAssign %q", p.inDef) - aast := newAssignAST(p, []byte(p.inDef[0]), []byte(strings.Join(p.inDef[1:], "\n")), "=") - aast.filename = p.mk.filename - aast.lineno = p.lineno - len(p.inDef) + aast, err := newAssignAST(p, []byte(p.inDef[0]), []byte(strings.Join(p.inDef[1:], "\n")), "=") + if err != nil { + return makefile{}, err + } + aast.srcpos = p.srcpos() + aast.srcpos.lineno -= len(p.inDef) p.addStatement(aast) p.inDef = nil p.defOpt = "" @@ -601,14 +610,16 @@ func (p *parser) parse() (mk makefile, err error) { if f, ok := p.isDirective(line, makeDirectives); ok { line = trimSpaceBytes(p.processMakefileLine(line)) line = f(p, line) + if p.err != nil { + return makefile{}, p.err + } if len(line) == 0 { continue } } if line[0] == '\t' { cast := &commandAST{cmd: string(p.processRecipeLine(line[1:]))} - cast.filename = p.mk.filename - cast.lineno = p.lineno + cast.srcpos = p.srcpos() p.addStatement(cast) continue } @@ -626,7 +637,7 @@ func (p *parser) parse() (mk makefile, err error) { parenStack = append(parenStack, ch) case ')', '}': if len(parenStack) == 0 { - warn(p.mk.filename, p.lineno, "Unmatched parens: %s", line) + warn(p.srcpos(), "Unmatched parens: %s", line) } else { cp := closeParen(parenStack[len(parenStack)-1]) if cp == ch { @@ -642,7 +653,10 @@ func (p *parser) parse() (mk makefile, err error) { case ':': if i+1 < len(line) && line[i+1] == '=' { if !isRule { - stmt = p.parseAssign(line, i, i+2) + stmt, err = p.parseAssign(line, i, i+2) + if err != nil { + return makefile{}, err + } } } else { isRule = true @@ -653,14 +667,20 @@ func (p *parser) parse() (mk makefile, err error) { } case '=': if !isRule { - stmt = p.parseAssign(line, i, i+1) + stmt, err = p.parseAssign(line, i, i+1) + if err != nil { + return makefile{}, err + } } if equalIndex < 0 { equalIndex = i } case '?', '+': if !isRule && i+1 < len(line) && line[i+1] == '=' { - stmt = p.parseAssign(line, i, i+2) + stmt, err = p.parseAssign(line, i, i+2) + if err != nil { + return makefile{}, err + } } } if stmt != nil { @@ -669,40 +689,42 @@ func (p *parser) parse() (mk makefile, err error) { } } if stmt == nil { - stmt = p.parseMaybeRule(line, equalIndex, semicolonIndex) + stmt, err = p.parseMaybeRule(line, equalIndex, semicolonIndex) + if err != nil { + return makefile{}, err + } if stmt != nil { p.addStatement(stmt) } } } - return p.mk, nil + return p.mk, p.err } -func defaultMakefile() string { +func defaultMakefile() (string, error) { candidates := []string{"GNUmakefile", "makefile", "Makefile"} for _, filename := range candidates { if exists(filename) { - return filename + return filename, nil } } - errorNoLocationExit("no targets specified and no makefile found.") - panic("") // Cannot be reached. + return "", errors.New("no targets specified and no makefile found") } -func parseMakefileReader(rd io.Reader, name string, lineno int) (makefile, error) { - parser := newParser(rd, name) - parser.lineno = lineno - parser.elineno = lineno +func parseMakefileReader(rd io.Reader, loc srcpos) (makefile, error) { + parser := newParser(rd, loc.filename) + parser.lineno = loc.lineno + parser.elineno = loc.lineno parser.linenoFixed = true return parser.parse() } -func parseMakefileString(s string, name string, lineno int) (makefile, error) { - return parseMakefileReader(strings.NewReader(s), name, lineno) +func parseMakefileString(s string, loc srcpos) (makefile, error) { + return parseMakefileReader(strings.NewReader(s), loc) } -func parseMakefileBytes(s []byte, name string, lineno int) (makefile, error) { - return parseMakefileReader(bytes.NewReader(s), name, lineno) +func parseMakefileBytes(s []byte, loc srcpos) (makefile, error) { + return parseMakefileReader(bytes.NewReader(s), loc) } type mkCacheEntry struct { diff --git a/pathutil.go b/pathutil.go index cb5eea9..eb7c2c0 100644 --- a/pathutil.go +++ b/pathutil.go @@ -36,7 +36,7 @@ var wildcardCache = &wildcardCacheT{ m: make(map[string][]string), } -func wildcardGlob(pat string) []string { +func wildcardGlob(pat string) ([]string, error) { // TODO(ukai): use find cache for glob if exists. pattern := filepath.Clean(pat) if pattern != pat { @@ -48,9 +48,9 @@ func wildcardGlob(pat string) []string { // return pat. _, err := os.Stat(pat) if err != nil { - return nil + return nil, nil } - return []string{pat} + return []string{pat}, nil } if strings.Contains(pattern[i+1:], "..") { // We ask shell to expand a glob to avoid this. @@ -66,18 +66,18 @@ func wildcardGlob(pat string) []string { for ws.Scan() { files = append(files, string(ws.Bytes())) } - return files + return files, nil } // prefix + meta + suffix, and suffix doesn't have '..' prefix := pattern[:i] i = strings.IndexAny(pat, "*?[") if i < 0 { - panic(fmt.Sprintf("wildcard metachar mismatch? pattern=%q pat=%q", pattern, pat)) + return nil, fmt.Errorf("wildcard metachar mismatch? pattern=%q pat=%q", pattern, pat) } oprefix := pat[:i] matched, err := filepath.Glob(pattern) if err != nil { - panic(err) + return nil, err } var files []string for _, m := range matched { @@ -88,16 +88,12 @@ func wildcardGlob(pat string) []string { } files = append(files, file) } - return files + return files, nil } - files, err := filepath.Glob(pat) - if err != nil { - panic(err) - } - return files + return filepath.Glob(pat) } -func wildcard(sw *ssvWriter, pat string) { +func wildcard(sw *ssvWriter, pat string) error { if UseWildcardCache { // TODO(ukai): make sure it didn't chdir? wildcardCache.mu.Lock() @@ -107,10 +103,13 @@ func wildcard(sw *ssvWriter, pat string) { for _, file := range files { sw.WriteString(file) } - return + return nil } } - files := wildcardGlob(pat) + files, err := wildcardGlob(pat) + if err != nil { + return err + } for _, file := range files { sw.WriteString(file) } @@ -119,6 +118,7 @@ func wildcard(sw *ssvWriter, pat string) { wildcardCache.m[pat] = files wildcardCache.mu.Unlock() } + return nil } type fileInfo struct { @@ -140,6 +140,7 @@ var ( androidDefaultLeafNames = []string{"CleanSpec.mk", "Android.mk"} ) +// AndroidFindCacheInit initializes find cache for android build. func AndroidFindCacheInit(prunes, leafNames []string) { if leafNames != nil { androidDefaultLeafNames = leafNames @@ -70,6 +70,7 @@ func handleNodeQuery(w io.Writer, q string, nodes []*DepNode) { } } +// Query queries q in g. func Query(w io.Writer, q string, g *DepGraph) { if q == "$MAKEFILE_LIST" { for _, mk := range g.accessedMks { diff --git a/rule_parser.go b/rule_parser.go index b7908ac..d71bd0f 100644 --- a/rule_parser.go +++ b/rule_parser.go @@ -56,6 +56,7 @@ func (p pattern) subst(repl, str string) string { } type rule struct { + srcpos outputs []string inputs []string orderOnlyInputs []string @@ -63,11 +64,13 @@ type rule struct { isDoubleColon bool isSuffixRule bool cmds []string - filename string - lineno int cmdLineno int } +func (r *rule) cmdpos() srcpos { + return srcpos{filename: r.filename, lineno: r.cmdLineno} +} + func isPatternRule(s []byte) (pattern, bool) { i := bytes.IndexByte(s, '%') if i < 0 { @@ -93,10 +96,10 @@ func (r *rule) parseInputs(s []byte) { } } -func (r *rule) parseVar(s []byte) *assignAST { +func (r *rule) parseVar(s []byte) (*assignAST, error) { eq := bytes.IndexByte(s, '=') if eq <= 0 { - return nil + return nil, nil } rhs := trimLeftSpaceBytes(s[eq+1:]) var lhs []byte @@ -116,10 +119,12 @@ func (r *rule) parseVar(s []byte) *assignAST { lhs = trimSpaceBytes(s[:eq]) op = "=" } - assign := newAssignAST(nil, lhs, rhs, op) - assign.filename = r.filename - assign.lineno = r.lineno - return assign + assign, err := newAssignAST(nil, lhs, rhs, op) + if err != nil { + return nil, err + } + assign.srcpos = r.srcpos + return assign, nil } func (r *rule) parse(line []byte) (*assignAST, error) { @@ -153,7 +158,11 @@ func (r *rule) parse(line []byte) (*assignAST, error) { } rest := line[index:] - if assign := r.parseVar(rest); assign != nil { + assign, err := r.parseVar(rest) + if err != nil { + return nil, err + } + if assign != nil { return assign, nil } index = bytes.IndexByte(rest, ':') diff --git a/serialize.go b/serialize.go index aadad3f..cb6c815 100644 --- a/serialize.go +++ b/serialize.go @@ -46,7 +46,10 @@ const ( valueTypeTmpval = 't' ) +// JSON is a json loader/saver. var JSON LoadSaver + +// GOB is a gob loader/saver. var GOB LoadSaver func init() { @@ -57,35 +60,46 @@ func init() { type jsonLoadSaver struct{} type gobLoadSaver struct{} -func dumpInt(w io.Writer, i int) { - v := int32(i) - err := binary.Write(w, binary.LittleEndian, &v) - if err != nil { - panic(err) +type dumpbuf struct { + w bytes.Buffer + err error +} + +func (d *dumpbuf) Int(i int) { + if d.err != nil { + return } + v := int32(i) + d.err = binary.Write(&d.w, binary.LittleEndian, &v) } -func dumpString(w io.Writer, s string) { - dumpInt(w, len(s)) - _, err := io.WriteString(w, s) - if err != nil { - panic(err) +func (d *dumpbuf) Str(s string) { + if d.err != nil { + return + } + d.Int(len(s)) + if d.err != nil { + return } + _, d.err = io.WriteString(&d.w, s) } -func dumpBytes(w io.Writer, b []byte) { - dumpInt(w, len(b)) - _, err := w.Write(b) - if err != nil { - panic(err) +func (d *dumpbuf) Bytes(b []byte) { + if d.err != nil { + return + } + d.Int(len(b)) + if d.err != nil { + return } + _, d.err = d.w.Write(b) } -func dumpByte(w io.Writer, b byte) { - err := writeByte(w, b) - if err != nil { - panic(err) +func (d *dumpbuf) Byte(b byte) { + if d.err != nil { + return } + d.err = writeByte(&d.w, b) } type serializableVar struct { @@ -124,21 +138,21 @@ type serializableGraph struct { Exports map[string]bool } -func encGob(v interface{}) string { +func encGob(v interface{}) (string, error) { var buf bytes.Buffer e := gob.NewEncoder(&buf) err := e.Encode(v) if err != nil { - panic(err) + return "", err } - return buf.String() + return buf.String(), nil } -func encVar(k string, v Var) string { - var buf bytes.Buffer - dumpString(&buf, k) - v.dump(&buf) - return buf.String() +func encVar(k string, v Var) (string, error) { + var dump dumpbuf + dump.Str(k) + v.dump(&dump) + return dump.w.String(), dump.err } type depNodesSerializer struct { @@ -148,6 +162,7 @@ type depNodesSerializer struct { targets []string targetMap map[string]int done map[string]bool + err error } func newDepNodesSerializer() *depNodesSerializer { @@ -170,6 +185,9 @@ func (ns *depNodesSerializer) serializeTarget(t string) int { } func (ns *depNodesSerializer) serializeDepNodes(nodes []*DepNode) { + if ns.err != nil { + return + } for _, n := range nodes { if ns.done[n.Output] { continue @@ -201,7 +219,11 @@ func (ns *depNodesSerializer) serializeDepNodes(nodes []*DepNode) { v := n.TargetSpecificVars[k] sv := serializableTargetSpecificVar{Name: k, Value: v.serialize()} //gob := encGob(sv) - gob := encVar(k, v) + gob, err := encVar(k, v) + if err != nil { + ns.err = err + return + } id, present := ns.tsvMap[gob] if !present { id = len(ns.tsvs) @@ -225,6 +247,9 @@ func (ns *depNodesSerializer) serializeDepNodes(nodes []*DepNode) { Lineno: n.Lineno, }) ns.serializeDepNodes(n.Deps) + if ns.err != nil { + return + } } } @@ -236,7 +261,7 @@ func makeSerializableVars(vars Vars) (r map[string]serializableVar) { return r } -func makeSerializableGraph(g *DepGraph, roots []string) serializableGraph { +func makeSerializableGraph(g *DepGraph, roots []string) (serializableGraph, error) { ns := newDepNodesSerializer() ns.serializeDepNodes(g.nodes) v := makeSerializableVars(g.vars) @@ -248,12 +273,15 @@ func makeSerializableGraph(g *DepGraph, roots []string) serializableGraph { Roots: roots, AccessedMks: g.accessedMks, Exports: g.exports, - } + }, ns.err } func (jsonLoadSaver) Save(g *DepGraph, filename string, roots []string) error { startTime := time.Now() - sg := makeSerializableGraph(g, roots) + sg, err := makeSerializableGraph(g, roots) + if err != nil { + return err + } o, err := json.MarshalIndent(sg, " ", " ") if err != nil { return err @@ -285,12 +313,18 @@ func (gobLoadSaver) Save(g *DepGraph, filename string, roots []string) error { var sg serializableGraph { startTime := time.Now() - sg = makeSerializableGraph(g, roots) + sg, err = makeSerializableGraph(g, roots) + if err != nil { + return err + } logStats("gob serialize prepare time: %q", time.Since(startTime)) } { startTime := time.Now() - e.Encode(sg) + err = e.Encode(sg) + if err != nil { + return err + } logStats("gob serialize output time: %q", time.Since(startTime)) } err = f.Close() @@ -309,9 +343,9 @@ func cacheFilename(mk string, roots []string) string { return url.QueryEscape(filename) } -func saveCache(g *DepGraph, roots []string) { +func saveCache(g *DepGraph, roots []string) error { if len(g.accessedMks) == 0 { - panic("No Makefile is read") + return fmt.Errorf("no Makefile is read") } cacheFile := cacheFilename(g.accessedMks[0].Filename, roots) for _, mk := range g.accessedMks { @@ -320,101 +354,164 @@ func saveCache(g *DepGraph, roots []string) { if exists(cacheFile) { os.Remove(cacheFile) } - return + return nil } } - GOB.Save(g, cacheFile, roots) + return GOB.Save(g, cacheFile, roots) } -func deserializeSingleChild(sv serializableVar) Value { +func deserializeSingleChild(sv serializableVar) (Value, error) { if len(sv.Children) != 1 { - panic(fmt.Sprintf("unexpected number of children: %q", sv)) + return nil, fmt.Errorf("unexpected number of children: %q", sv) } return deserializeVar(sv.Children[0]) } -func deserializeVar(sv serializableVar) (r Value) { +func deserializeVar(sv serializableVar) (r Value, err error) { switch sv.Type { case "literal": - return literal(sv.V) + return literal(sv.V), nil case "tmpval": - return tmpval([]byte(sv.V)) + return tmpval([]byte(sv.V)), nil case "expr": var e expr for _, v := range sv.Children { - e = append(e, deserializeVar(v)) + dv, err := deserializeVar(v) + if err != nil { + return nil, err + } + e = append(e, dv) } - return e + return e, nil case "varref": - return &varref{varname: deserializeSingleChild(sv)} + dv, err := deserializeSingleChild(sv) + if err != nil { + return nil, err + } + return &varref{varname: dv}, nil case "paramref": v, err := strconv.Atoi(sv.V) if err != nil { - panic(err) + return nil, err } - return paramref(v) + return paramref(v), nil case "varsubst": - return varsubst{ - varname: deserializeVar(sv.Children[0]), - pat: deserializeVar(sv.Children[1]), - subst: deserializeVar(sv.Children[2]), + varname, err := deserializeVar(sv.Children[0]) + if err != nil { + return nil, err + } + pat, err := deserializeVar(sv.Children[1]) + if err != nil { + return nil, err } + subst, err := deserializeVar(sv.Children[2]) + if err != nil { + return nil, err + } + return varsubst{ + varname: varname, + pat: pat, + subst: subst, + }, nil case "func": - name := deserializeVar(sv.Children[0]).(literal) + dv, err := deserializeVar(sv.Children[0]) + if err != nil { + return nil, err + } + name, ok := dv.(literal) + if !ok { + return nil, fmt.Errorf("func name is not literal %s: %T", dv, dv) + } f := funcMap[string(name[1:])]() f.AddArg(name) for _, a := range sv.Children[1:] { - f.AddArg(deserializeVar(a)) + dv, err := deserializeVar(a) + if err != nil { + return nil, err + } + f.AddArg(dv) } - return f + return f, nil case "funcEvalAssign": + rhs, err := deserializeVar(sv.Children[2]) + if err != nil { + return nil, err + } return &funcEvalAssign{ lhs: sv.Children[0].V, op: sv.Children[1].V, - rhs: deserializeVar(sv.Children[2]), - } + rhs: rhs, + }, nil case "funcNop": - return &funcNop{expr: sv.V} + return &funcNop{expr: sv.V}, nil case "simple": return &simpleVar{ value: sv.V, origin: sv.Origin, - } + }, nil case "recursive": + expr, err := deserializeSingleChild(sv) + if err != nil { + return nil, err + } return &recursiveVar{ - expr: deserializeSingleChild(sv), + expr: expr, origin: sv.Origin, - } + }, nil case ":=", "=", "+=", "?=": + dv, err := deserializeSingleChild(sv) + if err != nil { + return nil, err + } + v, ok := dv.(Var) + if !ok { + return nil, fmt.Errorf("not var: target specific var %s %T", dv, dv) + } return &targetSpecificVar{ - v: deserializeSingleChild(sv).(Var), + v: v, op: sv.Type, - } + }, nil default: - panic(fmt.Sprintf("unknown serialized variable type: %q", sv)) + return nil, fmt.Errorf("unknown serialized variable type: %q", sv) } } -func deserializeVars(vars map[string]serializableVar) Vars { +func deserializeVars(vars map[string]serializableVar) (Vars, error) { r := make(Vars) for k, v := range vars { - r[k] = deserializeVar(v).(Var) + dv, err := deserializeVar(v) + if err != nil { + return nil, err + } + vv, ok := dv.(Var) + if !ok { + return nil, fmt.Errorf("not var: %s: %T", dv, dv) + } + r[k] = vv } - return r + return r, nil } -func deserializeNodes(g serializableGraph) (r []*DepNode) { +func deserializeNodes(g serializableGraph) (r []*DepNode, err error) { nodes := g.Nodes tsvs := g.Tsvs targets := g.Targets // Deserialize all TSVs first so that multiple rules can share memory. var tsvValues []Var for _, sv := range tsvs { - tsvValues = append(tsvValues, deserializeVar(sv.Value).(Var)) + dv, err := deserializeVar(sv.Value) + if err != nil { + return nil, err + } + vv, ok := dv.(Var) + if !ok { + return nil, fmt.Errorf("not var: %s %T", dv, dv) + } + tsvValues = append(tsvValues, vv) } nodeMap := make(map[string]*DepNode) @@ -450,20 +547,20 @@ func deserializeNodes(g serializableGraph) (r []*DepNode) { for _, o := range n.Deps { c, present := nodeMap[targets[o]] if !present { - panic(fmt.Sprintf("unknown target: %d (%s)", o, targets[o])) + return nil, fmt.Errorf("unknown target: %d (%s)", o, targets[o]) } d.Deps = append(d.Deps, c) } for _, o := range n.Parents { c, present := nodeMap[targets[o]] if !present { - panic(fmt.Sprintf("unknown target: %d (%s)", o, targets[o])) + return nil, fmt.Errorf("unknown target: %d (%s)", o, targets[o]) } d.Parents = append(d.Parents, c) } } - return r + return r, nil } func human(n int) string { @@ -576,18 +673,24 @@ func showSerializedGraphStats(g serializableGraph) { showSerializedAccessedMksStats(g.AccessedMks) } -func deserializeGraph(g serializableGraph) *DepGraph { +func deserializeGraph(g serializableGraph) (*DepGraph, error) { if LogFlag || StatsFlag { showSerializedGraphStats(g) } - nodes := deserializeNodes(g) - vars := deserializeVars(g.Vars) + nodes, err := deserializeNodes(g) + if err != nil { + return nil, err + } + vars, err := deserializeVars(g.Vars) + if err != nil { + return nil, err + } return &DepGraph{ nodes: nodes, vars: vars, accessedMks: g.AccessedMks, exports: g.Exports, - } + }, nil } func (jsonLoadSaver) Load(filename string) (*DepGraph, error) { @@ -604,7 +707,10 @@ func (jsonLoadSaver) Load(filename string) (*DepGraph, error) { if err != nil { return nil, err } - dg := deserializeGraph(g) + dg, err := deserializeGraph(g) + if err != nil { + return nil, err + } logStats("gob deserialize time: %q", time.Since(startTime)) return dg, nil } @@ -623,12 +729,15 @@ func (gobLoadSaver) Load(filename string) (*DepGraph, error) { if err != nil { return nil, err } - dg := deserializeGraph(g) + dg, err := deserializeGraph(g) + if err != nil { + return nil, err + } logStats("json deserialize time: %q", time.Since(startTime)) return dg, nil } -func loadCache(makefile string, roots []string) *DepGraph { +func loadCache(makefile string, roots []string) (*DepGraph, error) { startTime := time.Now() defer func() { logStats("Cache lookup time: %q", time.Since(startTime)) @@ -637,37 +746,37 @@ func loadCache(makefile string, roots []string) *DepGraph { filename := cacheFilename(makefile, roots) if !exists(filename) { logAlways("Cache not found") - return nil + return nil, fmt.Errorf("cache not found: %s", filename) } g, err := GOB.Load(filename) if err != nil { logAlways("Cache load error: %v", err) - return nil + return nil, err } for _, mk := range g.accessedMks { if mk.State != fileExists && mk.State != fileNotExists { - panic(fmt.Sprintf("Internal error: broken state: %d", mk.State)) + return nil, fmt.Errorf("internal error: broken state: %d", mk.State) } if mk.State == fileNotExists { if exists(mk.Filename) { logAlways("Cache expired: %s", mk.Filename) - return nil + return nil, fmt.Errorf("cache expired: %s", mk.Filename) } } else { c, err := ioutil.ReadFile(mk.Filename) if err != nil { logAlways("Cache expired: %s", mk.Filename) - return nil + return nil, fmt.Errorf("cache expired: %s", mk.Filename) } h := sha1.Sum(c) if !bytes.Equal(h[:], mk.Hash[:]) { logAlways("Cache expired: %s", mk.Filename) - return nil + return nil, fmt.Errorf("cache expired: %s", mk.Filename) } } } g.isCached = true logAlways("Cache found!") - return g + return g, nil } diff --git a/shellutil.go b/shellutil.go index 71a6d4e..29ce420 100644 --- a/shellutil.go +++ b/shellutil.go @@ -256,12 +256,16 @@ func rot13(buf []byte) { } } -func (f *funcShellAndroidRot13) Eval(w io.Writer, ev *Evaluator) { +func (f *funcShellAndroidRot13) Eval(w io.Writer, ev *Evaluator) error { abuf := newBuf() - fargs := ev.args(abuf, f.v) + fargs, err := ev.args(abuf, f.v) + if err != nil { + return err + } rot13(fargs[0]) w.Write(fargs[0]) freeBuf(abuf) + return nil } type funcShellAndroidFindFileInDir struct { @@ -269,24 +273,26 @@ type funcShellAndroidFindFileInDir struct { dir Value } -func (f *funcShellAndroidFindFileInDir) Eval(w io.Writer, ev *Evaluator) { +func (f *funcShellAndroidFindFileInDir) Eval(w io.Writer, ev *Evaluator) error { abuf := newBuf() - fargs := ev.args(abuf, f.dir) + fargs, err := ev.args(abuf, f.dir) + if err != nil { + return err + } dir := string(trimSpaceBytes(fargs[0])) freeBuf(abuf) logf("shellAndroidFindFileInDir %s => %s", f.dir.String(), dir) if strings.Contains(dir, "..") { logf("shellAndroidFindFileInDir contains ..: call original shell") - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } if !androidFindCache.ready() { logf("shellAndroidFindFileInDir androidFindCache is not ready: call original shell") - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } sw := ssvWriter{w: w} androidFindCache.findInDir(&sw, dir) + return nil } type funcShellAndroidFindExtFilesUnder struct { @@ -296,9 +302,12 @@ type funcShellAndroidFindExtFilesUnder struct { ext string } -func (f *funcShellAndroidFindExtFilesUnder) Eval(w io.Writer, ev *Evaluator) { +func (f *funcShellAndroidFindExtFilesUnder) Eval(w io.Writer, ev *Evaluator) error { abuf := newBuf() - fargs := ev.args(abuf, f.chdir, f.roots) + fargs, err := ev.args(abuf, f.chdir, f.roots) + if err != nil { + return err + } chdir := string(trimSpaceBytes(fargs[0])) var roots []string hasDotDot := false @@ -314,13 +323,11 @@ func (f *funcShellAndroidFindExtFilesUnder) Eval(w io.Writer, ev *Evaluator) { logf("shellAndroidFindExtFilesUnder %s,%s => %s,%s", f.chdir.String(), f.roots.String(), chdir, roots) if strings.Contains(chdir, "..") || hasDotDot { logf("shellAndroidFindExtFilesUnder contains ..: call original shell") - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } if !androidFindCache.ready() { logf("shellAndroidFindExtFilesUnder androidFindCache is not ready: call original shell") - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } buf := newBuf() sw := ssvWriter{w: buf} @@ -328,12 +335,12 @@ func (f *funcShellAndroidFindExtFilesUnder) Eval(w io.Writer, ev *Evaluator) { if !androidFindCache.findExtFilesUnder(&sw, chdir, root, f.ext) { freeBuf(buf) logf("shellAndroidFindExtFilesUnder androidFindCache couldn't handle: call original shell") - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } } w.Write(buf.Bytes()) freeBuf(buf) + return nil } type funcShellAndroidFindJavaResourceFileGroup struct { @@ -341,24 +348,26 @@ type funcShellAndroidFindJavaResourceFileGroup struct { dir Value } -func (f *funcShellAndroidFindJavaResourceFileGroup) Eval(w io.Writer, ev *Evaluator) { +func (f *funcShellAndroidFindJavaResourceFileGroup) Eval(w io.Writer, ev *Evaluator) error { abuf := newBuf() - fargs := ev.args(abuf, f.dir) + fargs, err := ev.args(abuf, f.dir) + if err != nil { + return err + } dir := string(trimSpaceBytes(fargs[0])) freeBuf(abuf) logf("shellAndroidFindJavaResourceFileGroup %s => %s", f.dir.String(), dir) if strings.Contains(dir, "..") { logf("shellAndroidFindJavaResourceFileGroup contains ..: call original shell") - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } if !androidFindCache.ready() { logf("shellAndroidFindJavaResourceFileGroup androidFindCache is not ready: call original shell") - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } sw := ssvWriter{w: w} androidFindCache.findJavaResourceFileGroup(&sw, dir) + return nil } type funcShellAndroidFindleaves struct { @@ -369,18 +378,20 @@ type funcShellAndroidFindleaves struct { mindepth int } -func (f *funcShellAndroidFindleaves) Eval(w io.Writer, ev *Evaluator) { +func (f *funcShellAndroidFindleaves) Eval(w io.Writer, ev *Evaluator) error { if !androidFindCache.leavesReady() { logf("shellAndroidFindleaves androidFindCache is not ready: call original shell") - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } abuf := newBuf() var params []Value params = append(params, f.name) params = append(params, f.dirlist) params = append(params, f.prunes...) - fargs := ev.args(abuf, params...) + fargs, err := ev.args(abuf, params...) + if err != nil { + return err + } name := string(trimSpaceBytes(fargs[0])) var dirs []string ws := newWordScanner(fargs[1]) @@ -388,8 +399,7 @@ func (f *funcShellAndroidFindleaves) Eval(w io.Writer, ev *Evaluator) { dir := string(ws.Bytes()) if strings.Contains(dir, "..") { logf("shellAndroidFindleaves contains .. in %s: call original shell", dir) - f.funcShell.Eval(w, ev) - return + return f.funcShell.Eval(w, ev) } dirs = append(dirs, dir) } @@ -403,9 +413,11 @@ func (f *funcShellAndroidFindleaves) Eval(w io.Writer, ev *Evaluator) { for _, dir := range dirs { androidFindCache.findleaves(&sw, dir, name, prunes, f.mindepth) } + return nil } var ( + // ShellDateTimestamp is an timestamp used for $(shell date). ShellDateTimestamp time.Time shellDateFormatRef = map[string]string{ "%Y": "2006", @@ -442,6 +454,7 @@ func compactShellDate(sh *funcShell, v []Value) Value { } } -func (f *funcShellDate) Eval(w io.Writer, ev *Evaluator) { +func (f *funcShellDate) Eval(w io.Writer, ev *Evaluator) error { fmt.Fprint(w, ShellDateTimestamp.Format(f.format)) + return nil } @@ -39,10 +39,12 @@ const ( var traceEvent traceEventT +// TraceEventStart starts trace event. func TraceEventStart(f io.WriteCloser) { traceEvent.start(f) } +// TraceEventStop stops trace event. func TraceEventStop() { traceEvent.stop() } @@ -147,6 +149,7 @@ func (s *statsT) add(name, v string, t time.Time) { s.mu.Unlock() } +// DumpStats dumps statistics collected if EvalStatsFlag is set. func DumpStats() { if !EvalStatsFlag { return diff --git a/testcase/gen_testcase_parse_benchmark.go b/testcase/gen_testcase_parse_benchmark.go index a1d58ee..9e8c8ba 100644 --- a/testcase/gen_testcase_parse_benchmark.go +++ b/testcase/gen_testcase_parse_benchmark.go @@ -45,7 +45,10 @@ func BenchmarkTestcaseParse{{.Name}}(b *testing.B) { mk := string(data) b.ResetTimer() for i := 0; i < b.N; i++ { - parseMakefileString(mk, {{.Filename | printf "%q"}}, 0) + parseMakefileString(mk, srcpos{ + filename: {{.Filename | printf "%q"}}, + lineno: 0, + }) } } `)) @@ -20,10 +20,11 @@ import ( "io" ) +// Var is an interface of make variable. type Var interface { Value - Append(*Evaluator, string) Var - AppendVar(*Evaluator, Value) Var + Append(*Evaluator, string) (Var, error) + AppendVar(*Evaluator, Value) (Var, error) Flavor() string Origin() string IsDefined() bool @@ -34,17 +35,25 @@ type targetSpecificVar struct { op string } -func (v *targetSpecificVar) Append(ev *Evaluator, s string) Var { +func (v *targetSpecificVar) Append(ev *Evaluator, s string) (Var, error) { + nv, err := v.v.Append(ev, s) + if err != nil { + return nil, err + } return &targetSpecificVar{ - v: v.v.Append(ev, s), + v: nv, op: v.op, - } + }, nil } -func (v *targetSpecificVar) AppendVar(ev *Evaluator, v2 Value) Var { +func (v *targetSpecificVar) AppendVar(ev *Evaluator, v2 Value) (Var, error) { + nv, err := v.v.AppendVar(ev, v2) + if err != nil { + return nil, err + } return &targetSpecificVar{ - v: v.v.AppendVar(ev, v2), + v: nv, op: v.op, - } + }, nil } func (v *targetSpecificVar) Flavor() string { return v.v.Flavor() @@ -61,8 +70,8 @@ func (v *targetSpecificVar) String() string { return v.v.String() // return v.v.String() + " (op=" + v.op + ")" } -func (v *targetSpecificVar) Eval(w io.Writer, ev *Evaluator) { - v.v.Eval(w, ev) +func (v *targetSpecificVar) Eval(w io.Writer, ev *Evaluator) error { + return v.v.Eval(w, ev) } func (v *targetSpecificVar) serialize() serializableVar { @@ -72,10 +81,10 @@ func (v *targetSpecificVar) serialize() serializableVar { } } -func (v *targetSpecificVar) dump(w io.Writer) { - dumpByte(w, valueTypeTSV) - dumpString(w, v.op) - v.v.dump(w) +func (v *targetSpecificVar) dump(d *dumpbuf) { + d.Byte(valueTypeTSV) + d.Str(v.op) + v.v.dump(d) } type simpleVar struct { @@ -88,8 +97,9 @@ func (v *simpleVar) Origin() string { return v.origin } func (v *simpleVar) IsDefined() bool { return true } func (v *simpleVar) String() string { return v.value } -func (v *simpleVar) Eval(w io.Writer, ev *Evaluator) { +func (v *simpleVar) Eval(w io.Writer, ev *Evaluator) error { io.WriteString(w, v.value) + return nil } func (v *simpleVar) serialize() serializableVar { return serializableVar{ @@ -98,34 +108,40 @@ func (v *simpleVar) serialize() serializableVar { Origin: v.origin, } } -func (v *simpleVar) dump(w io.Writer) { - dumpByte(w, valueTypeSimple) - dumpString(w, v.value) - dumpString(w, v.origin) +func (v *simpleVar) dump(d *dumpbuf) { + d.Byte(valueTypeSimple) + d.Str(v.value) + d.Str(v.origin) } -func (v *simpleVar) Append(ev *Evaluator, s string) Var { +func (v *simpleVar) Append(ev *Evaluator, s string) (Var, error) { val, _, err := parseExpr([]byte(s), nil, false) if err != nil { - panic(err) + return nil, err } abuf := newBuf() io.WriteString(abuf, v.value) writeByte(abuf, ' ') - val.Eval(abuf, ev) + err = val.Eval(abuf, ev) + if err != nil { + return nil, err + } v.value = abuf.String() freeBuf(abuf) - return v + return v, nil } -func (v *simpleVar) AppendVar(ev *Evaluator, val Value) Var { +func (v *simpleVar) AppendVar(ev *Evaluator, val Value) (Var, error) { abuf := newBuf() io.WriteString(abuf, v.value) writeByte(abuf, ' ') - val.Eval(abuf, ev) + err := val.Eval(abuf, ev) + if err != nil { + return nil, err + } v.value = abuf.String() freeBuf(abuf) - return v + return v, nil } type automaticVar struct { @@ -137,34 +153,41 @@ func (v *automaticVar) Origin() string { return "automatic" } func (v *automaticVar) IsDefined() bool { return true } func (v *automaticVar) String() string { return string(v.value) } -func (v *automaticVar) Eval(w io.Writer, ev *Evaluator) { +func (v *automaticVar) Eval(w io.Writer, ev *Evaluator) error { w.Write(v.value) + return nil } func (v *automaticVar) serialize() serializableVar { - panic(fmt.Sprintf("cannnot serialize automatic var:%s", v.value)) + return serializableVar{Type: ""} } -func (v *automaticVar) dump(w io.Writer) { - panic(fmt.Sprintf("cannnot dump automatic var:%s", v.value)) +func (v *automaticVar) dump(d *dumpbuf) { + d.err = fmt.Errorf("cannnot dump automatic var:%s", v.value) } -func (v *automaticVar) Append(ev *Evaluator, s string) Var { +func (v *automaticVar) Append(ev *Evaluator, s string) (Var, error) { val, _, err := parseExpr([]byte(s), nil, false) if err != nil { - panic(err) + return nil, err } buf := bytes.NewBuffer(v.value) buf.WriteByte(' ') - val.Eval(buf, ev) + err = val.Eval(buf, ev) + if err != nil { + return nil, err + } v.value = buf.Bytes() - return v + return v, nil } -func (v *automaticVar) AppendVar(ev *Evaluator, val Value) Var { +func (v *automaticVar) AppendVar(ev *Evaluator, val Value) (Var, error) { buf := bytes.NewBuffer(v.value) buf.WriteByte(' ') - val.Eval(buf, ev) + err := val.Eval(buf, ev) + if err != nil { + return nil, err + } v.value = buf.Bytes() - return v + return v, nil } type recursiveVar struct { @@ -177,8 +200,9 @@ func (v *recursiveVar) Origin() string { return v.origin } func (v *recursiveVar) IsDefined() bool { return true } func (v *recursiveVar) String() string { return v.expr.String() } -func (v *recursiveVar) Eval(w io.Writer, ev *Evaluator) { +func (v *recursiveVar) Eval(w io.Writer, ev *Evaluator) error { v.expr.Eval(w, ev) + return nil } func (v *recursiveVar) serialize() serializableVar { return serializableVar{ @@ -187,13 +211,13 @@ func (v *recursiveVar) serialize() serializableVar { Origin: v.origin, } } -func (v *recursiveVar) dump(w io.Writer) { - dumpByte(w, valueTypeRecursive) - v.expr.dump(w) - dumpString(w, v.origin) +func (v *recursiveVar) dump(d *dumpbuf) { + d.Byte(valueTypeRecursive) + v.expr.dump(d) + d.Str(v.origin) } -func (v *recursiveVar) Append(_ *Evaluator, s string) Var { +func (v *recursiveVar) Append(_ *Evaluator, s string) (Var, error) { var exp expr if e, ok := v.expr.(expr); ok { exp = append(e, literal(" ")) @@ -202,7 +226,7 @@ func (v *recursiveVar) Append(_ *Evaluator, s string) Var { } sv, _, err := parseExpr([]byte(s), nil, true) if err != nil { - panic(err) + return nil, err } if aexpr, ok := sv.(expr); ok { exp = append(exp, aexpr...) @@ -210,20 +234,20 @@ func (v *recursiveVar) Append(_ *Evaluator, s string) Var { exp = append(exp, sv) } v.expr = exp - return v + return v, nil } -func (v *recursiveVar) AppendVar(ev *Evaluator, val Value) Var { +func (v *recursiveVar) AppendVar(ev *Evaluator, val Value) (Var, error) { var buf bytes.Buffer buf.WriteString(v.expr.String()) buf.WriteByte(' ') buf.WriteString(val.String()) e, _, err := parseExpr(buf.Bytes(), nil, true) if err != nil { - panic(err) + return nil, err } v.expr = e - return v + return v, nil } type undefinedVar struct{} @@ -232,25 +256,28 @@ func (undefinedVar) Flavor() string { return "undefined" } func (undefinedVar) Origin() string { return "undefined" } func (undefinedVar) IsDefined() bool { return false } func (undefinedVar) String() string { return "" } -func (undefinedVar) Eval(_ io.Writer, _ *Evaluator) { +func (undefinedVar) Eval(_ io.Writer, _ *Evaluator) error { + return nil } func (undefinedVar) serialize() serializableVar { return serializableVar{Type: "undefined"} } -func (undefinedVar) dump(w io.Writer) { - dumpByte(w, valueTypeUndefined) +func (undefinedVar) dump(d *dumpbuf) { + d.Byte(valueTypeUndefined) } -func (undefinedVar) Append(*Evaluator, string) Var { - return undefinedVar{} +func (undefinedVar) Append(*Evaluator, string) (Var, error) { + return undefinedVar{}, nil } -func (undefinedVar) AppendVar(_ *Evaluator, val Value) Var { - return undefinedVar{} +func (undefinedVar) AppendVar(_ *Evaluator, val Value) (Var, error) { + return undefinedVar{}, nil } +// Vars is a map for make variables. type Vars map[string]Var +// Lookup looks up named make variable. func (vt Vars) Lookup(name string) Var { if v, ok := vt[name]; ok { return v @@ -258,6 +285,7 @@ func (vt Vars) Lookup(name string) Var { return undefinedVar{} } +// Assign assigns v to name. func (vt Vars) Assign(name string, v Var) { switch v.Origin() { case "override", "environment override": @@ -271,12 +299,14 @@ func (vt Vars) Assign(name string, v Var) { vt[name] = v } +// NewVars creates new Vars. func NewVars(vt Vars) Vars { r := make(Vars) r.Merge(vt) return r } +// Merge merges vt2 into vt. func (vt Vars) Merge(vt2 Vars) { for k, v := range vt2 { vt[k] = v @@ -45,8 +45,9 @@ type runner struct { } type jobResult struct { - j *job - w *worker + j *job + w *worker + err error } type newDep struct { @@ -99,8 +100,8 @@ func (w *worker) Run() { for !done { select { case j := <-w.jobChan: - j.build() - w.wm.ReportResult(w, j) + err := j.build() + w.wm.ReportResult(w, j, err) case done = <-w.waitChan: } } @@ -116,19 +117,22 @@ func (w *worker) Wait() { <-w.doneChan } -func evalCmd(ev *Evaluator, r runner, s string) []runner { +func evalCmd(ev *Evaluator, r runner, s string) ([]runner, error) { r = newRunner(r, s) if strings.IndexByte(r.cmd, '$') < 0 { // fast path - return []runner{r} + return []runner{r}, nil } // TODO(ukai): parse once more earlier? expr, _, err := parseExpr([]byte(r.cmd), nil, false) if err != nil { - panic(fmt.Errorf("parse cmd %q: %v", r.cmd, err)) + return nil, ev.errorf("parse cmd %q: %v", r.cmd, err) } buf := newBuf() - expr.Eval(buf, ev) + err = expr.Eval(buf, ev) + if err != nil { + return nil, err + } cmds := buf.String() freeBuf(buf) var runners []runner @@ -140,7 +144,7 @@ func evalCmd(ev *Evaluator, r runner, s string) []runner { runners = append(runners, newRunner(r, cmd)) } } - return runners + return runners, nil } func newRunner(r runner, s string) runner { @@ -189,9 +193,9 @@ func (r runner) run(output string) error { return err } -func (j *job) createRunners() []runner { - runners, _ := j.ex.createRunners(j.n, false) - return runners +func (j *job) createRunners() ([]runner, error) { + runners, _, err := j.ex.createRunners(j.n, false) + return runners, err } // TODO(ukai): use time.Time? @@ -203,7 +207,7 @@ func getTimestamp(filename string) int64 { return st.ModTime().Unix() } -func (j *job) build() { +func (j *job) build() error { if j.n.IsPhony { j.outputTs = -2 // trigger cmd even if all inputs don't exist. } else { @@ -212,26 +216,28 @@ func (j *job) build() { if !j.n.HasRule { if j.outputTs >= 0 || j.n.IsPhony { - return + return nil } if len(j.parents) == 0 { - errorNoLocationExit("*** No rule to make target %q.", j.n.Output) - } else { - errorNoLocationExit("*** No rule to make target %q, needed by %q.", j.n.Output, j.parents[0].n.Output) + return fmt.Errorf("*** No rule to make target %q.", j.n.Output) } - errorNoLocationExit("no rule to make target %q", j.n.Output) + return fmt.Errorf("*** No rule to make target %q, needed by %q.", j.n.Output, j.parents[0].n.Output) } if j.outputTs >= j.depsTs { // TODO: stats. - return + return nil } - for _, r := range j.createRunners() { + rr, err := j.createRunners() + if err != nil { + return err + } + for _, r := range rr { err := r.run(j.n.Output) if err != nil { exit := exitStatus(err) - errorNoLocationExit("[%s] Error %d: %v", j.n.Output, exit, err) + return fmt.Errorf("[%s] Error %d: %v", j.n.Output, exit, err) } } @@ -243,21 +249,26 @@ func (j *job) build() { j.outputTs = time.Now().Unix() } } + return nil } -func (wm *workerManager) handleJobs() { +func (wm *workerManager) handleJobs() error { for { if wm.para == nil && len(wm.freeWorkers) == 0 { - return + return nil } if wm.readyQueue.Len() == 0 { - return + return nil } j := heap.Pop(&wm.readyQueue).(*job) logf("run: %s", j.n.Output) if wm.para != nil { - j.runners = j.createRunners() + var err error + j.runners, err = j.createRunners() + if err != nil { + return err + } if len(j.runners) == 0 { wm.updateParents(j) wm.finishCnt++ @@ -294,7 +305,7 @@ type workerManager struct { resultChan chan jobResult newDepChan chan newDep waitChan chan bool - doneChan chan bool + doneChan chan error freeWorkers []*worker busyWorkers map[*worker]bool ex *Executor @@ -305,21 +316,25 @@ type workerManager struct { finishCnt int } -func newWorkerManager(numJobs int, paraPath string) *workerManager { +func newWorkerManager(numJobs int, paraPath string) (*workerManager, error) { wm := &workerManager{ maxJobs: numJobs, jobChan: make(chan *job), resultChan: make(chan jobResult), newDepChan: make(chan newDep), waitChan: make(chan bool), - doneChan: make(chan bool), + doneChan: make(chan error), busyWorkers: make(map[*worker]bool), } if paraPath != "" { wm.runnings = make(map[string]*job) wm.paraChan = make(chan *paraResult) - wm.para = newParaWorker(wm.paraChan, numJobs, paraPath) + var err error + wm.para, err = newParaWorker(wm.paraChan, numJobs, paraPath) + if err != nil { + return nil, err + } go wm.para.Run() } else { wm.busyWorkers = make(map[*worker]bool) @@ -331,7 +346,7 @@ func newWorkerManager(numJobs int, paraPath string) *workerManager { } heap.Init(&wm.readyQueue) go wm.Run() - return wm + return wm, nil } func exitStatus(err error) int { @@ -363,7 +378,7 @@ func (wm *workerManager) handleNewDep(j *job, neededBy *job) { if j.numDeps < 0 { neededBy.numDeps-- if neededBy.id > 0 { - panic("already in WM... can this happen?") + panic("FIXME: already in WM... can this happen?") } } else { j.parents = append(j.parents, neededBy) @@ -372,6 +387,8 @@ func (wm *workerManager) handleNewDep(j *job, neededBy *job) { func (wm *workerManager) Run() { done := false + var err error +Loop: for wm.hasTodo() || len(wm.busyWorkers) > 0 || len(wm.runnings) > 0 || !done { select { case j := <-wm.jobChan: @@ -385,6 +402,10 @@ func (wm *workerManager) Run() { wm.freeWorkers = append(wm.freeWorkers, jr.w) wm.updateParents(jr.j) wm.finishCnt++ + if jr.err != nil { + err = jr.err + break Loop + } case af := <-wm.newDepChan: wm.handleNewDep(af.j, af.neededBy) logf("dep: %s (%d) %s", af.neededBy.n.Output, af.neededBy.numDeps, af.j.n.Output) @@ -406,7 +427,10 @@ func (wm *workerManager) Run() { } case done = <-wm.waitChan: } - wm.handleJobs() + err = wm.handleJobs() + if err != nil { + break Loop + } if wm.para != nil { numBusy := len(wm.runnings) @@ -421,7 +445,10 @@ func (wm *workerManager) Run() { if wm.para != nil { logf("Wait for para to finish") - wm.para.Wait() + err := wm.para.Wait() + if err != nil { + logf("para failed: %v", err) + } } else { for _, w := range wm.freeWorkers { w.Wait() @@ -430,22 +457,22 @@ func (wm *workerManager) Run() { w.Wait() } } - wm.doneChan <- true + wm.doneChan <- err } func (wm *workerManager) PostJob(j *job) { wm.jobChan <- j } -func (wm *workerManager) ReportResult(w *worker, j *job) { - wm.resultChan <- jobResult{w: w, j: j} +func (wm *workerManager) ReportResult(w *worker, j *job, err error) { + wm.resultChan <- jobResult{w: w, j: j, err: err} } func (wm *workerManager) ReportNewDep(j *job, neededBy *job) { wm.newDepChan <- newDep{j: j, neededBy: neededBy} } -func (wm *workerManager) Wait() { +func (wm *workerManager) Wait() error { wm.waitChan <- true - <-wm.doneChan + return <-wm.doneChan } |