| // 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 ( |
| "crypto/sha1" |
| "fmt" |
| "io/ioutil" |
| "strings" |
| "time" |
| |
| "github.com/golang/glog" |
| ) |
| |
| // DepGraph represents rules defined in makefiles. |
| type DepGraph struct { |
| nodes []*DepNode |
| vars Vars |
| accessedMks []*accessedMakefile |
| exports map[string]bool |
| vpaths searchPaths |
| } |
| |
| // Nodes returns all rules. |
| func (g *DepGraph) Nodes() []*DepNode { return g.nodes } |
| |
| // Vars returns all variables. |
| func (g *DepGraph) Vars() Vars { return g.vars } |
| |
| func (g *DepGraph) resolveVPATH() { |
| seen := make(map[*DepNode]bool) |
| var fix func(n *DepNode) |
| fix = func(n *DepNode) { |
| if seen[n] { |
| return |
| } |
| seen[n] = true |
| glog.V(3).Infof("vpath check %s [%#v]", n.Output, g.vpaths) |
| if output, ok := g.vpaths.exists(n.Output); ok { |
| glog.V(2).Infof("vpath fix %s=>%s", n.Output, output) |
| n.Output = output |
| } |
| for _, d := range n.Deps { |
| fix(d) |
| } |
| for _, d := range n.OrderOnlys { |
| fix(d) |
| } |
| for _, d := range n.Parents { |
| fix(d) |
| } |
| // fix ActualInputs? |
| } |
| for _, n := range g.nodes { |
| fix(n) |
| } |
| } |
| |
| // LoadReq is a request to load makefile. |
| type LoadReq struct { |
| Makefile string |
| Targets []string |
| CommandLineVars []string |
| EnvironmentVars []string |
| UseCache bool |
| EagerEvalCommand bool |
| } |
| |
| // FromCommandLine creates LoadReq from given command line. |
| func FromCommandLine(cmdline []string) LoadReq { |
| var vars []string |
| var targets []string |
| for _, arg := range cmdline { |
| if strings.IndexByte(arg, '=') >= 0 { |
| vars = append(vars, arg) |
| continue |
| } |
| targets = append(targets, arg) |
| } |
| mk, err := defaultMakefile() |
| if err != nil { |
| glog.Warningf("default makefile: %v", err) |
| } |
| return LoadReq{ |
| Makefile: mk, |
| Targets: targets, |
| CommandLineVars: vars, |
| } |
| } |
| |
| func initVars(vars Vars, kvlist []string, origin string) error { |
| for _, v := range kvlist { |
| kv := strings.SplitN(v, "=", 2) |
| glog.V(1).Infof("%s var %q", origin, v) |
| if len(kv) < 2 { |
| return fmt.Errorf("A weird %s variable %q", origin, kv) |
| } |
| vars.Assign(kv[0], &recursiveVar{ |
| expr: literal(kv[1]), |
| origin: origin, |
| }) |
| } |
| return nil |
| } |
| |
| // Load loads makefile. |
| func Load(req LoadReq) (*DepGraph, error) { |
| startTime := time.Now() |
| var err error |
| if req.Makefile == "" { |
| req.Makefile, err = defaultMakefile() |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| if req.UseCache { |
| g, err := loadCache(req.Makefile, req.Targets) |
| if err == nil { |
| return g, nil |
| } |
| } |
| |
| bmk, err := bootstrapMakefile(req.Targets) |
| if err != nil { |
| return nil, err |
| } |
| |
| content, err := ioutil.ReadFile(req.Makefile) |
| if err != nil { |
| return nil, err |
| } |
| mk, err := parseMakefile(content, req.Makefile) |
| if err != nil { |
| return nil, err |
| } |
| |
| for _, stmt := range mk.stmts { |
| stmt.show() |
| } |
| |
| mk.stmts = append(bmk.stmts, mk.stmts...) |
| |
| vars := make(Vars) |
| err = initVars(vars, req.EnvironmentVars, "environment") |
| if err != nil { |
| return nil, err |
| } |
| err = initVars(vars, req.CommandLineVars, "command line") |
| if err != nil { |
| return nil, err |
| } |
| er, err := eval(mk, vars, req.UseCache) |
| if err != nil { |
| return nil, err |
| } |
| vars.Merge(er.vars) |
| |
| logStats("eval time: %q", time.Since(startTime)) |
| logStats("shell func time: %q %d", shellStats.Duration(), shellStats.Count()) |
| |
| startTime = time.Now() |
| db, err := newDepBuilder(er, vars) |
| if err != nil { |
| return nil, err |
| } |
| logStats("dep build prepare time: %q", time.Since(startTime)) |
| |
| startTime = time.Now() |
| nodes, err := db.Eval(req.Targets) |
| if err != nil { |
| return nil, err |
| } |
| logStats("dep build time: %q", time.Since(startTime)) |
| var accessedMks []*accessedMakefile |
| // Always put the root Makefile as the first element. |
| accessedMks = append(accessedMks, &accessedMakefile{ |
| Filename: req.Makefile, |
| Hash: sha1.Sum(content), |
| State: fileExists, |
| }) |
| accessedMks = append(accessedMks, er.accessedMks...) |
| gd := &DepGraph{ |
| nodes: nodes, |
| vars: vars, |
| accessedMks: accessedMks, |
| exports: er.exports, |
| vpaths: er.vpaths, |
| } |
| if req.EagerEvalCommand { |
| startTime := time.Now() |
| err = evalCommands(nodes, vars) |
| if err != nil { |
| return nil, err |
| } |
| logStats("eager eval command time: %q", time.Since(startTime)) |
| } |
| if req.UseCache { |
| startTime := time.Now() |
| saveCache(gd, req.Targets) |
| logStats("serialize time: %q", time.Since(startTime)) |
| } |
| 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 |
| } |