Loading ui/build/ninja.go +59 −36 Original line number Diff line number Diff line Loading @@ -27,10 +27,16 @@ import ( "android/soong/ui/status" ) // Constructs and runs the Ninja command line with a restricted set of // environment variables. It's important to restrict the environment Ninja runs // for hermeticity reasons, and to avoid spurious rebuilds. func runNinja(ctx Context, config Config) { ctx.BeginTrace(metrics.PrimaryNinja, "ninja") defer ctx.EndTrace() // Sets up the FIFO status updater that reads the Ninja protobuf output, and // translates it to the soong_ui status output, displaying real-time // progress of the build. fifo := filepath.Join(config.OutDir(), ".ninja_fifo") nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo) defer nr.Close() Loading Loading @@ -64,8 +70,12 @@ func runNinja(ctx Context, config Config) { "-w", "missingdepfile=err") cmd := Command(ctx, config, "ninja", executable, args...) // Set up the nsjail sandbox Ninja runs in. cmd.Sandbox = ninjaSandbox if config.HasKatiSuffix() { // Reads and executes a shell script from Kati that sets/unsets the // environment Ninja runs in. cmd.Environment.AppendFromKati(config.KatiEnvFile()) } Loading @@ -78,8 +88,8 @@ func runNinja(ctx Context, config Config) { cmd.Args = append(cmd.Args, strings.Fields(extra)...) } logPath := filepath.Join(config.OutDir(), ".ninja_log") ninjaHeartbeatDuration := time.Minute * 5 // Get the ninja heartbeat interval from the environment before it's filtered away later. if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok { // For example, "1m" overrideDuration, err := time.ParseDuration(overrideText) Loading @@ -88,18 +98,22 @@ func runNinja(ctx Context, config Config) { } } // Filter the environment, as ninja does not rebuild files when environment variables change. // Filter the environment, as ninja does not rebuild files when environment // variables change. // // Anything listed here must not change the output of rules/actions when the value changes, // otherwise incremental builds may be unsafe. Vars explicitly set to stable values // elsewhere in soong_ui are fine. // Anything listed here must not change the output of rules/actions when the // value changes, otherwise incremental builds may be unsafe. Vars // explicitly set to stable values elsewhere in soong_ui are fine. // // For the majority of cases, either Soong or the makefiles should be replicating any // necessary environment variables in the command line of each action that needs it. // For the majority of cases, either Soong or the makefiles should be // replicating any necessary environment variables in the command line of // each action that needs it. if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") { ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.") } else { cmd.Environment.Allow(append([]string{ // Set the path to a symbolizer (e.g. llvm-symbolizer) so ASAN-based // tools can symbolize crashes. "ASAN_SYMBOLIZER_PATH", "HOME", "JAVA_HOME", Loading @@ -108,14 +122,19 @@ func runNinja(ctx Context, config Config) { "OUT_DIR", "PATH", "PWD", // https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDONTWRITEBYTECODE "PYTHONDONTWRITEBYTECODE", "TMPDIR", "USER", // TODO: remove these carefully // Options for the address sanitizer. "ASAN_OPTIONS", // The list of Android app modules to be built in an unbundled manner. "TARGET_BUILD_APPS", // The variant of the product being built. e.g. eng, userdebug, debug. "TARGET_BUILD_VARIANT", // The product name of the product being built, e.g. aosp_arm, aosp_flame. "TARGET_PRODUCT", // b/147197813 - used by art-check-debug-apex-gen "EMMA_INSTRUMENT_FRAMEWORK", Loading Loading @@ -162,6 +181,7 @@ func runNinja(ctx Context, config Config) { cmd.Environment.Set("DIST_DIR", config.DistDir()) cmd.Environment.Set("SHELL", "/bin/bash") // Print the environment variables that Ninja is operating in. ctx.Verboseln("Ninja environment: ") envVars := cmd.Environment.Environ() sort.Strings(envVars) Loading @@ -169,17 +189,21 @@ func runNinja(ctx Context, config Config) { ctx.Verbosef(" %s", envVar) } // Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics // Poll the Ninja log for updates regularly based on the heartbeat // frequency. If it isn't updated enough, then we want to surface the // possibility that Ninja is stuck, to the user. done := make(chan struct{}) defer close(done) ticker := time.NewTicker(ninjaHeartbeatDuration) defer ticker.Stop() checker := &statusChecker{} ninjaChecker := &ninjaStucknessChecker{ logPath: filepath.Join(config.OutDir(), ".ninja_log"), } go func() { for { select { case <-ticker.C: checker.check(ctx, config, logPath) ninjaChecker.check(ctx, config) case <-done: return } Loading @@ -190,33 +214,30 @@ func runNinja(ctx Context, config Config) { cmd.RunAndStreamOrFatal() } type statusChecker struct { prevTime time.Time // A simple struct for checking if Ninja gets stuck, using timestamps. type ninjaStucknessChecker struct { logPath string prevModTime time.Time } func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) { info, err := os.Stat(pathToCheck) var newTime time.Time // Check that a file has been modified since the last time it was checked. If // the mod time hasn't changed, then assume that Ninja got stuck, and print // diagnostics for debugging. func (c *ninjaStucknessChecker) check(ctx Context, config Config) { info, err := os.Stat(c.logPath) var newModTime time.Time if err == nil { newTime = info.ModTime() } if newTime == c.prevTime { // ninja may be stuck dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime) } c.prevTime = newTime newModTime = info.ModTime() } if newModTime == c.prevModTime { // The Ninja file hasn't been modified since the last time it was // checked, so Ninja could be stuck. Output some diagnostics. ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", c.logPath, newModTime) // dumpStucknessDiagnostics gets called when it is suspected that Ninja is stuck and we want to output some diagnostics func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, lastUpdated time.Time) { ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", statusPath, lastUpdated) // The "pstree" command doesn't exist on Mac, but "pstree" on Linux gives more convenient output than "ps" // So, we try pstree first, and ps second pstreeCommandText := fmt.Sprintf("pstree -pal %v", os.Getpid()) psCommandText := "ps -ef" commandText := pstreeCommandText + " || " + psCommandText // The "pstree" command doesn't exist on Mac, but "pstree" on Linux // gives more convenient output than "ps" So, we try pstree first, and // ps second commandText := fmt.Sprintf("pstree -pal %v || ps -ef", os.Getpid()) cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText) output := cmd.CombinedOutputOrFatal() Loading @@ -224,3 +245,5 @@ func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, las ctx.Verbosef("done\n") } c.prevModTime = newModTime } Loading
ui/build/ninja.go +59 −36 Original line number Diff line number Diff line Loading @@ -27,10 +27,16 @@ import ( "android/soong/ui/status" ) // Constructs and runs the Ninja command line with a restricted set of // environment variables. It's important to restrict the environment Ninja runs // for hermeticity reasons, and to avoid spurious rebuilds. func runNinja(ctx Context, config Config) { ctx.BeginTrace(metrics.PrimaryNinja, "ninja") defer ctx.EndTrace() // Sets up the FIFO status updater that reads the Ninja protobuf output, and // translates it to the soong_ui status output, displaying real-time // progress of the build. fifo := filepath.Join(config.OutDir(), ".ninja_fifo") nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo) defer nr.Close() Loading Loading @@ -64,8 +70,12 @@ func runNinja(ctx Context, config Config) { "-w", "missingdepfile=err") cmd := Command(ctx, config, "ninja", executable, args...) // Set up the nsjail sandbox Ninja runs in. cmd.Sandbox = ninjaSandbox if config.HasKatiSuffix() { // Reads and executes a shell script from Kati that sets/unsets the // environment Ninja runs in. cmd.Environment.AppendFromKati(config.KatiEnvFile()) } Loading @@ -78,8 +88,8 @@ func runNinja(ctx Context, config Config) { cmd.Args = append(cmd.Args, strings.Fields(extra)...) } logPath := filepath.Join(config.OutDir(), ".ninja_log") ninjaHeartbeatDuration := time.Minute * 5 // Get the ninja heartbeat interval from the environment before it's filtered away later. if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok { // For example, "1m" overrideDuration, err := time.ParseDuration(overrideText) Loading @@ -88,18 +98,22 @@ func runNinja(ctx Context, config Config) { } } // Filter the environment, as ninja does not rebuild files when environment variables change. // Filter the environment, as ninja does not rebuild files when environment // variables change. // // Anything listed here must not change the output of rules/actions when the value changes, // otherwise incremental builds may be unsafe. Vars explicitly set to stable values // elsewhere in soong_ui are fine. // Anything listed here must not change the output of rules/actions when the // value changes, otherwise incremental builds may be unsafe. Vars // explicitly set to stable values elsewhere in soong_ui are fine. // // For the majority of cases, either Soong or the makefiles should be replicating any // necessary environment variables in the command line of each action that needs it. // For the majority of cases, either Soong or the makefiles should be // replicating any necessary environment variables in the command line of // each action that needs it. if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") { ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.") } else { cmd.Environment.Allow(append([]string{ // Set the path to a symbolizer (e.g. llvm-symbolizer) so ASAN-based // tools can symbolize crashes. "ASAN_SYMBOLIZER_PATH", "HOME", "JAVA_HOME", Loading @@ -108,14 +122,19 @@ func runNinja(ctx Context, config Config) { "OUT_DIR", "PATH", "PWD", // https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDONTWRITEBYTECODE "PYTHONDONTWRITEBYTECODE", "TMPDIR", "USER", // TODO: remove these carefully // Options for the address sanitizer. "ASAN_OPTIONS", // The list of Android app modules to be built in an unbundled manner. "TARGET_BUILD_APPS", // The variant of the product being built. e.g. eng, userdebug, debug. "TARGET_BUILD_VARIANT", // The product name of the product being built, e.g. aosp_arm, aosp_flame. "TARGET_PRODUCT", // b/147197813 - used by art-check-debug-apex-gen "EMMA_INSTRUMENT_FRAMEWORK", Loading Loading @@ -162,6 +181,7 @@ func runNinja(ctx Context, config Config) { cmd.Environment.Set("DIST_DIR", config.DistDir()) cmd.Environment.Set("SHELL", "/bin/bash") // Print the environment variables that Ninja is operating in. ctx.Verboseln("Ninja environment: ") envVars := cmd.Environment.Environ() sort.Strings(envVars) Loading @@ -169,17 +189,21 @@ func runNinja(ctx Context, config Config) { ctx.Verbosef(" %s", envVar) } // Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics // Poll the Ninja log for updates regularly based on the heartbeat // frequency. If it isn't updated enough, then we want to surface the // possibility that Ninja is stuck, to the user. done := make(chan struct{}) defer close(done) ticker := time.NewTicker(ninjaHeartbeatDuration) defer ticker.Stop() checker := &statusChecker{} ninjaChecker := &ninjaStucknessChecker{ logPath: filepath.Join(config.OutDir(), ".ninja_log"), } go func() { for { select { case <-ticker.C: checker.check(ctx, config, logPath) ninjaChecker.check(ctx, config) case <-done: return } Loading @@ -190,33 +214,30 @@ func runNinja(ctx Context, config Config) { cmd.RunAndStreamOrFatal() } type statusChecker struct { prevTime time.Time // A simple struct for checking if Ninja gets stuck, using timestamps. type ninjaStucknessChecker struct { logPath string prevModTime time.Time } func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) { info, err := os.Stat(pathToCheck) var newTime time.Time // Check that a file has been modified since the last time it was checked. If // the mod time hasn't changed, then assume that Ninja got stuck, and print // diagnostics for debugging. func (c *ninjaStucknessChecker) check(ctx Context, config Config) { info, err := os.Stat(c.logPath) var newModTime time.Time if err == nil { newTime = info.ModTime() } if newTime == c.prevTime { // ninja may be stuck dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime) } c.prevTime = newTime newModTime = info.ModTime() } if newModTime == c.prevModTime { // The Ninja file hasn't been modified since the last time it was // checked, so Ninja could be stuck. Output some diagnostics. ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", c.logPath, newModTime) // dumpStucknessDiagnostics gets called when it is suspected that Ninja is stuck and we want to output some diagnostics func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, lastUpdated time.Time) { ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", statusPath, lastUpdated) // The "pstree" command doesn't exist on Mac, but "pstree" on Linux gives more convenient output than "ps" // So, we try pstree first, and ps second pstreeCommandText := fmt.Sprintf("pstree -pal %v", os.Getpid()) psCommandText := "ps -ef" commandText := pstreeCommandText + " || " + psCommandText // The "pstree" command doesn't exist on Mac, but "pstree" on Linux // gives more convenient output than "ps" So, we try pstree first, and // ps second commandText := fmt.Sprintf("pstree -pal %v || ps -ef", os.Getpid()) cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText) output := cmd.CombinedOutputOrFatal() Loading @@ -224,3 +245,5 @@ func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, las ctx.Verbosef("done\n") } c.prevModTime = newModTime }