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