• 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 build
16
17import (
18	"context"
19	"encoding/json"
20	"fmt"
21	"io/ioutil"
22	"os"
23	"os/exec"
24	"path/filepath"
25	"runtime"
26	"strconv"
27	"strings"
28	"time"
29
30	"android/soong/shared"
31
32	"google.golang.org/protobuf/proto"
33
34	smpb "android/soong/ui/metrics/metrics_proto"
35)
36
37const (
38	envConfigDir = "vendor/google/tools/soong_config"
39	jsonSuffix   = "json"
40
41	configFetcher         = "vendor/google/tools/soong/expconfigfetcher"
42	envConfigFetchTimeout = 10 * time.Second
43)
44
45type Config struct{ *configImpl }
46
47type configImpl struct {
48	// Some targets that are implemented in soong_build
49	// (bp2build, json-module-graph) are not here and have their own bits below.
50	arguments     []string
51	goma          bool
52	environ       *Environment
53	distDir       string
54	buildDateTime string
55
56	// From the arguments
57	parallel        int
58	keepGoing       int
59	verbose         bool
60	checkbuild      bool
61	dist            bool
62	jsonModuleGraph bool
63	bp2build        bool
64	queryview       bool
65	reportMkMetrics bool // Collect and report mk2bp migration progress metrics.
66	soongDocs       bool
67	skipConfig      bool
68	skipKati        bool
69	skipKatiNinja   bool
70	skipSoong       bool
71	skipNinja       bool
72	skipSoongTests  bool
73
74	// From the product config
75	katiArgs        []string
76	ninjaArgs       []string
77	katiSuffix      string
78	targetDevice    string
79	targetDeviceDir string
80	sandboxConfig   *SandboxConfig
81
82	// Autodetected
83	totalRAM uint64
84
85	brokenDupRules     bool
86	brokenUsesNetwork  bool
87	brokenNinjaEnvVars []string
88
89	pathReplaced bool
90
91	useBazel bool
92
93	// During Bazel execution, Bazel cannot write outside OUT_DIR.
94	// So if DIST_DIR is set to an external dir (outside of OUT_DIR), we need to rig it temporarily and then migrate files at the end of the build.
95	riggedDistDirForBazel string
96
97	// Set by multiproduct_kati
98	emptyNinjaFile bool
99
100	metricsUploader string
101
102	includeTags []string
103}
104
105const srcDirFileCheck = "build/soong/root.bp"
106
107var buildFiles = []string{"Android.mk", "Android.bp"}
108
109type BuildAction uint
110
111const (
112	// Builds all of the modules and their dependencies of a specified directory, relative to the root
113	// directory of the source tree.
114	BUILD_MODULES_IN_A_DIRECTORY BuildAction = iota
115
116	// Builds all of the modules and their dependencies of a list of specified directories. All specified
117	// directories are relative to the root directory of the source tree.
118	BUILD_MODULES_IN_DIRECTORIES
119
120	// Build a list of specified modules. If none was specified, simply build the whole source tree.
121	BUILD_MODULES
122)
123
124type bazelBuildMode int
125
126// Bazel-related build modes.
127const (
128	// Don't use bazel at all.
129	noBazel bazelBuildMode = iota
130
131	// Generate synthetic build files and incorporate these files into a build which
132	// partially uses Bazel. Build metadata may come from Android.bp or BUILD files.
133	mixedBuild
134)
135
136// checkTopDir validates that the current directory is at the root directory of the source tree.
137func checkTopDir(ctx Context) {
138	if _, err := os.Stat(srcDirFileCheck); err != nil {
139		if os.IsNotExist(err) {
140			ctx.Fatalf("Current working directory must be the source tree. %q not found.", srcDirFileCheck)
141		}
142		ctx.Fatalln("Error verifying tree state:", err)
143	}
144}
145
146// fetchEnvConfig optionally fetches environment config from an
147// experiments system to control Soong features dynamically.
148func fetchEnvConfig(ctx Context, config *configImpl, envConfigName string) error {
149	configName := envConfigName + "." + jsonSuffix
150	expConfigFetcher := &smpb.ExpConfigFetcher{}
151	defer func() {
152		ctx.Metrics.ExpConfigFetcher(expConfigFetcher)
153	}()
154
155	s, err := os.Stat(configFetcher)
156	if err != nil {
157		if os.IsNotExist(err) {
158			return nil
159		}
160		return err
161	}
162	if s.Mode()&0111 == 0 {
163		status := smpb.ExpConfigFetcher_ERROR
164		expConfigFetcher.Status = &status
165		return fmt.Errorf("configuration fetcher binary %v is not executable: %v", configFetcher, s.Mode())
166	}
167
168	tCtx, cancel := context.WithTimeout(ctx, envConfigFetchTimeout)
169	defer cancel()
170	fetchStart := time.Now()
171	cmd := exec.CommandContext(tCtx, configFetcher, "-output_config_dir", config.OutDir(),
172		"-output_config_name", configName)
173	if err := cmd.Start(); err != nil {
174		status := smpb.ExpConfigFetcher_ERROR
175		expConfigFetcher.Status = &status
176		return err
177	}
178
179	if err := cmd.Wait(); err != nil {
180		status := smpb.ExpConfigFetcher_ERROR
181		expConfigFetcher.Status = &status
182		return err
183	}
184	fetchEnd := time.Now()
185	expConfigFetcher.Micros = proto.Uint64(uint64(fetchEnd.Sub(fetchStart).Microseconds()))
186	outConfigFilePath := filepath.Join(config.OutDir(), configName)
187	expConfigFetcher.Filename = proto.String(outConfigFilePath)
188	if _, err := os.Stat(outConfigFilePath); err == nil {
189		status := smpb.ExpConfigFetcher_CONFIG
190		expConfigFetcher.Status = &status
191	} else {
192		status := smpb.ExpConfigFetcher_NO_CONFIG
193		expConfigFetcher.Status = &status
194	}
195	return nil
196}
197
198func loadEnvConfig(ctx Context, config *configImpl) error {
199	bc := os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG")
200	if bc == "" {
201		return nil
202	}
203
204	if err := fetchEnvConfig(ctx, config, bc); err != nil {
205		fmt.Fprintf(os.Stderr, "Failed to fetch config file: %v\n", err)
206	}
207
208	configDirs := []string{
209		config.OutDir(),
210		os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR"),
211		envConfigDir,
212	}
213	for _, dir := range configDirs {
214		cfgFile := filepath.Join(os.Getenv("TOP"), dir, fmt.Sprintf("%s.%s", bc, jsonSuffix))
215		envVarsJSON, err := ioutil.ReadFile(cfgFile)
216		if err != nil {
217			continue
218		}
219		ctx.Verbosef("Loading config file %v\n", cfgFile)
220		var envVars map[string]map[string]string
221		if err := json.Unmarshal(envVarsJSON, &envVars); err != nil {
222			fmt.Fprintf(os.Stderr, "Env vars config file %s did not parse correctly: %s", cfgFile, err.Error())
223			continue
224		}
225		for k, v := range envVars["env"] {
226			if os.Getenv(k) != "" {
227				continue
228			}
229			config.environ.Set(k, v)
230		}
231		ctx.Verbosef("Finished loading config file %v\n", cfgFile)
232		break
233	}
234
235	return nil
236}
237
238func NewConfig(ctx Context, args ...string) Config {
239	ret := &configImpl{
240		environ:       OsEnvironment(),
241		sandboxConfig: &SandboxConfig{},
242	}
243
244	// Default matching ninja
245	ret.parallel = runtime.NumCPU() + 2
246	ret.keepGoing = 1
247
248	ret.totalRAM = detectTotalRAM(ctx)
249
250	ret.parseArgs(ctx, args)
251
252	// Make sure OUT_DIR is set appropriately
253	if outDir, ok := ret.environ.Get("OUT_DIR"); ok {
254		ret.environ.Set("OUT_DIR", filepath.Clean(outDir))
255	} else {
256		outDir := "out"
257		if baseDir, ok := ret.environ.Get("OUT_DIR_COMMON_BASE"); ok {
258			if wd, err := os.Getwd(); err != nil {
259				ctx.Fatalln("Failed to get working directory:", err)
260			} else {
261				outDir = filepath.Join(baseDir, filepath.Base(wd))
262			}
263		}
264		ret.environ.Set("OUT_DIR", outDir)
265	}
266
267	// loadEnvConfig needs to know what the OUT_DIR is, so it should
268	// be called after we determine the appropriate out directory.
269	if err := loadEnvConfig(ctx, ret); err != nil {
270		ctx.Fatalln("Failed to parse env config files: %v", err)
271	}
272
273	if distDir, ok := ret.environ.Get("DIST_DIR"); ok {
274		ret.distDir = filepath.Clean(distDir)
275	} else {
276		ret.distDir = filepath.Join(ret.OutDir(), "dist")
277	}
278
279	if srcDirIsWritable, ok := ret.environ.Get("BUILD_BROKEN_SRC_DIR_IS_WRITABLE"); ok {
280		ret.sandboxConfig.SetSrcDirIsRO(srcDirIsWritable == "false")
281	}
282
283	ret.environ.Unset(
284		// We're already using it
285		"USE_SOONG_UI",
286
287		// We should never use GOROOT/GOPATH from the shell environment
288		"GOROOT",
289		"GOPATH",
290
291		// These should only come from Soong, not the environment.
292		"CLANG",
293		"CLANG_CXX",
294		"CCC_CC",
295		"CCC_CXX",
296
297		// Used by the goma compiler wrapper, but should only be set by
298		// gomacc
299		"GOMACC_PATH",
300
301		// We handle this above
302		"OUT_DIR_COMMON_BASE",
303
304		// This is handled above too, and set for individual commands later
305		"DIST_DIR",
306
307		// Variables that have caused problems in the past
308		"BASH_ENV",
309		"CDPATH",
310		"DISPLAY",
311		"GREP_OPTIONS",
312		"NDK_ROOT",
313		"POSIXLY_CORRECT",
314
315		// Drop make flags
316		"MAKEFLAGS",
317		"MAKELEVEL",
318		"MFLAGS",
319
320		// Set in envsetup.sh, reset in makefiles
321		"ANDROID_JAVA_TOOLCHAIN",
322
323		// Set by envsetup.sh, but shouldn't be used inside the build because envsetup.sh is optional
324		"ANDROID_BUILD_TOP",
325		"ANDROID_HOST_OUT",
326		"ANDROID_PRODUCT_OUT",
327		"ANDROID_HOST_OUT_TESTCASES",
328		"ANDROID_TARGET_OUT_TESTCASES",
329		"ANDROID_TOOLCHAIN",
330		"ANDROID_TOOLCHAIN_2ND_ARCH",
331		"ANDROID_DEV_SCRIPTS",
332		"ANDROID_EMULATOR_PREBUILTS",
333		"ANDROID_PRE_BUILD_PATHS",
334	)
335
336	if ret.UseGoma() || ret.ForceUseGoma() {
337		ctx.Println("Goma for Android has been deprecated and replaced with RBE. See go/rbe_for_android for instructions on how to use RBE.")
338		ctx.Fatalln("USE_GOMA / FORCE_USE_GOMA flag is no longer supported.")
339	}
340
341	// Tell python not to spam the source tree with .pyc files.
342	ret.environ.Set("PYTHONDONTWRITEBYTECODE", "1")
343
344	tmpDir := absPath(ctx, ret.TempDir())
345	ret.environ.Set("TMPDIR", tmpDir)
346
347	// Always set ASAN_SYMBOLIZER_PATH so that ASAN-based tools can symbolize any crashes
348	symbolizerPath := filepath.Join("prebuilts/clang/host", ret.HostPrebuiltTag(),
349		"llvm-binutils-stable/llvm-symbolizer")
350	ret.environ.Set("ASAN_SYMBOLIZER_PATH", absPath(ctx, symbolizerPath))
351
352	// Precondition: the current directory is the top of the source tree
353	checkTopDir(ctx)
354
355	srcDir := absPath(ctx, ".")
356	if strings.ContainsRune(srcDir, ' ') {
357		ctx.Println("You are building in a directory whose absolute path contains a space character:")
358		ctx.Println()
359		ctx.Printf("%q\n", srcDir)
360		ctx.Println()
361		ctx.Fatalln("Directory names containing spaces are not supported")
362	}
363
364	ret.metricsUploader = GetMetricsUploader(srcDir, ret.environ)
365
366	if outDir := ret.OutDir(); strings.ContainsRune(outDir, ' ') {
367		ctx.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:")
368		ctx.Println()
369		ctx.Printf("%q\n", outDir)
370		ctx.Println()
371		ctx.Fatalln("Directory names containing spaces are not supported")
372	}
373
374	if distDir := ret.RealDistDir(); strings.ContainsRune(distDir, ' ') {
375		ctx.Println("The absolute path of your dist directory ($DIST_DIR) contains a space character:")
376		ctx.Println()
377		ctx.Printf("%q\n", distDir)
378		ctx.Println()
379		ctx.Fatalln("Directory names containing spaces are not supported")
380	}
381
382	// Configure Java-related variables, including adding it to $PATH
383	java8Home := filepath.Join("prebuilts/jdk/jdk8", ret.HostPrebuiltTag())
384	java9Home := filepath.Join("prebuilts/jdk/jdk9", ret.HostPrebuiltTag())
385	java11Home := filepath.Join("prebuilts/jdk/jdk11", ret.HostPrebuiltTag())
386	java17Home := filepath.Join("prebuilts/jdk/jdk17", ret.HostPrebuiltTag())
387	javaHome := func() string {
388		if override, ok := ret.environ.Get("OVERRIDE_ANDROID_JAVA_HOME"); ok {
389			return override
390		}
391		if ret.environ.IsEnvTrue("EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN") {
392			return java17Home
393		}
394		if toolchain11, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN"); ok && toolchain11 != "true" {
395			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 11 toolchain is now the global default.")
396		}
397		return java11Home
398	}()
399	absJavaHome := absPath(ctx, javaHome)
400
401	ret.configureLocale(ctx)
402
403	newPath := []string{filepath.Join(absJavaHome, "bin")}
404	if path, ok := ret.environ.Get("PATH"); ok && path != "" {
405		newPath = append(newPath, path)
406	}
407
408	ret.environ.Unset("OVERRIDE_ANDROID_JAVA_HOME")
409	ret.environ.Set("JAVA_HOME", absJavaHome)
410	ret.environ.Set("ANDROID_JAVA_HOME", javaHome)
411	ret.environ.Set("ANDROID_JAVA8_HOME", java8Home)
412	ret.environ.Set("ANDROID_JAVA9_HOME", java9Home)
413	ret.environ.Set("ANDROID_JAVA11_HOME", java11Home)
414	ret.environ.Set("PATH", strings.Join(newPath, string(filepath.ListSeparator)))
415
416	outDir := ret.OutDir()
417	buildDateTimeFile := filepath.Join(outDir, "build_date.txt")
418	if buildDateTime, ok := ret.environ.Get("BUILD_DATETIME"); ok && buildDateTime != "" {
419		ret.buildDateTime = buildDateTime
420	} else {
421		ret.buildDateTime = strconv.FormatInt(time.Now().Unix(), 10)
422	}
423
424	ret.environ.Set("BUILD_DATETIME_FILE", buildDateTimeFile)
425
426	if ret.UseRBE() {
427		for k, v := range getRBEVars(ctx, Config{ret}) {
428			ret.environ.Set(k, v)
429		}
430	}
431
432	bpd := ret.BazelMetricsDir()
433	if err := os.RemoveAll(bpd); err != nil {
434		ctx.Fatalf("Unable to remove bazel profile directory %q: %v", bpd, err)
435	}
436
437	ret.useBazel = ret.environ.IsEnvTrue("USE_BAZEL")
438
439	if ret.UseBazel() {
440		if err := os.MkdirAll(bpd, 0777); err != nil {
441			ctx.Fatalf("Failed to create bazel profile directory %q: %v", bpd, err)
442		}
443	}
444
445	if ret.UseBazel() {
446		ret.riggedDistDirForBazel = filepath.Join(ret.OutDir(), "dist")
447	} else {
448		// Not rigged
449		ret.riggedDistDirForBazel = ret.distDir
450	}
451
452	c := Config{ret}
453	storeConfigMetrics(ctx, c)
454	return c
455}
456
457// NewBuildActionConfig returns a build configuration based on the build action. The arguments are
458// processed based on the build action and extracts any arguments that belongs to the build action.
459func NewBuildActionConfig(action BuildAction, dir string, ctx Context, args ...string) Config {
460	return NewConfig(ctx, getConfigArgs(action, dir, ctx, args)...)
461}
462
463// storeConfigMetrics selects a set of configuration information and store in
464// the metrics system for further analysis.
465func storeConfigMetrics(ctx Context, config Config) {
466	if ctx.Metrics == nil {
467		return
468	}
469
470	ctx.Metrics.BuildConfig(buildConfig(config))
471
472	s := &smpb.SystemResourceInfo{
473		TotalPhysicalMemory: proto.Uint64(config.TotalRAM()),
474		AvailableCpus:       proto.Int32(int32(runtime.NumCPU())),
475	}
476	ctx.Metrics.SystemResourceInfo(s)
477}
478
479func buildConfig(config Config) *smpb.BuildConfig {
480	c := &smpb.BuildConfig{
481		ForceUseGoma:    proto.Bool(config.ForceUseGoma()),
482		UseGoma:         proto.Bool(config.UseGoma()),
483		UseRbe:          proto.Bool(config.UseRBE()),
484		BazelAsNinja:    proto.Bool(config.UseBazel()),
485		BazelMixedBuild: proto.Bool(config.bazelBuildMode() == mixedBuild),
486	}
487	c.Targets = append(c.Targets, config.arguments...)
488
489	return c
490}
491
492// getConfigArgs processes the command arguments based on the build action and creates a set of new
493// arguments to be accepted by Config.
494func getConfigArgs(action BuildAction, dir string, ctx Context, args []string) []string {
495	// The next block of code verifies that the current directory is the root directory of the source
496	// tree. It then finds the relative path of dir based on the root directory of the source tree
497	// and verify that dir is inside of the source tree.
498	checkTopDir(ctx)
499	topDir, err := os.Getwd()
500	if err != nil {
501		ctx.Fatalf("Error retrieving top directory: %v", err)
502	}
503	dir, err = filepath.EvalSymlinks(dir)
504	if err != nil {
505		ctx.Fatalf("Unable to evaluate symlink of %s: %v", dir, err)
506	}
507	dir, err = filepath.Abs(dir)
508	if err != nil {
509		ctx.Fatalf("Unable to find absolute path %s: %v", dir, err)
510	}
511	relDir, err := filepath.Rel(topDir, dir)
512	if err != nil {
513		ctx.Fatalf("Unable to find relative path %s of %s: %v", relDir, topDir, err)
514	}
515	// If there are ".." in the path, it's not in the source tree.
516	if strings.Contains(relDir, "..") {
517		ctx.Fatalf("Directory %s is not under the source tree %s", dir, topDir)
518	}
519
520	configArgs := args[:]
521
522	// If the arguments contains GET-INSTALL-PATH, change the target name prefix from MODULES-IN- to
523	// GET-INSTALL-PATH-IN- to extract the installation path instead of building the modules.
524	targetNamePrefix := "MODULES-IN-"
525	if inList("GET-INSTALL-PATH", configArgs) {
526		targetNamePrefix = "GET-INSTALL-PATH-IN-"
527		configArgs = removeFromList("GET-INSTALL-PATH", configArgs)
528	}
529
530	var targets []string
531
532	switch action {
533	case BUILD_MODULES:
534		// No additional processing is required when building a list of specific modules or all modules.
535	case BUILD_MODULES_IN_A_DIRECTORY:
536		// If dir is the root source tree, all the modules are built of the source tree are built so
537		// no need to find the build file.
538		if topDir == dir {
539			break
540		}
541
542		buildFile := findBuildFile(ctx, relDir)
543		if buildFile == "" {
544			ctx.Fatalf("Build file not found for %s directory", relDir)
545		}
546		targets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
547	case BUILD_MODULES_IN_DIRECTORIES:
548		newConfigArgs, dirs := splitArgs(configArgs)
549		configArgs = newConfigArgs
550		targets = getTargetsFromDirs(ctx, relDir, dirs, targetNamePrefix)
551	}
552
553	// Tidy only override all other specified targets.
554	tidyOnly := os.Getenv("WITH_TIDY_ONLY")
555	if tidyOnly == "true" || tidyOnly == "1" {
556		configArgs = append(configArgs, "tidy_only")
557	} else {
558		configArgs = append(configArgs, targets...)
559	}
560
561	return configArgs
562}
563
564// convertToTarget replaces "/" to "-" in dir and pre-append the targetNamePrefix to the target name.
565func convertToTarget(dir string, targetNamePrefix string) string {
566	return targetNamePrefix + strings.ReplaceAll(dir, "/", "-")
567}
568
569// hasBuildFile returns true if dir contains an Android build file.
570func hasBuildFile(ctx Context, dir string) bool {
571	for _, buildFile := range buildFiles {
572		_, err := os.Stat(filepath.Join(dir, buildFile))
573		if err == nil {
574			return true
575		}
576		if !os.IsNotExist(err) {
577			ctx.Fatalf("Error retrieving the build file stats: %v", err)
578		}
579	}
580	return false
581}
582
583// findBuildFile finds a build file (makefile or blueprint file) by looking if there is a build file
584// in the current and any sub directory of dir. If a build file is not found, traverse the path
585// up by one directory and repeat again until either a build file is found or reached to the root
586// source tree. The returned filename of build file is "Android.mk". If one was not found, a blank
587// string is returned.
588func findBuildFile(ctx Context, dir string) string {
589	// If the string is empty or ".", assume it is top directory of the source tree.
590	if dir == "" || dir == "." {
591		return ""
592	}
593
594	found := false
595	for buildDir := dir; buildDir != "."; buildDir = filepath.Dir(buildDir) {
596		err := filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
597			if err != nil {
598				return err
599			}
600			if found {
601				return filepath.SkipDir
602			}
603			if info.IsDir() {
604				return nil
605			}
606			for _, buildFile := range buildFiles {
607				if info.Name() == buildFile {
608					found = true
609					return filepath.SkipDir
610				}
611			}
612			return nil
613		})
614		if err != nil {
615			ctx.Fatalf("Error finding Android build file: %v", err)
616		}
617
618		if found {
619			return filepath.Join(buildDir, "Android.mk")
620		}
621	}
622
623	return ""
624}
625
626// splitArgs iterates over the arguments list and splits into two lists: arguments and directories.
627func splitArgs(args []string) (newArgs []string, dirs []string) {
628	specialArgs := map[string]bool{
629		"showcommands": true,
630		"snod":         true,
631		"dist":         true,
632		"checkbuild":   true,
633	}
634
635	newArgs = []string{}
636	dirs = []string{}
637
638	for _, arg := range args {
639		// It's a dash argument if it starts with "-" or it's a key=value pair, it's not a directory.
640		if strings.IndexRune(arg, '-') == 0 || strings.IndexRune(arg, '=') != -1 {
641			newArgs = append(newArgs, arg)
642			continue
643		}
644
645		if _, ok := specialArgs[arg]; ok {
646			newArgs = append(newArgs, arg)
647			continue
648		}
649
650		dirs = append(dirs, arg)
651	}
652
653	return newArgs, dirs
654}
655
656// getTargetsFromDirs iterates over the dirs list and creates a list of targets to build. If a
657// directory from the dirs list does not exist, a fatal error is raised. relDir is related to the
658// source root tree where the build action command was invoked. Each directory is validated if the
659// build file can be found and follows the format "dir1:target1,target2,...". Target is optional.
660func getTargetsFromDirs(ctx Context, relDir string, dirs []string, targetNamePrefix string) (targets []string) {
661	for _, dir := range dirs {
662		// The directory may have specified specific modules to build. ":" is the separator to separate
663		// the directory and the list of modules.
664		s := strings.Split(dir, ":")
665		l := len(s)
666		if l > 2 { // more than one ":" was specified.
667			ctx.Fatalf("%s not in proper directory:target1,target2,... format (\":\" was specified more than once)", dir)
668		}
669
670		dir = filepath.Join(relDir, s[0])
671		if _, err := os.Stat(dir); err != nil {
672			ctx.Fatalf("couldn't find directory %s", dir)
673		}
674
675		// Verify that if there are any targets specified after ":". Each target is separated by ",".
676		var newTargets []string
677		if l == 2 && s[1] != "" {
678			newTargets = strings.Split(s[1], ",")
679			if inList("", newTargets) {
680				ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir)
681			}
682		}
683
684		// If there are specified targets to build in dir, an android build file must exist for the one
685		// shot build. For the non-targets case, find the appropriate build file and build all the
686		// modules in dir (or the closest one in the dir path).
687		if len(newTargets) > 0 {
688			if !hasBuildFile(ctx, dir) {
689				ctx.Fatalf("Couldn't locate a build file from %s directory", dir)
690			}
691		} else {
692			buildFile := findBuildFile(ctx, dir)
693			if buildFile == "" {
694				ctx.Fatalf("Build file not found for %s directory", dir)
695			}
696			newTargets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
697		}
698
699		targets = append(targets, newTargets...)
700	}
701
702	return targets
703}
704
705func (c *configImpl) parseArgs(ctx Context, args []string) {
706	for i := 0; i < len(args); i++ {
707		arg := strings.TrimSpace(args[i])
708		if arg == "showcommands" {
709			c.verbose = true
710		} else if arg == "--empty-ninja-file" {
711			c.emptyNinjaFile = true
712		} else if arg == "--skip-ninja" {
713			c.skipNinja = true
714		} else if arg == "--skip-make" {
715			// TODO(ccross): deprecate this, it has confusing behaviors.  It doesn't run kati,
716			//   but it does run a Kati ninja file if the .kati_enabled marker file was created
717			//   by a previous build.
718			c.skipConfig = true
719			c.skipKati = true
720		} else if arg == "--skip-kati" {
721			// TODO: remove --skip-kati once module builds have been migrated to --song-only
722			c.skipKati = true
723		} else if arg == "--soong-only" {
724			c.skipKati = true
725			c.skipKatiNinja = true
726		} else if arg == "--config-only" {
727			c.skipKati = true
728			c.skipKatiNinja = true
729			c.skipSoong = true
730		} else if arg == "--skip-config" {
731			c.skipConfig = true
732		} else if arg == "--skip-soong-tests" {
733			c.skipSoongTests = true
734		} else if arg == "--mk-metrics" {
735			c.reportMkMetrics = true
736		} else if len(arg) > 0 && arg[0] == '-' {
737			parseArgNum := func(def int) int {
738				if len(arg) > 2 {
739					p, err := strconv.ParseUint(arg[2:], 10, 31)
740					if err != nil {
741						ctx.Fatalf("Failed to parse %q: %v", arg, err)
742					}
743					return int(p)
744				} else if i+1 < len(args) {
745					p, err := strconv.ParseUint(args[i+1], 10, 31)
746					if err == nil {
747						i++
748						return int(p)
749					}
750				}
751				return def
752			}
753
754			if len(arg) > 1 && arg[1] == 'j' {
755				c.parallel = parseArgNum(c.parallel)
756			} else if len(arg) > 1 && arg[1] == 'k' {
757				c.keepGoing = parseArgNum(0)
758			} else {
759				ctx.Fatalln("Unknown option:", arg)
760			}
761		} else if k, v, ok := decodeKeyValue(arg); ok && len(k) > 0 {
762			if k == "OUT_DIR" {
763				ctx.Fatalln("OUT_DIR may only be set in the environment, not as a command line option.")
764			}
765			c.environ.Set(k, v)
766		} else if arg == "dist" {
767			c.dist = true
768		} else if arg == "json-module-graph" {
769			c.jsonModuleGraph = true
770		} else if arg == "bp2build" {
771			c.bp2build = true
772		} else if arg == "queryview" {
773			c.queryview = true
774		} else if arg == "soong_docs" {
775			c.soongDocs = true
776		} else {
777			if arg == "checkbuild" {
778				c.checkbuild = true
779			}
780			c.arguments = append(c.arguments, arg)
781		}
782	}
783}
784
785func (c *configImpl) configureLocale(ctx Context) {
786	cmd := Command(ctx, Config{c}, "locale", "locale", "-a")
787	output, err := cmd.Output()
788
789	var locales []string
790	if err == nil {
791		locales = strings.Split(string(output), "\n")
792	} else {
793		// If we're unable to list the locales, let's assume en_US.UTF-8
794		locales = []string{"en_US.UTF-8"}
795		ctx.Verbosef("Failed to list locales (%q), falling back to %q", err, locales)
796	}
797
798	// gettext uses LANGUAGE, which is passed directly through
799
800	// For LANG and LC_*, only preserve the evaluated version of
801	// LC_MESSAGES
802	userLang := ""
803	if lc_all, ok := c.environ.Get("LC_ALL"); ok {
804		userLang = lc_all
805	} else if lc_messages, ok := c.environ.Get("LC_MESSAGES"); ok {
806		userLang = lc_messages
807	} else if lang, ok := c.environ.Get("LANG"); ok {
808		userLang = lang
809	}
810
811	c.environ.UnsetWithPrefix("LC_")
812
813	if userLang != "" {
814		c.environ.Set("LC_MESSAGES", userLang)
815	}
816
817	// The for LANG, use C.UTF-8 if it exists (Debian currently, proposed
818	// for others)
819	if inList("C.UTF-8", locales) {
820		c.environ.Set("LANG", "C.UTF-8")
821	} else if inList("C.utf8", locales) {
822		// These normalize to the same thing
823		c.environ.Set("LANG", "C.UTF-8")
824	} else if inList("en_US.UTF-8", locales) {
825		c.environ.Set("LANG", "en_US.UTF-8")
826	} else if inList("en_US.utf8", locales) {
827		// These normalize to the same thing
828		c.environ.Set("LANG", "en_US.UTF-8")
829	} else {
830		ctx.Fatalln("System doesn't support either C.UTF-8 or en_US.UTF-8")
831	}
832}
833
834func (c *configImpl) Environment() *Environment {
835	return c.environ
836}
837
838func (c *configImpl) Arguments() []string {
839	return c.arguments
840}
841
842func (c *configImpl) SoongBuildInvocationNeeded() bool {
843	if len(c.Arguments()) > 0 {
844		// Explicit targets requested that are not special targets like b2pbuild
845		// or the JSON module graph
846		return true
847	}
848
849	if !c.JsonModuleGraph() && !c.Bp2Build() && !c.Queryview() && !c.SoongDocs() {
850		// Command line was empty, the default Ninja target is built
851		return true
852	}
853
854	// bp2build + dist may be used to dist bp2build logs but does not require SoongBuildInvocation
855	if c.Dist() && !c.Bp2Build() {
856		return true
857	}
858
859	// build.ninja doesn't need to be generated
860	return false
861}
862
863func (c *configImpl) OutDir() string {
864	if outDir, ok := c.environ.Get("OUT_DIR"); ok {
865		return outDir
866	}
867	return "out"
868}
869
870func (c *configImpl) DistDir() string {
871	if c.UseBazel() {
872		return c.riggedDistDirForBazel
873	} else {
874		return c.distDir
875	}
876}
877
878func (c *configImpl) RealDistDir() string {
879	return c.distDir
880}
881
882func (c *configImpl) NinjaArgs() []string {
883	if c.skipKati {
884		return c.arguments
885	}
886	return c.ninjaArgs
887}
888
889func (c *configImpl) BazelOutDir() string {
890	return filepath.Join(c.OutDir(), "bazel")
891}
892
893func (c *configImpl) SoongOutDir() string {
894	return filepath.Join(c.OutDir(), "soong")
895}
896
897func (c *configImpl) PrebuiltOS() string {
898	switch runtime.GOOS {
899	case "linux":
900		return "linux-x86"
901	case "darwin":
902		return "darwin-x86"
903	default:
904		panic("Unknown GOOS")
905	}
906}
907
908func (c *configImpl) HostToolDir() string {
909	if c.SkipKatiNinja() {
910		return filepath.Join(c.SoongOutDir(), "host", c.PrebuiltOS(), "bin")
911	} else {
912		return filepath.Join(c.OutDir(), "host", c.PrebuiltOS(), "bin")
913	}
914}
915
916func (c *configImpl) NamedGlobFile(name string) string {
917	return shared.JoinPath(c.SoongOutDir(), "globs-"+name+".ninja")
918}
919
920func (c *configImpl) UsedEnvFile(tag string) string {
921	return shared.JoinPath(c.SoongOutDir(), usedEnvFile+"."+tag)
922}
923
924func (c *configImpl) Bp2BuildMarkerFile() string {
925	return shared.JoinPath(c.SoongOutDir(), "bp2build_workspace_marker")
926}
927
928func (c *configImpl) SoongDocsHtml() string {
929	return shared.JoinPath(c.SoongOutDir(), "docs/soong_build.html")
930}
931
932func (c *configImpl) QueryviewMarkerFile() string {
933	return shared.JoinPath(c.SoongOutDir(), "queryview.marker")
934}
935
936func (c *configImpl) ModuleGraphFile() string {
937	return shared.JoinPath(c.SoongOutDir(), "module-graph.json")
938}
939
940func (c *configImpl) ModuleActionsFile() string {
941	return shared.JoinPath(c.SoongOutDir(), "module-actions.json")
942}
943
944func (c *configImpl) TempDir() string {
945	return shared.TempDirForOutDir(c.SoongOutDir())
946}
947
948func (c *configImpl) FileListDir() string {
949	return filepath.Join(c.OutDir(), ".module_paths")
950}
951
952func (c *configImpl) KatiSuffix() string {
953	if c.katiSuffix != "" {
954		return c.katiSuffix
955	}
956	panic("SetKatiSuffix has not been called")
957}
958
959// Checkbuild returns true if "checkbuild" was one of the build goals, which means that the
960// user is interested in additional checks at the expense of build time.
961func (c *configImpl) Checkbuild() bool {
962	return c.checkbuild
963}
964
965func (c *configImpl) Dist() bool {
966	return c.dist
967}
968
969func (c *configImpl) JsonModuleGraph() bool {
970	return c.jsonModuleGraph
971}
972
973func (c *configImpl) Bp2Build() bool {
974	return c.bp2build
975}
976
977func (c *configImpl) Queryview() bool {
978	return c.queryview
979}
980
981func (c *configImpl) SoongDocs() bool {
982	return c.soongDocs
983}
984
985func (c *configImpl) IsVerbose() bool {
986	return c.verbose
987}
988
989func (c *configImpl) SkipKati() bool {
990	return c.skipKati
991}
992
993func (c *configImpl) SkipKatiNinja() bool {
994	return c.skipKatiNinja
995}
996
997func (c *configImpl) SkipSoong() bool {
998	return c.skipSoong
999}
1000
1001func (c *configImpl) SkipNinja() bool {
1002	return c.skipNinja
1003}
1004
1005func (c *configImpl) SetSkipNinja(v bool) {
1006	c.skipNinja = v
1007}
1008
1009func (c *configImpl) SkipConfig() bool {
1010	return c.skipConfig
1011}
1012
1013func (c *configImpl) TargetProduct() string {
1014	if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
1015		return v
1016	}
1017	panic("TARGET_PRODUCT is not defined")
1018}
1019
1020func (c *configImpl) TargetDevice() string {
1021	return c.targetDevice
1022}
1023
1024func (c *configImpl) SetTargetDevice(device string) {
1025	c.targetDevice = device
1026}
1027
1028func (c *configImpl) TargetBuildVariant() string {
1029	if v, ok := c.environ.Get("TARGET_BUILD_VARIANT"); ok {
1030		return v
1031	}
1032	panic("TARGET_BUILD_VARIANT is not defined")
1033}
1034
1035func (c *configImpl) KatiArgs() []string {
1036	return c.katiArgs
1037}
1038
1039func (c *configImpl) Parallel() int {
1040	return c.parallel
1041}
1042
1043func (c *configImpl) GetIncludeTags() []string {
1044	return c.includeTags
1045}
1046
1047func (c *configImpl) SetIncludeTags(i []string) {
1048	c.includeTags = i
1049}
1050
1051func (c *configImpl) HighmemParallel() int {
1052	if i, ok := c.environ.GetInt("NINJA_HIGHMEM_NUM_JOBS"); ok {
1053		return i
1054	}
1055
1056	const minMemPerHighmemProcess = 8 * 1024 * 1024 * 1024
1057	parallel := c.Parallel()
1058	if c.UseRemoteBuild() {
1059		// Ninja doesn't support nested pools, and when remote builds are enabled the total ninja parallelism
1060		// is set very high (i.e. 500).  Using a large value here would cause the total number of running jobs
1061		// to be the sum of the sizes of the local and highmem pools, which will cause extra CPU contention.
1062		// Return 1/16th of the size of the local pool, rounding up.
1063		return (parallel + 15) / 16
1064	} else if c.totalRAM == 0 {
1065		// Couldn't detect the total RAM, don't restrict highmem processes.
1066		return parallel
1067	} else if c.totalRAM <= 16*1024*1024*1024 {
1068		// Less than 16GB of ram, restrict to 1 highmem processes
1069		return 1
1070	} else if c.totalRAM <= 32*1024*1024*1024 {
1071		// Less than 32GB of ram, restrict to 2 highmem processes
1072		return 2
1073	} else if p := int(c.totalRAM / minMemPerHighmemProcess); p < parallel {
1074		// If less than 8GB total RAM per process, reduce the number of highmem processes
1075		return p
1076	}
1077	// No restriction on highmem processes
1078	return parallel
1079}
1080
1081func (c *configImpl) TotalRAM() uint64 {
1082	return c.totalRAM
1083}
1084
1085// ForceUseGoma determines whether we should override Goma deprecation
1086// and use Goma for the current build or not.
1087func (c *configImpl) ForceUseGoma() bool {
1088	if v, ok := c.environ.Get("FORCE_USE_GOMA"); ok {
1089		v = strings.TrimSpace(v)
1090		if v != "" && v != "false" {
1091			return true
1092		}
1093	}
1094	return false
1095}
1096
1097func (c *configImpl) UseGoma() bool {
1098	if v, ok := c.environ.Get("USE_GOMA"); ok {
1099		v = strings.TrimSpace(v)
1100		if v != "" && v != "false" {
1101			return true
1102		}
1103	}
1104	return false
1105}
1106
1107func (c *configImpl) StartGoma() bool {
1108	if !c.UseGoma() {
1109		return false
1110	}
1111
1112	if v, ok := c.environ.Get("NOSTART_GOMA"); ok {
1113		v = strings.TrimSpace(v)
1114		if v != "" && v != "false" {
1115			return false
1116		}
1117	}
1118	return true
1119}
1120
1121func (c *configImpl) UseRBE() bool {
1122	if v, ok := c.Environment().Get("USE_RBE"); ok {
1123		v = strings.TrimSpace(v)
1124		if v != "" && v != "false" {
1125			return true
1126		}
1127	}
1128	return false
1129}
1130
1131func (c *configImpl) UseBazel() bool {
1132	return c.useBazel
1133}
1134
1135func (c *configImpl) bazelBuildMode() bazelBuildMode {
1136	if c.Environment().IsEnvTrue("USE_BAZEL_ANALYSIS") {
1137		return mixedBuild
1138	} else {
1139		return noBazel
1140	}
1141}
1142
1143func (c *configImpl) StartRBE() bool {
1144	if !c.UseRBE() {
1145		return false
1146	}
1147
1148	if v, ok := c.environ.Get("NOSTART_RBE"); ok {
1149		v = strings.TrimSpace(v)
1150		if v != "" && v != "false" {
1151			return false
1152		}
1153	}
1154	return true
1155}
1156
1157func (c *configImpl) rbeLogDir() string {
1158	for _, f := range []string{"RBE_log_dir", "FLAG_log_dir"} {
1159		if v, ok := c.environ.Get(f); ok {
1160			return v
1161		}
1162	}
1163	if c.Dist() {
1164		return c.LogsDir()
1165	}
1166	return c.OutDir()
1167}
1168
1169func (c *configImpl) rbeStatsOutputDir() string {
1170	for _, f := range []string{"RBE_output_dir", "FLAG_output_dir"} {
1171		if v, ok := c.environ.Get(f); ok {
1172			return v
1173		}
1174	}
1175	return c.rbeLogDir()
1176}
1177
1178func (c *configImpl) rbeLogPath() string {
1179	for _, f := range []string{"RBE_log_path", "FLAG_log_path"} {
1180		if v, ok := c.environ.Get(f); ok {
1181			return v
1182		}
1183	}
1184	return fmt.Sprintf("text://%v/reproxy_log.txt", c.rbeLogDir())
1185}
1186
1187func (c *configImpl) rbeExecRoot() string {
1188	for _, f := range []string{"RBE_exec_root", "FLAG_exec_root"} {
1189		if v, ok := c.environ.Get(f); ok {
1190			return v
1191		}
1192	}
1193	wd, err := os.Getwd()
1194	if err != nil {
1195		return ""
1196	}
1197	return wd
1198}
1199
1200func (c *configImpl) rbeDir() string {
1201	if v, ok := c.environ.Get("RBE_DIR"); ok {
1202		return v
1203	}
1204	return "prebuilts/remoteexecution-client/live/"
1205}
1206
1207func (c *configImpl) rbeReproxy() string {
1208	for _, f := range []string{"RBE_re_proxy", "FLAG_re_proxy"} {
1209		if v, ok := c.environ.Get(f); ok {
1210			return v
1211		}
1212	}
1213	return filepath.Join(c.rbeDir(), "reproxy")
1214}
1215
1216func (c *configImpl) rbeAuth() (string, string) {
1217	credFlags := []string{
1218		"use_application_default_credentials",
1219		"use_gce_credentials",
1220		"credential_file",
1221		"use_google_prod_creds",
1222	}
1223	for _, cf := range credFlags {
1224		for _, f := range []string{"RBE_" + cf, "FLAG_" + cf} {
1225			if v, ok := c.environ.Get(f); ok {
1226				v = strings.TrimSpace(v)
1227				if v != "" && v != "false" && v != "0" {
1228					return "RBE_" + cf, v
1229				}
1230			}
1231		}
1232	}
1233	return "RBE_use_application_default_credentials", "true"
1234}
1235
1236func (c *configImpl) UseRemoteBuild() bool {
1237	return c.UseGoma() || c.UseRBE()
1238}
1239
1240// RemoteParallel controls how many remote jobs (i.e., commands which contain
1241// gomacc) are run in parallel.  Note the parallelism of all other jobs is
1242// still limited by Parallel()
1243func (c *configImpl) RemoteParallel() int {
1244	if !c.UseRemoteBuild() {
1245		return 0
1246	}
1247	if i, ok := c.environ.GetInt("NINJA_REMOTE_NUM_JOBS"); ok {
1248		return i
1249	}
1250	return 500
1251}
1252
1253func (c *configImpl) SetKatiArgs(args []string) {
1254	c.katiArgs = args
1255}
1256
1257func (c *configImpl) SetNinjaArgs(args []string) {
1258	c.ninjaArgs = args
1259}
1260
1261func (c *configImpl) SetKatiSuffix(suffix string) {
1262	c.katiSuffix = suffix
1263}
1264
1265func (c *configImpl) LastKatiSuffixFile() string {
1266	return filepath.Join(c.OutDir(), "last_kati_suffix")
1267}
1268
1269func (c *configImpl) HasKatiSuffix() bool {
1270	return c.katiSuffix != ""
1271}
1272
1273func (c *configImpl) KatiEnvFile() string {
1274	return filepath.Join(c.OutDir(), "env"+c.KatiSuffix()+".sh")
1275}
1276
1277func (c *configImpl) KatiBuildNinjaFile() string {
1278	return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+katiBuildSuffix+".ninja")
1279}
1280
1281func (c *configImpl) KatiPackageNinjaFile() string {
1282	return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+katiPackageSuffix+".ninja")
1283}
1284
1285func (c *configImpl) SoongNinjaFile() string {
1286	return filepath.Join(c.SoongOutDir(), "build.ninja")
1287}
1288
1289func (c *configImpl) CombinedNinjaFile() string {
1290	if c.katiSuffix == "" {
1291		return filepath.Join(c.OutDir(), "combined.ninja")
1292	}
1293	return filepath.Join(c.OutDir(), "combined"+c.KatiSuffix()+".ninja")
1294}
1295
1296func (c *configImpl) SoongAndroidMk() string {
1297	return filepath.Join(c.SoongOutDir(), "Android-"+c.TargetProduct()+".mk")
1298}
1299
1300func (c *configImpl) SoongMakeVarsMk() string {
1301	return filepath.Join(c.SoongOutDir(), "make_vars-"+c.TargetProduct()+".mk")
1302}
1303
1304func (c *configImpl) ProductOut() string {
1305	return filepath.Join(c.OutDir(), "target", "product", c.TargetDevice())
1306}
1307
1308func (c *configImpl) DevicePreviousProductConfig() string {
1309	return filepath.Join(c.ProductOut(), "previous_build_config.mk")
1310}
1311
1312func (c *configImpl) KatiPackageMkDir() string {
1313	return filepath.Join(c.ProductOut(), "obj", "CONFIG", "kati_packaging")
1314}
1315
1316func (c *configImpl) hostOutRoot() string {
1317	return filepath.Join(c.OutDir(), "host")
1318}
1319
1320func (c *configImpl) HostOut() string {
1321	return filepath.Join(c.hostOutRoot(), c.HostPrebuiltTag())
1322}
1323
1324// This probably needs to be multi-valued, so not exporting it for now
1325func (c *configImpl) hostCrossOut() string {
1326	if runtime.GOOS == "linux" {
1327		return filepath.Join(c.hostOutRoot(), "windows-x86")
1328	} else {
1329		return ""
1330	}
1331}
1332
1333func (c *configImpl) HostPrebuiltTag() string {
1334	if runtime.GOOS == "linux" {
1335		return "linux-x86"
1336	} else if runtime.GOOS == "darwin" {
1337		return "darwin-x86"
1338	} else {
1339		panic("Unsupported OS")
1340	}
1341}
1342
1343func (c *configImpl) PrebuiltBuildTool(name string) string {
1344	if v, ok := c.environ.Get("SANITIZE_HOST"); ok {
1345		if sanitize := strings.Fields(v); inList("address", sanitize) {
1346			asan := filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "asan/bin", name)
1347			if _, err := os.Stat(asan); err == nil {
1348				return asan
1349			}
1350		}
1351	}
1352	return filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "bin", name)
1353}
1354
1355func (c *configImpl) SetBuildBrokenDupRules(val bool) {
1356	c.brokenDupRules = val
1357}
1358
1359func (c *configImpl) BuildBrokenDupRules() bool {
1360	return c.brokenDupRules
1361}
1362
1363func (c *configImpl) SetBuildBrokenUsesNetwork(val bool) {
1364	c.brokenUsesNetwork = val
1365}
1366
1367func (c *configImpl) BuildBrokenUsesNetwork() bool {
1368	return c.brokenUsesNetwork
1369}
1370
1371func (c *configImpl) SetBuildBrokenNinjaUsesEnvVars(val []string) {
1372	c.brokenNinjaEnvVars = val
1373}
1374
1375func (c *configImpl) BuildBrokenNinjaUsesEnvVars() []string {
1376	return c.brokenNinjaEnvVars
1377}
1378
1379func (c *configImpl) SetTargetDeviceDir(dir string) {
1380	c.targetDeviceDir = dir
1381}
1382
1383func (c *configImpl) TargetDeviceDir() string {
1384	return c.targetDeviceDir
1385}
1386
1387func (c *configImpl) BuildDateTime() string {
1388	return c.buildDateTime
1389}
1390
1391func (c *configImpl) MetricsUploaderApp() string {
1392	return c.metricsUploader
1393}
1394
1395// LogsDir returns the absolute path to the logs directory where build log and
1396// metrics files are located. By default, the logs directory is the out
1397// directory. If the argument dist is specified, the logs directory
1398// is <dist_dir>/logs.
1399func (c *configImpl) LogsDir() string {
1400	dir := c.OutDir()
1401	if c.Dist() {
1402		// Always write logs to the real dist dir, even if Bazel is using a rigged dist dir for other files
1403		dir = filepath.Join(c.RealDistDir(), "logs")
1404	}
1405	absDir, err := filepath.Abs(dir)
1406	if err != nil {
1407		fmt.Fprintf(os.Stderr, "\nError making log dir '%s' absolute: %s\n", dir, err.Error())
1408		os.Exit(1)
1409	}
1410	return absDir
1411}
1412
1413// BazelMetricsDir returns the <logs dir>/bazel_metrics directory
1414// where the bazel profiles are located.
1415func (c *configImpl) BazelMetricsDir() string {
1416	return filepath.Join(c.LogsDir(), "bazel_metrics")
1417}
1418
1419// MkFileMetrics returns the file path for make-related metrics.
1420func (c *configImpl) MkMetrics() string {
1421	return filepath.Join(c.LogsDir(), "mk_metrics.pb")
1422}
1423
1424func (c *configImpl) SetEmptyNinjaFile(v bool) {
1425	c.emptyNinjaFile = v
1426}
1427
1428func (c *configImpl) EmptyNinjaFile() bool {
1429	return c.emptyNinjaFile
1430}
1431
1432func GetMetricsUploader(topDir string, env *Environment) string {
1433	if p, ok := env.Get("METRICS_UPLOADER"); ok {
1434		metricsUploader := filepath.Join(topDir, p)
1435		if _, err := os.Stat(metricsUploader); err == nil {
1436			return metricsUploader
1437		}
1438	}
1439
1440	return ""
1441}
1442