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