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(®enNinja, "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