github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/cmd/nin/ninja.go (about) 1 // Copyright 2011 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 15 package main 16 17 import ( 18 "errors" 19 "flag" 20 "fmt" 21 "log" 22 "os" 23 "path/filepath" 24 "runtime" 25 "runtime/debug" 26 "runtime/pprof" 27 "runtime/trace" 28 "sort" 29 "strconv" 30 "strings" 31 32 "github.com/maruel/nin" 33 ) 34 35 // Command-line options. 36 type options struct { 37 // Build file to load. 38 inputFile string 39 40 // Directory to change into before running. 41 workingDir string 42 43 // tool to run rather than building. 44 tool *tool 45 46 // build.ninja parsing options. 47 parserOpts nin.ParseManifestOpts 48 49 cpuprofile string 50 memprofile string 51 trace string 52 } 53 54 // The Ninja main() loads up a series of data structures; various tools need 55 // to poke into these, so store them as fields on an object. 56 type ninjaMain struct { 57 // Command line used to run Ninja. 58 ninjaCommand string 59 60 // Build configuration set from flags (e.g. parallelism). 61 config *nin.BuildConfig 62 63 // Loaded state (rules, nodes). 64 state nin.State 65 66 // Functions for accessing the disk. 67 di nin.RealDiskInterface 68 69 // The build directory, used for storing the build log etc. 70 buildDir string 71 72 buildLog nin.BuildLog 73 depsLog nin.DepsLog 74 75 // The type of functions that are the entry points to tools (subcommands). 76 77 startTimeMillis int64 78 } 79 80 func newNinjaMain(ninjaCommand string, config *nin.BuildConfig) ninjaMain { 81 return ninjaMain{ 82 ninjaCommand: ninjaCommand, 83 config: config, 84 state: nin.NewState(), 85 buildLog: nin.NewBuildLog(), 86 startTimeMillis: nin.GetTimeMillis(), 87 } 88 } 89 90 func (n *ninjaMain) Close() error { 91 // TODO(maruel): Ensure the file handle is cleanly closed. 92 err1 := n.depsLog.Close() 93 err2 := n.buildLog.Close() 94 if err1 != nil { 95 return err1 96 } 97 return err2 98 } 99 100 type toolFunc func(*ninjaMain, *options, []string) int 101 102 func (n *ninjaMain) IsPathDead(s string) bool { 103 nd := n.state.Paths[s] 104 if nd != nil && nd.InEdge != nil { 105 return false 106 } 107 // Just checking nd isn't enough: If an old output is both in the build log 108 // and in the deps log, it will have a Node object in state. (It will also 109 // have an in edge if one of its inputs is another output that's in the deps 110 // log, but having a deps edge product an output that's input to another deps 111 // edge is rare, and the first recompaction will delete all old outputs from 112 // the deps log, and then a second recompaction will clear the build log, 113 // which seems good enough for this corner case.) 114 // Do keep entries around for files which still exist on disk, for 115 // generators that want to use this information. 116 mtime, err := n.di.Stat(s) 117 if mtime == -1 { 118 errorf("%s", err) // Log and ignore Stat() errors. 119 } 120 return mtime == 0 121 } 122 123 // Subtools, accessible via "-t foo". 124 type tool struct { 125 // Short name of the tool. 126 name string 127 128 // Description (shown in "-t list"). 129 desc string 130 131 when when 132 133 // Implementation of the tool. 134 tool toolFunc 135 } 136 137 // when to run the tool. 138 type when int32 139 140 const ( 141 // Run after parsing the command-line flags and potentially changing 142 // the current working directory (as early as possible). 143 runAfterFlags when = iota 144 145 // Run after loading build.ninja. 146 runAfterLoad 147 148 // Run after loading the build/deps logs. 149 runAfterLogs 150 ) 151 152 // Print usage information. 153 func usage() { 154 fmt.Fprintf(os.Stderr, "usage: nin [options] [targets...]\n\n") 155 fmt.Fprintf(os.Stderr, "if targets are unspecified, builds the 'default' target (see manual).\n\n") 156 flag.PrintDefaults() 157 } 158 159 // Choose a default value for the -j (parallelism) flag. 160 func guessParallelism() int { 161 switch processors := runtime.NumCPU(); processors { 162 case 0, 1: 163 return 2 164 case 2: 165 return 3 166 default: 167 return processors + 2 168 } 169 } 170 171 // Rebuild the build manifest, if necessary. 172 // Returns true if the manifest was rebuilt. 173 // Rebuild the manifest, if necessary. 174 // Fills in \a err on error. 175 // @return true if the manifest was rebuilt. 176 func (n *ninjaMain) RebuildManifest(inputFile string, status nin.Status) (bool, error) { 177 path := inputFile 178 if len(path) == 0 { 179 return false, errors.New("empty path") 180 } 181 node := n.state.Paths[nin.CanonicalizePath(path)] 182 if node == nil { 183 return false, errors.New("path not found") 184 } 185 186 builder := nin.NewBuilder(&n.state, n.config, &n.buildLog, &n.depsLog, &n.di, status, n.startTimeMillis) 187 if dirty, err := builder.AddTarget(node); !dirty { 188 return false, err 189 } 190 191 if builder.AlreadyUpToDate() { 192 return false, nil // Not an error, but we didn't rebuild. 193 } 194 195 if err := builder.Build(); err != nil { 196 return false, err 197 } 198 199 // The manifest was only rebuilt if it is now dirty (it may have been cleaned 200 // by a restat). 201 if !node.Dirty { 202 // Reset the state to prevent problems like 203 // https://github.com/ninja-build/ninja/issues/874 204 n.state.Reset() 205 return false, nil 206 } 207 208 return true, nil 209 } 210 211 // collectTarget gets the Node for a given command-line path, handling features 212 // like spell correction. 213 func (n *ninjaMain) collectTarget(cpath string) (*nin.Node, error) { 214 path := cpath 215 if len(path) == 0 { 216 return nil, errors.New("empty path") 217 } 218 path, slashBits := nin.CanonicalizePathBits(path) 219 220 // Special syntax: "foo.cc^" means "the first output of foo.cc". 221 firstDependent := false 222 if path != "" && path[len(path)-1] == '^' { 223 path = path[:len(path)-1] 224 firstDependent = true 225 } 226 227 node := n.state.Paths[path] 228 if node != nil { 229 if firstDependent { 230 if len(node.OutEdges) == 0 { 231 revDeps := n.depsLog.GetFirstReverseDepsNode(node) 232 if revDeps == nil { 233 // TODO(maruel): Use %q for real quoting. 234 return nil, fmt.Errorf("'%s' has no out edge", path) 235 } 236 node = revDeps 237 } else { 238 edge := node.OutEdges[0] 239 if len(edge.Outputs) == 0 { 240 edge.Dump("") 241 fatalf("edge has no outputs") 242 } 243 node = edge.Outputs[0] 244 } 245 } 246 return node, nil 247 } 248 // TODO(maruel): Use %q for real quoting. 249 err := fmt.Sprintf("unknown target '%s'", nin.PathDecanonicalized(path, slashBits)) 250 if path == "clean" { 251 err += ", did you mean 'nin -t clean'?" 252 } else if path == "help" { 253 err += ", did you mean 'nin -h'?" 254 } else { 255 suggestion := n.state.SpellcheckNode(path) 256 if suggestion != nil { 257 // TODO(maruel): Use %q for real quoting. 258 err += fmt.Sprintf(", did you mean '%s'?", suggestion.Path) 259 } 260 } 261 return nil, errors.New(err) 262 } 263 264 // collectTargetsFromArgs calls collectTarget for all command-line arguments. 265 func (n *ninjaMain) collectTargetsFromArgs(args []string) ([]*nin.Node, error) { 266 var targets []*nin.Node 267 if len(args) == 0 { 268 targets = n.state.DefaultNodes() 269 if len(targets) == 0 { 270 return targets, errors.New("could not determine root nodes of build graph") 271 } 272 return targets, nil 273 } 274 275 for i := 0; i < len(args); i++ { 276 node, err := n.collectTarget(args[i]) 277 if node == nil { 278 return targets, err 279 } 280 targets = append(targets, node) 281 } 282 return targets, nil 283 } 284 285 // The various subcommands, run via "-t XXX". 286 func toolGraph(n *ninjaMain, opts *options, args []string) int { 287 nodes, err := n.collectTargetsFromArgs(args) 288 if err != nil { 289 errorf("%s", err) 290 return 1 291 } 292 293 graph := nin.NewGraphViz(&n.state, &n.di) 294 graph.Start() 295 for _, n := range nodes { 296 graph.AddTarget(n) 297 } 298 graph.Finish() 299 return 0 300 } 301 302 func toolQuery(n *ninjaMain, opts *options, args []string) int { 303 if len(args) == 0 { 304 errorf("expected a target to query") 305 return 1 306 } 307 308 dyndepLoader := nin.NewDyndepLoader(&n.state, &n.di) 309 310 for i := 0; i < len(args); i++ { 311 node, err := n.collectTarget(args[i]) 312 if err != nil { 313 errorf("%s", err) 314 return 1 315 } 316 317 fmt.Printf("%s:\n", node.Path) 318 if edge := node.InEdge; edge != nil { 319 if edge.Dyndep != nil && edge.Dyndep.DyndepPending { 320 if err := dyndepLoader.LoadDyndeps(edge.Dyndep, nin.DyndepFile{}); err != nil { 321 warningf("%s\n", err) 322 } 323 } 324 fmt.Printf(" input: %s\n", edge.Rule.Name) 325 for in := 0; in < len(edge.Inputs); in++ { 326 label := "" 327 if edge.IsImplicit(in) { 328 label = "| " 329 } else if edge.IsOrderOnly(in) { 330 label = "|| " 331 } 332 fmt.Printf(" %s%s\n", label, edge.Inputs[in].Path) 333 } 334 if len(edge.Validations) != 0 { 335 fmt.Printf(" validations:\n") 336 for _, validation := range edge.Validations { 337 fmt.Printf(" %s\n", validation.Path) 338 } 339 } 340 } 341 fmt.Printf(" outputs:\n") 342 for _, edge := range node.OutEdges { 343 for _, out := range edge.Outputs { 344 fmt.Printf(" %s\n", out.Path) 345 } 346 } 347 validationEdges := node.ValidationOutEdges 348 if len(validationEdges) != 0 { 349 fmt.Printf(" validation for:\n") 350 for _, edge := range validationEdges { 351 for _, out := range edge.Outputs { 352 fmt.Printf(" %s\n", out.Path) 353 } 354 } 355 } 356 } 357 return 0 358 } 359 360 func toolBrowse(n *ninjaMain, opts *options, args []string) int { 361 runBrowsePython(&n.state, n.ninjaCommand, opts.inputFile, args) 362 return 0 363 } 364 365 /* Only defined on Windows in C++. 366 func toolMSVC(n *ninjaMain,opts *options, args []string) int { 367 // Reset getopt: push one argument onto the front of argv, reset optind. 368 //argc++ 369 //argv-- 370 //optind = 0 371 return msvcHelperMain(args) 372 } 373 */ 374 375 func toolTargetsListNodes(nodes []*nin.Node, depth int, indent int) int { 376 for _, n := range nodes { 377 for i := 0; i < indent; i++ { 378 fmt.Printf(" ") 379 } 380 target := n.Path 381 if n.InEdge != nil { 382 fmt.Printf("%s: %s\n", target, n.InEdge.Rule.Name) 383 if depth > 1 || depth <= 0 { 384 toolTargetsListNodes(n.InEdge.Inputs, depth-1, indent+1) 385 } 386 } else { 387 fmt.Printf("%s\n", target) 388 } 389 } 390 return 0 391 } 392 393 func toolTargetsSourceList(state *nin.State) int { 394 for _, e := range state.Edges { 395 for _, inps := range e.Inputs { 396 if inps.InEdge == nil { 397 fmt.Printf("%s\n", inps.Path) 398 } 399 } 400 } 401 return 0 402 } 403 404 func toolTargetsListRule(state *nin.State, ruleName string) int { 405 rules := map[string]struct{}{} 406 407 // Gather the outputs. 408 for _, e := range state.Edges { 409 if e.Rule.Name == ruleName { 410 for _, outNode := range e.Outputs { 411 rules[outNode.Path] = struct{}{} 412 } 413 } 414 } 415 416 names := make([]string, 0, len(rules)) 417 for n := range rules { 418 names = append(names, n) 419 } 420 sort.Strings(names) 421 // Print them. 422 for _, i := range names { 423 fmt.Printf("%s\n", i) 424 } 425 return 0 426 } 427 428 func toolTargetsList(state *nin.State) int { 429 for _, e := range state.Edges { 430 for _, outNode := range e.Outputs { 431 fmt.Printf("%s: %s\n", outNode.Path, e.Rule.Name) 432 } 433 } 434 return 0 435 } 436 437 func toolDeps(n *ninjaMain, opts *options, args []string) int { 438 var nodes []*nin.Node 439 if len(args) == 0 { 440 for _, ni := range n.depsLog.Nodes { 441 if n.depsLog.IsDepsEntryLiveFor(ni) { 442 nodes = append(nodes, ni) 443 } 444 } 445 } else { 446 var err error 447 nodes, err = n.collectTargetsFromArgs(args) 448 if err != nil { 449 errorf("%s", err) 450 return 1 451 } 452 } 453 454 di := nin.RealDiskInterface{} 455 for _, it := range nodes { 456 deps := n.depsLog.GetDeps(it) 457 if deps == nil { 458 fmt.Printf("%s: deps not found\n", it.Path) 459 continue 460 } 461 462 mtime, err := di.Stat(it.Path) 463 if mtime == -1 { 464 errorf("%s", err) // Log and ignore Stat() errors; 465 } 466 s := "VALID" 467 if mtime == 0 || mtime > deps.MTime { 468 s = "STALE" 469 } 470 fmt.Printf("%s: #deps %d, deps mtime %d (%s)\n", it.Path, len(deps.Nodes), deps.MTime, s) 471 for _, n := range deps.Nodes { 472 fmt.Printf(" %s\n", n.Path) 473 } 474 fmt.Printf("\n") 475 } 476 return 0 477 } 478 479 func toolMissingDeps(n *ninjaMain, opts *options, args []string) int { 480 nodes, err := n.collectTargetsFromArgs(args) 481 if err != nil { 482 errorf("%s", err) 483 return 1 484 } 485 printer := missingDependencyPrinter{} 486 scanner := nin.NewMissingDependencyScanner(&printer, &n.depsLog, &n.state, &nin.RealDiskInterface{}) 487 for _, it := range nodes { 488 scanner.ProcessNode(it) 489 } 490 scanner.PrintStats() 491 if scanner.HadMissingDeps() { 492 return 3 493 } 494 return 0 495 } 496 497 func toolTargets(n *ninjaMain, opts *options, args []string) int { 498 depth := 1 499 if len(args) >= 1 { 500 mode := args[0] 501 if mode == "rule" { 502 rule := "" 503 if len(args) > 1 { 504 rule = args[1] 505 } 506 if len(rule) == 0 { 507 return toolTargetsSourceList(&n.state) 508 } 509 return toolTargetsListRule(&n.state, rule) 510 } 511 if mode == "depth" { 512 if len(args) > 1 { 513 // TODO(maruel): Handle error. 514 depth, _ = strconv.Atoi(args[1]) 515 } 516 } else if mode == "all" { 517 return toolTargetsList(&n.state) 518 } else { 519 suggestion := nin.SpellcheckString(mode, "rule", "depth", "all") 520 if suggestion != "" { 521 errorf("unknown target tool mode '%s', did you mean '%s'?", mode, suggestion) 522 } else { 523 errorf("unknown target tool mode '%s'", mode) 524 } 525 return 1 526 } 527 } 528 529 if rootNodes := n.state.RootNodes(); len(rootNodes) != 0 { 530 return toolTargetsListNodes(rootNodes, depth, 0) 531 } 532 errorf("could not determine root nodes of build graph") 533 return 1 534 } 535 536 func toolRules(n *ninjaMain, opts *options, args []string) int { 537 // HACK: parse one additional flag. 538 //fmt.Printf("usage: nin -t rules [options]\n\noptions:\n -d also print the description of the rule\n -h print this message\n") 539 printDescription := false 540 for i := 0; i < len(args); i++ { 541 if args[i] == "-d" { 542 if i != len(args)-1 { 543 copy(args[i:], args[i+1:]) 544 args = args[:len(args)-1] 545 } 546 printDescription = true 547 } 548 } 549 550 rules := n.state.Bindings.Rules 551 names := make([]string, 0, len(rules)) 552 for n := range rules { 553 names = append(names, n) 554 } 555 sort.Strings(names) 556 557 // Print rules 558 for _, name := range names { 559 fmt.Printf("%s", name) 560 if printDescription { 561 rule := rules[name] 562 description := rule.Bindings["description"] 563 if description != nil { 564 fmt.Printf(": %s", description.Unparse()) 565 } 566 } 567 fmt.Printf("\n") 568 } 569 return 0 570 } 571 572 func toolWinCodePage(n *ninjaMain, opts *options, args []string) int { 573 panic("TODO") // Windows only 574 /* 575 if len(args) != 0 { 576 fmt.Printf("usage: nin -t wincodepage\n") 577 return 1 578 } 579 cp := "ANSI" 580 if GetACP() == CP_UTF8 { 581 cp = "UTF-8" 582 } 583 fmt.Printf("Build file encoding: %s\n", cp) 584 return 0 585 */ 586 } 587 588 type printCommandMode bool 589 590 const ( 591 pcmSingle printCommandMode = false 592 pcmAll printCommandMode = true 593 ) 594 595 func printCommands(edge *nin.Edge, seen map[*nin.Edge]struct{}, mode printCommandMode) { 596 if edge == nil { 597 return 598 } 599 if _, ok := seen[edge]; ok { 600 return 601 } 602 seen[edge] = struct{}{} 603 604 if mode == pcmAll { 605 for _, in := range edge.Inputs { 606 printCommands(in.InEdge, seen, mode) 607 } 608 } 609 610 if edge.Rule != nin.PhonyRule { 611 fmt.Printf("%s\n", (edge.EvaluateCommand(false))) 612 } 613 } 614 615 func toolCommands(n *ninjaMain, opts *options, args []string) int { 616 // HACK: parse one additional flag. 617 //fmt.Printf("usage: nin -t commands [options] [targets]\n\noptions:\n -s only print the final command to build [target], not the whole chain\n") 618 mode := pcmAll 619 for i := 0; i < len(args); i++ { 620 if args[i] == "-s" { 621 if i != len(args)-1 { 622 copy(args[i:], args[i+1:]) 623 args = args[:len(args)-1] 624 } 625 mode = pcmSingle 626 } 627 } 628 629 nodes, err := n.collectTargetsFromArgs(args) 630 if err != nil { 631 errorf("%s", err) 632 return 1 633 } 634 635 seen := map[*nin.Edge]struct{}{} 636 for _, in := range nodes { 637 printCommands(in.InEdge, seen, mode) 638 } 639 return 0 640 } 641 642 func toolClean(n *ninjaMain, opts *options, args []string) int { 643 // HACK: parse two additional flags. 644 // fmt.Printf("usage: nin -t clean [options] [targets]\n\noptions:\n -g also clean files marked as ninja generator output\n -r interpret targets as a list of rules to clean instead\n" ) 645 generator := false 646 cleanRules := false 647 for i := 0; i < len(args); i++ { 648 if args[i] == "-g" { 649 if i != len(args)-1 { 650 copy(args[i:], args[i+1:]) 651 args = args[:len(args)-1] 652 } 653 generator = true 654 } else if args[i] == "-r" { 655 if i != len(args)-1 { 656 copy(args[i:], args[i+1:]) 657 args = args[:len(args)-1] 658 } 659 cleanRules = true 660 } 661 } 662 663 if cleanRules && len(args) == 0 { 664 errorf("expected a rule to clean") 665 return 1 666 } 667 668 cleaner := nin.NewCleaner(&n.state, n.config, &n.di) 669 if len(args) >= 1 { 670 if cleanRules { 671 return cleaner.CleanRules(args) 672 } 673 return cleaner.CleanTargets(args) 674 } 675 return cleaner.CleanAll(generator) 676 } 677 678 func toolCleanDead(n *ninjaMain, opts *options, args []string) int { 679 cleaner := nin.NewCleaner(&n.state, n.config, &n.di) 680 return cleaner.CleanDead(n.buildLog.Entries) 681 } 682 683 type evaluateCommandMode bool 684 685 const ( 686 ecmNormal evaluateCommandMode = false 687 ecmExpandRSPFile evaluateCommandMode = true 688 ) 689 690 func evaluateCommandWithRspfile(edge *nin.Edge, mode evaluateCommandMode) string { 691 command := edge.EvaluateCommand(false) 692 if mode == ecmNormal { 693 return command 694 } 695 696 rspfile := edge.GetUnescapedRspfile() 697 if len(rspfile) == 0 { 698 return command 699 } 700 701 index := strings.Index(command, rspfile) 702 if index == 0 || index == -1 || command[index-1] != '@' { 703 return command 704 } 705 706 panic("TODO") 707 /* 708 rspfileContent := edge.GetBinding("rspfile_content") 709 newlineIndex := 0 710 for (newlineIndex = rspfileContent.find('\n', newlineIndex)) != string::npos { 711 rspfileContent.replace(newlineIndex, 1, 1, ' ') 712 newlineIndex++ 713 } 714 command.replace(index - 1, rspfile.length() + 1, rspfileContent) 715 return command 716 */ 717 } 718 719 func printCompdb(directory string, edge *nin.Edge, evalMode evaluateCommandMode) { 720 fmt.Printf("\n {\n \"directory\": \"") 721 printJSONString(directory) 722 fmt.Printf("\",\n \"command\": \"") 723 printJSONString(evaluateCommandWithRspfile(edge, evalMode)) 724 fmt.Printf("\",\n \"file\": \"") 725 printJSONString(edge.Inputs[0].Path) 726 fmt.Printf("\",\n \"output\": \"") 727 printJSONString(edge.Outputs[0].Path) 728 fmt.Printf("\"\n }") 729 } 730 731 func toolCompilationDatabase(n *ninjaMain, opts *options, args []string) int { 732 // HACK: parse one additional flag. 733 // fmt.Printf( "usage: nin -t compdb [options] [rules]\n\noptions:\n -x expand @rspfile style response file invocations\n" ) 734 evalMode := ecmNormal 735 for i := 0; i < len(args); i++ { 736 if args[i] == "-x" { 737 if i != len(args)-1 { 738 copy(args[i:], args[i+1:]) 739 args = args[:len(args)-1] 740 } 741 evalMode = ecmExpandRSPFile 742 } 743 } 744 745 first := true 746 cwd, err := os.Getwd() 747 if err != nil { 748 panic(err) 749 } 750 fmt.Printf("[") 751 for _, e := range n.state.Edges { 752 if len(e.Inputs) == 0 { 753 continue 754 } 755 if len(args) == 0 { 756 if !first { 757 fmt.Printf(",") 758 } 759 printCompdb(cwd, e, evalMode) 760 first = false 761 } else { 762 for i := 0; i != len(args); i++ { 763 if e.Rule.Name == args[i] { 764 if !first { 765 fmt.Printf(",") 766 } 767 printCompdb(cwd, e, evalMode) 768 first = false 769 } 770 } 771 } 772 } 773 774 fmt.Printf("\n]") 775 return 0 776 } 777 778 func toolRecompact(n *ninjaMain, opts *options, args []string) int { 779 if !n.EnsureBuildDirExists() { 780 return 1 781 } 782 783 // recompactOnly 784 if !n.OpenBuildLog(true) || !n.OpenDepsLog(true) { 785 return 1 786 } 787 788 return 0 789 } 790 791 func toolRestat(n *ninjaMain, opts *options, args []string) int { 792 if !n.EnsureBuildDirExists() { 793 return 1 794 } 795 796 logPath := ".ninja_log" 797 if n.buildDir != "" { 798 logPath = filepath.Join(n.buildDir, logPath) 799 } 800 801 status, err := n.buildLog.Load(logPath) 802 if status == nin.LoadError { 803 errorf("loading build log %s: %s", logPath, err) 804 return nin.ExitFailure 805 } 806 if status == nin.LoadNotFound { 807 // Nothing to restat, ignore this 808 return nin.ExitSuccess 809 } 810 if err != nil { 811 // Hack: Load() can return a warning via err by returning LOAD_SUCCESS. 812 warningf("%s", err) 813 } 814 815 if err := n.buildLog.Restat(logPath, &n.di, args); err != nil { 816 errorf("failed recompaction: %s", err) 817 return nin.ExitFailure 818 } 819 820 if !n.config.DryRun { 821 if err := n.buildLog.OpenForWrite(logPath, n); err != nil { 822 errorf("opening build log: %s", err) 823 return nin.ExitFailure 824 } 825 } 826 827 return nin.ExitSuccess 828 } 829 830 // Find the function to execute for \a toolName and return it via \a func. 831 // Returns a Tool, or NULL if Ninja should exit. 832 func chooseTool(toolName string) *tool { 833 tools := []*tool{ 834 {"browse", "browse dependency graph in a web browser", runAfterLoad, toolBrowse}, 835 //{"msvc", "build helper for MSVC cl.exe (EXPERIMENTAL)",runAfterFlags, toolMSVC}, 836 {"clean", "clean built files", runAfterLoad, toolClean}, 837 {"commands", "list all commands required to rebuild given targets", runAfterLoad, toolCommands}, 838 {"deps", "show dependencies stored in the deps log", runAfterLogs, toolDeps}, 839 {"missingdeps", "check deps log dependencies on generated files", runAfterLogs, toolMissingDeps}, 840 {"graph", "output graphviz dot file for targets", runAfterLoad, toolGraph}, 841 {"query", "show inputs/outputs for a path", runAfterLogs, toolQuery}, 842 {"targets", "list targets by their rule or depth in the DAG", runAfterLoad, toolTargets}, 843 {"compdb", "dump JSON compilation database to stdout", runAfterLoad, toolCompilationDatabase}, 844 {"recompact", "recompacts ninja-internal data structures", runAfterLoad, toolRecompact}, 845 {"restat", "restats all outputs in the build log", runAfterFlags, toolRestat}, 846 {"rules", "list all rules", runAfterLoad, toolRules}, 847 {"cleandead", "clean built files that are no longer produced by the manifest", runAfterLogs, toolCleanDead}, 848 //{"wincodepage", "print the Windows code page used by nin", runAfterFlags, toolWinCodePage}, 849 } 850 if toolName == "list" { 851 fmt.Printf("nin subtools:\n") 852 for _, t := range tools { 853 if t.desc != "" { 854 fmt.Printf("%11s %s\n", t.name, t.desc) 855 } 856 } 857 return nil 858 } 859 860 for _, t := range tools { 861 if t.name == toolName { 862 return t 863 } 864 } 865 866 var words []string 867 for _, t := range tools { 868 words = append(words, t.name) 869 } 870 suggestion := nin.SpellcheckString(toolName, words...) 871 if suggestion != "" { 872 fatalf("unknown tool '%s', did you mean '%s'?", toolName, suggestion) 873 } else { 874 fatalf("unknown tool '%s'", toolName) 875 } 876 return nil // Not reached. 877 } 878 879 var ( 880 disableExperimentalStatcache bool 881 metricsEnabled bool 882 ) 883 884 // debugEnable enables debugging modes. 885 // 886 // Returns false if Ninja should exit instead of continuing. 887 func debugEnable(values []string) bool { 888 for _, name := range values { 889 switch name { 890 case "list": 891 // TODO(maruel): Generate? 892 fmt.Printf("debugging modes:\n stats print operation counts/timing info\n explain explain what caused a command to execute\n keepdepfile don't delete depfiles after they're read by ninja\n keeprsp don't delete @response files on success\n nostatcache don't batch stat() calls per directory and cache them\nmultiple modes can be enabled via -d FOO -d BAR\n") 893 //#ifdef _WIN32//#endif 894 return false 895 case "stats": 896 metricsEnabled = true 897 nin.Metrics.Enable() 898 case "explain": 899 nin.Debug.Explaining = true 900 case "keepdepfile": 901 nin.Debug.KeepDepfile = true 902 case "keeprsp": 903 nin.Debug.KeepRsp = true 904 case "nostatcache": 905 disableExperimentalStatcache = true 906 default: 907 suggestion := nin.SpellcheckString(name, "stats", "explain", "keepdepfile", "keeprsp", "nostatcache") 908 if suggestion != "" { 909 errorf("unknown debug setting '%s', did you mean '%s'?", name, suggestion) 910 } else { 911 errorf("unknown debug setting '%s'", name) 912 } 913 return false 914 } 915 } 916 return true 917 } 918 919 // Set a warning flag. Returns false if Ninja should exit instead of 920 // continuing. 921 func warningEnable(name string, opts *options) bool { 922 if name == "list" { 923 fmt.Printf("warning flags:\n phonycycle={err,warn} phony build statement references itself\n") 924 return false 925 } else if name == "dupbuild=err" { 926 opts.parserOpts.ErrOnDupeEdge = true 927 return true 928 } else if name == "dupbuild=warn" { 929 opts.parserOpts.ErrOnDupeEdge = false 930 return true 931 } else if name == "phonycycle=err" { 932 opts.parserOpts.ErrOnPhonyCycle = true 933 return true 934 } else if name == "phonycycle=warn" { 935 opts.parserOpts.ErrOnPhonyCycle = false 936 return true 937 } else if name == "depfilemulti=err" || name == "depfilemulti=warn" { 938 warningf("deprecated warning 'depfilemulti'") 939 return true 940 } else { 941 suggestion := nin.SpellcheckString(name, "dupbuild=err", "dupbuild=warn", "phonycycle=err", "phonycycle=warn") 942 if suggestion != "" { 943 errorf("unknown warning flag '%s', did you mean '%s'?", name, suggestion) 944 } else { 945 errorf("unknown warning flag '%s'", name) 946 } 947 return false 948 } 949 } 950 951 // Open the build log. 952 // @return false on error. 953 func (n *ninjaMain) OpenBuildLog(recompactOnly bool) bool { 954 logPath := ".ninja_log" 955 if n.buildDir != "" { 956 logPath = n.buildDir + "/" + logPath 957 } 958 959 status, err := n.buildLog.Load(logPath) 960 if status == nin.LoadError { 961 errorf("loading build log %s: %s", logPath, err) 962 return false 963 } 964 if err != nil { 965 // Hack: Load() can return a warning via err by returning LOAD_SUCCESS. 966 warningf("%s", err) 967 } 968 969 if recompactOnly { 970 if status == nin.LoadNotFound { 971 return true 972 } 973 974 if err = n.buildLog.Recompact(logPath, n); err != nil { 975 errorf("failed recompaction: %s", err) 976 return false 977 } 978 return true 979 } 980 981 if !n.config.DryRun { 982 if err = n.buildLog.OpenForWrite(logPath, n); err != nil { 983 errorf("opening build log: %s", err) 984 return false 985 } 986 } 987 988 return true 989 } 990 991 // Open the deps log: load it, then open for writing. 992 // @return false on error. 993 // Open the deps log: load it, then open for writing. 994 // @return false on error. 995 func (n *ninjaMain) OpenDepsLog(recompactOnly bool) bool { 996 path := ".ninja_deps" 997 if n.buildDir != "" { 998 path = n.buildDir + "/" + path 999 } 1000 1001 status, err := n.depsLog.Load(path, &n.state) 1002 if status == nin.LoadError { 1003 errorf("loading deps log %s: %s", path, err) 1004 return false 1005 } 1006 if err != nil { 1007 // Load() can return a warning via err by returning LOAD_SUCCESS. 1008 warningf("%s", err) 1009 } 1010 1011 if recompactOnly { 1012 if status == nin.LoadNotFound { 1013 return true 1014 } 1015 if err := n.depsLog.Recompact(path); err != nil { 1016 errorf("failed recompaction: %s", err) 1017 return false 1018 } 1019 return true 1020 } 1021 1022 if !n.config.DryRun { 1023 if err := n.depsLog.OpenForWrite(path); err != nil { 1024 errorf("opening deps log: %s", err) 1025 return false 1026 } 1027 } 1028 1029 return true 1030 } 1031 1032 // Dump the output requested by '-d stats'. 1033 func (n *ninjaMain) DumpMetrics() { 1034 nin.Metrics.Report() 1035 1036 fmt.Printf("\n") 1037 // There's no such concept in Go's map. 1038 //count := len(n.state.paths) 1039 //buckets := len(n.state.paths) 1040 //fmt.Printf("path.node hash load %.2f (%d entries / %d buckets)\n", count/float64(buckets), count, buckets) 1041 } 1042 1043 // Ensure the build directory exists, creating it if necessary. 1044 // @return false on error. 1045 func (n *ninjaMain) EnsureBuildDirExists() bool { 1046 n.buildDir = n.state.Bindings.LookupVariable("builddir") 1047 if n.buildDir != "" && !n.config.DryRun { 1048 if err := nin.MakeDirs(&n.di, filepath.Join(n.buildDir, ".")); err != nil { 1049 errorf("creating build directory %s", n.buildDir) 1050 return false 1051 } 1052 } 1053 return true 1054 } 1055 1056 // Build the targets listed on the command line. 1057 // @return an exit code. 1058 func (n *ninjaMain) RunBuild(args []string, status nin.Status) int { 1059 targets, err := n.collectTargetsFromArgs(args) 1060 if err != nil { 1061 status.Error("%s", err) 1062 return 1 1063 } 1064 1065 n.di.AllowStatCache(!disableExperimentalStatcache) 1066 1067 builder := nin.NewBuilder(&n.state, n.config, &n.buildLog, &n.depsLog, &n.di, status, n.startTimeMillis) 1068 for i := 0; i < len(targets); i++ { 1069 if dirty, err := builder.AddTarget(targets[i]); !dirty { 1070 if err != nil { 1071 status.Error("%s", err) 1072 return 1 1073 } 1074 // Added a target that is already up-to-date; not really 1075 // an error. 1076 } 1077 } 1078 1079 // Make sure restat rules do not see stale timestamps. 1080 n.di.AllowStatCache(false) 1081 1082 if builder.AlreadyUpToDate() { 1083 status.Info("no work to do.") 1084 return 0 1085 } 1086 1087 if err := builder.Build(); err != nil { 1088 status.Info("build stopped: %s.", err) 1089 if strings.Contains(err.Error(), "interrupted by user") { 1090 return 2 1091 } 1092 return 1 1093 } 1094 return 0 1095 } 1096 1097 /* 1098 // This handler processes fatal crashes that you can't catch 1099 // Test example: C++ exception in a stack-unwind-block 1100 // Real-world example: ninja launched a compiler to process a tricky 1101 // C++ input file. The compiler got itself into a state where it 1102 // generated 3 GB of output and caused ninja to crash. 1103 func terminateHandler() { 1104 CreateWin32MiniDump(nil) 1105 Fatal("terminate handler called") 1106 } 1107 1108 // On Windows, we want to prevent error dialogs in case of exceptions. 1109 // This function handles the exception, and writes a minidump. 1110 func exceptionFilter(code unsigned int, ep *struct _EXCEPTION_POINTERS) int { 1111 Error("exception: 0x%X", code) // e.g. EXCEPTION_ACCESS_VIOLATION 1112 fflush(stderr) 1113 CreateWin32MiniDump(ep) 1114 return EXCEPTION_EXECUTE_HANDLER 1115 } 1116 */ 1117 1118 type multi []string 1119 1120 func (m *multi) String() string { 1121 return strings.Join(*m, ", ") 1122 } 1123 1124 func (m *multi) Set(s string) error { 1125 if len(s) == 0 { 1126 return errors.New("empty value") 1127 } 1128 *m = append(*m, s) 1129 return nil 1130 } 1131 1132 // Parse args for command-line options. 1133 // Returns an exit code, or -1 if Ninja should continue. 1134 func readFlags(opts *options, config *nin.BuildConfig) int { 1135 // TODO(maruel): For now just do something simple to get started but we'll 1136 // have to make it custom if we want it to be drop-in replacement. 1137 // It's funny how "opts" and "config" is a bit mixed up here. 1138 flag.StringVar(&opts.inputFile, "f", "build.ninja", "specify input build file") 1139 flag.StringVar(&opts.workingDir, "C", "", "change to DIR before doing anything else") 1140 opts.parserOpts.ErrOnDupeEdge = true 1141 flag.StringVar(&opts.cpuprofile, "cpuprofile", "", "activate the CPU sampling profiler") 1142 flag.StringVar(&opts.memprofile, "memprofile", "", "snapshot a heap dump at the end") 1143 flag.StringVar(&opts.trace, "trace", "", "capture a runtime trace") 1144 1145 flag.IntVar(&config.Parallelism, "j", guessParallelism(), "run N jobs in parallel (0 means infinity)") 1146 flag.IntVar(&config.FailuresAllowed, "k", 1, "keep going until N jobs fail (0 means infinity)") 1147 flag.Float64Var(&config.MaxLoadAvg, "l", 0, "do not start new jobs if the load average is greater than N") 1148 flag.BoolVar(&config.DryRun, "n", false, "dry run (don't run commands but act like they succeeded)") 1149 1150 // TODO(maruel): terminates toplevel options; further flags are passed to the tool 1151 t := flag.String("t", "", "run a subtool (use '-t list' to list subtools)") 1152 // TODO(maruel): It's supposed to be accumulative. 1153 var dbgEnable multi 1154 flag.Var(&dbgEnable, "d", "enable debugging (use '-d list' to list modes)") 1155 verbose := flag.Bool("v", false, "show all command lines while building") 1156 flag.BoolVar(verbose, "verbose", false, "show all command lines while building") 1157 quiet := flag.Bool("quiet", false, "don't show progress status, just command output") 1158 warning := flag.String("w", "", "adjust warnings (use '-w list' to list warnings)") 1159 version := flag.Bool("version", false, fmt.Sprintf("print nin version (%q)", nin.NinjaVersion)) 1160 1161 // Flags that do not exist in the C++ code: 1162 serial := flag.Bool("serial", false, "parse subninja files serially; default is concurrent") 1163 noprewarm := flag.Bool("noprewarm", false, "do not prewarm subninja files; instead process them in order") 1164 opts.parserOpts.Concurrency = nin.ParseManifestConcurrentParsing 1165 1166 flag.Usage = usage 1167 flag.Parse() 1168 1169 if *verbose && *quiet { 1170 fmt.Fprintf(os.Stderr, "can't use both -v and --quiet\n") 1171 return 2 1172 } 1173 if *verbose { 1174 config.Verbosity = nin.Verbose 1175 } 1176 if *quiet { 1177 config.Verbosity = nin.NoStatusUpdate 1178 } 1179 if *warning != "" { 1180 if !warningEnable(*warning, opts) { 1181 return 1 1182 } 1183 } 1184 if !debugEnable(dbgEnable) { 1185 return 1 1186 } 1187 if *version { 1188 fmt.Printf("%s\n", nin.NinjaVersion) 1189 return 0 1190 } 1191 if *t != "" { 1192 opts.tool = chooseTool(*t) 1193 if opts.tool == nil { 1194 return 0 1195 } 1196 } 1197 i := 0 1198 if opts.cpuprofile != "" { 1199 i++ 1200 } 1201 if opts.memprofile != "" { 1202 i++ 1203 } 1204 if opts.trace != "" { 1205 i++ 1206 } 1207 if i > 1 { 1208 fmt.Fprintf(os.Stderr, "can only use one of -cpuprofile, -memprofile or -trace at a time.\n") 1209 return 2 1210 } 1211 1212 if *serial { 1213 opts.parserOpts.Concurrency = nin.ParseManifestPrewarmSubninja 1214 } 1215 if *noprewarm { 1216 opts.parserOpts.Concurrency = nin.ParseManifestSerial 1217 } 1218 1219 /* 1220 OPT_VERSION := 1 1221 OPT_QUIET := 2 1222 option longOptions[] = { 1223 { "help", noArgument, nil, 'h' }, 1224 { "version", noArgument, nil, OPT_VERSION }, 1225 { "verbose", noArgument, nil, 'v' }, 1226 { "quiet", noArgument, nil, OPT_QUIET }, 1227 { nil, 0, nil, 0 } 1228 } 1229 1230 for opts.tool ==nil { 1231 opt := getoptLong(*argc, *argv, "d:f:j:k:l:nt:vw:C:h", longOptions, nil)) 1232 if opt == -1 { 1233 continue 1234 } 1235 switch opt { 1236 case 'd': 1237 if !debugEnable(optarg) { 1238 return 1 1239 } 1240 break 1241 case 'f': 1242 opts.inputFile = optarg 1243 break 1244 case 'j': { 1245 var end *char 1246 value := strtol(optarg, &end, 10) 1247 if *end != 0 || value < 0 { 1248 Fatal("invalid -j parameter") 1249 } 1250 1251 // We want to run N jobs in parallel. For N = 0, INT_MAX 1252 // is close enough to infinite for most sane builds. 1253 config.parallelism = value > 0 ? value : INT_MAX 1254 break 1255 } 1256 case 'k': { 1257 var end *char 1258 value := strtol(optarg, &end, 10) 1259 if *end != 0 { 1260 Fatal("-k parameter not numeric; did you mean -k 0?") 1261 } 1262 1263 // We want to go until N jobs fail, which means we should allow 1264 // N failures and then stop. For N <= 0, INT_MAX is close enough 1265 // to infinite for most sane builds. 1266 config.failuresAllowed = value > 0 ? value : INT_MAX 1267 break 1268 } 1269 case 'l': { 1270 var end *char 1271 value := strtod(optarg, &end) 1272 if end == optarg { 1273 Fatal("-l parameter not numeric: did you mean -l 0.0?") 1274 } 1275 config.maxLoadAverage = value 1276 break 1277 } 1278 case 'n': 1279 config.dryRun = true 1280 break 1281 case 't': 1282 opts.tool = chooseTool(optarg) 1283 if !opts.tool { 1284 return 0 1285 } 1286 break 1287 case 'v': 1288 config.verbosity = Verbose 1289 break 1290 case OPT_QUIET: 1291 config.verbosity = nin.NoStatusUpdate 1292 break 1293 case 'w': 1294 if !warningEnable(optarg, opts) { 1295 return 1 1296 } 1297 break 1298 case 'C': 1299 opts.workingDir = optarg 1300 break 1301 case OPT_VERSION: 1302 fmt.Printf("%s\n", nin.NinjaVersion) 1303 return 0 1304 case 'h': 1305 default: 1306 usage() 1307 return 1 1308 } 1309 } 1310 *argv += optind 1311 *argc -= optind 1312 */ 1313 return -1 1314 } 1315 1316 func mainImpl() int { 1317 // Use exit() instead of return in this function to avoid potentially 1318 // expensive cleanup when destructing ninjaMain. 1319 config := nin.NewBuildConfig() 1320 opts := options{} 1321 1322 //setvbuf(stdout, nil, _IOLBF, BUFSIZ) 1323 ninjaCommand := os.Args[0] 1324 exitCode := readFlags(&opts, &config) 1325 if exitCode >= 0 { 1326 return exitCode 1327 } 1328 // TODO(maruel): Handle os.Interrupt and cancel the context cleanly. 1329 1330 // Disable GC (TODO: unless running a stateful server). 1331 debug.SetGCPercent(-1) 1332 1333 if opts.cpuprofile != "" { 1334 f, err := os.Create(opts.cpuprofile) 1335 if err != nil { 1336 log.Fatal("could not create CPU profile: ", err) 1337 } 1338 defer f.Close() 1339 if err := pprof.StartCPUProfile(f); err != nil { 1340 log.Fatal("could not start CPU profile: ", err) 1341 } 1342 defer pprof.StopCPUProfile() 1343 } 1344 1345 if opts.memprofile != "" { 1346 // Take all memory allocation. This significantly slows down the process. 1347 runtime.MemProfileRate = 1 1348 defer func() { 1349 f, err := os.Create(opts.memprofile) 1350 if err != nil { 1351 log.Fatal("could not create memory profile: ", err) 1352 } 1353 defer f.Close() 1354 if err := pprof.Lookup("heap").WriteTo(f, 0); err != nil { 1355 log.Fatal("could not write memory profile: ", err) 1356 } 1357 }() 1358 } else { 1359 // No need. 1360 runtime.MemProfileRate = 0 1361 } 1362 if opts.trace != "" { 1363 f, err := os.Create(opts.trace) 1364 if err != nil { 1365 log.Fatal("could not create trace: ", err) 1366 } 1367 defer f.Close() 1368 // TODO(maruel): Use regions. 1369 if err := trace.Start(f); err != nil { 1370 log.Fatal("could not start trace: ", err) 1371 } 1372 defer trace.Stop() 1373 } 1374 1375 args := flag.Args() 1376 1377 status := newStatusPrinter(&config) 1378 if opts.workingDir != "" { 1379 // The formatting of this string, complete with funny quotes, is 1380 // so Emacs can properly identify that the cwd has changed for 1381 // subsequent commands. 1382 // Don't print this if a tool is being used, so that tool output 1383 // can be piped into a file without this string showing up. 1384 if opts.tool == nil && config.Verbosity != nin.NoStatusUpdate { 1385 status.Info("Entering directory `%s'", opts.workingDir) 1386 } 1387 if err := os.Chdir(opts.workingDir); err != nil { 1388 fatalf("chdir to '%s' - %s", opts.workingDir, err) 1389 } 1390 } 1391 1392 if opts.tool != nil && opts.tool.when == runAfterFlags { 1393 // None of the runAfterFlags actually use a ninjaMain, but it's needed 1394 // by other tools. 1395 ninja := newNinjaMain(ninjaCommand, &config) 1396 return opts.tool.tool(&ninja, &opts, args) 1397 } 1398 1399 // TODO(maruel): Let's wrap stdout/stderr with our own buffer? 1400 1401 /* 1402 // It'd be nice to use line buffering but MSDN says: "For some systems, 1403 // [_IOLBF] provides line buffering. However, for Win32, the behavior is the 1404 // same as _IOFBF - Full Buffering." 1405 // Buffering used to be disabled in the LinePrinter constructor but that 1406 // now disables it too early and breaks -t deps performance (see issue #2018) 1407 // so we disable it here instead, but only when not running a tool. 1408 if !opts.tool { 1409 setvbuf(stdout, nil, _IONBF, 0) 1410 } 1411 */ 1412 // Limit number of rebuilds, to prevent infinite loops. 1413 const cycleLimit = 100 1414 for cycle := 1; cycle <= cycleLimit; cycle++ { 1415 ninja := newNinjaMain(ninjaCommand, &config) 1416 input, err2 := ninja.di.ReadFile(opts.inputFile) 1417 if err2 != nil { 1418 status.Error("%s", err2) 1419 return 1 1420 } 1421 if err := nin.ParseManifest(&ninja.state, &ninja.di, opts.parserOpts, opts.inputFile, input); err != nil { 1422 status.Error("%s", err) 1423 return 1 1424 } 1425 1426 if opts.tool != nil && opts.tool.when == runAfterLoad { 1427 return opts.tool.tool(&ninja, &opts, args) 1428 } 1429 1430 if !ninja.EnsureBuildDirExists() { 1431 return 1 1432 } 1433 1434 if !ninja.OpenBuildLog(false) || !ninja.OpenDepsLog(false) { 1435 return 1 1436 } 1437 1438 if opts.tool != nil && opts.tool.when == runAfterLogs { 1439 return opts.tool.tool(&ninja, &opts, args) 1440 } 1441 1442 // Attempt to rebuild the manifest before building anything else 1443 if rebuilt, err := ninja.RebuildManifest(opts.inputFile, status); rebuilt { 1444 // In dryRun mode the regeneration will succeed without changing the 1445 // manifest forever. Better to return immediately. 1446 if config.DryRun { 1447 return 0 1448 } 1449 // Start the build over with the new manifest. 1450 continue 1451 } else if err != nil { 1452 status.Error("rebuilding '%s': %s", opts.inputFile, err) 1453 return 1 1454 } 1455 1456 result := ninja.RunBuild(args, status) 1457 if metricsEnabled { 1458 ninja.DumpMetrics() 1459 } 1460 return result 1461 } 1462 1463 status.Error("manifest '%s' still dirty after %d tries", opts.inputFile, cycleLimit) 1464 return 1 1465 }