1// Copyright 2015 Google Inc. All rights reserved
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18	"bytes"
19	"flag"
20	"fmt"
21	"os"
22	"os/exec"
23	"path/filepath"
24	"runtime"
25	"runtime/pprof"
26	"text/template"
27	"time"
28
29	"github.com/golang/glog"
30	"github.com/google/kati"
31)
32
33const shellDateTimeformat = time.RFC3339
34
35var (
36	makefileFlag string
37	jobsFlag     int
38
39	loadJSON string
40	saveJSON string
41	loadGOB  string
42	saveGOB  string
43	useCache bool
44
45	m2n  bool
46	goma bool
47
48	cpuprofile          string
49	heapprofile         string
50	memstats            string
51	traceEventFile      string
52	syntaxCheckOnlyFlag bool
53	queryFlag           string
54	eagerCmdEvalFlag    bool
55	generateNinja       bool
56	regenNinja          bool
57	ninjaSuffix         string
58	gomaDir             string
59	detectAndroidEcho   bool
60	shellDate           string
61)
62
63func init() {
64	// TODO: Make this default and replace this by -d flag.
65	flag.StringVar(&makefileFlag, "f", "", "Use it as a makefile")
66	flag.IntVar(&jobsFlag, "j", 1, "Allow N jobs at once.")
67
68	flag.StringVar(&loadGOB, "load", "", "")
69	flag.StringVar(&saveGOB, "save", "", "")
70	flag.StringVar(&loadJSON, "load_json", "", "")
71	flag.StringVar(&saveJSON, "save_json", "", "")
72	flag.BoolVar(&useCache, "use_cache", false, "Use cache.")
73
74	flag.BoolVar(&m2n, "m2n", false, "m2n mode")
75	flag.BoolVar(&goma, "goma", false, "ensure goma start")
76
77	flag.StringVar(&cpuprofile, "kati_cpuprofile", "", "write cpu profile to `file`")
78	flag.StringVar(&heapprofile, "kati_heapprofile", "", "write heap profile to `file`")
79	flag.StringVar(&memstats, "kati_memstats", "", "Show memstats with given templates")
80	flag.StringVar(&traceEventFile, "kati_trace_event", "", "write trace event to `file`")
81	flag.BoolVar(&syntaxCheckOnlyFlag, "c", false, "Syntax check only.")
82	flag.StringVar(&queryFlag, "query", "", "Show the target info")
83	flag.BoolVar(&eagerCmdEvalFlag, "eager_cmd_eval", false, "Eval commands first.")
84	flag.BoolVar(&generateNinja, "ninja", false, "Generate build.ninja.")
85	flag.BoolVar(&regenNinja, "gen_regen_rule", false, "Generate regenerate build.ninja rule.")
86	flag.StringVar(&ninjaSuffix, "ninja_suffix", "", "suffix for ninja files.")
87	flag.StringVar(&gomaDir, "goma_dir", "", "If specified, use goma to build C/C++ files.")
88	// TODO(ukai): implement --regen
89	flag.BoolVar(&detectAndroidEcho, "detect_android_echo", false, "detect echo as ninja description.")
90
91	flag.StringVar(&shellDate, "shell_date", "", "specify $(shell date) time as "+shellDateTimeformat)
92
93	flag.BoolVar(&kati.StatsFlag, "kati_stats", false, "Show a bunch of statistics")
94	flag.BoolVar(&kati.PeriodicStatsFlag, "kati_periodic_stats", false, "Show a bunch of periodic statistics")
95	flag.BoolVar(&kati.EvalStatsFlag, "kati_eval_stats", false, "Show eval statistics")
96
97	flag.BoolVar(&kati.DryRunFlag, "n", false, "Only print the commands that would be executed")
98
99	// TODO: Make this default.
100	flag.BoolVar(&kati.UseFindEmulator, "use_find_emulator", false, "use find emulator")
101	flag.BoolVar(&kati.UseShellBuiltins, "use_shell_builtins", true, "Use shell builtins")
102	flag.StringVar(&kati.IgnoreOptionalInclude, "ignore_optional_include", "", "If specified, skip reading -include directives start with the specified path.")
103}
104
105func writeHeapProfile() {
106	f, err := os.Create(heapprofile)
107	if err != nil {
108		panic(err)
109	}
110	pprof.WriteHeapProfile(f)
111	f.Close()
112}
113
114type memStatsDumper struct {
115	*template.Template
116}
117
118func (t memStatsDumper) dump() {
119	var ms runtime.MemStats
120	runtime.ReadMemStats(&ms)
121	var buf bytes.Buffer
122	err := t.Template.Execute(&buf, ms)
123	fmt.Println(buf.String())
124	if err != nil {
125		panic(err)
126	}
127}
128
129func load(req kati.LoadReq) (*kati.DepGraph, error) {
130	if loadGOB != "" {
131		g, err := kati.GOB.Load(loadGOB)
132		return g, err
133	}
134	if loadJSON != "" {
135		g, err := kati.JSON.Load(loadJSON)
136		return g, err
137	}
138	g, err := kati.Load(req)
139	return g, err
140}
141
142func save(g *kati.DepGraph, targets []string) error {
143	var err error
144	if saveGOB != "" {
145		err = kati.GOB.Save(g, saveGOB, targets)
146	}
147	if saveJSON != "" {
148		serr := kati.JSON.Save(g, saveJSON, targets)
149		if err == nil {
150			err = serr
151		}
152	}
153	return err
154}
155
156func m2nsetup() {
157	fmt.Println("kati: m2n mode")
158	generateNinja = true
159	kati.IgnoreOptionalInclude = "out/%.P"
160	kati.UseFindEmulator = true
161}
162
163func gomasetup() {
164	for _, k := range []string{"CC_WRAPPER", "CXX_WRAPPER", "JAVAC_WRAPPER"} {
165		v := os.Getenv(k)
166		if v != "" {
167			fmt.Printf("Note: %s=%s may confuse m2n --goma, unsetting", k, v)
168			os.Unsetenv(k)
169		}
170	}
171
172	if gomaDir == "" {
173		gomaDir = os.Getenv("GOMA_DIR")
174		if gomaDir == "" {
175			gomaDir = os.ExpandEnv("${HOME}/goma")
176		}
177	}
178	fmt.Printf("kati: setup goma: %s\n", gomaDir)
179	cmd := exec.Command(filepath.Join(gomaDir, "goma_ctl.py"), "ensure_start")
180	cmd.Stdout = os.Stdout
181	cmd.Stderr = os.Stderr
182	err := cmd.Run()
183	if err != nil {
184		fmt.Printf("goma failed to start: %v", err)
185		os.Exit(1)
186	}
187}
188
189func main() {
190	runtime.GOMAXPROCS(runtime.NumCPU())
191	m2ncmd := false
192	if filepath.Base(os.Args[0]) == "m2n" {
193		m2nsetup()
194		m2ncmd = true
195	}
196	flag.Parse()
197	args := flag.Args()
198	if m2n {
199		generateNinja = true
200		if !m2ncmd {
201			m2nsetup()
202		}
203		if len(args) > 1 {
204			fmt.Println("use only first argument as ONE_SHOT_MAKEFILE. ignore rest")
205		}
206		if len(args) > 0 {
207			err := os.Setenv("ONE_SHOT_MAKEFILE", filepath.Join(args[0], "Android.mk"))
208			if err != nil {
209				fmt.Println(err)
210				os.Exit(1)
211			}
212			fmt.Printf("ONE_SHOT_MAKEFILE=%s\n", os.ExpandEnv("${ONE_SHOT_MAKEFILE}"))
213		}
214		args = args[:0]
215	}
216	if goma {
217		gomasetup()
218	}
219	err := katiMain(args)
220	if err != nil {
221		fmt.Println(err)
222		// http://www.gnu.org/software/make/manual/html_node/Running.html
223		os.Exit(2)
224	}
225}
226
227func katiMain(args []string) error {
228	defer glog.Flush()
229	if cpuprofile != "" {
230		f, err := os.Create(cpuprofile)
231		if err != nil {
232			return err
233		}
234		pprof.StartCPUProfile(f)
235		defer pprof.StopCPUProfile()
236	}
237	if heapprofile != "" {
238		defer writeHeapProfile()
239	}
240	defer kati.DumpStats()
241	if memstats != "" {
242		ms := memStatsDumper{
243			Template: template.Must(template.New("memstats").Parse(memstats)),
244		}
245		ms.dump()
246		defer ms.dump()
247	}
248	if traceEventFile != "" {
249		f, err := os.Create(traceEventFile)
250		if err != nil {
251			panic(err)
252		}
253		kati.TraceEventStart(f)
254		defer kati.TraceEventStop()
255	}
256
257	if shellDate != "" {
258		if shellDate == "ref" {
259			shellDate = shellDateTimeformat[:20] // until Z, drop 07:00
260		}
261		t, err := time.Parse(shellDateTimeformat, shellDate)
262		if err != nil {
263			panic(err)
264		}
265		kati.ShellDateTimestamp = t
266	}
267
268	req := kati.FromCommandLine(args)
269	if makefileFlag != "" {
270		req.Makefile = makefileFlag
271	}
272	req.EnvironmentVars = os.Environ()
273	req.UseCache = useCache
274	req.EagerEvalCommand = eagerCmdEvalFlag
275
276	g, err := load(req)
277	if err != nil {
278		return err
279	}
280
281	err = save(g, req.Targets)
282	if err != nil {
283		return err
284	}
285
286	if generateNinja {
287		var args []string
288		if regenNinja {
289			args = os.Args
290		}
291		n := kati.NinjaGenerator{
292			Args:              args,
293			Suffix:            ninjaSuffix,
294			GomaDir:           gomaDir,
295			DetectAndroidEcho: detectAndroidEcho,
296		}
297		return n.Save(g, "", req.Targets)
298	}
299
300	if syntaxCheckOnlyFlag {
301		return nil
302	}
303
304	if queryFlag != "" {
305		kati.Query(os.Stdout, queryFlag, g)
306		return nil
307	}
308
309	execOpt := &kati.ExecutorOpt{
310		NumJobs: jobsFlag,
311	}
312	ex, err := kati.NewExecutor(execOpt)
313	if err != nil {
314		return err
315	}
316	err = ex.Exec(g, req.Targets)
317	if err != nil {
318		return err
319	}
320	return nil
321}
322