blob: 0bc36066ae2c8001ce1864f471ecdca5275b8d5c [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 {
334 name string
335 description string
336 action build.BuildAction
337 buildDependencies bool
338 set bool
339 }{{
340 name: "all-modules",
341 description: "Build action: build from the top of the source tree.",
Patrice Arruda39282062019-06-20 16:35:12 -0700342 action: build.BUILD_MODULES,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700343 buildDependencies: true,
344 }, {
Patrice Arruda22741022019-07-18 09:30:31 -0700345 // buildDependencies is set to true as mm is being deprecated. This is redirecting to mma build
346 // command behaviour. Once it has soaked for a while, the build command is deleted from here once
347 // it has been removed from the envsetup.sh.
Patrice Arrudab7b22822019-05-21 17:46:23 -0700348 name: "modules-in-a-dir-no-deps",
349 description: "Build action: builds all of the modules in the current directory without their dependencies.",
350 action: build.BUILD_MODULES_IN_A_DIRECTORY,
Patrice Arruda22741022019-07-18 09:30:31 -0700351 buildDependencies: true,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700352 }, {
Patrice Arrudaa9e12152019-07-18 09:37:38 -0700353 // buildDependencies is set to true as mmm is being deprecated. This is redirecting to mmma build
354 // command behaviour. Once it has soaked for a while, the build command is deleted from here once
355 // it has been removed from the envsetup.sh.
Patrice Arrudab7b22822019-05-21 17:46:23 -0700356 name: "modules-in-dirs-no-deps",
357 description: "Build action: builds all of the modules in the supplied directories without their dependencies.",
358 action: build.BUILD_MODULES_IN_DIRECTORIES,
Patrice Arrudaa9e12152019-07-18 09:37:38 -0700359 buildDependencies: true,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700360 }, {
361 name: "modules-in-a-dir",
362 description: "Build action: builds all of the modules in the current directory and their dependencies.",
363 action: build.BUILD_MODULES_IN_A_DIRECTORY,
364 buildDependencies: true,
365 }, {
366 name: "modules-in-dirs",
367 description: "Build action: builds all of the modules in the supplied directories and their dependencies.",
368 action: build.BUILD_MODULES_IN_DIRECTORIES,
369 buildDependencies: true,
370 }}
371 for i, flag := range buildActionFlags {
372 flags.BoolVar(&buildActionFlags[i].set, flag.name, false, flag.description)
373 }
374 dir := flags.String("dir", "", "Directory of the executed build command.")
375
376 // Only interested in the first two args which defines the build action and the directory.
377 // The remaining arguments are passed down to the config.
378 const numBuildActionFlags = 2
379 if len(args) < numBuildActionFlags {
380 flags.Usage()
381 ctx.Fatalln("Improper build action arguments.")
382 }
383 flags.Parse(args[0:numBuildActionFlags])
384
385 // The next block of code is to validate that exactly one build action is set and the dir flag
386 // is specified.
387 buildActionCount := 0
388 var buildAction build.BuildAction
389 buildDependency := false
390 for _, flag := range buildActionFlags {
391 if flag.set {
392 buildActionCount++
393 buildAction = flag.action
394 buildDependency = flag.buildDependencies
395 }
396 }
397 if buildActionCount != 1 {
398 ctx.Fatalln("Build action not defined.")
399 }
400 if *dir == "" {
401 ctx.Fatalln("-dir not specified.")
402 }
403
404 // Remove the build action flags from the args as they are not recognized by the config.
405 args = args[numBuildActionFlags:]
406 return build.NewBuildActionConfig(buildAction, *dir, buildDependency, ctx, args...)
407}
408
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700409func make(ctx build.Context, config build.Config, _ []string, logsDir string) {
410 if config.IsVerbose() {
411 writer := ctx.Writer
Colin Cross097ed2a2019-06-08 21:48:58 -0700412 fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.")
413 fmt.Fprintln(writer, "! Instead, the verbose log is always written to a compressed file in the output dir:")
414 fmt.Fprintln(writer, "!")
415 fmt.Fprintf(writer, "! gzip -cd %s/verbose.log.gz | less -R\n", logsDir)
416 fmt.Fprintln(writer, "!")
417 fmt.Fprintln(writer, "! Older versions are saved in verbose.log.#.gz files")
418 fmt.Fprintln(writer, "")
Dan Willemsenc6360832019-07-25 14:07:36 -0700419 select {
420 case <-time.After(5 * time.Second):
421 case <-ctx.Done():
422 return
423 }
424 }
425
426 if _, ok := config.Environment().Get("ONE_SHOT_MAKEFILE"); ok {
427 writer := ctx.Writer
428 fmt.Fprintln(writer, "! The variable `ONE_SHOT_MAKEFILE` is deprecated, and will be removed shortly.")
429 fmt.Fprintln(writer, "!")
430 fmt.Fprintln(writer, "! If you're using `mm`, you'll need to run `source build/envsetup.sh` to update.")
431 fmt.Fprintln(writer, "!")
432 fmt.Fprintln(writer, "! Otherwise, either specify a module name with m, or use mma / MODULES-IN-...")
433 fmt.Fprintln(writer, "")
434 select {
435 case <-time.After(30 * time.Second):
436 case <-ctx.Done():
437 return
438 }
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700439 }
440
441 toBuild := build.BuildAll
442 if config.Checkbuild() {
443 toBuild |= build.RunBuildTests
444 }
445 build.Build(ctx, config, toBuild)
446}
447
448// getCommand finds the appropriate command based on args[1] flag. args[0]
449// is the soong_ui filename.
450func getCommand(args []string) (*command, []string) {
451 if len(args) < 2 {
452 return nil, args
453 }
454
455 for _, c := range commands {
456 if c.flag == args[1] {
457 return &c, args[2:]
458 }
459
460 // special case for --make-mode: if soong_ui was called from
461 // build/make/core/main.mk, the makeparallel with --ninja
462 // option specified puts the -j<num> before --make-mode.
463 // TODO: Remove this hack once it has been fixed.
464 if c.flag == makeModeFlagName {
465 if inList(makeModeFlagName, args) {
466 return &c, args[1:]
467 }
468 }
469 }
470
471 // command not found
472 return nil, args
473}