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