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