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