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	"text/template"
22
23	"android/soong/ui/metrics"
24)
25
26// SetupOutDir ensures the out directory exists, and has the proper files to
27// prevent kati from recursing into it.
28func SetupOutDir(ctx Context, config Config) {
29	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk"))
30	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
31	if !config.SkipKati() {
32		// Run soong_build with Kati for a hybrid build, e.g. running the
33		// AndroidMk singleton and postinstall commands. Communicate this to
34		// soong_build by writing an empty .soong.kati_enabled marker file in the
35		// soong_build output directory for the soong_build primary builder to
36		// know if the user wants to run Kati after.
37		//
38		// This does not preclude running Kati for *product configuration purposes*.
39		ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.kati_enabled"))
40	}
41	// The ninja_build file is used by our buildbots to understand that the output
42	// can be parsed as ninja output.
43	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
44	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
45
46	if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok {
47		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0666) // a+rw
48		if err != nil {
49			ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
50		}
51	} else {
52		ctx.Fatalln("Missing BUILD_DATETIME_FILE")
53	}
54}
55
56var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
57builddir = {{.OutDir}}
58{{if .UseRemoteBuild }}pool local_pool
59 depth = {{.Parallel}}
60{{end -}}
61pool highmem_pool
62 depth = {{.HighmemParallel}}
63{{if and (not .SkipKatiNinja) .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}
64subninja {{.KatiPackageNinjaFile}}
65{{end -}}
66subninja {{.SoongNinjaFile}}
67`))
68
69func createCombinedBuildNinjaFile(ctx Context, config Config) {
70	// If we're in SkipKati mode but want to run kati ninja, skip creating this file if it already exists
71	if config.SkipKati() && !config.SkipKatiNinja() {
72		if _, err := os.Stat(config.CombinedNinjaFile()); err == nil || !os.IsNotExist(err) {
73			return
74		}
75	}
76
77	file, err := os.Create(config.CombinedNinjaFile())
78	if err != nil {
79		ctx.Fatalln("Failed to create combined ninja file:", err)
80	}
81	defer file.Close()
82
83	if err := combinedBuildNinjaTemplate.Execute(file, config); err != nil {
84		ctx.Fatalln("Failed to write combined ninja file:", err)
85	}
86}
87
88// These are bitmasks which can be used to check whether various flags are set e.g. whether to use Bazel.
89const (
90	_ = iota
91	// Whether to run the kati config step.
92	RunProductConfig = 1 << iota
93	// Whether to run soong to generate a ninja file.
94	RunSoong = 1 << iota
95	// Whether to run kati to generate a ninja file.
96	RunKati = 1 << iota
97	// Whether to include the kati-generated ninja file in the combined ninja.
98	RunKatiNinja = 1 << iota
99	// Whether to run ninja on the combined ninja.
100	RunNinja = 1 << iota
101	// Whether to run bazel on the combined ninja.
102	RunBazel        = 1 << iota
103	RunBuildTests   = 1 << iota
104	RunAll          = RunProductConfig | RunSoong | RunKati | RunKatiNinja | RunNinja
105	RunAllWithBazel = RunProductConfig | RunSoong | RunKati | RunKatiNinja | RunBazel
106)
107
108// checkProblematicFiles fails the build if existing Android.mk or CleanSpec.mk files are found at the root of the tree.
109func checkProblematicFiles(ctx Context) {
110	files := []string{"Android.mk", "CleanSpec.mk"}
111	for _, file := range files {
112		if _, err := os.Stat(file); !os.IsNotExist(err) {
113			absolute := absPath(ctx, file)
114			ctx.Printf("Found %s in tree root. This file needs to be removed to build.\n", file)
115			ctx.Fatalf("    rm %s\n", absolute)
116		}
117	}
118}
119
120// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
121func checkCaseSensitivity(ctx Context, config Config) {
122	outDir := config.OutDir()
123	lowerCase := filepath.Join(outDir, "casecheck.txt")
124	upperCase := filepath.Join(outDir, "CaseCheck.txt")
125	lowerData := "a"
126	upperData := "B"
127
128	if err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0666); err != nil { // a+rw
129		ctx.Fatalln("Failed to check case sensitivity:", err)
130	}
131
132	if err := ioutil.WriteFile(upperCase, []byte(upperData), 0666); err != nil { // a+rw
133		ctx.Fatalln("Failed to check case sensitivity:", err)
134	}
135
136	res, err := ioutil.ReadFile(lowerCase)
137	if err != nil {
138		ctx.Fatalln("Failed to check case sensitivity:", err)
139	}
140
141	if string(res) != lowerData {
142		ctx.Println("************************************************************")
143		ctx.Println("You are building on a case-insensitive filesystem.")
144		ctx.Println("Please move your source tree to a case-sensitive filesystem.")
145		ctx.Println("************************************************************")
146		ctx.Fatalln("Case-insensitive filesystems not supported")
147	}
148}
149
150// help prints a help/usage message, via the build/make/help.sh script.
151func help(ctx Context, config Config) {
152	cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
153	cmd.Sandbox = dumpvarsSandbox
154	cmd.RunAndPrintOrFatal()
155}
156
157// checkRAM warns if there probably isn't enough RAM to complete a build.
158func checkRAM(ctx Context, config Config) {
159	if totalRAM := config.TotalRAM(); totalRAM != 0 {
160		ram := float32(totalRAM) / (1024 * 1024 * 1024)
161		ctx.Verbosef("Total RAM: %.3vGB", ram)
162
163		if ram <= 16 {
164			ctx.Println("************************************************************")
165			ctx.Printf("You are building on a machine with %.3vGB of RAM\n", ram)
166			ctx.Println("")
167			ctx.Println("The minimum required amount of free memory is around 16GB,")
168			ctx.Println("and even with that, some configurations may not work.")
169			ctx.Println("")
170			ctx.Println("If you run into segfaults or other errors, try reducing your")
171			ctx.Println("-j value.")
172			ctx.Println("************************************************************")
173		} else if ram <= float32(config.Parallel()) {
174			// Want at least 1GB of RAM per job.
175			ctx.Printf("Warning: high -j%d count compared to %.3vGB of RAM", config.Parallel(), ram)
176			ctx.Println("If you run into segfaults or other errors, try a lower -j value")
177		}
178	}
179}
180
181// Build the tree. The 'what' argument can be used to chose which components of
182// the build to run, via checking various bitmasks.
183func Build(ctx Context, config Config) {
184	ctx.Verboseln("Starting build with args:", config.Arguments())
185	ctx.Verboseln("Environment:", config.Environment().Environ())
186
187	ctx.BeginTrace(metrics.Total, "total")
188	defer ctx.EndTrace()
189
190	if inList("help", config.Arguments()) {
191		help(ctx, config)
192		return
193	}
194
195	// Make sure that no other Soong process is running with the same output directory
196	buildLock := BecomeSingletonOrFail(ctx, config)
197	defer buildLock.Unlock()
198
199	if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
200		clean(ctx, config)
201		return
202	}
203
204	// checkProblematicFiles aborts the build if Android.mk or CleanSpec.mk are found at the root of the tree.
205	checkProblematicFiles(ctx)
206
207	checkRAM(ctx, config)
208
209	SetupOutDir(ctx, config)
210
211	// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
212	checkCaseSensitivity(ctx, config)
213
214	ensureEmptyDirectoriesExist(ctx, config.TempDir())
215
216	SetupPath(ctx, config)
217
218	what := RunAll
219	if config.UseBazel() {
220		what = RunAllWithBazel
221	}
222	if config.Checkbuild() {
223		what |= RunBuildTests
224	}
225	if config.SkipConfig() {
226		ctx.Verboseln("Skipping Config as requested")
227		what = what &^ RunProductConfig
228	}
229	if config.SkipKati() {
230		ctx.Verboseln("Skipping Kati as requested")
231		what = what &^ RunKati
232	}
233	if config.SkipKatiNinja() {
234		ctx.Verboseln("Skipping use of Kati ninja as requested")
235		what = what &^ RunKatiNinja
236	}
237	if config.SkipNinja() {
238		ctx.Verboseln("Skipping Ninja as requested")
239		what = what &^ RunNinja
240	}
241
242	if config.StartGoma() {
243		startGoma(ctx, config)
244	}
245
246	if config.StartRBE() {
247		startRBE(ctx, config)
248	}
249
250	if what&RunProductConfig != 0 {
251		runMakeProductConfig(ctx, config)
252	}
253
254	// Everything below here depends on product config.
255
256	if inList("installclean", config.Arguments()) ||
257		inList("install-clean", config.Arguments()) {
258		installClean(ctx, config)
259		ctx.Println("Deleted images and staging directories.")
260		return
261	}
262
263	if inList("dataclean", config.Arguments()) ||
264		inList("data-clean", config.Arguments()) {
265		dataClean(ctx, config)
266		ctx.Println("Deleted data files.")
267		return
268	}
269
270	if what&RunSoong != 0 {
271		runSoong(ctx, config)
272
273		if config.bazelBuildMode() == generateBuildFiles {
274			// Return early, if we're using Soong as solely the generator of BUILD files.
275			return
276		}
277	}
278
279	if what&RunKati != 0 {
280		genKatiSuffix(ctx, config)
281		runKatiCleanSpec(ctx, config)
282		runKatiBuild(ctx, config)
283		runKatiPackage(ctx, config)
284
285		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
286	} else if what&RunKatiNinja != 0 {
287		// Load last Kati Suffix if it exists
288		if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
289			ctx.Verboseln("Loaded previous kati config:", string(katiSuffix))
290			config.SetKatiSuffix(string(katiSuffix))
291		}
292	}
293
294	// Write combined ninja file
295	createCombinedBuildNinjaFile(ctx, config)
296
297	distGzipFile(ctx, config, config.CombinedNinjaFile())
298
299	if what&RunBuildTests != 0 {
300		testForDanglingRules(ctx, config)
301	}
302
303	if what&RunNinja != 0 {
304		if what&RunKati != 0 {
305			installCleanIfNecessary(ctx, config)
306		}
307
308		runNinjaForBuild(ctx, config)
309	}
310
311	// Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last.
312	if what&RunBazel != 0 {
313		runBazel(ctx, config)
314	}
315}
316
317// distGzipFile writes a compressed copy of src to the distDir if dist is enabled.  Failures
318// are printed but non-fatal.
319func distGzipFile(ctx Context, config Config, src string, subDirs ...string) {
320	if !config.Dist() {
321		return
322	}
323
324	subDir := filepath.Join(subDirs...)
325	destDir := filepath.Join(config.RealDistDir(), "soong_ui", subDir)
326
327	if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
328		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
329	}
330
331	if err := gzipFileToDir(src, destDir); err != nil {
332		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
333	}
334}
335
336// distFile writes a copy of src to the distDir if dist is enabled.  Failures are printed but
337// non-fatal.
338func distFile(ctx Context, config Config, src string, subDirs ...string) {
339	if !config.Dist() {
340		return
341	}
342
343	subDir := filepath.Join(subDirs...)
344	destDir := filepath.Join(config.RealDistDir(), "soong_ui", subDir)
345
346	if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
347		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
348	}
349
350	if _, err := copyFile(src, filepath.Join(destDir, filepath.Base(src))); err != nil {
351		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
352	}
353}
354