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 15package build 16 17import ( 18 "fmt" 19 "os" 20 "path/filepath" 21 "sort" 22 "strconv" 23 "strings" 24 "time" 25 26 "android/soong/ui/metrics" 27 "android/soong/ui/status" 28) 29 30func runNinja(ctx Context, config Config) { 31 ctx.BeginTrace(metrics.PrimaryNinja, "ninja") 32 defer ctx.EndTrace() 33 34 fifo := filepath.Join(config.OutDir(), ".ninja_fifo") 35 nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo) 36 defer nr.Close() 37 38 executable := config.PrebuiltBuildTool("ninja") 39 args := []string{ 40 "-d", "keepdepfile", 41 "-d", "keeprsp", 42 "-d", "stats", 43 "--frontend_file", fifo, 44 } 45 46 args = append(args, config.NinjaArgs()...) 47 48 var parallel int 49 if config.UseRemoteBuild() { 50 parallel = config.RemoteParallel() 51 } else { 52 parallel = config.Parallel() 53 } 54 args = append(args, "-j", strconv.Itoa(parallel)) 55 if config.keepGoing != 1 { 56 args = append(args, "-k", strconv.Itoa(config.keepGoing)) 57 } 58 59 args = append(args, "-f", config.CombinedNinjaFile()) 60 61 args = append(args, 62 "-o", "usesphonyoutputs=yes", 63 "-w", "dupbuild=err", 64 "-w", "missingdepfile=err") 65 66 cmd := Command(ctx, config, "ninja", executable, args...) 67 cmd.Sandbox = ninjaSandbox 68 if config.HasKatiSuffix() { 69 cmd.Environment.AppendFromKati(config.KatiEnvFile()) 70 } 71 72 // Allow both NINJA_ARGS and NINJA_EXTRA_ARGS, since both have been 73 // used in the past to specify extra ninja arguments. 74 if extra, ok := cmd.Environment.Get("NINJA_ARGS"); ok { 75 cmd.Args = append(cmd.Args, strings.Fields(extra)...) 76 } 77 if extra, ok := cmd.Environment.Get("NINJA_EXTRA_ARGS"); ok { 78 cmd.Args = append(cmd.Args, strings.Fields(extra)...) 79 } 80 81 logPath := filepath.Join(config.OutDir(), ".ninja_log") 82 ninjaHeartbeatDuration := time.Minute * 5 83 if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok { 84 // For example, "1m" 85 overrideDuration, err := time.ParseDuration(overrideText) 86 if err == nil && overrideDuration.Seconds() > 0 { 87 ninjaHeartbeatDuration = overrideDuration 88 } 89 } 90 91 // Filter the environment, as ninja does not rebuild files when environment variables change. 92 // 93 // Anything listed here must not change the output of rules/actions when the value changes, 94 // otherwise incremental builds may be unsafe. Vars explicitly set to stable values 95 // elsewhere in soong_ui are fine. 96 // 97 // For the majority of cases, either Soong or the makefiles should be replicating any 98 // necessary environment variables in the command line of each action that needs it. 99 if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") { 100 ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.") 101 } else { 102 cmd.Environment.Allow(append([]string{ 103 "ASAN_SYMBOLIZER_PATH", 104 "HOME", 105 "JAVA_HOME", 106 "LANG", 107 "LC_MESSAGES", 108 "OUT_DIR", 109 "PATH", 110 "PWD", 111 "PYTHONDONTWRITEBYTECODE", 112 "TMPDIR", 113 "USER", 114 115 // TODO: remove these carefully 116 "ASAN_OPTIONS", 117 "TARGET_BUILD_APPS", 118 "TARGET_BUILD_VARIANT", 119 "TARGET_PRODUCT", 120 // b/147197813 - used by art-check-debug-apex-gen 121 "EMMA_INSTRUMENT_FRAMEWORK", 122 123 // Goma -- gomacc may not need all of these 124 "GOMA_DIR", 125 "GOMA_DISABLED", 126 "GOMA_FAIL_FAST", 127 "GOMA_FALLBACK", 128 "GOMA_GCE_SERVICE_ACCOUNT", 129 "GOMA_TMP_DIR", 130 "GOMA_USE_LOCAL", 131 132 // RBE client 133 "RBE_compare", 134 "RBE_exec_root", 135 "RBE_exec_strategy", 136 "RBE_invocation_id", 137 "RBE_log_dir", 138 "RBE_platform", 139 "RBE_remote_accept_cache", 140 "RBE_remote_update_cache", 141 "RBE_server_address", 142 // TODO: remove old FLAG_ variables. 143 "FLAG_compare", 144 "FLAG_exec_root", 145 "FLAG_exec_strategy", 146 "FLAG_invocation_id", 147 "FLAG_log_dir", 148 "FLAG_platform", 149 "FLAG_remote_accept_cache", 150 "FLAG_remote_update_cache", 151 "FLAG_server_address", 152 153 // ccache settings 154 "CCACHE_COMPILERCHECK", 155 "CCACHE_SLOPPINESS", 156 "CCACHE_BASEDIR", 157 "CCACHE_CPP2", 158 "CCACHE_DIR", 159 }, config.BuildBrokenNinjaUsesEnvVars()...)...) 160 } 161 162 cmd.Environment.Set("DIST_DIR", config.DistDir()) 163 cmd.Environment.Set("SHELL", "/bin/bash") 164 165 ctx.Verboseln("Ninja environment: ") 166 envVars := cmd.Environment.Environ() 167 sort.Strings(envVars) 168 for _, envVar := range envVars { 169 ctx.Verbosef(" %s", envVar) 170 } 171 172 // Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics 173 done := make(chan struct{}) 174 defer close(done) 175 ticker := time.NewTicker(ninjaHeartbeatDuration) 176 defer ticker.Stop() 177 checker := &statusChecker{} 178 go func() { 179 for { 180 select { 181 case <-ticker.C: 182 checker.check(ctx, config, logPath) 183 case <-done: 184 return 185 } 186 } 187 }() 188 189 ctx.Status.Status("Starting ninja...") 190 cmd.RunAndStreamOrFatal() 191} 192 193type statusChecker struct { 194 prevTime time.Time 195} 196 197func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) { 198 info, err := os.Stat(pathToCheck) 199 var newTime time.Time 200 if err == nil { 201 newTime = info.ModTime() 202 } 203 if newTime == c.prevTime { 204 // ninja may be stuck 205 dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime) 206 } 207 c.prevTime = newTime 208} 209 210// dumpStucknessDiagnostics gets called when it is suspected that Ninja is stuck and we want to output some diagnostics 211func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, lastUpdated time.Time) { 212 213 ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", statusPath, lastUpdated) 214 215 // The "pstree" command doesn't exist on Mac, but "pstree" on Linux gives more convenient output than "ps" 216 // So, we try pstree first, and ps second 217 pstreeCommandText := fmt.Sprintf("pstree -pal %v", os.Getpid()) 218 psCommandText := "ps -ef" 219 commandText := pstreeCommandText + " || " + psCommandText 220 221 cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText) 222 output := cmd.CombinedOutputOrFatal() 223 ctx.Verbose(string(output)) 224 225 ctx.Verbosef("done\n") 226} 227