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