blob: 736c7e1240445cd26a044f9257957188e1654f82 [file] [log] [blame]
// Copyright 2015 Google Inc. All rights reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package kati
import (
"bytes"
"crypto/sha1"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
)
type fileState int
const (
fileExists fileState = iota
fileNotExists
fileInconsistent // Modified during kati is running.
)
type accessedMakefile struct {
Filename string
Hash [sha1.Size]byte
State fileState
}
type accessCache struct {
mu sync.Mutex
m map[string]*accessedMakefile
}
func newAccessCache() *accessCache {
return &accessCache{
m: make(map[string]*accessedMakefile),
}
}
func (ac *accessCache) update(fn string, hash [sha1.Size]byte, st fileState) string {
if ac == nil {
return ""
}
ac.mu.Lock()
defer ac.mu.Unlock()
rm, present := ac.m[fn]
if present {
switch rm.State {
case fileExists:
if st != fileExists {
return fmt.Sprintf("%s was removed after the previous read", fn)
} else if !bytes.Equal(hash[:], rm.Hash[:]) {
ac.m[fn].State = fileInconsistent
return fmt.Sprintf("%s was modified after the previous read", fn)
}
return ""
case fileNotExists:
if st != fileNotExists {
ac.m[fn].State = fileInconsistent
return fmt.Sprintf("%s was created after the previous read", fn)
}
case fileInconsistent:
return ""
}
return ""
}
ac.m[fn] = &accessedMakefile{
Filename: fn,
Hash: hash,
State: st,
}
return ""
}
func (ac *accessCache) Slice() []*accessedMakefile {
if ac == nil {
return nil
}
ac.mu.Lock()
defer ac.mu.Unlock()
r := []*accessedMakefile{}
for _, v := range ac.m {
r = append(r, v)
}
return r
}
type evalResult struct {
vars Vars
rules []*rule
ruleVars map[string]Vars
accessedMks []*accessedMakefile
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
outRules []*rule
outRuleVars map[string]Vars
vars Vars
lastRule *rule
currentScope Vars
avoidIO bool
hasIO bool
cache *accessCache
exports map[string]bool
srcpos
}
// NewEvaluator creates new Evaluator.
func NewEvaluator(vars map[string]Var) *Evaluator {
return &Evaluator{
outVars: make(Vars),
vars: vars,
outRuleVars: make(map[string]Vars),
exports: make(map[string]bool),
}
}
func (ev *Evaluator) args(buf *buffer, args ...Value) ([][]byte, error) {
pos := make([]int, 0, len(args))
for _, arg := range args {
err := arg.Eval(buf, ev)
if err != nil {
return nil, err
}
pos = append(pos, buf.Len())
}
v := buf.Bytes()
buf.args = buf.args[:0]
s := 0
for _, p := range pos {
buf.args = append(buf.args, v[s:p])
s = p
}
return buf.args, nil
}
func (ev *Evaluator) evalAssign(ast *assignAST) error {
ev.lastRule = nil
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 == "" {
return ast.errorf("*** empty variable name.")
}
ev.outVars.Assign(lhs, rhs)
return nil
}
func (ev *Evaluator) evalAssignAST(ast *assignAST) (string, Var, error) {
ev.srcpos = ast.srcpos
var lhs string
switch v := ast.lhs.(type) {
case literal:
lhs = string(v)
case tmpval:
lhs = string(v)
default:
buf := newBuf()
err := v.Eval(buf, ev)
if err != nil {
return "", nil, err
}
lhs = string(trimSpaceBytes(buf.Bytes()))
freeBuf(buf)
}
rhs, err := ast.evalRHS(ev, lhs)
if err != nil {
return "", nil, err
}
return lhs, rhs, nil
}
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, err := ev.evalAssignAST(assign)
if err != nil {
return err
}
if LogFlag {
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) error {
ev.lastRule = nil
ev.srcpos = ast.srcpos
lexpr := ast.expr
buf := newBuf()
err := lexpr.Eval(buf, ev)
if err != nil {
return err
}
line := buf.Bytes()
if ast.term == '=' {
line = append(line, ast.afterTerm...)
}
if LogFlag {
logf("rule? %q=>%q", ast.expr, line)
}
// See semicolon.mk.
if len(bytes.TrimRight(line, " \t\n;")) == 0 {
freeBuf(buf)
return nil
}
r := &rule{srcpos: ast.srcpos}
assign, err := r.parse(line)
if err != nil {
return ast.error(err)
}
freeBuf(buf)
if LogFlag {
logf("rule %q => outputs:%q, inputs:%q", line, r.outputs, r.inputs)
}
// TODO: Pretty print.
//logf("RULE: %s=%s (%d commands)", lhs, rhs, len(cmds))
if assign != nil {
if ast.term == ';' {
nexpr, _, err := parseExpr(ast.afterTerm, nil, false)
if err != nil {
return ast.errorf("parse error: %q: %v", string(ast.afterTerm), err)
}
lexpr = expr{lexpr, nexpr}
buf = newBuf()
err = lexpr.Eval(buf, ev)
if err != nil {
return err
}
assign, err = r.parse(buf.Bytes())
if err != nil {
return ast.error(err)
}
freeBuf(buf)
}
for _, output := range r.outputs {
ev.setTargetSpecificVar(assign, output)
}
for _, output := range r.outputPatterns {
ev.setTargetSpecificVar(assign, output.String())
}
return nil
}
if ast.term == ';' {
r.cmds = append(r.cmds, string(ast.afterTerm[1:]))
}
if LogFlag {
logf("rule outputs:%q cmds:%q", r.outputs, r.cmds)
}
ev.lastRule = r
ev.outRules = append(ev.outRules, r)
return nil
}
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.srcpos)
if err != nil {
return ast.errorf("parse failed: %q: %v", line, err)
}
if len(mk.stmts) == 1 && mk.stmts[0].(*assignAST) != nil {
err = ev.eval(mk.stmts[0])
if err != nil {
return err
}
}
return nil
}
// Or, a comment is OK.
if strings.TrimSpace(ast.cmd)[0] == '#' {
return nil
}
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)
if v.IsDefined() {
return v
}
}
v := ev.outVars.Lookup(name)
if v.IsDefined() {
return v
}
return ev.vars.Lookup(name)
}
func (ev *Evaluator) lookupVarInCurrentScope(name string) Var {
if ev.currentScope != nil {
v := ev.currentScope.Lookup(name)
return v
}
v := ev.outVars.Lookup(name)
if v.IsDefined() {
return v
}
return ev.vars.Lookup(name)
}
// 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, error) {
var buf bytes.Buffer
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 {
te := traceEvent.begin("include", literal(fname), traceEventMain)
defer func() {
traceEvent.end(te)
}()
var err error
makefileList := ev.outVars.Lookup("MAKEFILE_LIST")
makefileList, err = makefileList.Append(ev, mk.filename)
if err != nil {
return err
}
ev.outVars.Assign("MAKEFILE_LIST", makefileList)
for _, stmt := range mk.stmts {
err = ev.eval(stmt)
if err != nil {
return err
}
}
return nil
}
func (ev *Evaluator) evalInclude(ast *includeAST) error {
ev.lastRule = nil
ev.srcpos = ast.srcpos
logf("%s include %q", ev.srcpos, ast.expr)
v, _, err := parseExpr([]byte(ast.expr), nil, false)
if err != nil {
return ast.errorf("parse failed: %q: %v", ast.expr, err)
}
var buf bytes.Buffer
err = v.Eval(&buf, ev)
if err != nil {
return ast.errorf("%v", err)
}
pats := splitSpaces(buf.String())
buf.Reset()
var files []string
for _, pat := range pats {
if strings.Contains(pat, "*") || strings.Contains(pat, "?") {
matched, err := filepath.Glob(pat)
if err != nil {
return ast.errorf("glob error: %s: %v", pat, err)
}
files = append(files, matched...)
} else {
files = append(files, pat)
}
}
for _, fn := range files {
fn = trimLeadingCurdir(fn)
if IgnoreOptionalInclude != "" && ast.op == "-include" && matchPattern(fn, IgnoreOptionalInclude) {
continue
}
mk, hash, err := makefileCache.parse(fn)
if os.IsNotExist(err) {
if ast.op == "include" {
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.srcpos, "%s", msg)
}
err = ev.evalIncludeFile(fn, mk)
if err != nil {
return err
}
}
return nil
}
func (ev *Evaluator) evalIf(iast *ifAST) error {
var isTrue bool
switch iast.op {
case "ifdef", "ifndef":
expr := iast.lhs
buf := newBuf()
err := expr.Eval(buf, ev)
if err != nil {
return iast.errorf("%v\n expr:%s", err, expr)
}
v := ev.LookupVar(buf.String())
buf.Reset()
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)
isTrue = (val > 0) == (iast.op == "ifdef")
if LogFlag {
logf("%s lhs=%q value=%q => %t", iast.op, iast.lhs, value, isTrue)
}
case "ifeq", "ifneq":
lexpr := iast.lhs
rexpr := iast.rhs
buf := newBuf()
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)
isTrue = (lhs == rhs) == (iast.op == "ifeq")
if LogFlag {
logf("%s lhs=%q %q rhs=%q %q => %t", iast.op, iast.lhs, lhs, iast.rhs, rhs, isTrue)
}
default:
return iast.errorf("unknown if statement: %q", iast.op)
}
var stmts []ast
if isTrue {
stmts = iast.trueStmts
} else {
stmts = iast.falseStmts
}
for _, stmt := range stmts {
err := ev.eval(stmt)
if err != nil {
return err
}
}
return nil
}
func (ev *Evaluator) evalExport(ast *exportAST) error {
ev.lastRule = nil
ev.srcpos = ast.srcpos
v, _, err := parseExpr(ast.expr, nil, false)
if err != nil {
return ast.errorf("failed to parse: %q: %v", string(ast.expr), err)
}
var buf bytes.Buffer
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) error {
return stmt.eval(ev)
}
func eval(mk makefile, vars Vars, useCache bool) (er *evalResult, err error) {
ev := NewEvaluator(vars)
if useCache {
ev.cache = newAccessCache()
}
makefileList := vars.Lookup("MAKEFILE_LIST")
if !makefileList.IsDefined() {
makefileList = &simpleVar{value: "", origin: "file"}
}
makefileList, err = makefileList.Append(ev, mk.filename)
if err != nil {
return nil, err
}
ev.outVars.Assign("MAKEFILE_LIST", makefileList)
for _, stmt := range mk.stmts {
err = ev.eval(stmt)
if err != nil {
return nil, err
}
}
return &evalResult{
vars: ev.outVars,
rules: ev.outRules,
ruleVars: ev.outRuleVars,
accessedMks: ev.cache.Slice(),
exports: ev.exports,
}, nil
}