blob: 2e273b0faa0ef2136c7d1cc94b0a3093e415a861 [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 main
import (
"bytes"
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"text/template"
"time"
"github.com/golang/glog"
"github.com/google/kati"
)
const shellDateTimeformat = time.RFC3339
var (
makefileFlag string
jobsFlag int
loadJSON string
saveJSON string
loadGOB string
saveGOB string
useCache bool
m2n bool
goma bool
cpuprofile string
heapprofile string
memstats string
traceEventFile string
syntaxCheckOnlyFlag bool
queryFlag string
eagerCmdEvalFlag bool
generateNinja bool
ninjaSuffix string
gomaDir string
detectAndroidEcho bool
findCachePrunes string
findCacheLeafNames string
shellDate string
)
func init() {
// TODO: Make this default and replace this by -d flag.
flag.StringVar(&makefileFlag, "f", "", "Use it as a makefile")
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.BoolVar(&useCache, "use_cache", false, "Use cache.")
flag.BoolVar(&m2n, "m2n", false, "m2n mode")
flag.BoolVar(&goma, "goma", false, "ensure goma start")
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.StringVar(&traceEventFile, "kati_trace_event", "", "write trace event to `file`")
flag.BoolVar(&syntaxCheckOnlyFlag, "c", false, "Syntax check only.")
flag.StringVar(&queryFlag, "query", "", "Show the target info")
flag.BoolVar(&eagerCmdEvalFlag, "eager_cmd_eval", false, "Eval commands first.")
flag.BoolVar(&generateNinja, "ninja", false, "Generate build.ninja.")
flag.StringVar(&ninjaSuffix, "ninja_suffix", "", "suffix for ninja files.")
flag.StringVar(&gomaDir, "goma_dir", "", "If specified, use goma to build C/C++ files.")
flag.BoolVar(&detectAndroidEcho, "detect_android_echo", false, "detect echo as ninja description.")
flag.StringVar(&findCachePrunes, "find_cache_prunes", "",
"space separated prune directories for find cache.")
flag.StringVar(&findCacheLeafNames, "find_cache_leaf_names", "",
"space separated leaf names for find cache.")
flag.StringVar(&shellDate, "shell_date", "", "specify $(shell date) time as "+shellDateTimeformat)
flag.BoolVar(&kati.StatsFlag, "kati_stats", false, "Show a bunch of statistics")
flag.BoolVar(&kati.PeriodicStatsFlag, "kati_periodic_stats", false, "Show a bunch of periodic statistics")
flag.BoolVar(&kati.EvalStatsFlag, "kati_eval_stats", false, "Show eval statistics")
flag.BoolVar(&kati.DryRunFlag, "n", false, "Only print the commands that would be executed")
// TODO: Make this default.
flag.BoolVar(&kati.UseFindCache, "use_find_cache", false, "Use find cache.")
flag.BoolVar(&kati.UseShellBuiltins, "use_shell_builtins", true, "Use shell builtins")
flag.StringVar(&kati.IgnoreOptionalInclude, "ignore_optional_include", "", "If specified, skip reading -include directives start with the specified path.")
}
func writeHeapProfile() {
f, err := os.Create(heapprofile)
if err != nil {
panic(err)
}
pprof.WriteHeapProfile(f)
f.Close()
}
type memStatsDumper struct {
*template.Template
}
func (t memStatsDumper) dump() {
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
var buf bytes.Buffer
err := t.Template.Execute(&buf, ms)
fmt.Println(buf.String())
if err != nil {
panic(err)
}
}
func load(req kati.LoadReq) (*kati.DepGraph, error) {
if loadGOB != "" {
g, err := kati.GOB.Load(loadGOB)
return g, err
}
if loadJSON != "" {
g, err := kati.JSON.Load(loadJSON)
return g, err
}
g, err := kati.Load(req)
return g, err
}
func save(g *kati.DepGraph, targets []string) error {
var err error
if saveGOB != "" {
err = kati.GOB.Save(g, saveGOB, targets)
}
if saveJSON != "" {
serr := kati.JSON.Save(g, saveJSON, targets)
if err == nil {
err = serr
}
}
return err
}
func m2nsetup() {
fmt.Println("kati: m2n mode")
generateNinja = true
kati.IgnoreOptionalInclude = "out/%.P"
kati.UseFindCache = true
if findCachePrunes == "" {
findCachePrunes = ".git .repo out"
}
}
func gomasetup() {
for _, k := range []string{"CC_WRAPPER", "CXX_WRAPPER", "JAVAC_WRAPPER"} {
v := os.Getenv(k)
if v != "" {
fmt.Printf("Note: %s=%s may confuse m2n --goma, unsetting", k, v)
os.Unsetenv(k)
}
}
if gomaDir == "" {
gomaDir = os.Getenv("GOMA_DIR")
if gomaDir == "" {
gomaDir = os.ExpandEnv("${HOME}/goma")
}
}
fmt.Printf("kati: setup goma: %s\n", gomaDir)
cmd := exec.Command(filepath.Join(gomaDir, "goma_ctl.py"), "ensure_start")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Printf("goma failed to start: %v", err)
os.Exit(1)
}
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
m2ncmd := false
if filepath.Base(os.Args[0]) == "m2n" {
m2nsetup()
m2ncmd = true
}
flag.Parse()
args := flag.Args()
if m2n {
generateNinja = true
if !m2ncmd {
m2nsetup()
}
if len(args) > 1 {
fmt.Println("use only first argument as ONE_SHOT_MAKEFILE. ignore rest")
}
if len(args) > 0 {
err := os.Setenv("ONE_SHOT_MAKEFILE", filepath.Join(args[0], "Android.mk"))
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("ONE_SHOT_MAKEFILE=%s\n", os.ExpandEnv("${ONE_SHOT_MAKEFILE}"))
}
args = args[:0]
}
if goma {
gomasetup()
}
err := katiMain(args)
if err != nil {
fmt.Println(err)
// http://www.gnu.org/software/make/manual/html_node/Running.html
os.Exit(2)
}
}
func katiMain(args []string) error {
defer glog.Flush()
if cpuprofile != "" {
f, err := os.Create(cpuprofile)
if err != nil {
return err
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
if heapprofile != "" {
defer writeHeapProfile()
}
defer kati.DumpStats()
if memstats != "" {
ms := memStatsDumper{
Template: template.Must(template.New("memstats").Parse(memstats)),
}
ms.dump()
defer ms.dump()
}
if traceEventFile != "" {
f, err := os.Create(traceEventFile)
if err != nil {
panic(err)
}
kati.TraceEventStart(f)
defer kati.TraceEventStop()
}
if shellDate != "" {
if shellDate == "ref" {
shellDate = shellDateTimeformat[:20] // until Z, drop 07:00
}
t, err := time.Parse(shellDateTimeformat, shellDate)
if err != nil {
panic(err)
}
kati.ShellDateTimestamp = t
}
var leafNames []string
if findCacheLeafNames != "" {
leafNames = strings.Fields(findCacheLeafNames)
}
if findCachePrunes != "" {
kati.UseFindCache = true
kati.AndroidFindCacheInit(strings.Fields(findCachePrunes), leafNames)
}
req := kati.FromCommandLine(args)
if makefileFlag != "" {
req.Makefile = makefileFlag
}
req.EnvironmentVars = os.Environ()
req.UseCache = useCache
req.EagerEvalCommand = eagerCmdEvalFlag
g, err := load(req)
if err != nil {
return err
}
err = save(g, req.Targets)
if err != nil {
return err
}
if generateNinja {
n := kati.NinjaGenerator{
GomaDir: gomaDir,
DetectAndroidEcho: detectAndroidEcho,
}
return n.Save(g, ninjaSuffix, req.Targets)
}
if syntaxCheckOnlyFlag {
return nil
}
if queryFlag != "" {
kati.Query(os.Stdout, queryFlag, g)
return nil
}
execOpt := &kati.ExecutorOpt{
NumJobs: jobsFlag,
}
ex, err := kati.NewExecutor(execOpt)
if err != nil {
return err
}
err = ex.Exec(g, req.Targets)
if err != nil {
return err
}
return nil
}