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// Ensures the out directory exists, and has the proper files to prevent kati
27// 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.SkipMake() {
32		ensureEmptyFileExists(ctx, filepath.Join(config.SoongOutDir(), ".soong.in_make"))
33	}
34	// The ninja_build file is used by our buildbots to understand that the output
35	// can be parsed as ninja output.
36	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
37	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
38
39	if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok {
40		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0777)
41		if err != nil {
42			ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
43		}
44	} else {
45		ctx.Fatalln("Missing BUILD_DATETIME_FILE")
46	}
47}
48
49var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
50builddir = {{.OutDir}}
51{{if .UseRemoteBuild }}pool local_pool
52 depth = {{.Parallel}}
53{{end -}}
54pool highmem_pool
55 depth = {{.HighmemParallel}}
56{{if .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}
57subninja {{.KatiPackageNinjaFile}}
58{{end -}}
59subninja {{.SoongNinjaFile}}
60`))
61
62func createCombinedBuildNinjaFile(ctx Context, config Config) {
63	// If we're in SkipMake mode, skip creating this file if it already exists
64	if config.SkipMake() {
65		if _, err := os.Stat(config.CombinedNinjaFile()); err == nil || !os.IsNotExist(err) {
66			return
67		}
68	}
69
70	file, err := os.Create(config.CombinedNinjaFile())
71	if err != nil {
72		ctx.Fatalln("Failed to create combined ninja file:", err)
73	}
74	defer file.Close()
75
76	if err := combinedBuildNinjaTemplate.Execute(file, config); err != nil {
77		ctx.Fatalln("Failed to write combined ninja file:", err)
78	}
79}
80
81const (
82	BuildNone          = iota
83	BuildProductConfig = 1 << iota
84	BuildSoong         = 1 << iota
85	BuildKati          = 1 << iota
86	BuildNinja         = 1 << iota
87	RunBuildTests      = 1 << iota
88	BuildAll           = BuildProductConfig | BuildSoong | BuildKati | BuildNinja
89)
90
91func checkProblematicFiles(ctx Context) {
92	files := []string{"Android.mk", "CleanSpec.mk"}
93	for _, file := range files {
94		if _, err := os.Stat(file); !os.IsNotExist(err) {
95			absolute := absPath(ctx, file)
96			ctx.Printf("Found %s in tree root. This file needs to be removed to build.\n", file)
97			ctx.Fatalf("    rm %s\n", absolute)
98		}
99	}
100}
101
102func checkCaseSensitivity(ctx Context, config Config) {
103	outDir := config.OutDir()
104	lowerCase := filepath.Join(outDir, "casecheck.txt")
105	upperCase := filepath.Join(outDir, "CaseCheck.txt")
106	lowerData := "a"
107	upperData := "B"
108
109	err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0777)
110	if err != nil {
111		ctx.Fatalln("Failed to check case sensitivity:", err)
112	}
113
114	err = ioutil.WriteFile(upperCase, []byte(upperData), 0777)
115	if err != nil {
116		ctx.Fatalln("Failed to check case sensitivity:", err)
117	}
118
119	res, err := ioutil.ReadFile(lowerCase)
120	if err != nil {
121		ctx.Fatalln("Failed to check case sensitivity:", err)
122	}
123
124	if string(res) != lowerData {
125		ctx.Println("************************************************************")
126		ctx.Println("You are building on a case-insensitive filesystem.")
127		ctx.Println("Please move your source tree to a case-sensitive filesystem.")
128		ctx.Println("************************************************************")
129		ctx.Fatalln("Case-insensitive filesystems not supported")
130	}
131}
132
133func help(ctx Context, config Config, what int) {
134	cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
135	cmd.Sandbox = dumpvarsSandbox
136	cmd.RunAndPrintOrFatal()
137}
138
139// Build the tree. The 'what' argument can be used to chose which components of
140// the build to run.
141func Build(ctx Context, config Config, what int) {
142	ctx.Verboseln("Starting build with args:", config.Arguments())
143	ctx.Verboseln("Environment:", config.Environment().Environ())
144
145	if totalRAM := config.TotalRAM(); totalRAM != 0 {
146		ram := float32(totalRAM) / (1024 * 1024 * 1024)
147		ctx.Verbosef("Total RAM: %.3vGB", ram)
148
149		if ram <= 16 {
150			ctx.Println("************************************************************")
151			ctx.Printf("You are building on a machine with %.3vGB of RAM\n", ram)
152			ctx.Println("")
153			ctx.Println("The minimum required amount of free memory is around 16GB,")
154			ctx.Println("and even with that, some configurations may not work.")
155			ctx.Println("")
156			ctx.Println("If you run into segfaults or other errors, try reducing your")
157			ctx.Println("-j value.")
158			ctx.Println("************************************************************")
159		} else if ram <= float32(config.Parallel()) {
160			ctx.Printf("Warning: high -j%d count compared to %.3vGB of RAM", config.Parallel(), ram)
161			ctx.Println("If you run into segfaults or other errors, try a lower -j value")
162		}
163	}
164
165	ctx.BeginTrace(metrics.Total, "total")
166	defer ctx.EndTrace()
167
168	if config.SkipMake() {
169		ctx.Verboseln("Skipping Make/Kati as requested")
170		what = what & (BuildSoong | BuildNinja)
171	}
172
173	if inList("help", config.Arguments()) {
174		help(ctx, config, what)
175		return
176	} else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
177		clean(ctx, config, what)
178		return
179	}
180
181	// Make sure that no other Soong process is running with the same output directory
182	buildLock := BecomeSingletonOrFail(ctx, config)
183	defer buildLock.Unlock()
184
185	checkProblematicFiles(ctx)
186
187	SetupOutDir(ctx, config)
188
189	checkCaseSensitivity(ctx, config)
190
191	ensureEmptyDirectoriesExist(ctx, config.TempDir())
192
193	SetupPath(ctx, config)
194
195	if config.StartGoma() {
196		// Ensure start Goma compiler_proxy
197		startGoma(ctx, config)
198	}
199
200	if config.StartRBE() {
201		// Ensure RBE proxy is started
202		startRBE(ctx, config)
203	}
204
205	if what&BuildProductConfig != 0 {
206		// Run make for product config
207		runMakeProductConfig(ctx, config)
208	}
209
210	if inList("installclean", config.Arguments()) ||
211		inList("install-clean", config.Arguments()) {
212		installClean(ctx, config, what)
213		ctx.Println("Deleted images and staging directories.")
214		return
215	} else if inList("dataclean", config.Arguments()) ||
216		inList("data-clean", config.Arguments()) {
217		dataClean(ctx, config, what)
218		ctx.Println("Deleted data files.")
219		return
220	}
221
222	if what&BuildSoong != 0 {
223		// Run Soong
224		runSoong(ctx, config)
225	}
226
227	if what&BuildKati != 0 {
228		// Run ckati
229		genKatiSuffix(ctx, config)
230		runKatiCleanSpec(ctx, config)
231		runKatiBuild(ctx, config)
232		runKatiPackage(ctx, config)
233
234		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0777)
235	} else {
236		// Load last Kati Suffix if it exists
237		if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
238			ctx.Verboseln("Loaded previous kati config:", string(katiSuffix))
239			config.SetKatiSuffix(string(katiSuffix))
240		}
241	}
242
243	// Write combined ninja file
244	createCombinedBuildNinjaFile(ctx, config)
245
246	distGzipFile(ctx, config, config.CombinedNinjaFile())
247
248	if what&RunBuildTests != 0 {
249		testForDanglingRules(ctx, config)
250	}
251
252	if what&BuildNinja != 0 {
253		if !config.SkipMake() {
254			installCleanIfNecessary(ctx, config)
255		}
256
257		// Run ninja
258		runNinja(ctx, config)
259	}
260}
261
262// distGzipFile writes a compressed copy of src to the distDir if dist is enabled.  Failures
263// are printed but non-fatal.
264func distGzipFile(ctx Context, config Config, src string, subDirs ...string) {
265	if !config.Dist() {
266		return
267	}
268
269	subDir := filepath.Join(subDirs...)
270	destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
271
272	err := os.MkdirAll(destDir, 0777)
273	if err != nil {
274		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
275
276	}
277
278	err = gzipFileToDir(src, destDir)
279	if err != nil {
280		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
281	}
282}
283
284// distFile writes a copy of src to the distDir if dist is enabled.  Failures are printed but
285// non-fatal.
286func distFile(ctx Context, config Config, src string, subDirs ...string) {
287	if !config.Dist() {
288		return
289	}
290
291	subDir := filepath.Join(subDirs...)
292	destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
293
294	err := os.MkdirAll(destDir, 0777)
295	if err != nil {
296		ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
297
298	}
299
300	_, err = copyFile(src, filepath.Join(destDir, filepath.Base(src)))
301	if err != nil {
302		ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
303	}
304}
305