blob: ec4f90e0674f740b7fb40f3e8f31ec505c61bc95 [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"))
Dan Willemsenb82471a2018-05-17 16:37:09 -0700118
Colin Crosse0df1a32019-06-09 19:40:08 -0700119 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
Dan Willemsen6b783c82019-03-08 11:42:28 -0800183 // Fix up the source tree due to a repo bug where it doesn't remove
184 // linkfiles that have been removed
185 fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.bp")
186 fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.mk")
187
Jeff Gastonb64fc1c2017-08-04 12:30:12 -0700188 f := build.NewSourceFinder(buildCtx, config)
189 defer f.Shutdown()
190 build.FindSources(buildCtx, config, f)
191
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700192 c.run(buildCtx, config, args, logsDir)
Dan Willemsen051133b2017-07-14 11:29:29 -0700193}
194
Dan Willemsen6b783c82019-03-08 11:42:28 -0800195func fixBadDanglingLink(ctx build.Context, name string) {
196 _, err := os.Lstat(name)
197 if err != nil {
198 return
199 }
200 _, err = os.Stat(name)
201 if os.IsNotExist(err) {
202 err = os.Remove(name)
203 if err != nil {
204 ctx.Fatalf("Failed to remove dangling link %q: %v", name, err)
205 }
206 }
207}
208
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700209func dumpVar(ctx build.Context, config build.Config, args []string, _ string) {
Dan Willemsen051133b2017-07-14 11:29:29 -0700210 flags := flag.NewFlagSet("dumpvar", flag.ExitOnError)
211 flags.Usage = func() {
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700212 fmt.Fprintf(ctx.Writer, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0])
213 fmt.Fprintln(ctx.Writer, "In dumpvar mode, print the value of the legacy make variable VAR to stdout")
214 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700215
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700216 fmt.Fprintln(ctx.Writer, "'report_config' is a special case that prints the human-readable config banner")
217 fmt.Fprintln(ctx.Writer, "from the beginning of the build.")
218 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700219 flags.PrintDefaults()
220 }
221 abs := flags.Bool("abs", false, "Print the absolute path of the value")
222 flags.Parse(args)
223
224 if flags.NArg() != 1 {
225 flags.Usage()
226 os.Exit(1)
227 }
228
229 varName := flags.Arg(0)
230 if varName == "report_config" {
231 varData, err := build.DumpMakeVars(ctx, config, nil, build.BannerVars)
232 if err != nil {
233 ctx.Fatal(err)
234 }
235
236 fmt.Println(build.Banner(varData))
237 } else {
238 varData, err := build.DumpMakeVars(ctx, config, nil, []string{varName})
239 if err != nil {
240 ctx.Fatal(err)
241 }
242
243 if *abs {
244 var res []string
245 for _, path := range strings.Fields(varData[varName]) {
246 if abs, err := filepath.Abs(path); err == nil {
247 res = append(res, abs)
248 } else {
249 ctx.Fatalln("Failed to get absolute path of", path, err)
250 }
251 }
252 fmt.Println(strings.Join(res, " "))
253 } else {
254 fmt.Println(varData[varName])
255 }
256 }
257}
258
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700259func dumpVars(ctx build.Context, config build.Config, args []string, _ string) {
Dan Willemsen051133b2017-07-14 11:29:29 -0700260 flags := flag.NewFlagSet("dumpvars", flag.ExitOnError)
261 flags.Usage = func() {
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700262 fmt.Fprintf(ctx.Writer, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0])
263 fmt.Fprintln(ctx.Writer, "In dumpvars mode, dump the values of one or more legacy make variables, in")
264 fmt.Fprintln(ctx.Writer, "shell syntax. The resulting output may be sourced directly into a shell to")
265 fmt.Fprintln(ctx.Writer, "set corresponding shell variables.")
266 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700267
Patrice Arrudadb4c2f12019-06-17 17:27:09 -0700268 fmt.Fprintln(ctx.Writer, "'report_config' is a special case that dumps a variable containing the")
269 fmt.Fprintln(ctx.Writer, "human-readable config banner from the beginning of the build.")
270 fmt.Fprintln(ctx.Writer, "")
Dan Willemsen051133b2017-07-14 11:29:29 -0700271 flags.PrintDefaults()
272 }
273
274 varsStr := flags.String("vars", "", "Space-separated list of variables to dump")
275 absVarsStr := flags.String("abs-vars", "", "Space-separated list of variables to dump (using absolute paths)")
276
277 varPrefix := flags.String("var-prefix", "", "String to prepend to all variable names when dumping")
278 absVarPrefix := flags.String("abs-var-prefix", "", "String to prepent to all absolute path variable names when dumping")
279
280 flags.Parse(args)
281
282 if flags.NArg() != 0 {
283 flags.Usage()
284 os.Exit(1)
285 }
286
287 vars := strings.Fields(*varsStr)
288 absVars := strings.Fields(*absVarsStr)
289
290 allVars := append([]string{}, vars...)
291 allVars = append(allVars, absVars...)
292
293 if i := indexList("report_config", allVars); i != -1 {
294 allVars = append(allVars[:i], allVars[i+1:]...)
295 allVars = append(allVars, build.BannerVars...)
296 }
297
298 if len(allVars) == 0 {
299 return
300 }
301
302 varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
303 if err != nil {
304 ctx.Fatal(err)
305 }
306
307 for _, name := range vars {
308 if name == "report_config" {
309 fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(varData))
310 } else {
311 fmt.Printf("%s%s='%s'\n", *varPrefix, name, varData[name])
312 }
313 }
314 for _, name := range absVars {
315 var res []string
316 for _, path := range strings.Fields(varData[name]) {
317 abs, err := filepath.Abs(path)
318 if err != nil {
319 ctx.Fatalln("Failed to get absolute path of", path, err)
320 }
321 res = append(res, abs)
322 }
323 fmt.Printf("%s%s='%s'\n", *absVarPrefix, name, strings.Join(res, " "))
324 }
Dan Willemsen1e704462016-08-21 15:17:17 -0700325}
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700326
Patrice Arrudab7b22822019-05-21 17:46:23 -0700327func stdio() terminal.StdioInterface {
328 return terminal.StdioImpl{}
329}
330
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700331func customStdio() terminal.StdioInterface {
332 return terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
333}
334
335// dumpVarConfig does not require any arguments to be parsed by the NewConfig.
336func dumpVarConfig(ctx build.Context, args ...string) build.Config {
337 return build.NewConfig(ctx)
338}
339
Patrice Arrudab7b22822019-05-21 17:46:23 -0700340func buildActionConfig(ctx build.Context, args ...string) build.Config {
341 flags := flag.NewFlagSet("build-mode", flag.ContinueOnError)
342 flags.Usage = func() {
343 fmt.Fprintf(ctx.Writer, "usage: %s --build-mode --dir=<path> <build action> [<build arg 1> <build arg 2> ...]\n\n", os.Args[0])
344 fmt.Fprintln(ctx.Writer, "In build mode, build the set of modules based on the specified build")
345 fmt.Fprintln(ctx.Writer, "action. The --dir flag is required to determine what is needed to")
346 fmt.Fprintln(ctx.Writer, "build in the source tree based on the build action. See below for")
347 fmt.Fprintln(ctx.Writer, "the list of acceptable build action flags.")
348 fmt.Fprintln(ctx.Writer, "")
349 flags.PrintDefaults()
350 }
351
352 buildActionFlags := []struct {
Dan Willemsence41e942019-07-29 23:39:30 -0700353 name string
354 description string
355 action build.BuildAction
356 set bool
Patrice Arrudab7b22822019-05-21 17:46:23 -0700357 }{{
Dan Willemsence41e942019-07-29 23:39:30 -0700358 name: "all-modules",
359 description: "Build action: build from the top of the source tree.",
360 action: build.BUILD_MODULES,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700361 }, {
Dan Willemsence41e942019-07-29 23:39:30 -0700362 // This is redirecting to mma build command behaviour. Once it has soaked for a
363 // while, the build command is deleted from here once it has been removed from the
364 // envsetup.sh.
365 name: "modules-in-a-dir-no-deps",
366 description: "Build action: builds all of the modules in the current directory without their dependencies.",
367 action: build.BUILD_MODULES_IN_A_DIRECTORY,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700368 }, {
Dan Willemsence41e942019-07-29 23:39:30 -0700369 // This is redirecting to mmma build command behaviour. Once it has soaked for a
370 // while, the build command is deleted from here once it has been removed from the
371 // envsetup.sh.
372 name: "modules-in-dirs-no-deps",
373 description: "Build action: builds all of the modules in the supplied directories without their dependencies.",
374 action: build.BUILD_MODULES_IN_DIRECTORIES,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700375 }, {
Dan Willemsence41e942019-07-29 23:39:30 -0700376 name: "modules-in-a-dir",
377 description: "Build action: builds all of the modules in the current directory and their dependencies.",
378 action: build.BUILD_MODULES_IN_A_DIRECTORY,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700379 }, {
Dan Willemsence41e942019-07-29 23:39:30 -0700380 name: "modules-in-dirs",
381 description: "Build action: builds all of the modules in the supplied directories and their dependencies.",
382 action: build.BUILD_MODULES_IN_DIRECTORIES,
Patrice Arrudab7b22822019-05-21 17:46:23 -0700383 }}
384 for i, flag := range buildActionFlags {
385 flags.BoolVar(&buildActionFlags[i].set, flag.name, false, flag.description)
386 }
387 dir := flags.String("dir", "", "Directory of the executed build command.")
388
389 // Only interested in the first two args which defines the build action and the directory.
390 // The remaining arguments are passed down to the config.
391 const numBuildActionFlags = 2
392 if len(args) < numBuildActionFlags {
393 flags.Usage()
394 ctx.Fatalln("Improper build action arguments.")
395 }
396 flags.Parse(args[0:numBuildActionFlags])
397
398 // The next block of code is to validate that exactly one build action is set and the dir flag
399 // is specified.
400 buildActionCount := 0
401 var buildAction build.BuildAction
Patrice Arrudab7b22822019-05-21 17:46:23 -0700402 for _, flag := range buildActionFlags {
403 if flag.set {
404 buildActionCount++
405 buildAction = flag.action
Patrice Arrudab7b22822019-05-21 17:46:23 -0700406 }
407 }
408 if buildActionCount != 1 {
409 ctx.Fatalln("Build action not defined.")
410 }
411 if *dir == "" {
412 ctx.Fatalln("-dir not specified.")
413 }
414
415 // Remove the build action flags from the args as they are not recognized by the config.
416 args = args[numBuildActionFlags:]
Dan Willemsence41e942019-07-29 23:39:30 -0700417 return build.NewBuildActionConfig(buildAction, *dir, ctx, args...)
Patrice Arrudab7b22822019-05-21 17:46:23 -0700418}
419
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700420func make(ctx build.Context, config build.Config, _ []string, logsDir string) {
421 if config.IsVerbose() {
422 writer := ctx.Writer
Colin Cross097ed2a2019-06-08 21:48:58 -0700423 fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.")
424 fmt.Fprintln(writer, "! Instead, the verbose log is always written to a compressed file in the output dir:")
425 fmt.Fprintln(writer, "!")
426 fmt.Fprintf(writer, "! gzip -cd %s/verbose.log.gz | less -R\n", logsDir)
427 fmt.Fprintln(writer, "!")
428 fmt.Fprintln(writer, "! Older versions are saved in verbose.log.#.gz files")
429 fmt.Fprintln(writer, "")
Dan Willemsenc6360832019-07-25 14:07:36 -0700430 select {
431 case <-time.After(5 * time.Second):
432 case <-ctx.Done():
433 return
434 }
435 }
436
437 if _, ok := config.Environment().Get("ONE_SHOT_MAKEFILE"); ok {
438 writer := ctx.Writer
Dan Willemsence41e942019-07-29 23:39:30 -0700439 fmt.Fprintln(writer, "! The variable `ONE_SHOT_MAKEFILE` is obsolete.")
Dan Willemsenc6360832019-07-25 14:07:36 -0700440 fmt.Fprintln(writer, "!")
441 fmt.Fprintln(writer, "! If you're using `mm`, you'll need to run `source build/envsetup.sh` to update.")
442 fmt.Fprintln(writer, "!")
443 fmt.Fprintln(writer, "! Otherwise, either specify a module name with m, or use mma / MODULES-IN-...")
444 fmt.Fprintln(writer, "")
Dan Willemsence41e942019-07-29 23:39:30 -0700445 ctx.Fatal("done")
Patrice Arrudaa5c25422019-04-09 18:49:49 -0700446 }
447
448 toBuild := build.BuildAll
449 if config.Checkbuild() {
450 toBuild |= build.RunBuildTests
451 }
452 build.Build(ctx, config, toBuild)
453}
454
455// getCommand finds the appropriate command based on args[1] flag. args[0]
456// is the soong_ui filename.
457func getCommand(args []string) (*command, []string) {
458 if len(args) < 2 {
459 return nil, args
460 }
461
462 for _, c := range commands {
463 if c.flag == args[1] {
464 return &c, args[2:]
465 }
466
467 // special case for --make-mode: if soong_ui was called from
468 // build/make/core/main.mk, the makeparallel with --ninja
469 // option specified puts the -j<num> before --make-mode.
470 // TODO: Remove this hack once it has been fixed.
471 if c.flag == makeModeFlagName {
472 if inList(makeModeFlagName, args) {
473 return &c, args[1:]
474 }
475 }
476 }
477
478 // command not found
479 return nil, args
480}