// 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" "encoding/binary" "encoding/gob" "encoding/json" "fmt" "io" "io/ioutil" "net/url" "os" "sort" "strconv" "strings" "time" "github.com/golang/glog" ) const ( valueTypeRecursive = 'R' valueTypeSimple = 'S' valueTypeTSV = 'T' valueTypeUndefined = 'U' valueTypeAssign = 'a' valueTypeExpr = 'e' valueTypeFunc = 'f' valueTypeLiteral = 'l' valueTypeNop = 'n' valueTypeParamref = 'p' valueTypeVarref = 'r' valueTypeVarsubst = 's' valueTypeTmpval = 't' ) // JSON is a json loader/saver. var JSON LoadSaver // GOB is a gob loader/saver. var GOB LoadSaver func init() { JSON = jsonLoadSaver{} GOB = gobLoadSaver{} } type jsonLoadSaver struct{} type gobLoadSaver struct{} 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 (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 (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 (d *dumpbuf) Byte(b byte) { if d.err != nil { return } d.err = writeByte(&d.w, b) } type serializableVar struct { Type string V string Origin string Children []serializableVar } type serializableDepNode struct { Output int Cmds []string Deps []int OrderOnlys []int Parents []int HasRule bool IsPhony bool ActualInputs []int TargetSpecificVars []int Filename string Lineno int } type serializableTargetSpecificVar struct { Name string Value serializableVar } type serializableGraph struct { Nodes []*serializableDepNode Vars map[string]serializableVar Tsvs []serializableTargetSpecificVar Targets []string Roots []string AccessedMks []*accessedMakefile Exports map[string]bool } func encGob(v interface{}) (string, error) { var buf bytes.Buffer e := gob.NewEncoder(&buf) err := e.Encode(v) if err != nil { return "", err } return buf.String(), nil } 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 { nodes []*serializableDepNode tsvs []serializableTargetSpecificVar tsvMap map[string]int targets []string targetMap map[string]int done map[string]bool err error } func newDepNodesSerializer() *depNodesSerializer { return &depNodesSerializer{ tsvMap: make(map[string]int), targetMap: make(map[string]int), done: make(map[string]bool), } } func (ns *depNodesSerializer) serializeTarget(t string) int { id, present := ns.targetMap[t] if present { return id } id = len(ns.targets) ns.targetMap[t] = id ns.targets = append(ns.targets, t) return id } func (ns *depNodesSerializer) serializeDepNodes(nodes []*DepNode) { if ns.err != nil { return } for _, n := range nodes { if ns.done[n.Output] { continue } ns.done[n.Output] = true var deps []int for _, d := range n.Deps { deps = append(deps, ns.serializeTarget(d.Output)) } var orderonlys []int for _, d := range n.OrderOnlys { orderonlys = append(orderonlys, ns.serializeTarget(d.Output)) } var parents []int for _, d := range n.Parents { parents = append(parents, ns.serializeTarget(d.Output)) } var actualInputs []int for _, i := range n.ActualInputs { actualInputs = append(actualInputs, ns.serializeTarget(i)) } // Sort keys for consistent serialization. var tsvKeys []string for k := range n.TargetSpecificVars { tsvKeys = append(tsvKeys, k) } sort.Strings(tsvKeys) var vars []int for _, k := range tsvKeys { v := n.TargetSpecificVars[k] sv := serializableTargetSpecificVar{Name: k, Value: v.serialize()} //gob := encGob(sv) gob, err := encVar(k, v) if err != nil { ns.err = err return } id, present := ns.tsvMap[gob] if !present { id = len(ns.tsvs) ns.tsvMap[gob] = id ns.tsvs = append(ns.tsvs, sv) } vars = append(vars, id) } ns.nodes = append(ns.nodes, &serializableDepNode{ Output: ns.serializeTarget(n.Output), Cmds: n.Cmds, Deps: deps, OrderOnlys: orderonlys, Parents: parents, HasRule: n.HasRule, IsPhony: n.IsPhony, ActualInputs: actualInputs, TargetSpecificVars: vars, Filename: n.Filename, Lineno: n.Lineno, }) ns.serializeDepNodes(n.Deps) if ns.err != nil { return } ns.serializeDepNodes(n.OrderOnlys) if ns.err != nil { return } } } func makeSerializableVars(vars Vars) (r map[string]serializableVar) { r = make(map[string]serializableVar) for k, v := range vars { r[k] = v.serialize() } return r } func makeSerializableGraph(g *DepGraph, roots []string) (serializableGraph, error) { ns := newDepNodesSerializer() ns.serializeDepNodes(g.nodes) v := makeSerializableVars(g.vars) return serializableGraph{ Nodes: ns.nodes, Vars: v, Tsvs: ns.tsvs, Targets: ns.targets, Roots: roots, AccessedMks: g.accessedMks, Exports: g.exports, }, ns.err } func (jsonLoadSaver) Save(g *DepGraph, filename string, roots []string) error { startTime := time.Now() sg, err := makeSerializableGraph(g, roots) if err != nil { return err } o, err := json.MarshalIndent(sg, " ", " ") if err != nil { return err } f, err := os.Create(filename) if err != nil { return err } _, err = f.Write(o) if err != nil { f.Close() return err } err = f.Close() if err != nil { return err } logStats("json serialize time: %q", time.Since(startTime)) return nil } func (gobLoadSaver) Save(g *DepGraph, filename string, roots []string) error { startTime := time.Now() f, err := os.Create(filename) if err != nil { return err } e := gob.NewEncoder(f) var sg serializableGraph { startTime := time.Now() sg, err = makeSerializableGraph(g, roots) if err != nil { return err } logStats("gob serialize prepare time: %q", time.Since(startTime)) } { startTime := time.Now() err = e.Encode(sg) if err != nil { return err } logStats("gob serialize output time: %q", time.Since(startTime)) } err = f.Close() if err != nil { return err } logStats("gob serialize time: %q", time.Since(startTime)) return nil } func cacheFilename(mk string, roots []string) string { filename := ".kati_cache." + mk for _, r := range roots { filename += "." + r } return url.QueryEscape(filename) } func saveCache(g *DepGraph, roots []string) error { if len(g.accessedMks) == 0 { return fmt.Errorf("no Makefile is read") } cacheFile := cacheFilename(g.accessedMks[0].Filename, roots) for _, mk := range g.accessedMks { // Inconsistent, do not dump this result. if mk.State == fileInconsistent { if exists(cacheFile) { os.Remove(cacheFile) } return nil } } return GOB.Save(g, cacheFile, roots) } func deserializeSingleChild(sv serializableVar) (Value, error) { if len(sv.Children) != 1 { return nil, fmt.Errorf("unexpected number of children: %q", sv) } return deserializeVar(sv.Children[0]) } func deserializeVar(sv serializableVar) (r Value, err error) { switch sv.Type { case "literal": return literal(sv.V), nil case "tmpval": return tmpval([]byte(sv.V)), nil case "expr": var e expr for _, v := range sv.Children { dv, err := deserializeVar(v) if err != nil { return nil, err } e = append(e, dv) } return e, nil case "varref": dv, err := deserializeSingleChild(sv) if err != nil { return nil, err } return &varref{varname: dv, paren: sv.V[0]}, nil case "paramref": v, err := strconv.Atoi(sv.V) if err != nil { return nil, err } return paramref(v), nil case "varsubst": 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, paren: sv.V[0], }, nil case "func": 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:] { dv, err := deserializeVar(a) if err != nil { return nil, err } f.AddArg(dv) } 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: rhs, }, nil case "funcNop": return &funcNop{expr: sv.V}, nil case "simple": return &simpleVar{ value: strings.Split(sv.V, " "), origin: sv.Origin, }, nil case "recursive": expr, err := deserializeSingleChild(sv) if err != nil { return nil, err } return &recursiveVar{ 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: v, op: sv.Type, }, nil default: return nil, fmt.Errorf("unknown serialized variable type: %q", sv) } } func deserializeVars(vars map[string]serializableVar) (Vars, error) { r := make(Vars) for k, v := range vars { 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, nil } 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 { 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) for _, n := range nodes { var actualInputs []string for _, i := range n.ActualInputs { actualInputs = append(actualInputs, targets[i]) } d := &DepNode{ Output: targets[n.Output], Cmds: n.Cmds, HasRule: n.HasRule, IsPhony: n.IsPhony, ActualInputs: actualInputs, Filename: n.Filename, Lineno: n.Lineno, TargetSpecificVars: make(Vars), } for _, id := range n.TargetSpecificVars { sv := tsvs[id] d.TargetSpecificVars[sv.Name] = tsvValues[id] } nodeMap[targets[n.Output]] = d r = append(r, d) } for _, n := range nodes { d := nodeMap[targets[n.Output]] for _, o := range n.Deps { c, present := nodeMap[targets[o]] if !present { return nil, fmt.Errorf("unknown target: %d (%s)", o, targets[o]) } d.Deps = append(d.Deps, c) } for _, o := range n.OrderOnlys { c, present := nodeMap[targets[o]] if !present { return nil, fmt.Errorf("unknown target: %d (%s)", o, targets[o]) } d.OrderOnlys = append(d.OrderOnlys, c) } for _, o := range n.Parents { c, present := nodeMap[targets[o]] if !present { return nil, fmt.Errorf("unknown target: %d (%s)", o, targets[o]) } d.Parents = append(d.Parents, c) } } return r, nil } func human(n int) string { if n >= 10*1000*1000*1000 { return fmt.Sprintf("%.2fGB", float32(n)/1000/1000/1000) } if n >= 10*1000*1000 { return fmt.Sprintf("%.2fMB", float32(n)/1000/1000) } if n >= 10*1000 { return fmt.Sprintf("%.2fkB", float32(n)/1000) } return fmt.Sprintf("%dB", n) } func showSerializedNodesStats(nodes []*serializableDepNode) { outputSize := 0 cmdSize := 0 depsSize := 0 orderOnlysSize := 0 actualInputSize := 0 tsvSize := 0 filenameSize := 0 linenoSize := 0 for _, n := range nodes { outputSize += 4 for _, c := range n.Cmds { cmdSize += len(c) } depsSize += 4 * len(n.Deps) orderOnlysSize += 4 * len(n.OrderOnlys) actualInputSize += 4 * len(n.ActualInputs) tsvSize += 4 * len(n.TargetSpecificVars) filenameSize += len(n.Filename) linenoSize += 4 } size := outputSize + cmdSize + depsSize + orderOnlysSize + actualInputSize + tsvSize + filenameSize + linenoSize logStats("%d nodes %s", len(nodes), human(size)) logStats(" output %s", human(outputSize)) logStats(" command %s", human(cmdSize)) logStats(" deps %s", human(depsSize)) logStats(" orderonlys %s", human(orderOnlysSize)) logStats(" inputs %s", human(actualInputSize)) logStats(" tsv %s", human(tsvSize)) logStats(" filename %s", human(filenameSize)) logStats(" lineno %s", human(linenoSize)) } func (v serializableVar) size() int { size := 0 size += len(v.Type) size += len(v.V) size += len(v.Origin) for _, c := range v.Children { size += c.size() } return size } func showSerializedVarsStats(vars map[string]serializableVar) { nameSize := 0 valueSize := 0 for k, v := range vars { nameSize += len(k) valueSize += v.size() } size := nameSize + valueSize logStats("%d vars %s", len(vars), human(size)) logStats(" name %s", human(nameSize)) logStats(" value %s", human(valueSize)) } func showSerializedTsvsStats(vars []serializableTargetSpecificVar) { nameSize := 0 valueSize := 0 for _, v := range vars { nameSize += len(v.Name) valueSize += v.Value.size() } size := nameSize + valueSize logStats("%d tsvs %s", len(vars), human(size)) logStats(" name %s", human(nameSize)) logStats(" value %s", human(valueSize)) } func showSerializedTargetsStats(targets []string) { size := 0 for _, t := range targets { size += len(t) } logStats("%d targets %s", len(targets), human(size)) } func showSerializedAccessedMksStats(accessedMks []*accessedMakefile) { size := 0 for _, rm := range accessedMks { size += len(rm.Filename) + len(rm.Hash) + 4 } logStats("%d makefiles %s", len(accessedMks), human(size)) } func showSerializedGraphStats(g serializableGraph) { showSerializedNodesStats(g.Nodes) showSerializedVarsStats(g.Vars) showSerializedTsvsStats(g.Tsvs) showSerializedTargetsStats(g.Targets) showSerializedAccessedMksStats(g.AccessedMks) } func deserializeGraph(g serializableGraph) (*DepGraph, error) { if StatsFlag { showSerializedGraphStats(g) } 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) { startTime := time.Now() f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() d := json.NewDecoder(f) g := serializableGraph{Vars: make(map[string]serializableVar)} err = d.Decode(&g) if err != nil { return nil, err } dg, err := deserializeGraph(g) if err != nil { return nil, err } logStats("gob deserialize time: %q", time.Since(startTime)) return dg, nil } func (gobLoadSaver) Load(filename string) (*DepGraph, error) { startTime := time.Now() f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() d := gob.NewDecoder(f) g := serializableGraph{Vars: make(map[string]serializableVar)} err = d.Decode(&g) if err != nil { return nil, err } 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, error) { startTime := time.Now() defer func() { logStats("Cache lookup time: %q", time.Since(startTime)) }() filename := cacheFilename(makefile, roots) if !exists(filename) { glog.Warningf("Cache not found %q", filename) return nil, fmt.Errorf("cache not found: %s", filename) } g, err := GOB.Load(filename) if err != nil { glog.Warning("Cache load error %q: %v", filename, err) return nil, err } for _, mk := range g.accessedMks { if mk.State != fileExists && mk.State != fileNotExists { return nil, fmt.Errorf("internal error: broken state: %d", mk.State) } if mk.State == fileNotExists { if exists(mk.Filename) { glog.Infof("Cache expired: %s", mk.Filename) return nil, fmt.Errorf("cache expired: %s", mk.Filename) } } else { c, err := ioutil.ReadFile(mk.Filename) if err != nil { glog.Infof("Cache expired: %s", mk.Filename) return nil, fmt.Errorf("cache expired: %s", mk.Filename) } h := sha1.Sum(c) if !bytes.Equal(h[:], mk.Hash[:]) { glog.Infof("Cache expired: %s", mk.Filename) return nil, fmt.Errorf("cache expired: %s", mk.Filename) } } } glog.Info("Cache found in %q", filename) return g, nil }