Home | History | Annotate | Download | only in build
      1 // Copyright 2017 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 build
     16 
     17 import (
     18 	"fmt"
     19 	"os"
     20 	"path/filepath"
     21 	"strconv"
     22 	"strings"
     23 	"time"
     24 )
     25 
     26 func runNinja(ctx Context, config Config) {
     27 	ctx.BeginTrace("ninja")
     28 	defer ctx.EndTrace()
     29 
     30 	executable := config.PrebuiltBuildTool("ninja")
     31 	args := []string{
     32 		"-d", "keepdepfile",
     33 	}
     34 
     35 	args = append(args, config.NinjaArgs()...)
     36 
     37 	var parallel int
     38 	if config.UseGoma() {
     39 		parallel = config.RemoteParallel()
     40 	} else {
     41 		parallel = config.Parallel()
     42 	}
     43 	args = append(args, "-j", strconv.Itoa(parallel))
     44 	if config.keepGoing != 1 {
     45 		args = append(args, "-k", strconv.Itoa(config.keepGoing))
     46 	}
     47 
     48 	args = append(args, "-f", config.CombinedNinjaFile())
     49 
     50 	if config.IsVerbose() {
     51 		args = append(args, "-v")
     52 	}
     53 	args = append(args, "-w", "dupbuild=err")
     54 
     55 	cmd := Command(ctx, config, "ninja", executable, args...)
     56 	if config.HasKatiSuffix() {
     57 		cmd.Environment.AppendFromKati(config.KatiEnvFile())
     58 	}
     59 
     60 	// Allow both NINJA_ARGS and NINJA_EXTRA_ARGS, since both have been
     61 	// used in the past to specify extra ninja arguments.
     62 	if extra, ok := cmd.Environment.Get("NINJA_ARGS"); ok {
     63 		cmd.Args = append(cmd.Args, strings.Fields(extra)...)
     64 	}
     65 	if extra, ok := cmd.Environment.Get("NINJA_EXTRA_ARGS"); ok {
     66 		cmd.Args = append(cmd.Args, strings.Fields(extra)...)
     67 	}
     68 
     69 	if _, ok := cmd.Environment.Get("NINJA_STATUS"); !ok {
     70 		cmd.Environment.Set("NINJA_STATUS", "[%p %f/%t] ")
     71 	}
     72 
     73 	cmd.Stdin = ctx.Stdin()
     74 	cmd.Stdout = ctx.Stdout()
     75 	cmd.Stderr = ctx.Stderr()
     76 	logPath := filepath.Join(config.OutDir(), ".ninja_log")
     77 	ninjaHeartbeatDuration := time.Minute * 5
     78 	if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
     79 		// For example, "1m"
     80 		overrideDuration, err := time.ParseDuration(overrideText)
     81 		if err == nil && overrideDuration.Seconds() > 0 {
     82 			ninjaHeartbeatDuration = overrideDuration
     83 		}
     84 	}
     85 	// Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics
     86 	done := make(chan struct{})
     87 	defer close(done)
     88 	ticker := time.NewTicker(ninjaHeartbeatDuration)
     89 	defer ticker.Stop()
     90 	checker := &statusChecker{}
     91 	go func() {
     92 		for {
     93 			select {
     94 			case <-ticker.C:
     95 				checker.check(ctx, config, logPath)
     96 			case <-done:
     97 				return
     98 			}
     99 		}
    100 	}()
    101 
    102 	startTime := time.Now()
    103 	defer ctx.ImportNinjaLog(logPath, startTime)
    104 
    105 	cmd.RunOrFatal()
    106 }
    107 
    108 type statusChecker struct {
    109 	prevTime time.Time
    110 }
    111 
    112 func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) {
    113 	info, err := os.Stat(pathToCheck)
    114 	var newTime time.Time
    115 	if err == nil {
    116 		newTime = info.ModTime()
    117 	}
    118 	if newTime == c.prevTime {
    119 		// ninja may be stuck
    120 		dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime)
    121 	}
    122 	c.prevTime = newTime
    123 }
    124 
    125 // dumpStucknessDiagnostics gets called when it is suspected that Ninja is stuck and we want to output some diagnostics
    126 func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, lastUpdated time.Time) {
    127 
    128 	ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", statusPath, lastUpdated)
    129 
    130 	// The "pstree" command doesn't exist on Mac, but "pstree" on Linux gives more convenient output than "ps"
    131 	// So, we try pstree first, and ps second
    132 	pstreeCommandText := fmt.Sprintf("pstree -pal %v", os.Getpid())
    133 	psCommandText := "ps -ef"
    134 	commandText := pstreeCommandText + " || " + psCommandText
    135 
    136 	cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
    137 	output := cmd.CombinedOutputOrFatal()
    138 	ctx.Verbose(string(output))
    139 
    140 	ctx.Verbosef("done\n")
    141 }
    142