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