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