blob: 39303bf2ef8664af2b5f189fc442b4781885c844 [file] [log] [blame]
Dan Willemsen1e704462016-08-21 15:17:17 -07001// 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"
Dan Willemsen051133b2017-07-14 11:29:29 -070019 "flag"
20 "fmt"
Dan Willemsen1e704462016-08-21 15:17:17 -070021 "os"
22 "path/filepath"
23 "strconv"
24 "strings"
25 "time"
26
27 "android/soong/ui/build"
28 "android/soong/ui/logger"
Nan Zhang17f27672018-12-12 16:01:49 -080029 "android/soong/ui/metrics"
Dan Willemsenb82471a2018-05-17 16:37:09 -070030 "android/soong/ui/status"
31 "android/soong/ui/terminal"
Dan Willemsend9f6fa22016-08-21 15:17:17 -070032 "android/soong/ui/tracer"
Dan Willemsen1e704462016-08-21 15:17:17 -070033)
34
Patrice Arrudaa5c25422019-04-09 18:49:49 -070035// A command represents an operation to be executed in the soong build
36// system.
37type command struct {
38 // the flag name (must have double dashes)
39 flag string
40
41 // description for the flag (to display when running help)
42 description string
43
44 // Creates the build configuration based on the args and build context.
45 config func(ctx build.Context, args ...string) build.Config
46
47 // Returns what type of IO redirection this Command requires.
48 stdio func() terminal.StdioInterface
49
50 // run the command
51 run func(ctx build.Context, config build.Config, args []string, logsDir string)
52}
53
54const makeModeFlagName = "--make-mode"
55
56// list of supported commands (flags) supported by soong ui
57var commands []command = []command{
58 {
59 flag: makeModeFlagName,
60 description: "build the modules by the target name (i.e. soong_docs)",
61 config: func(ctx build.Context, args ...string) build.Config {
62 return build.NewConfig(ctx, args...)
63 },
Patrice Arrudab7b22822019-05-21 17:46:23 -070064 stdio: stdio,
65 run: make,
Patrice Arrudaa5c25422019-04-09 18:49:49 -070066 }, {
67 flag: "--dumpvar-mode",
68 description: "print the value of the legacy make variable VAR to stdout",
69 config: dumpVarConfig,
70 stdio: customStdio,
71 run: dumpVar,
72 }, {
73 flag: "--dumpvars-mode",
74 description: "dump the values of one or more legacy make variables, in shell syntax",
75 config: dumpVarConfig,
76 stdio: customStdio,
77 run: dumpVars,
Patrice Arrudab7b22822019-05-21 17:46:23 -070078 }, {
79 flag: "--build-mode",
80 description: "build modules based on the specified build action",
81 config: buildActionConfig,
82 stdio: stdio,
83 run: make,
Patrice Arrudaa5c25422019-04-09 18:49:49 -070084 },
85}
86
87// indexList returns the index of first found s. -1 is return if s is not
88// found.
Dan Willemsen1e704462016-08-21 15:17:17 -070089func indexList(s string, list []string) int {
90 for i, l := range list {
91 if l == s {
92 return i
93 }
94 }
Dan Willemsen1e704462016-08-21 15:17:17 -070095 return -1
96}
97
Patrice Arrudaa5c25422019-04-09 18:49:49 -070098// inList returns true if one or more of s is in the list.
Dan Willemsen1e704462016-08-21 15:17:17 -070099func inList(s string, list []string) bool {
100 return indexList(s, list) != -1
101}
102
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700103// Main execution of soong_ui. The command format is as follows:
104//
105// soong_ui <command> [<arg 1> <arg 2> ... <arg n>]
106//
107// Command is the type of soong_ui execution. Only one type of
108// execution is specified. The args are specific to the command.
Dan Willemsen1e704462016-08-21 15:17:17 -0700109func main() {
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700110 c, args := getCommand(os.Args)
111 if c == nil {
112 fmt.Fprintf(os.Stderr, "The `soong` native UI is not yet available.\n")
113 os.Exit(1)
Dan Willemsenc35b3812018-07-16 19:59:10 -0700114 }
115
Colin Crosse0df1a32019-06-09 19:40:08 -0700116 output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"),
117 build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"))
118
119 log := logger.New(output)
Dan Willemsen1e704462016-08-21 15:17:17 -0700120 defer log.Cleanup()
121
Dan Willemsen1e704462016-08-21 15:17:17 -0700122 ctx, cancel := context.WithCancel(context.Background())
123 defer cancel()
124
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700125 trace := tracer.New(log)
126 defer trace.Close()
Dan Willemsen1e704462016-08-21 15:17:17 -0700127
Nan Zhang17f27672018-12-12 16:01:49 -0800128 met := metrics.New()
129
Dan Willemsenb82471a2018-05-17 16:37:09 -0700130 stat := &status.Status{}
131 defer stat.Finish()
Colin Crosse0df1a32019-06-09 19:40:08 -0700132 stat.AddOutput(output)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700133 stat.AddOutput(trace.StatusTracer())
134
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700135 build.SetupSignals(log, cancel, func() {
136 trace.Close()
137 log.Cleanup()
Dan Willemsenb82471a2018-05-17 16:37:09 -0700138 stat.Finish()
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700139 })
140
Dan Willemsen59339a22018-07-22 21:18:45 -0700141 buildCtx := build.Context{ContextImpl: &build.ContextImpl{
Dan Willemsenb82471a2018-05-17 16:37:09 -0700142 Context: ctx,
143 Logger: log,
Nan Zhang17f27672018-12-12 16:01:49 -0800144 Metrics: met,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700145 Tracer: trace,
Colin Crosse0df1a32019-06-09 19:40:08 -0700146 Writer: output,
Dan Willemsenb82471a2018-05-17 16:37:09 -0700147 Status: stat,
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700148 }}
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700149
150 config := c.config(buildCtx, args...)
Dan Willemsen1e704462016-08-21 15:17:17 -0700151
Dan Willemsend9f6fa22016-08-21 15:17:17 -0700152 build.SetupOutDir(buildCtx, config)
Dan Willemsen8a073a82017-02-04 17:30:44 -0800153
Dan Willemsenb82471a2018-05-17 16:37:09 -0700154 logsDir := config.OutDir()
Dan Willemsen8a073a82017-02-04 17:30:44 -0800155 if config.Dist() {
Dan Willemsenb82471a2018-05-17 16:37:09 -0700156 logsDir = filepath.Join(config.DistDir(), "logs")
Dan Willemsen8a073a82017-02-04 17:30:44 -0800157 }
Dan Willemsen1e704462016-08-21 15:17:17 -0700158
Dan Willemsenb82471a2018-05-17 16:37:09 -0700159 os.MkdirAll(logsDir, 0777)
160 log.SetOutput(filepath.Join(logsDir, "soong.log"))
161 trace.SetOutput(filepath.Join(logsDir, "build.trace"))
162 stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
163 stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
Patrice Arruda297ceba2019-06-06 16:44:37 -0700164 stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, "build_error")))
Colin Cross7b624532019-06-21 15:08:30 -0700165 stat.AddOutput(status.NewCriticalPath(log))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700166
Patrice Arruda0cc5b212019-06-14 15:27:46 -0700167 defer met.Dump(filepath.Join(logsDir, "soong_metrics"))
Nan Zhangd50f53b2019-01-07 20:26:51 -0800168
Dan Willemsen1e704462016-08-21 15:17:17 -0700169 if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
170 if !strings.HasSuffix(start, "N") {
171 if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
172 log.Verbosef("Took %dms to start up.",
173 time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
Nan Zhang17f27672018-12-12 16:01:49 -0800174 buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
Dan Willemsen1e704462016-08-21 15:17:17 -0700175 }
176 }
Dan Willemsencae59bc2017-07-13 14:27:31 -0700177
178 if executable, err := os.Executable(); err == nil {
179 trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
180 }
Dan Willemsen1e704462016-08-21 15:17:17 -0700181 }
182
Jeff Gastonb64fc1c2017-08-04 12:30:12 -0700183 f := build.NewSourceFinder(buildCtx, config)
184 defer f.Shutdown()
185 build.FindSources(buildCtx, config, f)
186
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700187 c.run(buildCtx, config, args, logsDir)
Dan Willemsen051133b2017-07-14 11:29:29 -0700188}
189
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700190func dumpVar(ctx build.Context, config build.Config, args []string, _ string) {
Dan Willemsen051133b2017-07-14 11:29:29 -0700191 flags := flag.NewFlagSet("dumpvar", flag.ExitOnError)
192 flags.Usage = func() {
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700193 fmt.Fprintf(ctx.Writer, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0])
194 fmt.Fprintln(ctx.Writer, "In dumpvar mode, print the value of the legacy make variable VAR to stdout")
195 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700196
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700197 fmt.Fprintln(ctx.Writer, "'report_config' is a special case that prints the human-readable config banner")
198 fmt.Fprintln(ctx.Writer, "from the beginning of the build.")
199 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700200 flags.PrintDefaults()
201 }
202 abs := flags.Bool("abs", false, "Print the absolute path of the value")
203 flags.Parse(args)
204
205 if flags.NArg() != 1 {
206 flags.Usage()
207 os.Exit(1)
208 }
209
210 varName := flags.Arg(0)
211 if varName == "report_config" {
212 varData, err := build.DumpMakeVars(ctx, config, nil, build.BannerVars)
213 if err != nil {
214 ctx.Fatal(err)
215 }
216
217 fmt.Println(build.Banner(varData))
218 } else {
219 varData, err := build.DumpMakeVars(ctx, config, nil, []string{varName})
220 if err != nil {
221 ctx.Fatal(err)
222 }
223
224 if *abs {
225 var res []string
226 for _, path := range strings.Fields(varData[varName]) {
227 if abs, err := filepath.Abs(path); err == nil {
228 res = append(res, abs)
229 } else {
230 ctx.Fatalln("Failed to get absolute path of", path, err)
231 }
232 }
233 fmt.Println(strings.Join(res, " "))
234 } else {
235 fmt.Println(varData[varName])
236 }
237 }
238}
239
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700240func dumpVars(ctx build.Context, config build.Config, args []string, _ string) {
Dan Willemsen051133b2017-07-14 11:29:29 -0700241 flags := flag.NewFlagSet("dumpvars", flag.ExitOnError)
242 flags.Usage = func() {
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700243 fmt.Fprintf(ctx.Writer, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0])
244 fmt.Fprintln(ctx.Writer, "In dumpvars mode, dump the values of one or more legacy make variables, in")
245 fmt.Fprintln(ctx.Writer, "shell syntax. The resulting output may be sourced directly into a shell to")
246 fmt.Fprintln(ctx.Writer, "set corresponding shell variables.")
247 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700248
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700249 fmt.Fprintln(ctx.Writer, "'report_config' is a special case that dumps a variable containing the")
250 fmt.Fprintln(ctx.Writer, "human-readable config banner from the beginning of the build.")
251 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700252 flags.PrintDefaults()
253 }
254
255 varsStr := flags.String("vars", "", "Space-separated list of variables to dump")
256 absVarsStr := flags.String("abs-vars", "", "Space-separated list of variables to dump (using absolute paths)")
257
258 varPrefix := flags.String("var-prefix", "", "String to prepend to all variable names when dumping")
259 absVarPrefix := flags.String("abs-var-prefix", "", "String to prepent to all absolute path variable names when dumping")
260
261 flags.Parse(args)
262
263 if flags.NArg() != 0 {
264 flags.Usage()
265 os.Exit(1)
266 }
267
268 vars := strings.Fields(*varsStr)
269 absVars := strings.Fields(*absVarsStr)
270
271 allVars := append([]string{}, vars...)
272 allVars = append(allVars, absVars...)
273
274 if i := indexList("report_config", allVars); i != -1 {
275 allVars = append(allVars[:i], allVars[i+1:]...)
276 allVars = append(allVars, build.BannerVars...)
277 }
278
279 if len(allVars) == 0 {
280 return
281 }
282
283 varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
284 if err != nil {
285 ctx.Fatal(err)
286 }
287
288 for _, name := range vars {
289 if name == "report_config" {
290 fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(varData))
291 } else {
292 fmt.Printf("%s%s='%s'\n", *varPrefix, name, varData[name])
293 }
294 }
295 for _, name := range absVars {
296 var res []string
297 for _, path := range strings.Fields(varData[name]) {
298 abs, err := filepath.Abs(path)
299 if err != nil {
300 ctx.Fatalln("Failed to get absolute path of", path, err)
301 }
302 res = append(res, abs)
303 }
304 fmt.Printf("%s%s='%s'\n", *absVarPrefix, name, strings.Join(res, " "))
305 }
Dan Willemsen1e704462016-08-21 15:17:17 -0700306}
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700307
Patrice Arrudab7b22822019-05-21 17:46:23 -0700308func stdio() terminal.StdioInterface {
309 return terminal.StdioImpl{}
310}
311
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700312func customStdio() terminal.StdioInterface {
313 return terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
314}
315
316// dumpVarConfig does not require any arguments to be parsed by the NewConfig.
317func dumpVarConfig(ctx build.Context, args ...string) build.Config {
318 return build.NewConfig(ctx)
319}
320
Patrice Arrudab7b22822019-05-21 17:46:23 -0700321func buildActionConfig(ctx build.Context, args ...string) build.Config {
322 flags := flag.NewFlagSet("build-mode", flag.ContinueOnError)
323 flags.Usage = func() {
324 fmt.Fprintf(ctx.Writer, "usage: %s --build-mode --dir=<path> <build action> [<build arg 1> <build arg 2> ...]\n\n", os.Args[0])
325 fmt.Fprintln(ctx.Writer, "In build mode, build the set of modules based on the specified build")
326 fmt.Fprintln(ctx.Writer, "action. The --dir flag is required to determine what is needed to")
327 fmt.Fprintln(ctx.Writer, "build in the source tree based on the build action. See below for")
328 fmt.Fprintln(ctx.Writer, "the list of acceptable build action flags.")
329 fmt.Fprintln(ctx.Writer, "")
330 flags.PrintDefaults()
331 }
332
333 buildActionFlags := []struct {
Dan Willemsence41e942019-07-29 23:39:30 -0700334 name string
335 description string
336 action build.BuildAction
337 set bool
Patrice Arrudab7b22822019-05-21 17:46:23 -0700338 }{{
Dan Willemsence41e942019-07-29 23:39:30 -0700339 name: "all-modules",
340 description: "Build action: build from the top of the source tree.",
341 action: build.BUILD_MODULES,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700342 }, {
Dan Willemsence41e942019-07-29 23:39:30 -0700343 // This is redirecting to mma build command behaviour. Once it has soaked for a
344 // while, the build command is deleted from here once it has been removed from the
345 // envsetup.sh.
346 name: "modules-in-a-dir-no-deps",
347 description: "Build action: builds all of the modules in the current directory without their dependencies.",
348 action: build.BUILD_MODULES_IN_A_DIRECTORY,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700349 }, {
Dan Willemsence41e942019-07-29 23:39:30 -0700350 // This is redirecting to mmma build command behaviour. Once it has soaked for a
351 // while, the build command is deleted from here once it has been removed from the
352 // envsetup.sh.
353 name: "modules-in-dirs-no-deps",
354 description: "Build action: builds all of the modules in the supplied directories without their dependencies.",
355 action: build.BUILD_MODULES_IN_DIRECTORIES,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700356 }, {
Dan Willemsence41e942019-07-29 23:39:30 -0700357 name: "modules-in-a-dir",
358 description: "Build action: builds all of the modules in the current directory and their dependencies.",
359 action: build.BUILD_MODULES_IN_A_DIRECTORY,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700360 }, {
Dan Willemsence41e942019-07-29 23:39:30 -0700361 name: "modules-in-dirs",
362 description: "Build action: builds all of the modules in the supplied directories and their dependencies.",
363 action: build.BUILD_MODULES_IN_DIRECTORIES,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700364 }}
365 for i, flag := range buildActionFlags {
366 flags.BoolVar(&buildActionFlags[i].set, flag.name, false, flag.description)
367 }
368 dir := flags.String("dir", "", "Directory of the executed build command.")
369
370 // Only interested in the first two args which defines the build action and the directory.
371 // The remaining arguments are passed down to the config.
372 const numBuildActionFlags = 2
373 if len(args) < numBuildActionFlags {
374 flags.Usage()
375 ctx.Fatalln("Improper build action arguments.")
376 }
377 flags.Parse(args[0:numBuildActionFlags])
378
379 // The next block of code is to validate that exactly one build action is set and the dir flag
380 // is specified.
381 buildActionCount := 0
382 var buildAction build.BuildAction
Patrice Arrudab7b22822019-05-21 17:46:23 -0700383 for _, flag := range buildActionFlags {
384 if flag.set {
385 buildActionCount++
386 buildAction = flag.action
Patrice Arrudab7b22822019-05-21 17:46:23 -0700387 }
388 }
389 if buildActionCount != 1 {
390 ctx.Fatalln("Build action not defined.")
391 }
392 if *dir == "" {
393 ctx.Fatalln("-dir not specified.")
394 }
395
396 // Remove the build action flags from the args as they are not recognized by the config.
397 args = args[numBuildActionFlags:]
Dan Willemsence41e942019-07-29 23:39:30 -0700398 return build.NewBuildActionConfig(buildAction, *dir, ctx, args...)
Patrice Arrudab7b22822019-05-21 17:46:23 -0700399}
400
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700401func make(ctx build.Context, config build.Config, _ []string, logsDir string) {
402 if config.IsVerbose() {
403 writer := ctx.Writer
Colin Cross097ed2a2019-06-08 21:48:58 -0700404 fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.")
405 fmt.Fprintln(writer, "! Instead, the verbose log is always written to a compressed file in the output dir:")
406 fmt.Fprintln(writer, "!")
407 fmt.Fprintf(writer, "! gzip -cd %s/verbose.log.gz | less -R\n", logsDir)
408 fmt.Fprintln(writer, "!")
409 fmt.Fprintln(writer, "! Older versions are saved in verbose.log.#.gz files")
410 fmt.Fprintln(writer, "")
Dan Willemsenc6360832019-07-25 14:07:36 -0700411 select {
412 case <-time.After(5 * time.Second):
413 case <-ctx.Done():
414 return
415 }
416 }
417
418 if _, ok := config.Environment().Get("ONE_SHOT_MAKEFILE"); ok {
419 writer := ctx.Writer
Dan Willemsence41e942019-07-29 23:39:30 -0700420 fmt.Fprintln(writer, "! The variable `ONE_SHOT_MAKEFILE` is obsolete.")
Dan Willemsenc6360832019-07-25 14:07:36 -0700421 fmt.Fprintln(writer, "!")
422 fmt.Fprintln(writer, "! If you're using `mm`, you'll need to run `source build/envsetup.sh` to update.")
423 fmt.Fprintln(writer, "!")
424 fmt.Fprintln(writer, "! Otherwise, either specify a module name with m, or use mma / MODULES-IN-...")
425 fmt.Fprintln(writer, "")
Dan Willemsence41e942019-07-29 23:39:30 -0700426 ctx.Fatal("done")
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700427 }
428
429 toBuild := build.BuildAll
430 if config.Checkbuild() {
431 toBuild |= build.RunBuildTests
432 }
433 build.Build(ctx, config, toBuild)
434}
435
436// getCommand finds the appropriate command based on args[1] flag. args[0]
437// is the soong_ui filename.
438func getCommand(args []string) (*command, []string) {
439 if len(args) < 2 {
440 return nil, args
441 }
442
443 for _, c := range commands {
444 if c.flag == args[1] {
445 return &c, args[2:]
446 }
447
448 // special case for --make-mode: if soong_ui was called from
449 // build/make/core/main.mk, the makeparallel with --ninja
450 // option specified puts the -j<num> before --make-mode.
451 // TODO: Remove this hack once it has been fixed.
452 if c.flag == makeModeFlagName {
453 if inList(makeModeFlagName, args) {
454 return &c, args[1:]
455 }
456 }
457 }
458
459 // command not found
460 return nil, args
461}