xref: /aosp_15_r20/build/soong/ui/build/build.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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	"io/ioutil"
20	"os"
21	"path/filepath"
22	"sync"
23	"text/template"
24
25	"android/soong/elf"
26	"android/soong/ui/metrics"
27)
28
29// SetupOutDir ensures the out directory exists, and has the proper files to
30// prevent kati from recursing into it.
31func SetupOutDir(ctx Context, config Config) {
32	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk"))
33	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
34	ensureEmptyDirectoriesExist(ctx, config.TempDir())
35
36	// Potentially write a marker file for whether kati is enabled. This is used by soong_build to
37	// potentially run the AndroidMk singleton and postinstall commands.
38	// Note that the absence of the  file does not not preclude running Kati for product
39	// configuration purposes.
40	katiEnabledMarker := filepath.Join(config.SoongOutDir(), ".soong.kati_enabled")
41	if config.SkipKatiNinja() {
42		os.Remove(katiEnabledMarker)
43		// Note that we can not remove the file for SkipKati builds yet -- some continuous builds
44		// --skip-make builds rely on kati targets being defined.
45	} else if !config.SkipKati() {
46		ensureEmptyFileExists(ctx, katiEnabledMarker)
47	}
48
49	// The ninja_build file is used by our buildbots to understand that the output
50	// can be parsed as ninja output.
51	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
52	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
53
54	if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok {
55		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0666) // a+rw
56		if err != nil {
57			ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
58		}
59	} else {
60		ctx.Fatalln("Missing BUILD_DATETIME_FILE")
61	}
62
63	// BUILD_NUMBER should be set to the source control value that
64	// represents the current state of the source code.  E.g., a
65	// perforce changelist number or a git hash.  Can be an arbitrary string
66	// (to allow for source control that uses something other than numbers),
67	// but must be a single word and a valid file name.
68	//
69	// If no BUILD_NUMBER is set, create a useful "I am an engineering build"
70	// value.  Make it start with a non-digit so that anyone trying to parse
71	// it as an integer will probably get "0". This value used to contain
72	// a timestamp, but now that more dependencies are tracked in order to
73	// reduce the importance of `m installclean`, changing it every build
74	// causes unnecessary rebuilds for local development.
75	buildNumber, ok := config.environ.Get("BUILD_NUMBER")
76	if ok {
77		writeValueIfChanged(ctx, config, config.OutDir(), "file_name_tag.txt", buildNumber)
78	} else {
79		var username string
80		if username, ok = config.environ.Get("BUILD_USERNAME"); !ok {
81			ctx.Fatalln("Missing BUILD_USERNAME")
82		}
83		buildNumber = fmt.Sprintf("eng.%.6s", username)
84		writeValueIfChanged(ctx, config, config.OutDir(), "file_name_tag.txt", username)
85	}
86	// Write the build number to a file so it can be read back in
87	// without changing the command line every time.  Avoids rebuilds
88	// when using ninja.
89	writeValueIfChanged(ctx, config, config.SoongOutDir(), "build_number.txt", buildNumber)
90}
91
92var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
93builddir = {{.OutDir}}
94{{if .UseRemoteBuild }}pool local_pool
95 depth = {{.Parallel}}
96{{end -}}
97pool highmem_pool
98 depth = {{.HighmemParallel}}
99{{if and (not .SkipKatiNinja) .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}
100subninja {{.KatiPackageNinjaFile}}
101{{end -}}
102subninja {{.SoongNinjaFile}}
103`))
104
105func createCombinedBuildNinjaFile(ctx Context, config Config) {
106	// If we're in SkipKati mode but want to run kati ninja, skip creating this file if it already exists
107	if config.SkipKati() && !config.SkipKatiNinja() {
108		if _, err := os.Stat(config.CombinedNinjaFile()); err == nil || !os.IsNotExist(err) {
109			return
110		}
111	}
112
113	file, err := os.Create(config.CombinedNinjaFile())
114	if err != nil {
115		ctx.Fatalln("Failed to create combined ninja file:", err)
116	}
117	defer file.Close()
118
119	if err := combinedBuildNinjaTemplate.Execute(file, config); err != nil {
120		ctx.Fatalln("Failed to write combined ninja file:", err)
121	}
122}
123
124// These are bitmasks which can be used to check whether various flags are set
125const (
126	_ = iota
127	// Whether to run the kati config step.
128	RunProductConfig = 1 << iota
129	// Whether to run soong to generate a ninja file.
130	RunSoong = 1 << iota
131	// Whether to run kati to generate a ninja file.
132	RunKati = 1 << iota
133	// Whether to include the kati-generated ninja file in the combined ninja.
134	RunKatiNinja = 1 << iota
135	// Whether to run ninja on the combined ninja.
136	RunNinja       = 1 << iota
137	RunDistActions = 1 << iota
138	RunBuildTests  = 1 << iota
139)
140
141// checkProblematicFiles fails the build if existing Android.mk or CleanSpec.mk files are found at the root of the tree.
142func checkProblematicFiles(ctx Context) {
143	files := []string{"Android.mk", "CleanSpec.mk"}
144	for _, file := range files {
145		if _, err := os.Stat(file); !os.IsNotExist(err) {
146			absolute := absPath(ctx, file)
147			ctx.Printf("Found %s in tree root. This file needs to be removed to build.\n", file)
148			ctx.Fatalf("    rm %s\n", absolute)
149		}
150	}
151}
152
153// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
154func checkCaseSensitivity(ctx Context, config Config) {
155	outDir := config.OutDir()
156	lowerCase := filepath.Join(outDir, "casecheck.txt")
157	upperCase := filepath.Join(outDir, "CaseCheck.txt")
158	lowerData := "a"
159	upperData := "B"
160
161	if err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0666); err != nil { // a+rw
162		ctx.Fatalln("Failed to check case sensitivity:", err)
163	}
164
165	if err := ioutil.WriteFile(upperCase, []byte(upperData), 0666); err != nil { // a+rw
166		ctx.Fatalln("Failed to check case sensitivity:", err)
167	}
168
169	res, err := ioutil.ReadFile(lowerCase)
170	if err != nil {
171		ctx.Fatalln("Failed to check case sensitivity:", err)
172	}
173
174	if string(res) != lowerData {
175		ctx.Println("************************************************************")
176		ctx.Println("You are building on a case-insensitive filesystem.")
177		ctx.Println("Please move your source tree to a case-sensitive filesystem.")
178		ctx.Println("************************************************************")
179		ctx.Fatalln("Case-insensitive filesystems not supported")
180	}
181}
182
183// help prints a help/usage message, via the build/make/help.sh script.
184func help(ctx Context, config Config) {
185	cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
186	cmd.Sandbox = dumpvarsSandbox
187	cmd.RunAndPrintOrFatal()
188}
189
190// checkRAM warns if there probably isn't enough RAM to complete a build.
191func checkRAM(ctx Context, config Config) {
192	if totalRAM := config.TotalRAM(); totalRAM != 0 {
193		ram := float32(totalRAM) / (1024 * 1024 * 1024)
194		ctx.Verbosef("Total RAM: %.3vGB", ram)
195
196		if ram <= 16 {
197			ctx.Println("************************************************************")
198			ctx.Printf("You are building on a machine with %.3vGB of RAM\n", ram)
199			ctx.Println("")
200			ctx.Println("The minimum required amount of free memory is around 16GB,")
201			ctx.Println("and even with that, some configurations may not work.")
202			ctx.Println("")
203			ctx.Println("If you run into segfaults or other errors, try reducing your")
204			ctx.Println("-j value.")
205			ctx.Println("************************************************************")
206		} else if ram <= float32(config.Parallel()) {
207			// Want at least 1GB of RAM per job.
208			ctx.Printf("Warning: high -j%d count compared to %.3vGB of RAM", config.Parallel(), ram)
209			ctx.Println("If you run into segfaults or other errors, try a lower -j value")
210		}
211	}
212}
213
214func abfsBuildStarted(ctx Context, config Config) {
215	abfsBox := config.PrebuiltBuildTool("abfsbox")
216	cmdArgs := []string{"build-started", "--"}
217	cmdArgs = append(cmdArgs, config.Arguments()...)
218	cmd := Command(ctx, config, "abfsbox", abfsBox, cmdArgs...)
219	cmd.Sandbox = noSandbox
220	cmd.RunAndPrintOrFatal()
221}
222
223func abfsBuildFinished(ctx Context, config Config, finished bool) {
224	var errMsg string
225	if !finished {
226		errMsg = "build was interrupted"
227	}
228	abfsBox := config.PrebuiltBuildTool("abfsbox")
229	cmdArgs := []string{"build-finished", "-e", errMsg, "--"}
230	cmdArgs = append(cmdArgs, config.Arguments()...)
231	cmd := Command(ctx, config, "abfsbox", abfsBox, cmdArgs...)
232	cmd.RunAndPrintOrFatal()
233}
234
235// Build the tree. Various flags in `config` govern which components of
236// the build to run.
237func Build(ctx Context, config Config) {
238	done := false
239	if config.UseABFS() {
240		abfsBuildStarted(ctx, config)
241		defer func() {
242			abfsBuildFinished(ctx, config, done)
243		}()
244	}
245
246	ctx.Verboseln("Starting build with args:", config.Arguments())
247	ctx.Verboseln("Environment:", config.Environment().Environ())
248
249	ctx.BeginTrace(metrics.Total, "total")
250	defer ctx.EndTrace()
251
252	if inList("help", config.Arguments()) {
253		help(ctx, config)
254		return
255	}
256
257	// Make sure that no other Soong process is running with the same output directory
258	buildLock := BecomeSingletonOrFail(ctx, config)
259	defer buildLock.Unlock()
260
261	logArgsOtherThan := func(specialTargets ...string) {
262		var ignored []string
263		for _, a := range config.Arguments() {
264			if !inList(a, specialTargets) {
265				ignored = append(ignored, a)
266			}
267		}
268		if len(ignored) > 0 {
269			ctx.Printf("ignoring arguments %q", ignored)
270		}
271	}
272
273	if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
274		logArgsOtherThan("clean", "clobber")
275		clean(ctx, config)
276		return
277	}
278
279	defer waitForDist(ctx)
280
281	// checkProblematicFiles aborts the build if Android.mk or CleanSpec.mk are found at the root of the tree.
282	checkProblematicFiles(ctx)
283
284	checkRAM(ctx, config)
285
286	SetupOutDir(ctx, config)
287
288	// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
289	checkCaseSensitivity(ctx, config)
290
291	SetupPath(ctx, config)
292
293	what := evaluateWhatToRun(config, ctx.Verboseln)
294
295	if config.StartGoma() {
296		startGoma(ctx, config)
297	}
298
299	rbeCh := make(chan bool)
300	var rbePanic any
301	if config.StartRBE() {
302		cleanupRBELogsDir(ctx, config)
303		checkRBERequirements(ctx, config)
304		go func() {
305			defer func() {
306				rbePanic = recover()
307				close(rbeCh)
308			}()
309			startRBE(ctx, config)
310		}()
311		defer DumpRBEMetrics(ctx, config, filepath.Join(config.LogsDir(), "rbe_metrics.pb"))
312	} else {
313		close(rbeCh)
314	}
315
316	if what&RunProductConfig != 0 {
317		runMakeProductConfig(ctx, config)
318	}
319
320	// Everything below here depends on product config.
321
322	if inList("installclean", config.Arguments()) ||
323		inList("install-clean", config.Arguments()) {
324		logArgsOtherThan("installclean", "install-clean")
325		installClean(ctx, config)
326		ctx.Println("Deleted images and staging directories.")
327		return
328	}
329
330	if inList("dataclean", config.Arguments()) ||
331		inList("data-clean", config.Arguments()) {
332		logArgsOtherThan("dataclean", "data-clean")
333		dataClean(ctx, config)
334		ctx.Println("Deleted data files.")
335		return
336	}
337
338	if what&RunSoong != 0 {
339		runSoong(ctx, config)
340	}
341
342	if what&RunKati != 0 {
343		genKatiSuffix(ctx, config)
344		runKatiCleanSpec(ctx, config)
345		runKatiBuild(ctx, config)
346		runKatiPackage(ctx, config)
347
348		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
349	} else if what&RunKatiNinja != 0 {
350		// Load last Kati Suffix if it exists
351		if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
352			ctx.Verboseln("Loaded previous kati config:", string(katiSuffix))
353			config.SetKatiSuffix(string(katiSuffix))
354		}
355	}
356
357	// Write combined ninja file
358	createCombinedBuildNinjaFile(ctx, config)
359
360	distGzipFile(ctx, config, config.CombinedNinjaFile())
361
362	if what&RunBuildTests != 0 {
363		testForDanglingRules(ctx, config)
364	}
365
366	<-rbeCh
367	if rbePanic != nil {
368		// If there was a ctx.Fatal in startRBE, rethrow it.
369		panic(rbePanic)
370	}
371
372	if what&RunNinja != 0 {
373		if what&RunKati != 0 {
374			installCleanIfNecessary(ctx, config)
375		}
376		runNinjaForBuild(ctx, config)
377		updateBuildIdDir(ctx, config)
378	}
379
380	if what&RunDistActions != 0 {
381		runDistActions(ctx, config)
382	}
383	done = true
384}
385
386func updateBuildIdDir(ctx Context, config Config) {
387	ctx.BeginTrace(metrics.RunShutdownTool, "update_build_id_dir")
388	defer ctx.EndTrace()
389
390	symbolsDir := filepath.Join(config.ProductOut(), "symbols")
391	if err := elf.UpdateBuildIdDir(symbolsDir); err != nil {
392		ctx.Printf("failed to update %s/.build-id: %v", symbolsDir, err)
393	}
394}
395
396func evaluateWhatToRun(config Config, verboseln func(v ...interface{})) int {
397	//evaluate what to run
398	what := 0
399	if config.Checkbuild() {
400		what |= RunBuildTests
401	}
402	if !config.SkipConfig() {
403		what |= RunProductConfig
404	} else {
405		verboseln("Skipping Config as requested")
406	}
407	if !config.SkipSoong() {
408		what |= RunSoong
409	} else {
410		verboseln("Skipping use of Soong as requested")
411	}
412	if !config.SkipKati() {
413		what |= RunKati
414	} else {
415		verboseln("Skipping Kati as requested")
416	}
417	if !config.SkipKatiNinja() {
418		what |= RunKatiNinja
419	} else {
420		verboseln("Skipping use of Kati ninja as requested")
421	}
422	if !config.SkipNinja() {
423		what |= RunNinja
424	} else {
425		verboseln("Skipping Ninja as requested")
426	}
427
428	if !config.SoongBuildInvocationNeeded() {
429		// This means that the output of soong_build is not needed and thus it would
430		// run unnecessarily. In addition, if this code wasn't there invocations
431		// with only special-cased target names would result in
432		// passing Ninja the empty target list and it would then build the default
433		// targets which is not what the user asked for.
434		what = what &^ RunNinja
435		what = what &^ RunKati
436	}
437
438	if config.Dist() {
439		what |= RunDistActions
440	}
441
442	return what
443}
444
445var distWaitGroup sync.WaitGroup
446
447// waitForDist waits for all backgrounded distGzipFile and distFile writes to finish
448func waitForDist(ctx Context) {
449	ctx.BeginTrace("soong_ui", "dist")
450	defer ctx.EndTrace()
451
452	distWaitGroup.Wait()
453}
454
455// distGzipFile writes a compressed copy of src to the distDir if dist is enabled.  Failures
456// are printed but non-fatal. Uses the distWaitGroup func for backgrounding (optimization).
457func distGzipFile(ctx Context, config Config, src string, subDirs ...string) {
458	if !config.Dist() {
459		return
460	}
461
462	subDir := filepath.Join(subDirs...)
463	destDir := filepath.Join(config.RealDistDir(), "soong_ui", subDir)
464
465	if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
466		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
467	}
468
469	distWaitGroup.Add(1)
470	go func() {
471		defer distWaitGroup.Done()
472		if err := gzipFileToDir(src, destDir); err != nil {
473			ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
474		}
475	}()
476}
477
478// distFile writes a copy of src to the distDir if dist is enabled.  Failures are printed but
479// non-fatal. Uses the distWaitGroup func for backgrounding (optimization).
480func distFile(ctx Context, config Config, src string, subDirs ...string) {
481	if !config.Dist() {
482		return
483	}
484
485	subDir := filepath.Join(subDirs...)
486	destDir := filepath.Join(config.RealDistDir(), "soong_ui", subDir)
487
488	if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
489		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
490	}
491
492	distWaitGroup.Add(1)
493	go func() {
494		defer distWaitGroup.Done()
495		if _, err := copyFile(src, filepath.Join(destDir, filepath.Base(src))); err != nil {
496			ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
497		}
498	}()
499}
500
501// Actions to run on every build where 'dist' is in the actions.
502// Be careful, anything added here slows down EVERY CI build
503func runDistActions(ctx Context, config Config) {
504	runStagingSnapshot(ctx, config)
505}
506