blob: bcc6da133f315420ea4071a1d1ea2e526b444699 [file] [log] [blame]
package main
import (
"bytes"
"crypto/sha1"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"text/template"
"time"
)
var (
katiLogFlag bool
makefileFlag string
dryRunFlag bool
jobsFlag int
cpuprofile string
heapprofile string
memstats string
katiStatsFlag bool
katiPeriodicStatsFlag bool
katiEvalStatsFlag bool
loadJson string
saveJson string
loadGob string
saveGob string
syntaxCheckOnlyFlag bool
queryFlag string
eagerCmdEvalFlag bool
useParaFlag bool
useCache bool
generateNinja bool
ignoreOptionalInclude string
gomaDir string
katiDir string
)
type DepGraph struct {
nodes []*DepNode
vars Vars
readMks []*ReadMakefile
exports map[string]bool
isCached bool
}
func parseFlags() {
// TODO: Make this default and replace this by -d flag.
flag.BoolVar(&katiLogFlag, "kati_log", false, "Verbose kati specific log")
flag.StringVar(&makefileFlag, "f", "", "Use it as a makefile")
flag.BoolVar(&dryRunFlag, "n", false, "Only print the commands that would be executed")
flag.IntVar(&jobsFlag, "j", 1, "Allow N jobs at once.")
flag.StringVar(&loadGob, "load", "", "")
flag.StringVar(&saveGob, "save", "", "")
flag.StringVar(&loadJson, "load_json", "", "")
flag.StringVar(&saveJson, "save_json", "", "")
flag.StringVar(&cpuprofile, "kati_cpuprofile", "", "write cpu profile to `file`")
flag.StringVar(&heapprofile, "kati_heapprofile", "", "write heap profile to `file`")
flag.StringVar(&memstats, "kati_memstats", "", "Show memstats with given templates")
flag.BoolVar(&katiStatsFlag, "kati_stats", false, "Show a bunch of statistics")
flag.BoolVar(&katiPeriodicStatsFlag, "kati_periodic_stats", false, "Show a bunch of periodic statistics")
flag.BoolVar(&katiEvalStatsFlag, "kati_eval_stats", false, "Show eval statistics")
flag.BoolVar(&eagerCmdEvalFlag, "eager_cmd_eval", false, "Eval commands first.")
flag.BoolVar(&useParaFlag, "use_para", false, "Use para.")
flag.BoolVar(&syntaxCheckOnlyFlag, "c", false, "Syntax check only.")
flag.StringVar(&queryFlag, "query", "", "Show the target info")
// TODO: Make this default.
flag.BoolVar(&useCache, "use_cache", false, "Use cache.")
flag.BoolVar(&generateNinja, "ninja", false, "Generate build.ninja.")
flag.StringVar(&ignoreOptionalInclude, "ignore_optional_include", "", "If specified, skip reading -include directives start with the specified path.")
flag.StringVar(&gomaDir, "goma_dir", "", "If specified, use goma to build C/C++ files.")
flag.Parse()
}
func parseCommandLine() ([]string, []string) {
var vars []string
var targets []string
for _, arg := range flag.Args() {
if strings.IndexByte(arg, '=') >= 0 {
vars = append(vars, arg)
continue
}
targets = append(targets, arg)
}
return vars, targets
}
func getBootstrapMakefile(targets []string) Makefile {
bootstrap := `
CC:=cc
CXX:=g++
AR:=ar
MAKE:=kati
# Pretend to be GNU make 3.81, for compatibility.
MAKE_VERSION:=3.81
SHELL:=/bin/sh
# TODO: Add more builtin vars.
# http://www.gnu.org/software/make/manual/make.html#Catalogue-of-Rules
# The document above is actually not correct. See default.c:
# http://git.savannah.gnu.org/cgit/make.git/tree/default.c?id=4.1
.c.o:
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<
.cc.o:
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<
# TODO: Add more builtin rules.
`
bootstrap += fmt.Sprintf("MAKECMDGOALS:=%s\n", strings.Join(targets, " "))
cwd, err := filepath.Abs(".")
if err != nil {
panic(err)
}
bootstrap += fmt.Sprintf("CURDIR:=%s\n", cwd)
mk, err := ParseMakefileString(bootstrap, BootstrapMakefile, 0)
if err != nil {
panic(err)
}
return mk
}
func maybeWriteHeapProfile() {
if heapprofile != "" {
f, err := os.Create(heapprofile)
if err != nil {
panic(err)
}
pprof.WriteHeapProfile(f)
}
}
func getDepGraph(clvars []string, targets []string) *DepGraph {
startTime := time.Now()
if loadGob != "" {
g := LoadDepGraph(loadGob)
LogStats("deserialize time: %q", time.Now().Sub(startTime))
return g
}
if loadJson != "" {
g := LoadDepGraphFromJson(loadJson)
LogStats("deserialize time: %q", time.Now().Sub(startTime))
return g
}
makefile := makefileFlag
if makefile == "" {
makefile = GetDefaultMakefile()
}
if useCache {
g := LoadDepGraphCache(makefile, targets)
if g != nil {
return g
}
}
bmk := getBootstrapMakefile(targets)
content, err := ioutil.ReadFile(makefile)
if err != nil {
panic(err)
}
mk, err := ParseMakefile(content, makefile)
if err != nil {
panic(err)
}
for _, stmt := range mk.stmts {
stmt.show()
}
mk.stmts = append(bmk.stmts, mk.stmts...)
vars := make(Vars)
for _, env := range os.Environ() {
kv := strings.SplitN(env, "=", 2)
Log("envvar %q", kv)
if len(kv) < 2 {
panic(fmt.Sprintf("A weird environ variable %q", kv))
}
vars.Assign(kv[0], RecursiveVar{
expr: literal(kv[1]),
origin: "environment",
})
}
vars.Assign("MAKEFILE_LIST", SimpleVar{value: []byte{}, origin: "file"})
for _, v := range clvars {
kv := strings.SplitN(v, "=", 2)
Log("cmdlinevar %q", kv)
if len(kv) < 2 {
panic(fmt.Sprintf("unexpected command line var %q", kv))
}
vars.Assign(kv[0], RecursiveVar{
expr: literal(kv[1]),
origin: "command line",
})
}
er, err := Eval(mk, vars)
if err != nil {
panic(err)
}
vars.Merge(er.vars)
LogStats("eval time: %q", time.Now().Sub(startTime))
LogStats("shell func time: %q", shellFuncTime)
startTime = time.Now()
db := NewDepBuilder(er, vars)
LogStats("dep build prepare time: %q", time.Now().Sub(startTime))
startTime = time.Now()
nodes, err2 := db.Eval(targets)
if err2 != nil {
panic(err2)
}
LogStats("dep build time: %q", time.Now().Sub(startTime))
var readMks []*ReadMakefile
// Always put the root Makefile as the first element.
readMks = append(readMks, &ReadMakefile{
Filename: makefile,
Hash: sha1.Sum(content),
State: FILE_EXISTS,
})
readMks = append(readMks, er.readMks...)
return &DepGraph{
nodes: nodes,
vars: vars,
readMks: readMks,
exports: er.exports,
}
}
func findKatiDir() {
switch runtime.GOOS {
case "linux":
kati, err := os.Readlink("/proc/self/exe")
if err != nil {
panic(err)
}
katiDir = filepath.Dir(kati)
default:
panic(fmt.Sprintf("unknown OS: %s", runtime.GOOS))
}
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
findKatiDir()
parseFlags()
if cpuprofile != "" {
f, err := os.Create(cpuprofile)
if err != nil {
panic(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
defer maybeWriteHeapProfile()
defer dumpStats()
if memstats != "" {
t := template.Must(template.New("memstats").Parse(memstats))
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
var buf bytes.Buffer
err := t.Execute(&buf, ms)
fmt.Println(buf.String())
if err != nil {
panic(err)
}
defer func() {
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
var buf bytes.Buffer
t.Execute(&buf, ms)
fmt.Println(buf.String())
}()
}
clvars, targets := parseCommandLine()
InitMakefileCache()
g := getDepGraph(clvars, targets)
nodes := g.nodes
vars := g.vars
if eagerCmdEvalFlag {
startTime := time.Now()
EvalCommands(nodes, vars)
LogStats("eager eval command time: %q", time.Now().Sub(startTime))
}
if saveGob != "" {
startTime := time.Now()
DumpDepGraph(g, saveGob, targets)
LogStats("serialize time: %q", time.Now().Sub(startTime))
}
if saveJson != "" {
startTime := time.Now()
DumpDepGraphAsJson(g, saveJson, targets)
LogStats("serialize time: %q", time.Now().Sub(startTime))
}
if useCache && !g.isCached {
startTime := time.Now()
DumpDepGraphCache(g, targets)
LogStats("serialize time: %q", time.Now().Sub(startTime))
}
if generateNinja {
startTime := time.Now()
GenerateNinja(g)
LogStats("generate ninja time: %q", time.Now().Sub(startTime))
return
}
if syntaxCheckOnlyFlag {
return
}
if queryFlag != "" {
HandleQuery(queryFlag, g)
return
}
// TODO: Handle target specific variables.
ev := newEvaluator(vars)
for name, export := range g.exports {
if export {
os.Setenv(name, ev.EvaluateVar(name))
} else {
os.Unsetenv(name)
}
}
startTime := time.Now()
ex := NewExecutor(vars)
err := ex.Exec(nodes)
if err != nil {
panic(err)
}
LogStats("exec time: %q", time.Now().Sub(startTime))
}