Merge "soong_ui ninja.go: comment and refactor."

This commit is contained in:
Treehugger Robot 2020-11-22 20:15:46 +00:00 committed by Gerrit Code Review
commit 5898d56912

View file

@ -27,10 +27,16 @@ import (
"android/soong/ui/status" "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) { func runNinja(ctx Context, config Config) {
ctx.BeginTrace(metrics.PrimaryNinja, "ninja") ctx.BeginTrace(metrics.PrimaryNinja, "ninja")
defer ctx.EndTrace() 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") fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo) nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo)
defer nr.Close() defer nr.Close()
@ -64,8 +70,12 @@ func runNinja(ctx Context, config Config) {
"-w", "missingdepfile=err") "-w", "missingdepfile=err")
cmd := Command(ctx, config, "ninja", executable, args...) cmd := Command(ctx, config, "ninja", executable, args...)
// Set up the nsjail sandbox Ninja runs in.
cmd.Sandbox = ninjaSandbox cmd.Sandbox = ninjaSandbox
if config.HasKatiSuffix() { if config.HasKatiSuffix() {
// Reads and executes a shell script from Kati that sets/unsets the
// environment Ninja runs in.
cmd.Environment.AppendFromKati(config.KatiEnvFile()) cmd.Environment.AppendFromKati(config.KatiEnvFile())
} }
@ -78,8 +88,8 @@ func runNinja(ctx Context, config Config) {
cmd.Args = append(cmd.Args, strings.Fields(extra)...) cmd.Args = append(cmd.Args, strings.Fields(extra)...)
} }
logPath := filepath.Join(config.OutDir(), ".ninja_log")
ninjaHeartbeatDuration := time.Minute * 5 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 { if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
// For example, "1m" // For example, "1m"
overrideDuration, err := time.ParseDuration(overrideText) overrideDuration, err := time.ParseDuration(overrideText)
@ -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, // Anything listed here must not change the output of rules/actions when the
// otherwise incremental builds may be unsafe. Vars explicitly set to stable values // value changes, otherwise incremental builds may be unsafe. Vars
// elsewhere in soong_ui are fine. // 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 // For the majority of cases, either Soong or the makefiles should be
// necessary environment variables in the command line of each action that needs it. // replicating any necessary environment variables in the command line of
// each action that needs it.
if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") { if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") {
ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.") ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.")
} else { } else {
cmd.Environment.Allow(append([]string{ 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", "ASAN_SYMBOLIZER_PATH",
"HOME", "HOME",
"JAVA_HOME", "JAVA_HOME",
@ -108,14 +122,19 @@ func runNinja(ctx Context, config Config) {
"OUT_DIR", "OUT_DIR",
"PATH", "PATH",
"PWD", "PWD",
// https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDONTWRITEBYTECODE
"PYTHONDONTWRITEBYTECODE", "PYTHONDONTWRITEBYTECODE",
"TMPDIR", "TMPDIR",
"USER", "USER",
// TODO: remove these carefully // TODO: remove these carefully
// Options for the address sanitizer.
"ASAN_OPTIONS", "ASAN_OPTIONS",
// The list of Android app modules to be built in an unbundled manner.
"TARGET_BUILD_APPS", "TARGET_BUILD_APPS",
// The variant of the product being built. e.g. eng, userdebug, debug.
"TARGET_BUILD_VARIANT", "TARGET_BUILD_VARIANT",
// The product name of the product being built, e.g. aosp_arm, aosp_flame.
"TARGET_PRODUCT", "TARGET_PRODUCT",
// b/147197813 - used by art-check-debug-apex-gen // b/147197813 - used by art-check-debug-apex-gen
"EMMA_INSTRUMENT_FRAMEWORK", "EMMA_INSTRUMENT_FRAMEWORK",
@ -162,6 +181,7 @@ func runNinja(ctx Context, config Config) {
cmd.Environment.Set("DIST_DIR", config.DistDir()) cmd.Environment.Set("DIST_DIR", config.DistDir())
cmd.Environment.Set("SHELL", "/bin/bash") cmd.Environment.Set("SHELL", "/bin/bash")
// Print the environment variables that Ninja is operating in.
ctx.Verboseln("Ninja environment: ") ctx.Verboseln("Ninja environment: ")
envVars := cmd.Environment.Environ() envVars := cmd.Environment.Environ()
sort.Strings(envVars) sort.Strings(envVars)
@ -169,17 +189,21 @@ func runNinja(ctx Context, config Config) {
ctx.Verbosef(" %s", envVar) 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{}) done := make(chan struct{})
defer close(done) defer close(done)
ticker := time.NewTicker(ninjaHeartbeatDuration) ticker := time.NewTicker(ninjaHeartbeatDuration)
defer ticker.Stop() defer ticker.Stop()
checker := &statusChecker{} ninjaChecker := &ninjaStucknessChecker{
logPath: filepath.Join(config.OutDir(), ".ninja_log"),
}
go func() { go func() {
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
checker.check(ctx, config, logPath) ninjaChecker.check(ctx, config)
case <-done: case <-done:
return return
} }
@ -190,37 +214,36 @@ func runNinja(ctx Context, config Config) {
cmd.RunAndStreamOrFatal() cmd.RunAndStreamOrFatal()
} }
type statusChecker struct { // A simple struct for checking if Ninja gets stuck, using timestamps.
prevTime time.Time type ninjaStucknessChecker struct {
logPath string
prevModTime time.Time
} }
func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) { // Check that a file has been modified since the last time it was checked. If
info, err := os.Stat(pathToCheck) // the mod time hasn't changed, then assume that Ninja got stuck, and print
var newTime time.Time // diagnostics for debugging.
func (c *ninjaStucknessChecker) check(ctx Context, config Config) {
info, err := os.Stat(c.logPath)
var newModTime time.Time
if err == nil { if err == nil {
newTime = info.ModTime() newModTime = info.ModTime()
} }
if newTime == c.prevTime { if newModTime == c.prevModTime {
// ninja may be stuck // The Ninja file hasn't been modified since the last time it was
dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime) // 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)
// 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()
ctx.Verbose(string(output))
ctx.Verbosef("done\n")
} }
c.prevTime = newTime c.prevModTime = 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
cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
output := cmd.CombinedOutputOrFatal()
ctx.Verbose(string(output))
ctx.Verbosef("done\n")
} }