blob: b63918f551fe3179355ad6fc0b79c50d84e273cb [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")))
164
Patrice Arruda0cc5b212019-06-14 15:27:46 -0700165 defer met.Dump(filepath.Join(logsDir, "soong_metrics"))
Nan Zhangd50f53b2019-01-07 20:26:51 -0800166
Dan Willemsen1e704462016-08-21 15:17:17 -0700167 if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
168 if !strings.HasSuffix(start, "N") {
169 if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
170 log.Verbosef("Took %dms to start up.",
171 time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
Nan Zhang17f27672018-12-12 16:01:49 -0800172 buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
Dan Willemsen1e704462016-08-21 15:17:17 -0700173 }
174 }
Dan Willemsencae59bc2017-07-13 14:27:31 -0700175
176 if executable, err := os.Executable(); err == nil {
177 trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
178 }
Dan Willemsen1e704462016-08-21 15:17:17 -0700179 }
180
Jeff Gastonb64fc1c2017-08-04 12:30:12 -0700181 f := build.NewSourceFinder(buildCtx, config)
182 defer f.Shutdown()
183 build.FindSources(buildCtx, config, f)
184
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700185 c.run(buildCtx, config, args, logsDir)
Dan Willemsen051133b2017-07-14 11:29:29 -0700186}
187
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700188func dumpVar(ctx build.Context, config build.Config, args []string, _ string) {
Dan Willemsen051133b2017-07-14 11:29:29 -0700189 flags := flag.NewFlagSet("dumpvar", flag.ExitOnError)
190 flags.Usage = func() {
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700191 fmt.Fprintf(ctx.Writer, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0])
192 fmt.Fprintln(ctx.Writer, "In dumpvar mode, print the value of the legacy make variable VAR to stdout")
193 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700194
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700195 fmt.Fprintln(ctx.Writer, "'report_config' is a special case that prints the human-readable config banner")
196 fmt.Fprintln(ctx.Writer, "from the beginning of the build.")
197 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700198 flags.PrintDefaults()
199 }
200 abs := flags.Bool("abs", false, "Print the absolute path of the value")
201 flags.Parse(args)
202
203 if flags.NArg() != 1 {
204 flags.Usage()
205 os.Exit(1)
206 }
207
208 varName := flags.Arg(0)
209 if varName == "report_config" {
210 varData, err := build.DumpMakeVars(ctx, config, nil, build.BannerVars)
211 if err != nil {
212 ctx.Fatal(err)
213 }
214
215 fmt.Println(build.Banner(varData))
216 } else {
217 varData, err := build.DumpMakeVars(ctx, config, nil, []string{varName})
218 if err != nil {
219 ctx.Fatal(err)
220 }
221
222 if *abs {
223 var res []string
224 for _, path := range strings.Fields(varData[varName]) {
225 if abs, err := filepath.Abs(path); err == nil {
226 res = append(res, abs)
227 } else {
228 ctx.Fatalln("Failed to get absolute path of", path, err)
229 }
230 }
231 fmt.Println(strings.Join(res, " "))
232 } else {
233 fmt.Println(varData[varName])
234 }
235 }
236}
237
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700238func dumpVars(ctx build.Context, config build.Config, args []string, _ string) {
Dan Willemsen051133b2017-07-14 11:29:29 -0700239 flags := flag.NewFlagSet("dumpvars", flag.ExitOnError)
240 flags.Usage = func() {
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700241 fmt.Fprintf(ctx.Writer, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0])
242 fmt.Fprintln(ctx.Writer, "In dumpvars mode, dump the values of one or more legacy make variables, in")
243 fmt.Fprintln(ctx.Writer, "shell syntax. The resulting output may be sourced directly into a shell to")
244 fmt.Fprintln(ctx.Writer, "set corresponding shell variables.")
245 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700246
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700247 fmt.Fprintln(ctx.Writer, "'report_config' is a special case that dumps a variable containing the")
248 fmt.Fprintln(ctx.Writer, "human-readable config banner from the beginning of the build.")
249 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700250 flags.PrintDefaults()
251 }
252
253 varsStr := flags.String("vars", "", "Space-separated list of variables to dump")
254 absVarsStr := flags.String("abs-vars", "", "Space-separated list of variables to dump (using absolute paths)")
255
256 varPrefix := flags.String("var-prefix", "", "String to prepend to all variable names when dumping")
257 absVarPrefix := flags.String("abs-var-prefix", "", "String to prepent to all absolute path variable names when dumping")
258
259 flags.Parse(args)
260
261 if flags.NArg() != 0 {
262 flags.Usage()
263 os.Exit(1)
264 }
265
266 vars := strings.Fields(*varsStr)
267 absVars := strings.Fields(*absVarsStr)
268
269 allVars := append([]string{}, vars...)
270 allVars = append(allVars, absVars...)
271
272 if i := indexList("report_config", allVars); i != -1 {
273 allVars = append(allVars[:i], allVars[i+1:]...)
274 allVars = append(allVars, build.BannerVars...)
275 }
276
277 if len(allVars) == 0 {
278 return
279 }
280
281 varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
282 if err != nil {
283 ctx.Fatal(err)
284 }
285
286 for _, name := range vars {
287 if name == "report_config" {
288 fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(varData))
289 } else {
290 fmt.Printf("%s%s='%s'\n", *varPrefix, name, varData[name])
291 }
292 }
293 for _, name := range absVars {
294 var res []string
295 for _, path := range strings.Fields(varData[name]) {
296 abs, err := filepath.Abs(path)
297 if err != nil {
298 ctx.Fatalln("Failed to get absolute path of", path, err)
299 }
300 res = append(res, abs)
301 }
302 fmt.Printf("%s%s='%s'\n", *absVarPrefix, name, strings.Join(res, " "))
303 }
Dan Willemsen1e704462016-08-21 15:17:17 -0700304}
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700305
Patrice Arrudab7b22822019-05-21 17:46:23 -0700306func stdio() terminal.StdioInterface {
307 return terminal.StdioImpl{}
308}
309
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700310func customStdio() terminal.StdioInterface {
311 return terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
312}
313
314// dumpVarConfig does not require any arguments to be parsed by the NewConfig.
315func dumpVarConfig(ctx build.Context, args ...string) build.Config {
316 return build.NewConfig(ctx)
317}
318
Patrice Arrudab7b22822019-05-21 17:46:23 -0700319func buildActionConfig(ctx build.Context, args ...string) build.Config {
320 flags := flag.NewFlagSet("build-mode", flag.ContinueOnError)
321 flags.Usage = func() {
322 fmt.Fprintf(ctx.Writer, "usage: %s --build-mode --dir=<path> <build action> [<build arg 1> <build arg 2> ...]\n\n", os.Args[0])
323 fmt.Fprintln(ctx.Writer, "In build mode, build the set of modules based on the specified build")
324 fmt.Fprintln(ctx.Writer, "action. The --dir flag is required to determine what is needed to")
325 fmt.Fprintln(ctx.Writer, "build in the source tree based on the build action. See below for")
326 fmt.Fprintln(ctx.Writer, "the list of acceptable build action flags.")
327 fmt.Fprintln(ctx.Writer, "")
328 flags.PrintDefaults()
329 }
330
331 buildActionFlags := []struct {
332 name string
333 description string
334 action build.BuildAction
335 buildDependencies bool
336 set bool
337 }{{
338 name: "all-modules",
339 description: "Build action: build from the top of the source tree.",
Patrice Arruda39282062019-06-20 16:35:12 -0700340 action: build.BUILD_MODULES,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700341 buildDependencies: true,
342 }, {
343 name: "modules-in-a-dir-no-deps",
344 description: "Build action: builds all of the modules in the current directory without their dependencies.",
345 action: build.BUILD_MODULES_IN_A_DIRECTORY,
346 buildDependencies: false,
347 }, {
348 name: "modules-in-dirs-no-deps",
349 description: "Build action: builds all of the modules in the supplied directories without their dependencies.",
350 action: build.BUILD_MODULES_IN_DIRECTORIES,
351 buildDependencies: false,
352 }, {
353 name: "modules-in-a-dir",
354 description: "Build action: builds all of the modules in the current directory and their dependencies.",
355 action: build.BUILD_MODULES_IN_A_DIRECTORY,
356 buildDependencies: true,
357 }, {
358 name: "modules-in-dirs",
359 description: "Build action: builds all of the modules in the supplied directories and their dependencies.",
360 action: build.BUILD_MODULES_IN_DIRECTORIES,
361 buildDependencies: true,
362 }}
363 for i, flag := range buildActionFlags {
364 flags.BoolVar(&buildActionFlags[i].set, flag.name, false, flag.description)
365 }
366 dir := flags.String("dir", "", "Directory of the executed build command.")
367
368 // Only interested in the first two args which defines the build action and the directory.
369 // The remaining arguments are passed down to the config.
370 const numBuildActionFlags = 2
371 if len(args) < numBuildActionFlags {
372 flags.Usage()
373 ctx.Fatalln("Improper build action arguments.")
374 }
375 flags.Parse(args[0:numBuildActionFlags])
376
377 // The next block of code is to validate that exactly one build action is set and the dir flag
378 // is specified.
379 buildActionCount := 0
380 var buildAction build.BuildAction
381 buildDependency := false
382 for _, flag := range buildActionFlags {
383 if flag.set {
384 buildActionCount++
385 buildAction = flag.action
386 buildDependency = flag.buildDependencies
387 }
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:]
398 return build.NewBuildActionConfig(buildAction, *dir, buildDependency, ctx, args...)
399}
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, "")
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700411 time.Sleep(5 * time.Second)
412 }
413
414 toBuild := build.BuildAll
415 if config.Checkbuild() {
416 toBuild |= build.RunBuildTests
417 }
418 build.Build(ctx, config, toBuild)
419}
420
421// getCommand finds the appropriate command based on args[1] flag. args[0]
422// is the soong_ui filename.
423func getCommand(args []string) (*command, []string) {
424 if len(args) < 2 {
425 return nil, args
426 }
427
428 for _, c := range commands {
429 if c.flag == args[1] {
430 return &c, args[2:]
431 }
432
433 // special case for --make-mode: if soong_ui was called from
434 // build/make/core/main.mk, the makeparallel with --ninja
435 // option specified puts the -j<num> before --make-mode.
436 // TODO: Remove this hack once it has been fixed.
437 if c.flag == makeModeFlagName {
438 if inList(makeModeFlagName, args) {
439 return &c, args[1:]
440 }
441 }
442 }
443
444 // command not found
445 return nil, args
446}