• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// 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"
19	"flag"
20	"fmt"
21	"io/ioutil"
22	"os"
23	"path/filepath"
24	"strconv"
25	"strings"
26	"syscall"
27	"time"
28
29	"android/soong/shared"
30	"android/soong/ui/build"
31	"android/soong/ui/logger"
32	"android/soong/ui/metrics"
33	"android/soong/ui/signal"
34	"android/soong/ui/status"
35	"android/soong/ui/terminal"
36	"android/soong/ui/tracer"
37)
38
39// A command represents an operation to be executed in the soong build
40// system.
41type command struct {
42	// The flag name (must have double dashes).
43	flag string
44
45	// Description for the flag (to display when running help).
46	description string
47
48	// Stream the build status output into the simple terminal mode.
49	simpleOutput bool
50
51	// Sets a prefix string to use for filenames of log files.
52	logsPrefix string
53
54	// Creates the build configuration based on the args and build context.
55	config func(ctx build.Context, args ...string) build.Config
56
57	// Returns what type of IO redirection this Command requires.
58	stdio func() terminal.StdioInterface
59
60	// run the command
61	run func(ctx build.Context, config build.Config, args []string, logsDir string)
62}
63
64// list of supported commands (flags) supported by soong ui
65var commands = []command{
66	{
67		flag:        "--make-mode",
68		description: "build the modules by the target name (i.e. soong_docs)",
69		config: func(ctx build.Context, args ...string) build.Config {
70			return build.NewConfig(ctx, args...)
71		},
72		stdio: stdio,
73		run:   runMake,
74	}, {
75		flag:         "--dumpvar-mode",
76		description:  "print the value of the legacy make variable VAR to stdout",
77		simpleOutput: true,
78		logsPrefix:   "dumpvars-",
79		config:       dumpVarConfig,
80		stdio:        customStdio,
81		run:          dumpVar,
82	}, {
83		flag:         "--dumpvars-mode",
84		description:  "dump the values of one or more legacy make variables, in shell syntax",
85		simpleOutput: true,
86		logsPrefix:   "dumpvars-",
87		config:       dumpVarConfig,
88		stdio:        customStdio,
89		run:          dumpVars,
90	}, {
91		flag:        "--build-mode",
92		description: "build modules based on the specified build action",
93		config:      buildActionConfig,
94		stdio:       stdio,
95		run:         runMake,
96	},
97}
98
99// indexList returns the index of first found s. -1 is return if s is not
100// found.
101func indexList(s string, list []string) int {
102	for i, l := range list {
103		if l == s {
104			return i
105		}
106	}
107	return -1
108}
109
110// inList returns true if one or more of s is in the list.
111func inList(s string, list []string) bool {
112	return indexList(s, list) != -1
113}
114
115// Main execution of soong_ui. The command format is as follows:
116//
117//    soong_ui <command> [<arg 1> <arg 2> ... <arg n>]
118//
119// Command is the type of soong_ui execution. Only one type of
120// execution is specified. The args are specific to the command.
121func main() {
122	shared.ReexecWithDelveMaybe(os.Getenv("SOONG_UI_DELVE"), shared.ResolveDelveBinary())
123
124	buildStarted := time.Now()
125
126	c, args, err := getCommand(os.Args)
127	if err != nil {
128		fmt.Fprintf(os.Stderr, "Error parsing `soong` args: %s.\n", err)
129		os.Exit(1)
130	}
131
132	// Create a terminal output that mimics Ninja's.
133	output := terminal.NewStatusOutput(c.stdio().Stdout(), os.Getenv("NINJA_STATUS"), c.simpleOutput,
134		build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD"),
135		build.OsEnvironment().IsEnvTrue("SOONG_UI_ANSI_OUTPUT"))
136
137	// Attach a new logger instance to the terminal output.
138	log := logger.New(output)
139	defer log.Cleanup()
140
141	// Create a context to simplify the program termination process.
142	ctx, cancel := context.WithCancel(context.Background())
143	defer cancel()
144
145	// Create a new trace file writer, making it log events to the log instance.
146	trace := tracer.New(log)
147	defer trace.Close()
148
149	// Create and start a new metric record.
150	met := metrics.New()
151	met.SetBuildDateTime(buildStarted)
152	met.SetBuildCommand(os.Args)
153
154	// Create a new Status instance, which manages action counts and event output channels.
155	stat := &status.Status{}
156	defer stat.Finish()
157	// Hook up the terminal output and tracer to Status.
158	stat.AddOutput(output)
159	stat.AddOutput(trace.StatusTracer())
160
161	// Set up a cleanup procedure in case the normal termination process doesn't work.
162	signal.SetupSignals(log, cancel, func() {
163		trace.Close()
164		log.Cleanup()
165		stat.Finish()
166	})
167
168	buildCtx := build.Context{ContextImpl: &build.ContextImpl{
169		Context: ctx,
170		Logger:  log,
171		Metrics: met,
172		Tracer:  trace,
173		Writer:  output,
174		Status:  stat,
175	}}
176
177	config := c.config(buildCtx, args...)
178
179	build.SetupOutDir(buildCtx, config)
180
181	if config.UseBazel() && config.Dist() {
182		defer populateExternalDistDir(buildCtx, config)
183	}
184
185	// Set up files to be outputted in the log directory.
186	logsDir := config.LogsDir()
187
188	// Common list of metric file definition.
189	buildErrorFile := filepath.Join(logsDir, c.logsPrefix+"build_error")
190	rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb")
191	soongMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_metrics")
192
193	build.PrintOutDirWarning(buildCtx, config)
194
195	os.MkdirAll(logsDir, 0777)
196	log.SetOutput(filepath.Join(logsDir, c.logsPrefix+"soong.log"))
197	trace.SetOutput(filepath.Join(logsDir, c.logsPrefix+"build.trace"))
198	stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, c.logsPrefix+"verbose.log")))
199	stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, c.logsPrefix+"error.log")))
200	stat.AddOutput(status.NewProtoErrorLog(log, buildErrorFile))
201	stat.AddOutput(status.NewCriticalPath(log))
202	stat.AddOutput(status.NewBuildProgressLog(log, filepath.Join(logsDir, c.logsPrefix+"build_progress.pb")))
203
204	buildCtx.Verbosef("Detected %.3v GB total RAM", float32(config.TotalRAM())/(1024*1024*1024))
205	buildCtx.Verbosef("Parallelism (local/remote/highmem): %v/%v/%v",
206		config.Parallel(), config.RemoteParallel(), config.HighmemParallel())
207
208	setMaxFiles(buildCtx)
209
210	{
211		// The order of the function calls is important. The last defer function call
212		// is the first one that is executed to save the rbe metrics to a protobuf
213		// file. The soong metrics file is then next. Bazel profiles are written
214		// before the uploadMetrics is invoked. The written files are then uploaded
215		// if the uploading of the metrics is enabled.
216		files := []string{
217			buildErrorFile,           // build error strings
218			rbeMetricsFile,           // high level metrics related to remote build execution.
219			soongMetricsFile,         // high level metrics related to this build system.
220			config.BazelMetricsDir(), // directory that contains a set of bazel metrics.
221		}
222		defer build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, files...)
223		defer met.Dump(soongMetricsFile)
224	}
225
226	// Read the time at the starting point.
227	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
228		// soong_ui.bash uses the date command's %N (nanosec) flag when getting the start time,
229		// which Darwin doesn't support. Check if it was executed properly before parsing the value.
230		if !strings.HasSuffix(start, "N") {
231			if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
232				log.Verbosef("Took %dms to start up.",
233					time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
234				buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
235			}
236		}
237
238		if executable, err := os.Executable(); err == nil {
239			trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
240		}
241	}
242
243	// Fix up the source tree due to a repo bug where it doesn't remove
244	// linkfiles that have been removed
245	fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.bp")
246	fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.mk")
247
248	// Create a source finder.
249	f := build.NewSourceFinder(buildCtx, config)
250	defer f.Shutdown()
251	build.FindSources(buildCtx, config, f)
252
253	c.run(buildCtx, config, args, logsDir)
254}
255
256func fixBadDanglingLink(ctx build.Context, name string) {
257	_, err := os.Lstat(name)
258	if err != nil {
259		return
260	}
261	_, err = os.Stat(name)
262	if os.IsNotExist(err) {
263		err = os.Remove(name)
264		if err != nil {
265			ctx.Fatalf("Failed to remove dangling link %q: %v", name, err)
266		}
267	}
268}
269
270func dumpVar(ctx build.Context, config build.Config, args []string, _ string) {
271	flags := flag.NewFlagSet("dumpvar", flag.ExitOnError)
272	flags.Usage = func() {
273		fmt.Fprintf(ctx.Writer, "usage: %s --dumpvar-mode [--abs] <VAR>\n\n", os.Args[0])
274		fmt.Fprintln(ctx.Writer, "In dumpvar mode, print the value of the legacy make variable VAR to stdout")
275		fmt.Fprintln(ctx.Writer, "")
276
277		fmt.Fprintln(ctx.Writer, "'report_config' is a special case that prints the human-readable config banner")
278		fmt.Fprintln(ctx.Writer, "from the beginning of the build.")
279		fmt.Fprintln(ctx.Writer, "")
280		flags.PrintDefaults()
281	}
282	abs := flags.Bool("abs", false, "Print the absolute path of the value")
283	flags.Parse(args)
284
285	if flags.NArg() != 1 {
286		flags.Usage()
287		os.Exit(1)
288	}
289
290	varName := flags.Arg(0)
291	if varName == "report_config" {
292		varData, err := build.DumpMakeVars(ctx, config, nil, build.BannerVars)
293		if err != nil {
294			ctx.Fatal(err)
295		}
296
297		fmt.Println(build.Banner(varData))
298	} else {
299		varData, err := build.DumpMakeVars(ctx, config, nil, []string{varName})
300		if err != nil {
301			ctx.Fatal(err)
302		}
303
304		if *abs {
305			var res []string
306			for _, path := range strings.Fields(varData[varName]) {
307				if abs, err := filepath.Abs(path); err == nil {
308					res = append(res, abs)
309				} else {
310					ctx.Fatalln("Failed to get absolute path of", path, err)
311				}
312			}
313			fmt.Println(strings.Join(res, " "))
314		} else {
315			fmt.Println(varData[varName])
316		}
317	}
318}
319
320func dumpVars(ctx build.Context, config build.Config, args []string, _ string) {
321	flags := flag.NewFlagSet("dumpvars", flag.ExitOnError)
322	flags.Usage = func() {
323		fmt.Fprintf(ctx.Writer, "usage: %s --dumpvars-mode [--vars=\"VAR VAR ...\"]\n\n", os.Args[0])
324		fmt.Fprintln(ctx.Writer, "In dumpvars mode, dump the values of one or more legacy make variables, in")
325		fmt.Fprintln(ctx.Writer, "shell syntax. The resulting output may be sourced directly into a shell to")
326		fmt.Fprintln(ctx.Writer, "set corresponding shell variables.")
327		fmt.Fprintln(ctx.Writer, "")
328
329		fmt.Fprintln(ctx.Writer, "'report_config' is a special case that dumps a variable containing the")
330		fmt.Fprintln(ctx.Writer, "human-readable config banner from the beginning of the build.")
331		fmt.Fprintln(ctx.Writer, "")
332		flags.PrintDefaults()
333	}
334
335	varsStr := flags.String("vars", "", "Space-separated list of variables to dump")
336	absVarsStr := flags.String("abs-vars", "", "Space-separated list of variables to dump (using absolute paths)")
337
338	varPrefix := flags.String("var-prefix", "", "String to prepend to all variable names when dumping")
339	absVarPrefix := flags.String("abs-var-prefix", "", "String to prepent to all absolute path variable names when dumping")
340
341	flags.Parse(args)
342
343	if flags.NArg() != 0 {
344		flags.Usage()
345		os.Exit(1)
346	}
347
348	vars := strings.Fields(*varsStr)
349	absVars := strings.Fields(*absVarsStr)
350
351	allVars := append([]string{}, vars...)
352	allVars = append(allVars, absVars...)
353
354	if i := indexList("report_config", allVars); i != -1 {
355		allVars = append(allVars[:i], allVars[i+1:]...)
356		allVars = append(allVars, build.BannerVars...)
357	}
358
359	if len(allVars) == 0 {
360		return
361	}
362
363	varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
364	if err != nil {
365		ctx.Fatal(err)
366	}
367
368	for _, name := range vars {
369		if name == "report_config" {
370			fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(varData))
371		} else {
372			fmt.Printf("%s%s='%s'\n", *varPrefix, name, varData[name])
373		}
374	}
375	for _, name := range absVars {
376		var res []string
377		for _, path := range strings.Fields(varData[name]) {
378			abs, err := filepath.Abs(path)
379			if err != nil {
380				ctx.Fatalln("Failed to get absolute path of", path, err)
381			}
382			res = append(res, abs)
383		}
384		fmt.Printf("%s%s='%s'\n", *absVarPrefix, name, strings.Join(res, " "))
385	}
386}
387
388func stdio() terminal.StdioInterface {
389	return terminal.StdioImpl{}
390}
391
392// dumpvar and dumpvars use stdout to output variable values, so use stderr instead of stdout when
393// reporting events to keep stdout clean from noise.
394func customStdio() terminal.StdioInterface {
395	return terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
396}
397
398// dumpVarConfig does not require any arguments to be parsed by the NewConfig.
399func dumpVarConfig(ctx build.Context, args ...string) build.Config {
400	return build.NewConfig(ctx)
401}
402
403func buildActionConfig(ctx build.Context, args ...string) build.Config {
404	flags := flag.NewFlagSet("build-mode", flag.ContinueOnError)
405	flags.Usage = func() {
406		fmt.Fprintf(ctx.Writer, "usage: %s --build-mode --dir=<path> <build action> [<build arg 1> <build arg 2> ...]\n\n", os.Args[0])
407		fmt.Fprintln(ctx.Writer, "In build mode, build the set of modules based on the specified build")
408		fmt.Fprintln(ctx.Writer, "action. The --dir flag is required to determine what is needed to")
409		fmt.Fprintln(ctx.Writer, "build in the source tree based on the build action. See below for")
410		fmt.Fprintln(ctx.Writer, "the list of acceptable build action flags.")
411		fmt.Fprintln(ctx.Writer, "")
412		flags.PrintDefaults()
413	}
414
415	buildActionFlags := []struct {
416		name        string
417		description string
418		action      build.BuildAction
419		set         bool
420	}{{
421		name:        "all-modules",
422		description: "Build action: build from the top of the source tree.",
423		action:      build.BUILD_MODULES,
424	}, {
425		// This is redirecting to mma build command behaviour. Once it has soaked for a
426		// while, the build command is deleted from here once it has been removed from the
427		// envsetup.sh.
428		name:        "modules-in-a-dir-no-deps",
429		description: "Build action: builds all of the modules in the current directory without their dependencies.",
430		action:      build.BUILD_MODULES_IN_A_DIRECTORY,
431	}, {
432		// This is redirecting to mmma build command behaviour. Once it has soaked for a
433		// while, the build command is deleted from here once it has been removed from the
434		// envsetup.sh.
435		name:        "modules-in-dirs-no-deps",
436		description: "Build action: builds all of the modules in the supplied directories without their dependencies.",
437		action:      build.BUILD_MODULES_IN_DIRECTORIES,
438	}, {
439		name:        "modules-in-a-dir",
440		description: "Build action: builds all of the modules in the current directory and their dependencies.",
441		action:      build.BUILD_MODULES_IN_A_DIRECTORY,
442	}, {
443		name:        "modules-in-dirs",
444		description: "Build action: builds all of the modules in the supplied directories and their dependencies.",
445		action:      build.BUILD_MODULES_IN_DIRECTORIES,
446	}}
447	for i, flag := range buildActionFlags {
448		flags.BoolVar(&buildActionFlags[i].set, flag.name, false, flag.description)
449	}
450	dir := flags.String("dir", "", "Directory of the executed build command.")
451
452	// Only interested in the first two args which defines the build action and the directory.
453	// The remaining arguments are passed down to the config.
454	const numBuildActionFlags = 2
455	if len(args) < numBuildActionFlags {
456		flags.Usage()
457		ctx.Fatalln("Improper build action arguments.")
458	}
459	flags.Parse(args[0:numBuildActionFlags])
460
461	// The next block of code is to validate that exactly one build action is set and the dir flag
462	// is specified.
463	buildActionCount := 0
464	var buildAction build.BuildAction
465	for _, flag := range buildActionFlags {
466		if flag.set {
467			buildActionCount++
468			buildAction = flag.action
469		}
470	}
471	if buildActionCount != 1 {
472		ctx.Fatalln("Build action not defined.")
473	}
474	if *dir == "" {
475		ctx.Fatalln("-dir not specified.")
476	}
477
478	// Remove the build action flags from the args as they are not recognized by the config.
479	args = args[numBuildActionFlags:]
480	return build.NewBuildActionConfig(buildAction, *dir, ctx, args...)
481}
482
483func runMake(ctx build.Context, config build.Config, _ []string, logsDir string) {
484	if config.IsVerbose() {
485		writer := ctx.Writer
486		fmt.Fprintln(writer, "! The argument `showcommands` is no longer supported.")
487		fmt.Fprintln(writer, "! Instead, the verbose log is always written to a compressed file in the output dir:")
488		fmt.Fprintln(writer, "!")
489		fmt.Fprintf(writer, "!   gzip -cd %s/verbose.log.gz | less -R\n", logsDir)
490		fmt.Fprintln(writer, "!")
491		fmt.Fprintln(writer, "! Older versions are saved in verbose.log.#.gz files")
492		fmt.Fprintln(writer, "")
493		select {
494		case <-time.After(5 * time.Second):
495		case <-ctx.Done():
496			return
497		}
498	}
499
500	if _, ok := config.Environment().Get("ONE_SHOT_MAKEFILE"); ok {
501		writer := ctx.Writer
502		fmt.Fprintln(writer, "! The variable `ONE_SHOT_MAKEFILE` is obsolete.")
503		fmt.Fprintln(writer, "!")
504		fmt.Fprintln(writer, "! If you're using `mm`, you'll need to run `source build/envsetup.sh` to update.")
505		fmt.Fprintln(writer, "!")
506		fmt.Fprintln(writer, "! Otherwise, either specify a module name with m, or use mma / MODULES-IN-...")
507		fmt.Fprintln(writer, "")
508		ctx.Fatal("done")
509	}
510
511	build.Build(ctx, config)
512}
513
514// getCommand finds the appropriate command based on args[1] flag. args[0]
515// is the soong_ui filename.
516func getCommand(args []string) (*command, []string, error) {
517	if len(args) < 2 {
518		return nil, nil, fmt.Errorf("Too few arguments: %q", args)
519	}
520
521	for _, c := range commands {
522		if c.flag == args[1] {
523			return &c, args[2:], nil
524		}
525	}
526
527	// command not found
528	flags := make([]string, len(commands))
529	for i, c := range commands {
530		flags[i] = c.flag
531	}
532	return nil, nil, fmt.Errorf("Command not found: %q\nDid you mean one of these: %q", args, flags)
533}
534
535// For Bazel support, this moves files and directories from e.g. out/dist/$f to DIST_DIR/$f if necessary.
536func populateExternalDistDir(ctx build.Context, config build.Config) {
537	// Make sure that internalDistDirPath and externalDistDirPath are both absolute paths, so we can compare them
538	var err error
539	var internalDistDirPath string
540	var externalDistDirPath string
541	if internalDistDirPath, err = filepath.Abs(config.DistDir()); err != nil {
542		ctx.Fatalf("Unable to find absolute path of %s: %s", internalDistDirPath, err)
543	}
544	if externalDistDirPath, err = filepath.Abs(config.RealDistDir()); err != nil {
545		ctx.Fatalf("Unable to find absolute path of %s: %s", externalDistDirPath, err)
546	}
547	if externalDistDirPath == internalDistDirPath {
548		return
549	}
550
551	// Make sure the internal DIST_DIR actually exists before trying to read from it
552	if _, err = os.Stat(internalDistDirPath); os.IsNotExist(err) {
553		ctx.Println("Skipping Bazel dist dir migration - nothing to do!")
554		return
555	}
556
557	// Make sure the external DIST_DIR actually exists before trying to write to it
558	if err = os.MkdirAll(externalDistDirPath, 0755); err != nil {
559		ctx.Fatalf("Unable to make directory %s: %s", externalDistDirPath, err)
560	}
561
562	ctx.Println("Populating external DIST_DIR...")
563
564	populateExternalDistDirHelper(ctx, config, internalDistDirPath, externalDistDirPath)
565}
566
567func populateExternalDistDirHelper(ctx build.Context, config build.Config, internalDistDirPath string, externalDistDirPath string) {
568	files, err := ioutil.ReadDir(internalDistDirPath)
569	if err != nil {
570		ctx.Fatalf("Can't read internal distdir %s: %s", internalDistDirPath, err)
571	}
572	for _, f := range files {
573		internalFilePath := filepath.Join(internalDistDirPath, f.Name())
574		externalFilePath := filepath.Join(externalDistDirPath, f.Name())
575
576		if f.IsDir() {
577			// Moving a directory - check if there is an existing directory to merge with
578			externalLstat, err := os.Lstat(externalFilePath)
579			if err != nil {
580				if !os.IsNotExist(err) {
581					ctx.Fatalf("Can't lstat external %s: %s", externalDistDirPath, err)
582				}
583				// Otherwise, if the error was os.IsNotExist, that's fine and we fall through to the rename at the bottom
584			} else {
585				if externalLstat.IsDir() {
586					// Existing dir - try to merge the directories?
587					populateExternalDistDirHelper(ctx, config, internalFilePath, externalFilePath)
588					continue
589				} else {
590					// Existing file being replaced with a directory. Delete the existing file...
591					if err := os.RemoveAll(externalFilePath); err != nil {
592						ctx.Fatalf("Unable to remove existing %s: %s", externalFilePath, err)
593					}
594				}
595			}
596		} else {
597			// Moving a file (not a dir) - delete any existing file or directory
598			if err := os.RemoveAll(externalFilePath); err != nil {
599				ctx.Fatalf("Unable to remove existing %s: %s", externalFilePath, err)
600			}
601		}
602
603		// The actual move - do a rename instead of a copy in order to save disk space.
604		if err := os.Rename(internalFilePath, externalFilePath); err != nil {
605			ctx.Fatalf("Unable to rename %s -> %s due to error %s", internalFilePath, externalFilePath, err)
606		}
607	}
608}
609
610func setMaxFiles(ctx build.Context) {
611	var limits syscall.Rlimit
612
613	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
614	if err != nil {
615		ctx.Println("Failed to get file limit:", err)
616		return
617	}
618
619	ctx.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
620	if limits.Cur == limits.Max {
621		return
622	}
623
624	limits.Cur = limits.Max
625	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
626	if err != nil {
627		ctx.Println("Failed to increase file limit:", err)
628	}
629}
630