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