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 main 16 17import ( 18 "context" 19 "flag" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "strconv" 25 "strings" 26 "syscall" 27 "time" 28 29 "android/soong/shared" 30 "android/soong/ui/build" 31 "android/soong/ui/logger" 32 "android/soong/ui/metrics" 33 "android/soong/ui/signal" 34 "android/soong/ui/status" 35 "android/soong/ui/terminal" 36 "android/soong/ui/tracer" 37) 38 39// A command represents an operation to be executed in the soong build 40// system. 41type command struct { 42 // The flag name (must have double dashes). 43 flag string 44 45 // Description for the flag (to display when running help). 46 description string 47 48 // Stream the build status output into the simple terminal mode. 49 simpleOutput bool 50 51 // Sets a prefix string to use for filenames of log files. 52 logsPrefix string 53 54 // Creates the build configuration based on the args and build context. 55 config func(ctx build.Context, args ...string) build.Config 56 57 // Returns what type of IO redirection this Command requires. 58 stdio func() terminal.StdioInterface 59 60 // run the command 61 run func(ctx build.Context, config build.Config, args []string) 62} 63 64// list of supported commands (flags) supported by soong ui 65var commands = []command{ 66 { 67 flag: "--make-mode", 68 description: "build the modules by the target name (i.e. soong_docs)", 69 config: build.NewConfig, 70 stdio: stdio, 71 run: runMake, 72 }, { 73 flag: "--dumpvar-mode", 74 description: "print the value of the legacy make variable VAR to stdout", 75 simpleOutput: true, 76 logsPrefix: "dumpvars-", 77 config: dumpVarConfig, 78 stdio: customStdio, 79 run: dumpVar, 80 }, { 81 flag: "--dumpvars-mode", 82 description: "dump the values of one or more legacy make variables, in shell syntax", 83 simpleOutput: true, 84 logsPrefix: "dumpvars-", 85 config: dumpVarConfig, 86 stdio: customStdio, 87 run: dumpVars, 88 }, { 89 flag: "--build-mode", 90 description: "build modules based on the specified build action", 91 config: buildActionConfig, 92 stdio: stdio, 93 run: runMake, 94 }, { 95 flag: "--upload-metrics-only", 96 description: "upload metrics without building anything", 97 config: uploadOnlyConfig, 98 stdio: stdio, 99 // Upload-only mode mostly skips to the metrics-uploading phase of soong_ui. 100 // However, this invocation marks the true "end of the build", and thus we 101 // need to update the total runtime of the build to include this upload step. 102 run: updateTotalRealTime, 103 }, 104} 105 106// indexList returns the index of first found s. -1 is return if s is not 107// found. 108func indexList(s string, list []string) int { 109 for i, l := range list { 110 if l == s { 111 return i 112 } 113 } 114 return -1 115} 116 117// inList returns true if one or more of s is in the list. 118func inList(s string, list []string) bool { 119 return indexList(s, list) != -1 120} 121 122func deleteStaleMetrics(metricsFilePathSlice []string) error { 123 for _, metricsFilePath := range metricsFilePathSlice { 124 if err := os.Remove(metricsFilePath); err != nil && !os.IsNotExist(err) { 125 return fmt.Errorf("Failed to remove %s\nError message: %w", metricsFilePath, err) 126 } 127 } 128 return nil 129} 130 131// Main execution of soong_ui. The command format is as follows: 132// 133// soong_ui <command> [<arg 1> <arg 2> ... <arg n>] 134// 135// Command is the type of soong_ui execution. Only one type of 136// execution is specified. The args are specific to the command. 137func main() { 138 shared.ReexecWithDelveMaybe(os.Getenv("SOONG_UI_DELVE"), shared.ResolveDelveBinary()) 139 140 buildStarted := time.Now() 141 142 c, args, err := getCommand(os.Args) 143 if err != nil { 144 fmt.Fprintf(os.Stderr, "Error parsing `soong` args: %s.\n", err) 145 os.Exit(1) 146 } 147 148 // Create a terminal output that mimics Ninja's. 149 output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"), c.simpleOutput, 150 build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"), 151 build.OsEnvironment().IsEnvTrue("SOONG_UI_ANSI_OUTPUT")) 152 153 // Create and start a new metric record. 154 met := metrics.New() 155 met.SetBuildDateTime(buildStarted) 156 met.SetBuildCommand(os.Args) 157 158 // Attach a new logger instance to the terminal output. 159 log := logger.NewWithMetrics(output, met) 160 defer log.Cleanup() 161 162 // Create a context to simplify the program termination process. 163 ctx, cancel := context.WithCancel(context.Background()) 164 defer cancel() 165 166 // Create a new trace file writer, making it log events to the log instance. 167 trace := tracer.New(log) 168 defer trace.Close() 169 170 // Create a new Status instance, which manages action counts and event output channels. 171 stat := &status.Status{} 172 173 // Hook up the terminal output and tracer to Status. 174 stat.AddOutput(output) 175 stat.AddOutput(trace.StatusTracer()) 176 177 // Set up a cleanup procedure in case the normal termination process doesn't work. 178 signal.SetupSignals(log, cancel, func() { 179 trace.Close() 180 log.Cleanup() 181 stat.Finish() 182 }) 183 criticalPath := status.NewCriticalPath() 184 buildCtx := build.Context{ContextImpl: &build.ContextImpl{ 185 Context: ctx, 186 Logger: log, 187 Metrics: met, 188 Tracer: trace, 189 Writer: output, 190 Status: stat, 191 CriticalPath: criticalPath, 192 }} 193 194 config := c.config(buildCtx, args...) 195 config.SetLogsPrefix(c.logsPrefix) 196 logsDir := config.LogsDir() 197 buildStarted = config.BuildStartedTimeOrDefault(buildStarted) 198 199 buildErrorFile := filepath.Join(logsDir, c.logsPrefix+"build_error") 200 soongMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_metrics") 201 rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb") 202 bp2buildMetricsFile := filepath.Join(logsDir, c.logsPrefix+"bp2build_metrics.pb") 203 bazelMetricsFile := filepath.Join(logsDir, c.logsPrefix+"bazel_metrics.pb") 204 soongBuildMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_build_metrics.pb") 205 206 //the profile file generated by Bazel" 207 bazelProfileFile := filepath.Join(logsDir, c.logsPrefix+"analyzed_bazel_profile.txt") 208 metricsFiles := []string{ 209 buildErrorFile, // build error strings 210 rbeMetricsFile, // high level metrics related to remote build execution. 211 bp2buildMetricsFile, // high level metrics related to bp2build. 212 soongMetricsFile, // high level metrics related to this build system. 213 bazelMetricsFile, // high level metrics related to bazel execution 214 soongBuildMetricsFile, // high level metrics related to soong build(except bp2build) 215 config.BazelMetricsDir(), // directory that contains a set of bazel metrics. 216 } 217 218 os.MkdirAll(logsDir, 0777) 219 220 log.SetOutput(filepath.Join(logsDir, c.logsPrefix+"soong.log")) 221 222 trace.SetOutput(filepath.Join(logsDir, c.logsPrefix+"build.trace")) 223 224 defer func() { 225 stat.Finish() 226 criticalPath.WriteToMetrics(met) 227 met.Dump(soongMetricsFile) 228 if !config.SkipMetricsUpload() { 229 build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, bazelProfileFile, bazelMetricsFile, metricsFiles...) 230 } 231 }() 232 c.run(buildCtx, config, args) 233 234} 235 236func logAndSymlinkSetup(buildCtx build.Context, config build.Config) { 237 log := buildCtx.ContextImpl.Logger 238 logsPrefix := config.GetLogsPrefix() 239 build.SetupOutDir(buildCtx, config) 240 logsDir := config.LogsDir() 241 242 // Common list of metric file definition. 243 buildErrorFile := filepath.Join(logsDir, logsPrefix+"build_error") 244 rbeMetricsFile := filepath.Join(logsDir, logsPrefix+"rbe_metrics.pb") 245 soongMetricsFile := filepath.Join(logsDir, logsPrefix+"soong_metrics") 246 bp2buildMetricsFile := filepath.Join(logsDir, logsPrefix+"bp2build_metrics.pb") 247 soongBuildMetricsFile := filepath.Join(logsDir, logsPrefix+"soong_build_metrics.pb") 248 249 //Delete the stale metrics files 250 staleFileSlice := []string{buildErrorFile, rbeMetricsFile, soongMetricsFile, bp2buildMetricsFile, soongBuildMetricsFile} 251 if err := deleteStaleMetrics(staleFileSlice); err != nil { 252 log.Fatalln(err) 253 } 254 255 build.PrintOutDirWarning(buildCtx, config) 256 257 stat := buildCtx.Status 258 stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, logsPrefix+"verbose.log"))) 259 stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, logsPrefix+"error.log"))) 260 stat.AddOutput(status.NewProtoErrorLog(log, buildErrorFile)) 261 stat.AddOutput(status.NewCriticalPathLogger(log, buildCtx.CriticalPath)) 262 stat.AddOutput(status.NewBuildProgressLog(log, filepath.Join(logsDir, logsPrefix+"build_progress.pb"))) 263 264 buildCtx.Verbosef("Detected %.3v GB total RAM", float32(config.TotalRAM())/(1024*1024*1024)) 265 buildCtx.Verbosef("Parallelism (local/remote/highmem): %v/%v/%v", 266 config.Parallel(), config.RemoteParallel(), config.HighmemParallel()) 267 268 setMaxFiles(buildCtx) 269 270 defer build.CheckProdCreds(buildCtx, config) 271 272 // Read the time at the starting point. 273 if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok { 274 // soong_ui.bash uses the date command's %N (nanosec) flag when getting the start time, 275 // which Darwin doesn't support. Check if it was executed properly before parsing the value. 276 if !strings.HasSuffix(start, "N") { 277 if start_time, err := strconv.ParseUint(start, 10, 64); err == nil { 278 log.Verbosef("Took %dms to start up.", 279 time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds()) 280 buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano())) 281 } 282 } 283 284 if executable, err := os.Executable(); err == nil { 285 buildCtx.ContextImpl.Tracer.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace")) 286 } 287 } 288 289 // Fix up the source tree due to a repo bug where it doesn't remove 290 // linkfiles that have been removed 291 fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.bp") 292 fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.mk") 293 294 // Create a source finder. 295 f := build.NewSourceFinder(buildCtx, config) 296 defer f.Shutdown() 297 build.FindSources(buildCtx, config, f) 298} 299 300func fixBadDanglingLink(ctx build.Context, name string) { 301 _, err := os.Lstat(name) 302 if err != nil { 303 return 304 } 305 _, err = os.Stat(name) 306 if os.IsNotExist(err) { 307 err = os.Remove(name) 308 if err != nil { 309 ctx.Fatalf("Failed to remove dangling link %q: %v", name, err) 310 } 311 } 312} 313 314func dumpVar(ctx build.Context, config build.Config, args []string) { 315 logAndSymlinkSetup(ctx, config) 316 flags := flag.NewFlagSet("dumpvar", flag.ExitOnError) 317 flags.SetOutput(ctx.Writer) 318 319 flags.Usage = func() { 320 fmt.Fprintf(ctx.Writer, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0]) 321 fmt.Fprintln(ctx.Writer, "In dumpvar mode, print the value of the legacy make variable VAR to stdout") 322 fmt.Fprintln(ctx.Writer, "") 323 324 fmt.Fprintln(ctx.Writer, "'report_config' is a special case that prints the human-readable config banner") 325 fmt.Fprintln(ctx.Writer, "from the beginning of the build.") 326 fmt.Fprintln(ctx.Writer, "") 327 flags.PrintDefaults() 328 } 329 abs := flags.Bool("abs", false, "Print the absolute path of the value") 330 flags.Parse(args) 331 332 if flags.NArg() != 1 { 333 flags.Usage() 334 ctx.Fatalf("Invalid usage") 335 } 336 337 varName := flags.Arg(0) 338 if varName == "report_config" { 339 varData, err := build.DumpMakeVars(ctx, config, nil, build.BannerVars) 340 if err != nil { 341 ctx.Fatal(err) 342 } 343 344 fmt.Println(build.Banner(varData)) 345 } else { 346 varData, err := build.DumpMakeVars(ctx, config, nil, []string{varName}) 347 if err != nil { 348 ctx.Fatal(err) 349 } 350 351 if *abs { 352 var res []string 353 for _, path := range strings.Fields(varData[varName]) { 354 if abs, err := filepath.Abs(path); err == nil { 355 res = append(res, abs) 356 } else { 357 ctx.Fatalln("Failed to get absolute path of", path, err) 358 } 359 } 360 fmt.Println(strings.Join(res, " ")) 361 } else { 362 fmt.Println(varData[varName]) 363 } 364 } 365} 366 367func dumpVars(ctx build.Context, config build.Config, args []string) { 368 logAndSymlinkSetup(ctx, config) 369 370 flags := flag.NewFlagSet("dumpvars", flag.ExitOnError) 371 flags.SetOutput(ctx.Writer) 372 373 flags.Usage = func() { 374 fmt.Fprintf(ctx.Writer, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0]) 375 fmt.Fprintln(ctx.Writer, "In dumpvars mode, dump the values of one or more legacy make variables, in") 376 fmt.Fprintln(ctx.Writer, "shell syntax. The resulting output may be sourced directly into a shell to") 377 fmt.Fprintln(ctx.Writer, "set corresponding shell variables.") 378 fmt.Fprintln(ctx.Writer, "") 379 380 fmt.Fprintln(ctx.Writer, "'report_config' is a special case that dumps a variable containing the") 381 fmt.Fprintln(ctx.Writer, "human-readable config banner from the beginning of the build.") 382 fmt.Fprintln(ctx.Writer, "") 383 flags.PrintDefaults() 384 } 385 386 varsStr := flags.String("vars", "", "Space-separated list of variables to dump") 387 absVarsStr := flags.String("abs-vars", "", "Space-separated list of variables to dump (using absolute paths)") 388 389 varPrefix := flags.String("var-prefix", "", "String to prepend to all variable names when dumping") 390 absVarPrefix := flags.String("abs-var-prefix", "", "String to prepent to all absolute path variable names when dumping") 391 392 flags.Parse(args) 393 394 if flags.NArg() != 0 { 395 flags.Usage() 396 ctx.Fatalf("Invalid usage") 397 } 398 399 vars := strings.Fields(*varsStr) 400 absVars := strings.Fields(*absVarsStr) 401 402 allVars := append([]string{}, vars...) 403 allVars = append(allVars, absVars...) 404 405 if i := indexList("report_config", allVars); i != -1 { 406 allVars = append(allVars[:i], allVars[i+1:]...) 407 allVars = append(allVars, build.BannerVars...) 408 } 409 410 if len(allVars) == 0 { 411 return 412 } 413 414 varData, err := build.DumpMakeVars(ctx, config, nil, allVars) 415 if err != nil { 416 ctx.Fatal(err) 417 } 418 419 for _, name := range vars { 420 if name == "report_config" { 421 fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(varData)) 422 } else { 423 fmt.Printf("%s%s='%s'\n", *varPrefix, name, varData[name]) 424 } 425 } 426 for _, name := range absVars { 427 var res []string 428 for _, path := range strings.Fields(varData[name]) { 429 abs, err := filepath.Abs(path) 430 if err != nil { 431 ctx.Fatalln("Failed to get absolute path of", path, err) 432 } 433 res = append(res, abs) 434 } 435 fmt.Printf("%s%s='%s'\n", *absVarPrefix, name, strings.Join(res, " ")) 436 } 437} 438 439func stdio() terminal.StdioInterface { 440 return terminal.StdioImpl{} 441} 442 443// dumpvar and dumpvars use stdout to output variable values, so use stderr instead of stdout when 444// reporting events to keep stdout clean from noise. 445func customStdio() terminal.StdioInterface { 446 return terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr) 447} 448 449// dumpVarConfig does not require any arguments to be parsed by the NewConfig. 450func dumpVarConfig(ctx build.Context, args ...string) build.Config { 451 return build.NewConfig(ctx) 452} 453 454// uploadOnlyConfig explicitly requires no arguments. 455func uploadOnlyConfig(ctx build.Context, args ...string) build.Config { 456 if len(args) > 0 { 457 fmt.Printf("--upload-only does not require arguments.") 458 } 459 return build.UploadOnlyConfig(ctx) 460} 461 462func buildActionConfig(ctx build.Context, args ...string) build.Config { 463 flags := flag.NewFlagSet("build-mode", flag.ContinueOnError) 464 flags.SetOutput(ctx.Writer) 465 466 flags.Usage = func() { 467 fmt.Fprintf(ctx.Writer, "usage: %s --build-mode --dir=<path> <build action> [<build arg 1> <build arg 2> ...]\n\n", os.Args[0]) 468 fmt.Fprintln(ctx.Writer, "In build mode, build the set of modules based on the specified build") 469 fmt.Fprintln(ctx.Writer, "action. The --dir flag is required to determine what is needed to") 470 fmt.Fprintln(ctx.Writer, "build in the source tree based on the build action. See below for") 471 fmt.Fprintln(ctx.Writer, "the list of acceptable build action flags.") 472 fmt.Fprintln(ctx.Writer, "") 473 flags.PrintDefaults() 474 } 475 476 buildActionFlags := []struct { 477 name string 478 description string 479 action build.BuildAction 480 set bool 481 }{{ 482 name: "all-modules", 483 description: "Build action: build from the top of the source tree.", 484 action: build.BUILD_MODULES, 485 }, { 486 // This is redirecting to mma build command behaviour. Once it has soaked for a 487 // while, the build command is deleted from here once it has been removed from the 488 // envsetup.sh. 489 name: "modules-in-a-dir-no-deps", 490 description: "Build action: builds all of the modules in the current directory without their dependencies.", 491 action: build.BUILD_MODULES_IN_A_DIRECTORY, 492 }, { 493 // This is redirecting to mmma build command behaviour. Once it has soaked for a 494 // while, the build command is deleted from here once it has been removed from the 495 // envsetup.sh. 496 name: "modules-in-dirs-no-deps", 497 description: "Build action: builds all of the modules in the supplied directories without their dependencies.", 498 action: build.BUILD_MODULES_IN_DIRECTORIES, 499 }, { 500 name: "modules-in-a-dir", 501 description: "Build action: builds all of the modules in the current directory and their dependencies.", 502 action: build.BUILD_MODULES_IN_A_DIRECTORY, 503 }, { 504 name: "modules-in-dirs", 505 description: "Build action: builds all of the modules in the supplied directories and their dependencies.", 506 action: build.BUILD_MODULES_IN_DIRECTORIES, 507 }} 508 for i, flag := range buildActionFlags { 509 flags.BoolVar(&buildActionFlags[i].set, flag.name, false, flag.description) 510 } 511 dir := flags.String("dir", "", "Directory of the executed build command.") 512 513 // Only interested in the first two args which defines the build action and the directory. 514 // The remaining arguments are passed down to the config. 515 const numBuildActionFlags = 2 516 if len(args) < numBuildActionFlags { 517 flags.Usage() 518 ctx.Fatalln("Improper build action arguments: too few arguments") 519 } 520 parseError := flags.Parse(args[0:numBuildActionFlags]) 521 522 // The next block of code is to validate that exactly one build action is set and the dir flag 523 // is specified. 524 buildActionFound := false 525 var buildAction build.BuildAction 526 for _, f := range buildActionFlags { 527 if f.set { 528 if buildActionFound { 529 if parseError == nil { 530 //otherwise Parse() already called Usage() 531 flags.Usage() 532 } 533 ctx.Fatalf("Build action already specified, omit: --%s\n", f.name) 534 } 535 buildActionFound = true 536 buildAction = f.action 537 } 538 } 539 if !buildActionFound { 540 if parseError == nil { 541 //otherwise Parse() already called Usage() 542 flags.Usage() 543 } 544 ctx.Fatalln("Build action not defined.") 545 } 546 if *dir == "" { 547 ctx.Fatalln("-dir not specified.") 548 } 549 550 // Remove the build action flags from the args as they are not recognized by the config. 551 args = args[numBuildActionFlags:] 552 return build.NewBuildActionConfig(buildAction, *dir, ctx, args...) 553} 554 555func runMake(ctx build.Context, config build.Config, _ []string) { 556 logAndSymlinkSetup(ctx, config) 557 logsDir := config.LogsDir() 558 if config.IsVerbose() { 559 writer := ctx.Writer 560 fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.") 561 fmt.Fprintln(writer, "! Instead, the verbose log is always written to a compressed file in the output dir:") 562 fmt.Fprintln(writer, "!") 563 fmt.Fprintf(writer, "! gzip -cd %s/verbose.log.gz | less -R\n", logsDir) 564 fmt.Fprintln(writer, "!") 565 fmt.Fprintln(writer, "! Older versions are saved in verbose.log.#.gz files") 566 fmt.Fprintln(writer, "") 567 ctx.Fatal("Invalid argument") 568 } 569 570 if _, ok := config.Environment().Get("ONE_SHOT_MAKEFILE"); ok { 571 writer := ctx.Writer 572 fmt.Fprintln(writer, "! The variable `ONE_SHOT_MAKEFILE` is obsolete.") 573 fmt.Fprintln(writer, "!") 574 fmt.Fprintln(writer, "! If you're using `mm`, you'll need to run `source build/envsetup.sh` to update.") 575 fmt.Fprintln(writer, "!") 576 fmt.Fprintln(writer, "! Otherwise, either specify a module name with m, or use mma / MODULES-IN-...") 577 fmt.Fprintln(writer, "") 578 ctx.Fatal("Invalid environment") 579 } 580 581 build.Build(ctx, config) 582} 583 584// getCommand finds the appropriate command based on args[1] flag. args[0] 585// is the soong_ui filename. 586func getCommand(args []string) (*command, []string, error) { 587 listFlags := func() []string { 588 flags := make([]string, len(commands)) 589 for i, c := range commands { 590 flags[i] = c.flag 591 } 592 return flags 593 } 594 595 if len(args) < 2 { 596 return nil, nil, fmt.Errorf("Too few arguments: %q\nUse one of these: %q", args, listFlags()) 597 } 598 599 for _, c := range commands { 600 if c.flag == args[1] { 601 return &c, args[2:], nil 602 } 603 } 604 return nil, nil, fmt.Errorf("Command not found: %q\nDid you mean one of these: %q", args[1], listFlags()) 605} 606 607// For Bazel support, this moves files and directories from e.g. out/dist/$f to DIST_DIR/$f if necessary. 608func populateExternalDistDir(ctx build.Context, config build.Config) { 609 // Make sure that internalDistDirPath and externalDistDirPath are both absolute paths, so we can compare them 610 var err error 611 var internalDistDirPath string 612 var externalDistDirPath string 613 if internalDistDirPath, err = filepath.Abs(config.DistDir()); err != nil { 614 ctx.Fatalf("Unable to find absolute path of %s: %s", internalDistDirPath, err) 615 } 616 if externalDistDirPath, err = filepath.Abs(config.RealDistDir()); err != nil { 617 ctx.Fatalf("Unable to find absolute path of %s: %s", externalDistDirPath, err) 618 } 619 if externalDistDirPath == internalDistDirPath { 620 return 621 } 622 623 // Make sure the internal DIST_DIR actually exists before trying to read from it 624 if _, err = os.Stat(internalDistDirPath); os.IsNotExist(err) { 625 ctx.Println("Skipping Bazel dist dir migration - nothing to do!") 626 return 627 } 628 629 // Make sure the external DIST_DIR actually exists before trying to write to it 630 if err = os.MkdirAll(externalDistDirPath, 0755); err != nil { 631 ctx.Fatalf("Unable to make directory %s: %s", externalDistDirPath, err) 632 } 633 634 ctx.Println("Populating external DIST_DIR...") 635 636 populateExternalDistDirHelper(ctx, config, internalDistDirPath, externalDistDirPath) 637} 638 639func populateExternalDistDirHelper(ctx build.Context, config build.Config, internalDistDirPath string, externalDistDirPath string) { 640 files, err := ioutil.ReadDir(internalDistDirPath) 641 if err != nil { 642 ctx.Fatalf("Can't read internal distdir %s: %s", internalDistDirPath, err) 643 } 644 for _, f := range files { 645 internalFilePath := filepath.Join(internalDistDirPath, f.Name()) 646 externalFilePath := filepath.Join(externalDistDirPath, f.Name()) 647 648 if f.IsDir() { 649 // Moving a directory - check if there is an existing directory to merge with 650 externalLstat, err := os.Lstat(externalFilePath) 651 if err != nil { 652 if !os.IsNotExist(err) { 653 ctx.Fatalf("Can't lstat external %s: %s", externalDistDirPath, err) 654 } 655 // Otherwise, if the error was os.IsNotExist, that's fine and we fall through to the rename at the bottom 656 } else { 657 if externalLstat.IsDir() { 658 // Existing dir - try to merge the directories? 659 populateExternalDistDirHelper(ctx, config, internalFilePath, externalFilePath) 660 continue 661 } else { 662 // Existing file being replaced with a directory. Delete the existing file... 663 if err := os.RemoveAll(externalFilePath); err != nil { 664 ctx.Fatalf("Unable to remove existing %s: %s", externalFilePath, err) 665 } 666 } 667 } 668 } else { 669 // Moving a file (not a dir) - delete any existing file or directory 670 if err := os.RemoveAll(externalFilePath); err != nil { 671 ctx.Fatalf("Unable to remove existing %s: %s", externalFilePath, err) 672 } 673 } 674 675 // The actual move - do a rename instead of a copy in order to save disk space. 676 if err := os.Rename(internalFilePath, externalFilePath); err != nil { 677 ctx.Fatalf("Unable to rename %s -> %s due to error %s", internalFilePath, externalFilePath, err) 678 } 679 } 680} 681 682func setMaxFiles(ctx build.Context) { 683 var limits syscall.Rlimit 684 685 err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits) 686 if err != nil { 687 ctx.Println("Failed to get file limit:", err) 688 return 689 } 690 691 ctx.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max) 692 if limits.Cur == limits.Max { 693 return 694 } 695 696 limits.Cur = limits.Max 697 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits) 698 if err != nil { 699 ctx.Println("Failed to increase file limit:", err) 700 } 701} 702 703func updateTotalRealTime(ctx build.Context, config build.Config, args []string) { 704 soongMetricsFile := filepath.Join(config.LogsDir(), "soong_metrics") 705 706 //read file into proto 707 data, err := os.ReadFile(soongMetricsFile) 708 if err != nil { 709 ctx.Fatal(err) 710 } 711 met := ctx.ContextImpl.Metrics 712 713 err = met.UpdateTotalRealTime(data) 714 if err != nil { 715 ctx.Fatal(err) 716 } 717} 718