ninja.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. package build
  15. import (
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "sort"
  20. "strconv"
  21. "strings"
  22. "time"
  23. "android/soong/shared"
  24. "android/soong/ui/metrics"
  25. "android/soong/ui/status"
  26. )
  27. const (
  28. // File containing the environment state when ninja is executed
  29. ninjaEnvFileName = "ninja.environment"
  30. ninjaLogFileName = ".ninja_log"
  31. ninjaWeightListFileName = ".ninja_weight_list"
  32. )
  33. // Constructs and runs the Ninja command line with a restricted set of
  34. // environment variables. It's important to restrict the environment Ninja runs
  35. // for hermeticity reasons, and to avoid spurious rebuilds.
  36. func runNinjaForBuild(ctx Context, config Config) {
  37. ctx.BeginTrace(metrics.PrimaryNinja, "ninja")
  38. defer ctx.EndTrace()
  39. // Sets up the FIFO status updater that reads the Ninja protobuf output, and
  40. // translates it to the soong_ui status output, displaying real-time
  41. // progress of the build.
  42. fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
  43. nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo)
  44. defer nr.Close()
  45. executable := config.PrebuiltBuildTool("ninja")
  46. args := []string{
  47. "-d", "keepdepfile",
  48. "-d", "keeprsp",
  49. "-d", "stats",
  50. "--frontend_file", fifo,
  51. }
  52. args = append(args, config.NinjaArgs()...)
  53. var parallel int
  54. if config.UseRemoteBuild() {
  55. parallel = config.RemoteParallel()
  56. } else {
  57. parallel = config.Parallel()
  58. }
  59. args = append(args, "-j", strconv.Itoa(parallel))
  60. if config.keepGoing != 1 {
  61. args = append(args, "-k", strconv.Itoa(config.keepGoing))
  62. }
  63. args = append(args, "-f", config.CombinedNinjaFile())
  64. args = append(args,
  65. "-o", "usesphonyoutputs=yes",
  66. "-w", "dupbuild=err",
  67. "-w", "missingdepfile=err")
  68. cmd := Command(ctx, config, "ninja", executable, args...)
  69. // Set up the nsjail sandbox Ninja runs in.
  70. cmd.Sandbox = ninjaSandbox
  71. if config.HasKatiSuffix() {
  72. // Reads and executes a shell script from Kati that sets/unsets the
  73. // environment Ninja runs in.
  74. cmd.Environment.AppendFromKati(config.KatiEnvFile())
  75. }
  76. switch config.NinjaWeightListSource() {
  77. case NINJA_LOG:
  78. cmd.Args = append(cmd.Args, "-o", "usesninjalogasweightlist=yes")
  79. case EVENLY_DISTRIBUTED:
  80. // pass empty weight list means ninja considers every tasks's weight as 1(default value).
  81. cmd.Args = append(cmd.Args, "-o", "usesweightlist=/dev/null")
  82. case EXTERNAL_FILE:
  83. fallthrough
  84. case HINT_FROM_SOONG:
  85. // The weight list is already copied/generated.
  86. ninjaWeightListPath := filepath.Join(config.OutDir(), ninjaWeightListFileName)
  87. cmd.Args = append(cmd.Args, "-o", "usesweightlist="+ninjaWeightListPath)
  88. }
  89. // Allow both NINJA_ARGS and NINJA_EXTRA_ARGS, since both have been
  90. // used in the past to specify extra ninja arguments.
  91. if extra, ok := cmd.Environment.Get("NINJA_ARGS"); ok {
  92. cmd.Args = append(cmd.Args, strings.Fields(extra)...)
  93. }
  94. if extra, ok := cmd.Environment.Get("NINJA_EXTRA_ARGS"); ok {
  95. cmd.Args = append(cmd.Args, strings.Fields(extra)...)
  96. }
  97. ninjaHeartbeatDuration := time.Minute * 5
  98. // Get the ninja heartbeat interval from the environment before it's filtered away later.
  99. if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
  100. // For example, "1m"
  101. overrideDuration, err := time.ParseDuration(overrideText)
  102. if err == nil && overrideDuration.Seconds() > 0 {
  103. ninjaHeartbeatDuration = overrideDuration
  104. }
  105. }
  106. // Filter the environment, as ninja does not rebuild files when environment
  107. // variables change.
  108. //
  109. // Anything listed here must not change the output of rules/actions when the
  110. // value changes, otherwise incremental builds may be unsafe. Vars
  111. // explicitly set to stable values elsewhere in soong_ui are fine.
  112. //
  113. // For the majority of cases, either Soong or the makefiles should be
  114. // replicating any necessary environment variables in the command line of
  115. // each action that needs it.
  116. if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") {
  117. ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.")
  118. } else {
  119. cmd.Environment.Allow(append([]string{
  120. // Set the path to a symbolizer (e.g. llvm-symbolizer) so ASAN-based
  121. // tools can symbolize crashes.
  122. "ASAN_SYMBOLIZER_PATH",
  123. "HOME",
  124. "JAVA_HOME",
  125. "LANG",
  126. "LC_MESSAGES",
  127. "OUT_DIR",
  128. "PATH",
  129. "PWD",
  130. // https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDONTWRITEBYTECODE
  131. "PYTHONDONTWRITEBYTECODE",
  132. "TMPDIR",
  133. "USER",
  134. // TODO: remove these carefully
  135. // Options for the address sanitizer.
  136. "ASAN_OPTIONS",
  137. // The list of Android app modules to be built in an unbundled manner.
  138. "TARGET_BUILD_APPS",
  139. // The variant of the product being built. e.g. eng, userdebug, debug.
  140. "TARGET_BUILD_VARIANT",
  141. // The product name of the product being built, e.g. aosp_arm, aosp_flame.
  142. "TARGET_PRODUCT",
  143. // b/147197813 - used by art-check-debug-apex-gen
  144. "EMMA_INSTRUMENT_FRAMEWORK",
  145. // RBE client
  146. "RBE_compare",
  147. "RBE_num_local_reruns",
  148. "RBE_num_remote_reruns",
  149. "RBE_exec_root",
  150. "RBE_exec_strategy",
  151. "RBE_invocation_id",
  152. "RBE_log_dir",
  153. "RBE_num_retries_if_mismatched",
  154. "RBE_platform",
  155. "RBE_remote_accept_cache",
  156. "RBE_remote_update_cache",
  157. "RBE_server_address",
  158. // TODO: remove old FLAG_ variables.
  159. "FLAG_compare",
  160. "FLAG_exec_root",
  161. "FLAG_exec_strategy",
  162. "FLAG_invocation_id",
  163. "FLAG_log_dir",
  164. "FLAG_platform",
  165. "FLAG_remote_accept_cache",
  166. "FLAG_remote_update_cache",
  167. "FLAG_server_address",
  168. // ccache settings
  169. "CCACHE_COMPILERCHECK",
  170. "CCACHE_SLOPPINESS",
  171. "CCACHE_BASEDIR",
  172. "CCACHE_CPP2",
  173. "CCACHE_DIR",
  174. // LLVM compiler wrapper options
  175. "TOOLCHAIN_RUSAGE_OUTPUT",
  176. }, config.BuildBrokenNinjaUsesEnvVars()...)...)
  177. }
  178. cmd.Environment.Set("DIST_DIR", config.DistDir())
  179. cmd.Environment.Set("SHELL", "/bin/bash")
  180. // Print the environment variables that Ninja is operating in.
  181. ctx.Verboseln("Ninja environment: ")
  182. envVars := cmd.Environment.Environ()
  183. sort.Strings(envVars)
  184. for _, envVar := range envVars {
  185. ctx.Verbosef(" %s", envVar)
  186. }
  187. // Write the env vars available during ninja execution to a file
  188. ninjaEnvVars := cmd.Environment.AsMap()
  189. data, err := shared.EnvFileContents(ninjaEnvVars)
  190. if err != nil {
  191. ctx.Panicf("Could not parse environment variables for ninja run %s", err)
  192. }
  193. // Write the file in every single run. This is fine because
  194. // 1. It is not a dep of Soong analysis, so will not retrigger Soong analysis.
  195. // 2. Is is fairly lightweight (~1Kb)
  196. ninjaEnvVarsFile := shared.JoinPath(config.SoongOutDir(), ninjaEnvFileName)
  197. err = os.WriteFile(ninjaEnvVarsFile, data, 0666)
  198. if err != nil {
  199. ctx.Panicf("Could not write ninja environment file %s", err)
  200. }
  201. // Poll the Ninja log for updates regularly based on the heartbeat
  202. // frequency. If it isn't updated enough, then we want to surface the
  203. // possibility that Ninja is stuck, to the user.
  204. done := make(chan struct{})
  205. defer close(done)
  206. ticker := time.NewTicker(ninjaHeartbeatDuration)
  207. defer ticker.Stop()
  208. ninjaChecker := &ninjaStucknessChecker{
  209. logPath: filepath.Join(config.OutDir(), ninjaLogFileName),
  210. }
  211. go func() {
  212. for {
  213. select {
  214. case <-ticker.C:
  215. ninjaChecker.check(ctx, config)
  216. case <-done:
  217. return
  218. }
  219. }
  220. }()
  221. ctx.Status.Status("Starting ninja...")
  222. cmd.RunAndStreamOrFatal()
  223. }
  224. // A simple struct for checking if Ninja gets stuck, using timestamps.
  225. type ninjaStucknessChecker struct {
  226. logPath string
  227. prevModTime time.Time
  228. }
  229. // Check that a file has been modified since the last time it was checked. If
  230. // the mod time hasn't changed, then assume that Ninja got stuck, and print
  231. // diagnostics for debugging.
  232. func (c *ninjaStucknessChecker) check(ctx Context, config Config) {
  233. info, err := os.Stat(c.logPath)
  234. var newModTime time.Time
  235. if err == nil {
  236. newModTime = info.ModTime()
  237. }
  238. if newModTime == c.prevModTime {
  239. // The Ninja file hasn't been modified since the last time it was
  240. // checked, so Ninja could be stuck. Output some diagnostics.
  241. ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", c.logPath, newModTime)
  242. // The "pstree" command doesn't exist on Mac, but "pstree" on Linux
  243. // gives more convenient output than "ps" So, we try pstree first, and
  244. // ps second
  245. commandText := fmt.Sprintf("pstree -pal %v || ps -ef", os.Getpid())
  246. cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
  247. output := cmd.CombinedOutputOrFatal()
  248. ctx.Verbose(string(output))
  249. ctx.Verbosef("done\n")
  250. }
  251. c.prevModTime = newModTime
  252. }