• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	"os/exec"
21	"path/filepath"
22	"sort"
23	"strconv"
24	"strings"
25	"time"
26
27	"android/soong/shared"
28	"android/soong/ui/metrics"
29	"android/soong/ui/status"
30)
31
32const (
33	// File containing the environment state when ninja is executed
34	ninjaEnvFileName        = "ninja.environment"
35	ninjaLogFileName        = ".ninja_log"
36	ninjaWeightListFileName = ".ninja_weight_list"
37)
38
39// Runs ninja with the arguments from the command line, as found in
40// config.NinjaArgs().
41func runNinjaForBuild(ctx Context, config Config) {
42	runNinja(ctx, config, config.NinjaArgs())
43}
44
45// Constructs and runs the Ninja command line with a restricted set of
46// environment variables. It's important to restrict the environment Ninja runs
47// for hermeticity reasons, and to avoid spurious rebuilds.
48func runNinja(ctx Context, config Config, ninjaArgs []string) {
49	ctx.BeginTrace(metrics.PrimaryNinja, "ninja")
50	defer ctx.EndTrace()
51
52	// Sets up the FIFO status updater that reads the Ninja protobuf output, and
53	// translates it to the soong_ui status output, displaying real-time
54	// progress of the build.
55	fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
56	nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo)
57	defer nr.Close()
58
59	var executable string
60	var args []string
61	switch config.ninjaCommand {
62	case NINJA_N2:
63		executable = config.N2Bin()
64		args = []string{
65			"-d", "trace",
66			// TODO: implement these features, or remove them.
67			//"-d", "keepdepfile",
68			//"-d", "keeprsp",
69			//"-d", "stats",
70			"--frontend-file", fifo,
71		}
72	case NINJA_SISO:
73		executable = config.SisoBin()
74		args = []string{
75			"ninja",
76			"--log_dir", config.SoongOutDir(),
77			// TODO: implement these features, or remove them.
78			//"-d", "trace",
79			//"-d", "keepdepfile",
80			//"-d", "keeprsp",
81			//"-d", "stats",
82			//"--frontend-file", fifo,
83		}
84	default:
85		// NINJA_NINJA or NINJA_NINJAGO.
86		executable = config.NinjaBin()
87		args = []string{
88			"-d", "keepdepfile",
89			"-d", "keeprsp",
90			"-d", "stats",
91			"--frontend_file", fifo,
92			"-o", "usesphonyoutputs=yes",
93			"-w", "dupbuild=err",
94			"-w", "missingdepfile=err",
95		}
96	}
97	args = append(args, ninjaArgs...)
98
99	var parallel int
100	if config.UseRemoteBuild() {
101		parallel = config.RemoteParallel()
102	} else {
103		parallel = config.Parallel()
104	}
105	args = append(args, "-j", strconv.Itoa(parallel))
106	if config.keepGoing != 1 {
107		args = append(args, "-k", strconv.Itoa(config.keepGoing))
108	}
109
110	args = append(args, "-f", config.CombinedNinjaFile())
111
112	if !config.BuildBrokenMissingOutputs() {
113		// Missing outputs will be treated as errors.
114		// BUILD_BROKEN_MISSING_OUTPUTS can be used to bypass this check.
115		if config.ninjaCommand != NINJA_N2 {
116			args = append(args,
117				"-w", "missingoutfile=err",
118			)
119		}
120	}
121
122	cmd := Command(ctx, config, "ninja", executable, args...)
123
124	// Set up the nsjail sandbox Ninja runs in.
125	cmd.Sandbox = ninjaSandbox
126	if config.HasKatiSuffix() {
127		// Reads and executes a shell script from Kati that sets/unsets the
128		// environment Ninja runs in.
129		cmd.Environment.AppendFromKati(config.KatiEnvFile())
130	}
131
132	// TODO(b/346806126): implement this for the other ninjaCommand values.
133	if config.ninjaCommand == NINJA_NINJA {
134		switch config.NinjaWeightListSource() {
135		case NINJA_LOG:
136			cmd.Args = append(cmd.Args, "-o", "usesninjalogasweightlist=yes")
137		case EVENLY_DISTRIBUTED:
138			// pass empty weight list means ninja considers every tasks's weight as 1(default value).
139			cmd.Args = append(cmd.Args, "-o", "usesweightlist=/dev/null")
140		case EXTERNAL_FILE:
141			fallthrough
142		case HINT_FROM_SOONG:
143			// The weight list is already copied/generated.
144			ninjaWeightListPath := filepath.Join(config.OutDir(), ninjaWeightListFileName)
145			cmd.Args = append(cmd.Args, "-o", "usesweightlist="+ninjaWeightListPath)
146		}
147	}
148
149	// Allow both NINJA_ARGS and NINJA_EXTRA_ARGS, since both have been
150	// used in the past to specify extra ninja arguments.
151	if extra, ok := cmd.Environment.Get("NINJA_ARGS"); ok {
152		cmd.Args = append(cmd.Args, strings.Fields(extra)...)
153	}
154	if extra, ok := cmd.Environment.Get("NINJA_EXTRA_ARGS"); ok {
155		cmd.Args = append(cmd.Args, strings.Fields(extra)...)
156	}
157
158	ninjaHeartbeatDuration := time.Minute * 5
159	// Get the ninja heartbeat interval from the environment before it's filtered away later.
160	if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
161		// For example, "1m"
162		overrideDuration, err := time.ParseDuration(overrideText)
163		if err == nil && overrideDuration.Seconds() > 0 {
164			ninjaHeartbeatDuration = overrideDuration
165		}
166	}
167
168	// Filter the environment, as ninja does not rebuild files when environment
169	// variables change.
170	//
171	// Anything listed here must not change the output of rules/actions when the
172	// value changes, otherwise incremental builds may be unsafe. Vars
173	// explicitly set to stable values elsewhere in soong_ui are fine.
174	//
175	// For the majority of cases, either Soong or the makefiles should be
176	// replicating any necessary environment variables in the command line of
177	// each action that needs it.
178	if cmd.Environment.IsEnvTrue("ALLOW_NINJA_ENV") {
179		ctx.Println("Allowing all environment variables during ninja; incremental builds may be unsafe.")
180	} else {
181		cmd.Environment.Allow(append([]string{
182			// Set the path to a symbolizer (e.g. llvm-symbolizer) so ASAN-based
183			// tools can symbolize crashes.
184			"ASAN_SYMBOLIZER_PATH",
185			"HOME",
186			"JAVA_HOME",
187			"LANG",
188			"LC_MESSAGES",
189			"OUT_DIR",
190			"PATH",
191			"PWD",
192			// https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDONTWRITEBYTECODE
193			"PYTHONDONTWRITEBYTECODE",
194			"TMPDIR",
195			"USER",
196
197			// TODO: remove these carefully
198			// Options for the address sanitizer.
199			"ASAN_OPTIONS",
200			// The list of Android app modules to be built in an unbundled manner.
201			"TARGET_BUILD_APPS",
202			// The variant of the product being built. e.g. eng, userdebug, debug.
203			"TARGET_BUILD_VARIANT",
204			// The product name of the product being built, e.g. aosp_arm, aosp_flame.
205			"TARGET_PRODUCT",
206			// b/147197813 - used by art-check-debug-apex-gen
207			"EMMA_INSTRUMENT_FRAMEWORK",
208
209			// RBE client
210			"RBE_compare",
211			"RBE_num_local_reruns",
212			"RBE_num_remote_reruns",
213			"RBE_exec_root",
214			"RBE_exec_strategy",
215			"RBE_invocation_id",
216			"RBE_log_dir",
217			"RBE_num_retries_if_mismatched",
218			"RBE_platform",
219			"RBE_remote_accept_cache",
220			"RBE_remote_update_cache",
221			"RBE_server_address",
222			// TODO: remove old FLAG_ variables.
223			"FLAG_compare",
224			"FLAG_exec_root",
225			"FLAG_exec_strategy",
226			"FLAG_invocation_id",
227			"FLAG_log_dir",
228			"FLAG_platform",
229			"FLAG_remote_accept_cache",
230			"FLAG_remote_update_cache",
231			"FLAG_server_address",
232
233			// ccache settings
234			"CCACHE_COMPILERCHECK",
235			"CCACHE_SLOPPINESS",
236			"CCACHE_BASEDIR",
237			"CCACHE_CPP2",
238			"CCACHE_DIR",
239
240			// LLVM compiler wrapper options
241			"TOOLCHAIN_RUSAGE_OUTPUT",
242
243			// We don't want this build broken flag to cause reanalysis, so allow it through to the
244			// actions.
245			"BUILD_BROKEN_INCORRECT_PARTITION_IMAGES",
246			// Do not do reanalysis just because we changed ninja commands.
247			"SOONG_NINJA",
248			"SOONG_USE_N2",
249			"RUST_BACKTRACE",
250			"RUST_LOG",
251
252			// SOONG_USE_PARTIAL_COMPILE only determines which half of the rule we execute.
253			// When it transitions true => false, we build phony target "partialcompileclean",
254			// which removes all files that could have been created while it was true.
255			"SOONG_USE_PARTIAL_COMPILE",
256
257			// Directory for ExecutionMetrics
258			"SOONG_METRICS_AGGREGATION_DIR",
259		}, config.BuildBrokenNinjaUsesEnvVars()...)...)
260	}
261
262	cmd.Environment.Set("DIST_DIR", config.DistDir())
263	cmd.Environment.Set("SHELL", "/bin/bash")
264	switch config.ninjaCommand {
265	case NINJA_N2:
266		cmd.Environment.Set("RUST_BACKTRACE", "1")
267	default:
268		// Only set RUST_BACKTRACE for n2.
269	}
270
271	// Set up the metrics aggregation directory.
272	ctx.ExecutionMetrics.SetDir(filepath.Join(config.OutDir(), "soong", "metrics_aggregation"))
273	cmd.Environment.Set("SOONG_METRICS_AGGREGATION_DIR", ctx.ExecutionMetrics.MetricsAggregationDir)
274
275	// Print the environment variables that Ninja is operating in.
276	ctx.Verboseln("Ninja environment: ")
277	envVars := cmd.Environment.Environ()
278	sort.Strings(envVars)
279	for _, envVar := range envVars {
280		ctx.Verbosef("  %s", envVar)
281	}
282
283	// Write the env vars available during ninja execution to a file
284	ninjaEnvVars := cmd.Environment.AsMap()
285	data, err := shared.EnvFileContents(ninjaEnvVars)
286	if err != nil {
287		ctx.Panicf("Could not parse environment variables for ninja run %s", err)
288	}
289	// Write the file in every single run. This is fine because
290	// 1. It is not a dep of Soong analysis, so will not retrigger Soong analysis.
291	// 2. Is is fairly lightweight (~1Kb)
292	ninjaEnvVarsFile := shared.JoinPath(config.SoongOutDir(), ninjaEnvFileName)
293	err = os.WriteFile(ninjaEnvVarsFile, data, 0666)
294	if err != nil {
295		ctx.Panicf("Could not write ninja environment file %s", err)
296	}
297
298	// Poll the Ninja log for updates regularly based on the heartbeat
299	// frequency. If it isn't updated enough, then we want to surface the
300	// possibility that Ninja is stuck, to the user.
301	done := make(chan struct{})
302	defer close(done)
303	ticker := time.NewTicker(ninjaHeartbeatDuration)
304	defer ticker.Stop()
305	ninjaChecker := &ninjaStucknessChecker{
306		logPath: filepath.Join(config.OutDir(), ninjaLogFileName),
307	}
308	go func() {
309		for {
310			select {
311			case <-ticker.C:
312				ninjaChecker.check(ctx, config)
313			case <-done:
314				return
315			}
316		}
317	}()
318
319	ctx.ExecutionMetrics.Start()
320	defer ctx.ExecutionMetrics.Finish(ctx)
321	ctx.Status.Status("Starting ninja...")
322	cmd.RunAndStreamOrFatal()
323}
324
325// A simple struct for checking if Ninja gets stuck, using timestamps.
326type ninjaStucknessChecker struct {
327	logPath     string
328	prevModTime time.Time
329}
330
331// Check that a file has been modified since the last time it was checked. If
332// the mod time hasn't changed, then assume that Ninja got stuck, and print
333// diagnostics for debugging.
334func (c *ninjaStucknessChecker) check(ctx Context, config Config) {
335	info, err := os.Stat(c.logPath)
336	var newModTime time.Time
337	if err == nil {
338		newModTime = info.ModTime()
339	}
340	if newModTime == c.prevModTime {
341		// The Ninja file hasn't been modified since the last time it was
342		// checked, so Ninja could be stuck. Output some diagnostics.
343		ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", c.logPath, newModTime)
344		ctx.Printf("ninja may be stuck, check %v for list of running processes.",
345			filepath.Join(config.LogsDir(), config.logsPrefix+"soong.log"))
346
347		// The "pstree" command doesn't exist on Mac, but "pstree" on Linux
348		// gives more convenient output than "ps" So, we try pstree first, and
349		// ps second
350		commandText := fmt.Sprintf("pstree -palT %v || ps -ef", os.Getpid())
351
352		cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
353		output := cmd.CombinedOutputOrFatal()
354		ctx.Verbose(string(output))
355
356		ctx.Verbosef("done\n")
357	}
358	c.prevModTime = newModTime
359}
360
361// Constructs and runs the Ninja command line to get the inputs of a goal.
362// For n2 and siso, this will always run ninja, because they don't have the
363// `-t inputs` command.  This command will use the inputs command's -d option,
364// to use the dep file iff ninja was the executor. For other executors, the
365// results will be wrong.
366func runNinjaInputs(ctx Context, config Config, goal string) ([]string, error) {
367	var executable string
368	switch config.ninjaCommand {
369	case NINJA_N2, NINJA_SISO:
370		executable = config.PrebuiltBuildTool("ninja")
371	default:
372		executable = config.NinjaBin()
373	}
374
375	args := []string{
376		"-f",
377		config.CombinedNinjaFile(),
378		"-t",
379		"inputs",
380	}
381	// Add deps file arg for ninja
382	// TODO: Update as inputs command is implemented
383	if config.ninjaCommand == NINJA_NINJA && !config.UseABFS() {
384		args = append(args, "-d")
385	}
386	args = append(args, goal)
387
388	// This is just ninja -t inputs, so we won't bother running it in the sandbox,
389	// so use exec.Command, not soong_ui's command.
390	cmd := exec.Command(executable, args...)
391
392	cmd.Stdin = os.Stdin
393	cmd.Stderr = os.Stderr
394
395	out, err := cmd.Output()
396	if err != nil {
397		fmt.Printf("Error getting goal inputs for %s: %s\n", goal, err)
398		return nil, err
399	}
400
401	return strings.Split(strings.TrimSpace(string(out)), "\n"), nil
402}
403