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