Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/build/build/soong/ui/build/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 67 kB image not shown  

Quelle  config.go   Sprache: unbekannt

 
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package build

import (
 "encoding/json"
 "errors"
 "fmt"
 "io/ioutil"
 "math/rand"
 "os"
 "os/exec"
 "os/user"
 "path/filepath"
 "runtime"
 "slices"
 "strconv"
 "strings"
 "sync"
 "syscall"
 "time"

 "google.golang.org/protobuf/proto"

 "android/soong/finder/fs"
 "android/soong/shared"
 "android/soong/ui/metrics"

 "github.com/google/blueprint"

 smpb "android/soong/ui/metrics/metrics_proto"
)

const (
 envConfigDir = "vendor/google/tools/soong_config"
 jsonSuffix   = "json"
)

var (
 rbeRandPrefix             int
 googleProdCredsExistCache bool
)

func init() {
 rand.Seed(time.Now().UnixNano())
 rbeRandPrefix = rand.Intn(1000)
}

// Which builder are we using?
type ninjaCommandType int

const (
 _ = iota
 NINJA_NINJA
 NINJA_N2
 NINJA_SISO
 NINJA_NINJAGO
)

var NINJA_DEFAULT ninjaCommandType = NINJA_SISO

func (n ninjaCommandType) String() string {
 switch n {
 case NINJA_NINJA:
  return "ninja"
 case NINJA_N2:
  return "n2"
 case NINJA_SISO:
  return "siso"
 case NINJA_NINJAGO:
  return "ninjago"
 default:
  return fmt.Sprintf("%v", int(n))
 }
}

type Config struct{ *configImpl }

type configImpl struct {
 // Some targets that are implemented in soong_build
 arguments       []string
 environ         *Environment
 distDir         string
 buildDateTime   string
 buildNumber     string
 distFileNameTag string
 logsPrefix      string

 // Whether this config was generated by NewDumpVarConfig().
 isDumpVar bool

 // From the arguments
 parallel        int
 keepGoing       int
 verbose         bool
 checkbuild      bool
 dist            bool
 jsonModuleGraph bool
 reportMkMetrics bool // Collect and report mk2bp migration progress metrics.
 soongDocs       bool
 skipConfig      bool
 // Either the user or product config requested that we skip soong (for the banner). The other
 // skip flags tell whether *this* soong_ui invocation will skip kati - which will be true
 // during lunch.
 soongOnlyRequested                  bool
 skipKati                            bool
 skipKatiControlledByFlags           bool
 skipKatiNinja                       bool
 skipSoong                           bool
 skipNinja                           bool
 skipSoongTests                      bool
 skipMetricsUpload                   bool
 buildStartedTime                    int64 // For metrics-upload-only - manually specify a build-started time
 buildFromSourceStub                 bool
 incrementalBuildActions             bool
 incrementalBuildActionsSetInEnv     bool
 incrementalProviderTest             bool
 partialAnalysisTargets              string
 ensureAllowlistIntegrity            bool // For CI builds - make sure modules are mixed-built
 runCIPDProxyServer                  bool
 runCIPDProxyServerControlledByFlags bool

 // From the product config
 katiArgs        []string
 ninjaArgs       []string
 katiSuffix      string
 useRkati        bool
 targetDevice    string
 targetDeviceDir string
 sandboxConfig   *SandboxConfig

 enforceNoReanalysis bool

 // Autodetected
 totalRAM      uint64
 systemCpuInfo *metrics.CpuInfo
 systemMemInfo *metrics.MemInfo

 brokenDupRules       bool
 brokenUsesNetwork    bool
 brokenNinjaEnvVars   []string
 brokenMissingOutputs bool

 pathReplaced bool

 // Set by multiproduct_kati
 emptyNinjaFile bool

 metricsUploader string

 includeTags    []string
 sourceRootDirs []string

 // Data source to write ninja weight list
 ninjaWeightListSource NinjaWeightListSource

 // This file is a detailed dump of all soong-defined modules for debugging purposes.
 // There's quite a bit of overlap with module-info.json and soong module graph. We
 // could consider merging them.
 moduleDebugFile      string
 incrementalDebugFile string

 // Variables that are set when we determine PRODUCT_RELEASE_CONFIG_MAPS.
 // This should only include:
 // - PRODUCT_RELEASE_CONFIG_MAPS (because we need to set it), and
 // - Any build flag that soong_ui needs to know prior to running product config.
 //   There should be a bug for any such flag, to refactor soong_ui to remove the
 //   need for it.
 earlyVars map[string]string

 // Which builder are we using
 ninjaCommand ninjaCommandType

 // Control which JDK is used for builds
 useJdk25 bool

 // The directory where Siso config can be found.
 sisoConfigDir string

 // Variables for Siso RBE config.
 SisoStringVars map[string]string
 SisoBoolVars   map[string]bool

 // cached value to avoid process spawning
 useABFSMu sync.Mutex
 useABFS   *bool
}

type NinjaWeightListSource uint

const (
 // ninja doesn't use weight list.
 NOT_USED NinjaWeightListSource = iota
 // ninja uses weight list based on previous builds by ninja log
 NINJA_LOG
 // ninja thinks every task has the same weight.
 EVENLY_DISTRIBUTED
 // ninja uses an external custom weight list
 EXTERNAL_FILE
 // ninja uses a prioritized module list from Soong
 HINT_FROM_SOONG
 // If ninja log exists, use NINJA_LOG, if not, use HINT_FROM_SOONG instead.
 // We can assume it is an incremental build if ninja log exists.
 DEFAULT
)
const srcDirFileCheck = "build/soong/root.bp"

var buildFiles = []string{"Android.mk", "Android.bp"}

type BuildAction uint

const (
 // Builds all of the modules and their dependencies of a specified directory, relative to the root
 // directory of the source tree.
 BUILD_MODULES_IN_A_DIRECTORY BuildAction = iota

 // Builds all of the modules and their dependencies of a list of specified directories. All specified
 // directories are relative to the root directory of the source tree.
 BUILD_MODULES_IN_DIRECTORIES

 // Build a list of specified modules. If none was specified, simply build the whole source tree.
 BUILD_MODULES
)

// checkTopDir validates that the current directory is at the root directory of the source tree.
func checkTopDir(ctx Context) {
 if _, err := os.Stat(srcDirFileCheck); err != nil {
  if os.IsNotExist(err) {
   ctx.Fatalf("Current working directory must be the source tree. %q not found.", srcDirFileCheck)
  }
  ctx.Fatalln("Error verifying tree state:", err)
 }
}

func loadEnvConfig(ctx Context, config *configImpl, bc string) error {
 if bc == "" {
  return nil
 }

 configDirs := []string{
  config.OutDir(),
  os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG_DIR"),
  envConfigDir,
 }
 for _, dir := range configDirs {
  cfgFile := filepath.Join(os.Getenv("TOP"), dir, fmt.Sprintf("%s.%s", bc, jsonSuffix))
  envVarsJSON, err := ioutil.ReadFile(cfgFile)
  if err != nil {
   continue
  }
  ctx.Verbosef("Loading config file %v\n", cfgFile)
  var envVars map[string]map[string]string
  if err := json.Unmarshal(envVarsJSON, &envVars); err != nil {
   fmt.Fprintf(os.Stderr, "Env vars config file %s did not parse correctly: %s", cfgFile, err.Error())
   continue
  }
  for k, v := range envVars["env"] {
   if os.Getenv(k) != "" {
    continue
   }
   config.environ.Set(k, v)
  }
  ctx.Verbosef("Finished loading config file %v\n", cfgFile)
  break
 }

 return nil
}

// NewDumpVarConfig does not parse any arguments.
func NewDumpVarConfig(ctx Context, args ...string) Config {
 return newConfig(ctx, true)
}

func NewConfig(ctx Context, args ...string) Config {
 return newConfig(ctx, false, args...)
}

func newConfig(ctx Context, isDumpVar bool, args ...string) Config {
 ret := &configImpl{
  environ:               OsEnvironment(),
  sandboxConfig:         &SandboxConfig{},
  ninjaWeightListSource: DEFAULT,
  isDumpVar:             isDumpVar,
 }
 wd, err := os.Getwd()
 if err != nil {
  ctx.Fatalln("Failed to get working directory:", err)
 }

 // If ResolveSoongEnvVars did work, log it.
 for _, msg := range resolveSoongEnvVarsMsgs {
  ctx.Verbosef("%s\n", msg)
 }

 // Skip soong tests by default on Linux
 if runtime.GOOS == "linux" {
  ret.skipSoongTests = true
 }

 // Default matching ninja
 ret.parallel = runtime.NumCPU() + 2
 ret.keepGoing = 1

 ret.totalRAM = detectTotalRAM(ctx)
 ret.systemCpuInfo, err = metrics.NewCpuInfo(fs.OsFs)
 if err != nil {
  ctx.Fatalln("Failed to get cpuinfo:", err)
 }
 ret.systemMemInfo, err = metrics.NewMemInfo(fs.OsFs)
 if err != nil {
  ctx.Fatalln("Failed to get meminfo:", err)
 }
 ret.parseArgs(ctx, args)

 switch os.Getenv("SOONG_NINJA") {
 case "ninja":
  ret.ninjaCommand = NINJA_NINJA
 case "n2":
  ret.ninjaCommand = NINJA_N2
 case "siso":
  ret.ninjaCommand = NINJA_SISO
 case "ninjago":
  ret.ninjaCommand = NINJA_NINJAGO
 default:
  ret.ninjaCommand = NINJA_DEFAULT
 }

 // TODO(b/490207582): SISO doesn't work on older versions of mac os that our CI builders use
 if runtime.GOOS == "darwin" && ret.ninjaCommand == NINJA_SISO {
  ret.ninjaCommand = NINJA_NINJA
 }

 if value, ok := ret.environ.Get("SOONG_ONLY"); ok && !ret.skipKatiControlledByFlags {
  if value == "true" || value == "1" || value == "y" || value == "yes" {
   ret.soongOnlyRequested = true
   ret.skipKatiControlledByFlags = true
   ret.skipKati = true
   ret.skipKatiNinja = true
  } else {
   ret.skipKatiControlledByFlags = true
   ret.skipKati = false
   ret.skipKatiNinja = false
  }
 }

 if ret.environ.IsEnvTrue("SOONG_INCREMENTAL_ANALYSIS") {
  ret.incrementalBuildActions = true
  ret.incrementalBuildActionsSetInEnv = true
 } else if ret.environ.IsFalse("SOONG_INCREMENTAL_ANALYSIS") {
  ret.incrementalBuildActions = false
  ret.incrementalBuildActionsSetInEnv = true
 }

 if value, ok := ret.environ.Get("SOONG_PARTIAL_ANALYSIS"); ok {
  ret.partialAnalysisTargets = value
 }

 if ret.ninjaWeightListSource == HINT_FROM_SOONG {
  ret.environ.Set("SOONG_GENERATES_NINJA_HINT", "always")
 } else if ret.ninjaWeightListSource == DEFAULT {
  defaultNinjaWeightListSource := NINJA_LOG
  if _, err := os.Stat(filepath.Join(ret.OutDir(), ninjaLogFileName)); errors.Is(err, os.ErrNotExist) {
   ctx.Verboseln("$OUT/.ninja_log doesn't exist, use HINT_FROM_SOONG instead")
   defaultNinjaWeightListSource = HINT_FROM_SOONG
  } else {
   ctx.Verboseln("$OUT/.ninja_log exist, use NINJA_LOG")
  }
  ret.ninjaWeightListSource = defaultNinjaWeightListSource
  // soong_build generates ninja hint depending on ninja log existence.
  // Set it "depend" to avoid soong re-run due to env variable change.
  ret.environ.Set("SOONG_GENERATES_NINJA_HINT", "depend")
 }

 // Make sure OUT_DIR is set appropriately
 if outDir, ok := ret.environ.Get("OUT_DIR"); ok {
  ret.environ.Set("OUT_DIR", filepath.Clean(outDir))
 } else {
  outDir := "out"
  if baseDir, ok := ret.environ.Get("OUT_DIR_COMMON_BASE"); ok {
   outDir = filepath.Join(baseDir, filepath.Base(wd))
  }
  ret.environ.Set("OUT_DIR", outDir)
 }

 // loadEnvConfig needs to know what the OUT_DIR is, so it should
 // be called after we determine the appropriate out directory.
 bc := os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG")

 if bc != "" {
  if err := loadEnvConfig(ctx, ret, bc); err != nil {
   ctx.Fatalln("Failed to parse env config files: %v", err)
  }
  if !ret.canSupportRBE() {
   // Explicitly set USE_RBE env variable to false when we cannot run
   // an RBE build to avoid ninja local execution pool issues.
   ret.environ.Set("USE_RBE", "false")
  }
 }

 rbeValue, rbeOk := ret.environ.GetBool("USE_RBE")
 if rbeOk {
  // Normalize USE_RBE to "true" or "false", because product config and other
  // makefiles only support those values.
  ret.environ.SetBool("USE_RBE", rbeValue)
 }
 rewrapperValue, rewrapperOk := ret.environ.GetBool("USE_REWRAPPER")
 if rewrapperOk {
  // Normalize USE_REWRAPPER to "true" or "false", because product config and
  // other makefiles only support those values.
  ret.environ.SetBool("USE_REWRAPPER", rewrapperValue)
 }

 if ret.ninjaCommand != NINJA_SISO {
  // If we are not using Siso, force USE_REWRAPPER to be the same as USE_RBE.
  if rbeValue != rewrapperValue {
   if rbeOk {
    ret.environ.SetBool("USE_REWRAPPER", rbeValue)
   } else {
    ret.environ.Unset("USE_REWRAPPER")
   }
  }
 } else {
  // If we are using Siso, force USE_REWRAPPER=false when USE_RBE is not "true".
  // These are separate only for Siso.
  if !rbeValue && rewrapperValue {
   // If not using RBE, don't use rewrapper.
   ret.environ.Set("USE_REWRAPPER", "false")
  } else if rbeValue && !rewrapperOk {
   // Always have a value in USE_REWRAPPER when USE_RBE is true.
   ret.environ.SetBool("USE_REWRAPPER", ret.UseRewrapper())
  }
  // Otherwise, rbeValue is true and USE_REWRAPPER was already set.
 }

 // Siso specific default values.
 if ret.ninjaCommand == NINJA_SISO {
  // If SISO_CONFIG_DIR is not set, set it to the default directory.
  if value, ok := ret.environ.Get("SISO_CONFIG_DIR"); ok {
   ret.sisoConfigDir = value
  } else {
   ret.sisoConfigDir = DEFAULT_SISO_CONFIG_DIR
  }
 }

 if distDir, ok := ret.environ.Get("DIST_DIR"); ok {
  ret.distDir = filepath.Clean(distDir)
 } else {
  ret.distDir = filepath.Join(ret.OutDir(), "dist")
 }

 ret.setupSandboxConfig(ctx, ret.environ.AsMap())

 if os.Getenv("GENERATE_SOONG_DEBUG") == "true" {
  ret.moduleDebugFile, _ = filepath.Abs(shared.JoinPath(ret.SoongOutDir(), "soong-debug-info.json"))
 }

 if os.Getenv("GENERATE_INCREMENTAL_DEBUG") == "true" {
  ret.incrementalDebugFile, _ = filepath.Abs(shared.JoinPath(ret.SoongOutDir(), "incremental-debug-info.json"))
 }

 // If SOONG_USE_PARTIAL_COMPILE is set, make it one of "true" or the empty string.
 // This simplifies the generated Ninja rules, so that they only need to check for the empty string.
 value, ok := ret.environ.Get("SOONG_USE_PARTIAL_COMPILE")
 if ok {
  if value == "true" || value == "1" || value == "y" || value == "yes" {
   value = "true"
  } else {
   value = ""
  }
  ret.environ.Set("SOONG_USE_PARTIAL_COMPILE", value)
 }

 if !ret.runCIPDProxyServerControlledByFlags {
  if value, ok := ret.environ.Get("SOONG_RUN_CIPD_PROXY_SERVER"); ok {
   parsedVal, err := strconv.ParseBool(value)
   if err == nil {
    ret.runCIPDProxyServer = parsedVal
    if !ret.runCIPDProxyServer {
     ctx.Verbosef("SOONG_RUN_CIPD_PROXY_SERVER=%q, disabling proxy", value)
    }
   } else {
    ctx.Verbosef("SOONG_RUN_CIPD_PROXY_SERVER (%q) is not a valid boolean", value)
   }
  }
 }

 ret.environ.Unset(
  // We're already using it
  "USE_SOONG_UI",

  // We should never use GOROOT/GOPATH from the shell environment
  "GOROOT",
  "GOPATH",

  // These should only come from Soong, not the environment.
  "CLANG",
  "CLANG_CXX",
  "CCC_CC",
  "CCC_CXX",

  // Used by the goma compiler wrapper, but should only be set by
  // gomacc
  "GOMACC_PATH",

  // We handle this above
  "OUT_DIR_COMMON_BASE",

  // This is handled above too, and set for individual commands later
  "DIST_DIR",

  // Variables that have caused problems in the past
  "BASH_ENV",
  "CDPATH",
  "DISPLAY",
  "GREP_OPTIONS",
  "JAVAC",
  "LEX",
  "NDK_ROOT",
  "POSIXLY_CORRECT",

  // Drop make flags
  "MAKEFLAGS",
  "MAKELEVEL",
  "MFLAGS",

  // Set in envsetup.sh, reset in makefiles
  "ANDROID_JAVA_TOOLCHAIN",

  // Set by envsetup.sh, but shouldn't be used inside the build because envsetup.sh is optional
  "ANDROID_BUILD_TOP",
  "ANDROID_HOST_OUT",
  "ANDROID_PRODUCT_OUT",
  "ANDROID_HOST_OUT_TESTCASES",
  "ANDROID_TARGET_OUT_TESTCASES",
  "ANDROID_TOOLCHAIN",
  "ANDROID_TOOLCHAIN_2ND_ARCH",
  "ANDROID_DEV_SCRIPTS",
  "ANDROID_EMULATOR_PREBUILTS",
  "ANDROID_PRE_BUILD_PATHS",

  // We read it here already, don't let others share in the fun
  "GENERATE_SOONG_DEBUG",
  "INCREMENTAL_SOONG_DEBUG",

  // Use config.ninjaCommand instead.
  "SOONG_NINJA",

  // Already incorporated into the config object
  "SOONG_ONLY",
 )

 // Tell python not to spam the source tree with .pyc files.
 ret.environ.Set("PYTHONDONTWRITEBYTECODE", "1")

 tmpDir := absPath(ctx, ret.TempDir())
 ret.environ.Set("TMPDIR", tmpDir)

 // Always set ASAN_SYMBOLIZER_PATH so that ASAN-based tools can symbolize any crashes
 symbolizerPath := filepath.Join("prebuilts/clang/host", ret.HostPrebuiltTag(),
  "llvm-binutils-stable/llvm-symbolizer")
 ret.environ.Set("ASAN_SYMBOLIZER_PATH", absPath(ctx, symbolizerPath))

 // Precondition: the current directory is the top of the source tree
 checkTopDir(ctx)

 srcDir := absPath(ctx, ".")
 if strings.ContainsRune(srcDir, ' ') {
  ctx.Println("You are building in a directory whose absolute path contains a space character:")
  ctx.Println()
  ctx.Printf("%q\n", srcDir)
  ctx.Println()
  ctx.Fatalln("Directory names containing spaces are not supported")
 }

 ret.metricsUploader = GetMetricsUploader(srcDir, ret.environ)

 if outDir := ret.OutDir(); strings.ContainsRune(outDir, ' ') {
  ctx.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:")
  ctx.Println()
  ctx.Printf("%q\n", outDir)
  ctx.Println()
  ctx.Fatalln("Directory names containing spaces are not supported")
 }

 if distDir := ret.RealDistDir(); strings.ContainsRune(distDir, ' ') {
  ctx.Println("The absolute path of your dist directory ($DIST_DIR) contains a space character:")
  ctx.Println()
  ctx.Printf("%q\n", distDir)
  ctx.Println()
  ctx.Fatalln("Directory names containing spaces are not supported")
 }

 ConfigJavaEnvironment(ctx, ret)

 // b/286885495https://bugzilla.redhat.com/show_bug.cgi?id=2227130: some versions of Fedora include patches
 // to unzip to enable zipbomb detection that incorrectly handle zip64 and data descriptors and fail on large
 // zip files produced by soong_zip.  Disable zipbomb detection.
 ret.environ.Set("UNZIP_DISABLE_ZIPBOMB_DETECTION", "TRUE")

 outDir := ret.OutDir()
 buildDateTimeFile := filepath.Join(outDir, "build_date.txt")
 if buildDateTime, ok := ret.environ.Get("BUILD_DATETIME"); ok && buildDateTime != "" {
  ret.buildDateTime = buildDateTime
 } else {
  ret.buildDateTime = strconv.FormatInt(time.Now().Unix(), 10)
 }

 ret.environ.Set("BUILD_DATETIME_FILE", buildDateTimeFile)

 if _, ok := ret.environ.Get("BUILD_USERNAME"); !ok {
  username := "unknown"
  if u, err := user.Current(); err == nil {
   username = u.Username
  } else {
   ctx.Println("Failed to get current user:", err)
  }
  ret.environ.Set("BUILD_USERNAME", username)
 }
 ret.environ.Set("PWD", wd)

 if ret.UseRBE() {
  for k, v := range getRBEVars(ctx, Config{ret}) {
   ret.environ.Set(k, v)
  }
 }
 ret.updateSisoConfigVars(map[string]string{})

 // BUILD_NUMBER should be set to the source control value that
 // represents the current state of the source code.  E.g., a
 // perforce changelist number or a git hash.  Can be an arbitrary string
 // (to allow for source control that uses something other than numbers),
 // but must be a single word and a valid file name.
 //
 // If no BUILD_NUMBER is set, create a useful "I am an engineering build"
 // value.  Make it start with a non-digit so that anyone trying to parse
 // it as an integer will probably get "0". This value used to contain
 // a timestamp, but now that more dependencies are tracked in order to
 // reduce the importance of `m installclean`, changing it every build
 // causes unnecessary rebuilds for local development.
 buildNumber, ok := ret.environ.Get("BUILD_NUMBER")
 var distFileNameTag string
 if ok {
  distFileNameTag = buildNumber
 } else {
  var username string
  if username, ok = ret.environ.Get("BUILD_USERNAME"); !ok {
   ctx.Fatalln("Missing BUILD_USERNAME")
  }
  buildNumber = fmt.Sprintf("eng.%.6s", username)
  distFileNameTag = username
 }

 ret.buildNumber = buildNumber
 ret.distFileNameTag = distFileNameTag

 c := Config{ret}
 storeConfigMetrics(ctx, c)
 return c
}

// Update or create the config variables for Siso.
//
// This is called in newConfig() to ensure that siso_config parses.
// It is then called in dumpMakeVars() to set values based on product config.
func (config *configImpl) updateSisoConfigVars(makeVars map[string]string) {
 if config.SisoStringVars == nil {
  config.SisoStringVars = make(map[string]string)
 }
 for _, k := range sisoStringVars {
  // If the variable is not found, we want to set it to the empty string.
  config.SisoStringVars[k] = makeVars[k]
 }
 if config.IsActionSandboxedBuild() {
  config.SisoStringVars["nsjail_path"] = config.PrebuiltBuildTool("nsjail")
 }
 config.SisoBoolVars = map[string]bool{
  "use_rbe":      config.UseRBE(),
  "use_reclient": config.UseRewrapper(),
 }
}

func ConfigJavaEnvironment(ctx Context, config *configImpl) {
 // Configure Java-related variables, including adding it to $PATH
 java8Home := filepath.Join("prebuilts/jdk/jdk8", config.HostPrebuiltTag())
 java21Home := filepath.Join("prebuilts/jdk/jdk21", config.HostPrebuiltTag())
 java25Home := filepath.Join("prebuilts/jdk/jdk25", config.HostPrebuiltTag())
 javaHome := func() string {
  if override, ok := config.environ.Get("OVERRIDE_ANDROID_JAVA_HOME"); ok {
   return override
  }
  if toolchain11, ok := config.environ.Get("EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN"); ok && toolchain11 != "true" {
   ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
  }
  if toolchain17, ok := config.environ.Get("EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN"); ok && toolchain17 != "true" {
   ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
  }
  if toolchain21, ok := config.environ.Get("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN"); ok && toolchain21 != "true" {
   ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
  }
  if config.useJdk25 {
   return java25Home
  }
  return java21Home
 }()
 absJavaHome := absPath(ctx, javaHome)

 config.configureLocale(ctx)

 newPath := []string{filepath.Join(absJavaHome, "bin")}
 if path, ok := config.environ.Get("PATH"); ok && path != "" {
  newPath = append(newPath, path)
 }

 config.environ.Set("JAVA_HOME", absJavaHome)
 config.environ.Set("ANDROID_JAVA_HOME", javaHome)
 config.environ.Set("ANDROID_JAVA8_HOME", java8Home)
 config.environ.Set("PATH", strings.Join(newPath, string(filepath.ListSeparator)))
}

// NewBuildActionConfig returns a build configuration based on the build action. The arguments are
// processed based on the build action and extracts any arguments that belongs to the build action.
func NewBuildActionConfig(action BuildAction, dir string, ctx Context, args ...string) Config {
 return NewConfig(ctx, getConfigArgs(action, dir, ctx, args)...)
}

type earlyReleaseConfigInfo struct {
 // Map of VariableName: Value
 ValueMap map[string]string

 // Any error
 err error
}

// Prepare for getting make variables.  For them to be accurate, we need to have
// obtained PRODUCT_RELEASE_CONFIG_MAPS.
//
// Returns:
//
// chan to pass to CollectEarlyReleaseConfig to finish setting the value.
//
// TODO: when converting product config to a declarative language, make sure
// that PRODUCT_RELEASE_CONFIG_MAPS is properly handled as a separate step in
// that process.
func QueryEarlyReleaseConfig(ctx Context, config Config) chan *earlyReleaseConfigInfo {
 mapsCh := make(chan *earlyReleaseConfigInfo)
 go func() {
  defer close(mapsCh)
  getEarlyReleaseConfig(ctx, config, mapsCh)
 }()
 return mapsCh
}

var earlyReleaseConfigVars = []string{
 "PRODUCT_RELEASE_CONFIG_MAPS",
}

func getEarlyReleaseConfig(ctx Context, config Config, mapsCh chan *earlyReleaseConfigInfo) {
 e := ctx.BeginTrace(metrics.RunKati, "CollectEarlyReleaseConfig")
 defer e.End()

 ret := &earlyReleaseConfigInfo{}

 if config.SkipConfig() {
  // This duplicates the logic from Build to skip product config
  // if the user has explicitly said to.
  return
 }

 // Get the PRODUCT_RELEASE_CONFIG_MAPS for this product, to avoid polluting the environment
 // when we run product config to get the rest of the make vars.
 earlyVars, err := dumpMakeVars(ctx, config, nil, earlyReleaseConfigVars, "", DUMPVARS_PRE_CONFIG)
 if err != nil {
  ret.err = err
 } else {
  ret.ValueMap = earlyVars
 }
 mapsCh <- ret
}

// Wait for the Query to finish, and set PRODUCT_RELEASE_CONFIG_MAPS in the environment.
func CollectEarlyReleaseConfig(ctx Context, config Config, mapsCh chan *earlyReleaseConfigInfo) {
 if config.SkipConfig() {
  return
 }
 earlyVars := <-mapsCh
 if earlyVars.err != nil {
  ctx.Fatalln("Error getting 'pre product config' release config:", earlyVars.err)
 }
 config.earlyVars = earlyVars.ValueMap
 config.Environment().Set("PRODUCT_RELEASE_CONFIG_MAPS", config.earlyVars["PRODUCT_RELEASE_CONFIG_MAPS"])
}

func (config *configImpl) setupSandboxConfig(ctx Context, makeVars map[string]string) {
 config.sandboxConfig.SetSrcDirIsRO(makeVars["BUILD_BROKEN_SRC_DIR_IS_WRITABLE"] != "true")
 config.sandboxConfig.SetSrcDirRWAllowlist(strings.Fields(makeVars["BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST"]))
}

// storeConfigMetrics selects a set of configuration information and store in
// the metrics system for further analysis.
func storeConfigMetrics(ctx Context, config Config) {
 if ctx.Metrics == nil {
  return
 }

 ctx.Metrics.BuildConfig(buildConfig(config))

 cpuInfo := &smpb.SystemCpuInfo{
  VendorId:  proto.String(config.systemCpuInfo.VendorId),
  ModelName: proto.String(config.systemCpuInfo.ModelName),
  CpuCores:  proto.Int32(config.systemCpuInfo.CpuCores),
  Flags:     proto.String(config.systemCpuInfo.Flags),
 }
 memInfo := &smpb.SystemMemInfo{
  MemTotal:     proto.Uint64(config.systemMemInfo.MemTotal),
  MemFree:      proto.Uint64(config.systemMemInfo.MemFree),
  MemAvailable: proto.Uint64(config.systemMemInfo.MemAvailable),
 }

 s := &smpb.SystemResourceInfo{
  TotalPhysicalMemory: proto.Uint64(config.TotalRAM()),
  AvailableCpus:       proto.Int32(int32(runtime.NumCPU())),
  CpuInfo:             cpuInfo,
  MemInfo:             memInfo,
 }
 ctx.Metrics.SystemResourceInfo(s)
}

func getNinjaWeightListSourceInMetric(s NinjaWeightListSource) *smpb.BuildConfig_NinjaWeightListSource {
 switch s {
 case NINJA_LOG:
  return smpb.BuildConfig_NINJA_LOG.Enum()
 case EVENLY_DISTRIBUTED:
  return smpb.BuildConfig_EVENLY_DISTRIBUTED.Enum()
 case EXTERNAL_FILE:
  return smpb.BuildConfig_EXTERNAL_FILE.Enum()
 case HINT_FROM_SOONG:
  return smpb.BuildConfig_HINT_FROM_SOONG.Enum()
 default:
  return smpb.BuildConfig_NOT_USED.Enum()
 }
}

// getCartfsCopiedOutDir returns true if .cartfs-copied file exists in the out
// directory.
func getCartfsFirstBuildCopiedOutDir(ourDir string) bool {
 cartfsCopiedFile := filepath.Join(ourDir, ".cartfs-copied")
 _, statErr := os.Stat(cartfsCopiedFile)

 if errors.Is(statErr, os.ErrNotExist) {
  return false
 }

 if statErr != nil {
  bugLink := fmt.Sprintf("http://go/dx-source-bug?title=Error+statting+.cartfs-copied+file&assignee=samclewis@google.com&cc=samclewis@google.com&cc=ajp@google.com&description=%s", statErr.Error())
  fmt.Fprintf(os.Stderr, `
Error statting .cartfs-copied file. Please do two things:
1) manually delete out/.cartfs-copied and retry the build.
2) click this link to submit a prefilled bug report: %s
`, bugLink)
  os.Exit(1)
 }

 // If we reach here, statErr was nil, meaning the file exists.
 // Attempt to remove it before returning true.
 if removeErr := os.Remove(cartfsCopiedFile); removeErr != nil {
  // It's unlikely we will ever hit this, but if we do, let's make it clear
  // how to fix it and make it easy to report the bug.
  bugLink := fmt.Sprintf("http://go/dx-source-bug?title=Error+removing+.cartfs-copied+file&assignee=samclewis@google.com&cc=samclewis@google.com&cc=ajp@google.com&description=%s", removeErr.Error())
  fmt.Fprintf(os.Stderr, `
Error removing .cartfs-copied file. Please do two things:
1) manually delete out/.cartfs-copied and retry the build.
2) click this link to submit a prefilled bug report: %s
`, bugLink)
  os.Exit(1)
 }

 return true
}

func buildConfig(config Config) *smpb.BuildConfig {
 var soongEnvVars *smpb.SoongEnvVars
 ensure := func() *smpb.SoongEnvVars {
  // Create soongEnvVars if it doesn't already exist.
  if soongEnvVars == nil {
   soongEnvVars = &smpb.SoongEnvVars{}
  }
  return soongEnvVars
 }
 if value, ok := config.environ.Get("SOONG_PARTIAL_COMPILE"); ok {
  ensure().PartialCompile = proto.String(value)
 }
 if value, ok := config.environ.Get("SOONG_USE_PARTIAL_COMPILE"); ok {
  ensure().UsePartialCompile = proto.String(value)
 }
 if value, ok := config.environ.Get("NETWORK_FILE_SYSTEM_TYPE"); ok {
  ensure().NetworkFileSystemType = proto.String(value)
 }
 if value, ok := config.environ.Get("METRICS_BUILD_TRIGGER"); ok {
  ensure().BuildTrigger = proto.String(value)
 }
 if value, ok := config.environ.Get("SOONG_INCREMENTAL_ANALYSIS"); ok {
  ensure().SoongIncrementalAnalysis = proto.String(value)
 }
 ensure().SoongNinja = proto.String(config.ninjaCommand.String())
 c := &smpb.BuildConfig{
  UseRbe:                       proto.Bool(config.UseRBE()),
  UseRewrapper:                 proto.Bool(config.UseRewrapper()),
  NinjaWeightListSource:        getNinjaWeightListSourceInMetric(config.NinjaWeightListSource()),
  SoongEnvVars:                 soongEnvVars,
  SoongOnly:                    proto.Bool(config.soongOnlyRequested),
  CartfsFirstBuildCopiedOutDir: proto.Bool(getCartfsFirstBuildCopiedOutDir(config.OutDir())),
 }
 c.Targets = append(c.Targets, config.arguments...)

 return c
}

// getConfigArgs processes the command arguments based on the build action and creates a set of new
// arguments to be accepted by Config.
func getConfigArgs(action BuildAction, dir string, ctx Context, args []string) []string {
 // The next block of code verifies that the current directory is the root directory of the source
 // tree. It then finds the relative path of dir based on the root directory of the source tree
 // and verify that dir is inside of the source tree.
 checkTopDir(ctx)
 topDir, err := os.Getwd()
 if err != nil {
  ctx.Fatalf("Error retrieving top directory: %v", err)
 }
 dir, err = filepath.EvalSymlinks(dir)
 if err != nil {
  ctx.Fatalf("Unable to evaluate symlink of %s: %v", dir, err)
 }
 dir, err = filepath.Abs(dir)
 if err != nil {
  ctx.Fatalf("Unable to find absolute path %s: %v", dir, err)
 }
 relDir, err := filepath.Rel(topDir, dir)
 if err != nil {
  ctx.Fatalf("Unable to find relative path %s of %s: %v", relDir, topDir, err)
 }
 // If there are ".." in the path, it's not in the source tree.
 if strings.Contains(relDir, "..") {
  ctx.Fatalf("Directory %s is not under the source tree %s", dir, topDir)
 }

 configArgs := args[:]

 // If the arguments contains GET-INSTALL-PATH, change the target name prefix from MODULES-IN- to
 // GET-INSTALL-PATH-IN- to extract the installation path instead of building the modules.
 targetNamePrefix := "MODULES-IN-"
 if inList("GET-INSTALL-PATH", configArgs) {
  targetNamePrefix = "GET-INSTALL-PATH-IN-"
  configArgs = removeFromList("GET-INSTALL-PATH", configArgs)
 }

 var targets []string

 switch action {
 case BUILD_MODULES:
  // No additional processing is required when building a list of specific modules or all modules.
 case BUILD_MODULES_IN_A_DIRECTORY:
  // If dir is the root source tree, all the modules are built of the source tree are built so
  // no need to find the build file.
  if topDir == dir {
   break
  }

  buildFile := findBuildFile(ctx, relDir)
  if buildFile == "" {
   ctx.Fatalf("Build file not found for %s directory", relDir)
  }
  targets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
 case BUILD_MODULES_IN_DIRECTORIES:
  newConfigArgs, dirs := splitArgs(configArgs)
  configArgs = newConfigArgs
  targets = getTargetsFromDirs(ctx, relDir, dirs, targetNamePrefix)
 }

 // Tidy only override all other specified targets.
 tidyOnly := os.Getenv("WITH_TIDY_ONLY")
 if tidyOnly == "true" || tidyOnly == "1" {
  configArgs = append(configArgs, "tidy_only")
 } else {
  configArgs = append(configArgs, targets...)
 }

 return configArgs
}

// convertToTarget replaces "/" to "-" in dir and pre-append the targetNamePrefix to the target name.
func convertToTarget(dir string, targetNamePrefix string) string {
 return targetNamePrefix + strings.ReplaceAll(dir, "/", "-")
}

// hasBuildFile returns true if dir contains an Android build file.
func hasBuildFile(ctx Context, dir string) bool {
 for _, buildFile := range buildFiles {
  _, err := os.Stat(filepath.Join(dir, buildFile))
  if err == nil {
   return true
  }
  if !os.IsNotExist(err) {
   ctx.Fatalf("Error retrieving the build file stats: %v", err)
  }
 }
 return false
}

// findBuildFile finds a build file (makefile or blueprint file) by looking if there is a build file
// in the current and any sub directory of dir. If a build file is not found, traverse the path
// up by one directory and repeat again until either a build file is found or reached to the root
// source tree. The returned filename of build file is "Android.mk". If one was not found, a blank
// string is returned.
func findBuildFile(ctx Context, dir string) string {
 // If the string is empty or ".", assume it is top directory of the source tree.
 if dir == "" || dir == "." {
  return ""
 }

 found := false
 for buildDir := dir; buildDir != "."; buildDir = filepath.Dir(buildDir) {
  err := filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
   if err != nil {
    return err
   }
   if found {
    return filepath.SkipDir
   }
   if info.IsDir() {
    return nil
   }
   for _, buildFile := range buildFiles {
    if info.Name() == buildFile {
     found = true
     return filepath.SkipDir
    }
   }
   return nil
  })
  if err != nil {
   ctx.Fatalf("Error finding Android build file: %v", err)
  }

  if found {
   return filepath.Join(buildDir, "Android.mk")
  }
 }

 return ""
}

// splitArgs iterates over the arguments list and splits into two lists: arguments and directories.
func splitArgs(args []string) (newArgs []string, dirs []string) {
 specialArgs := map[string]bool{
  "showcommands": true,
  "snod":         true,
  "dist":         true,
  "checkbuild":   true,
 }

 newArgs = []string{}
 dirs = []string{}

 for _, arg := range args {
  // It's a dash argument if it starts with "-" or it's a key=value pair, it's not a directory.
  if strings.IndexRune(arg, '-') == 0 || strings.IndexRune(arg, '=') != -1 {
   newArgs = append(newArgs, arg)
   continue
  }

  if _, ok := specialArgs[arg]; ok {
   newArgs = append(newArgs, arg)
   continue
  }

  dirs = append(dirs, arg)
 }

 return newArgs, dirs
}

// getTargetsFromDirs iterates over the dirs list and creates a list of targets to build. If a
// directory from the dirs list does not exist, a fatal error is raised. relDir is related to the
// source root tree where the build action command was invoked. Each directory is validated if the
// build file can be found and follows the format "dir1:target1,target2,...". Target is optional.
func getTargetsFromDirs(ctx Context, relDir string, dirs []string, targetNamePrefix string) (targets []string) {
 for _, dir := range dirs {
  // The directory may have specified specific modules to build. ":" is the separator to separate
  // the directory and the list of modules.
  s := strings.Split(dir, ":")
  l := len(s)
  if l > 2 { // more than one ":" was specified.
   ctx.Fatalf("%s not in proper directory:target1,target2,... format (\":\" was specified more than once)", dir)
  }

  dir = filepath.Join(relDir, s[0])
  if _, err := os.Stat(dir); err != nil {
   ctx.Fatalf("couldn't find directory %s", dir)
  }

  // Verify that if there are any targets specified after ":". Each target is separated by ",".
  var newTargets []string
  if l == 2 && s[1] != "" {
   newTargets = strings.Split(s[1], ",")
   if inList("", newTargets) {
    ctx.Fatalf("%s not in proper directory:target1,target2,... format", dir)
   }
  }

  // If there are specified targets to build in dir, an android build file must exist for the one
  // shot build. For the non-targets case, find the appropriate build file and build all the
  // modules in dir (or the closest one in the dir path).
  if len(newTargets) > 0 {
   if !hasBuildFile(ctx, dir) {
    ctx.Fatalf("Couldn't locate a build file from %s directory", dir)
   }
  } else {
   buildFile := findBuildFile(ctx, dir)
   if buildFile == "" {
    ctx.Fatalf("Build file not found for %s directory", dir)
   }
   newTargets = []string{convertToTarget(filepath.Dir(buildFile), targetNamePrefix)}
  }

  targets = append(targets, newTargets...)
 }

 return targets
}

func (c *configImpl) EnforceNoReanalysis() bool {
 return c.enforceNoReanalysis || c.Environment().IsEnvTrue("SOONG_ENFORCE_NO_REANALYSIS")
}

func (c *configImpl) parseArgs(ctx Context, args []string) {
 for i := 0; i < len(args); i++ {
  arg := strings.TrimSpace(args[i])
  if arg == "showcommands" {
   c.verbose = true
  } else if arg == "--empty-ninja-file" {
   c.emptyNinjaFile = true
  } else if arg == "--skip-ninja" {
   c.skipNinja = true
  } else if arg == "--soong-only" {
   if c.skipKatiControlledByFlags {
    ctx.Fatalf("Cannot specify both --soong-only and --no-soong-only")
   }
   c.soongOnlyRequested = true
   c.skipKatiControlledByFlags = true
   c.skipKati = true
   c.skipKatiNinja = true
  } else if arg == "--no-soong-only" {
   if c.skipKatiControlledByFlags {
    ctx.Fatalf("Cannot specify both --soong-only and --no-soong-only")
   }
   c.skipKatiControlledByFlags = true
   c.skipKati = false
   c.skipKatiNinja = false
  } else if arg == "--config-only" {
   c.skipKati = true
   c.skipKatiNinja = true
   c.skipSoong = true
  } else if arg == "--skip-config" {
   c.skipConfig = true
  } else if arg == "--skip-soong-tests" {
   c.skipSoongTests = true
  } else if arg == "--no-skip-soong-tests" {
   c.skipSoongTests = false
  } else if arg == "--skip-metrics-upload" {
   c.skipMetricsUpload = true
  } else if arg == "--mk-metrics" {
   c.reportMkMetrics = true
  } else if strings.HasPrefix(arg, "--ninja_weight_source=") {
   source := strings.TrimPrefix(arg, "--ninja_weight_source=")
   if source == "ninja_log" {
    c.ninjaWeightListSource = NINJA_LOG
   } else if source == "evenly_distributed" {
    c.ninjaWeightListSource = EVENLY_DISTRIBUTED
   } else if source == "not_used" {
    c.ninjaWeightListSource = NOT_USED
   } else if source == "soong" {
    c.ninjaWeightListSource = HINT_FROM_SOONG
   } else if strings.HasPrefix(source, "file,") {
    c.ninjaWeightListSource = EXTERNAL_FILE
    filePath := strings.TrimPrefix(source, "file,")
    err := validateNinjaWeightList(filePath)
    if err != nil {
     ctx.Fatalf("Malformed weight list from %s: %s", filePath, err)
    }
    _, err = copyFile(filePath, filepath.Join(c.OutDir(), ".ninja_weight_list"))
    if err != nil {
     ctx.Fatalf("Error to copy ninja weight list from %s: %s", filePath, err)
    }
   } else {
    ctx.Fatalf("unknown option for ninja_weight_source: %s", source)
   }
  } else if arg == "--build-from-source-stub" {
   c.buildFromSourceStub = true
  } else if arg == "--incremental-build-actions" {
   c.incrementalBuildActions = true
  } else if arg == "--incremental-provider-test" {
   c.incrementalProviderTest = true
  } else if strings.HasPrefix(arg, "--build-command=") {
   buildCmd := strings.TrimPrefix(arg, "--build-command=")
   // remove quotations
   buildCmd = strings.TrimPrefix(buildCmd, "\"")
   buildCmd = strings.TrimSuffix(buildCmd, "\"")
   ctx.Metrics.SetBuildCommand([]string{buildCmd})
  } else if strings.HasPrefix(arg, "--build-started-time-unix-millis=") {
   buildTimeStr := strings.TrimPrefix(arg, "--build-started-time-unix-millis=")
   val, err := strconv.ParseInt(buildTimeStr, 1064)
   if err == nil {
    c.buildStartedTime = val
   } else {
    ctx.Fatalf("Error parsing build-time-started-unix-millis", err)
   }
  } else if arg == "--ensure-allowlist-integrity" {
   c.ensureAllowlistIntegrity = true
  } else if arg == "--enforce-no-reanalysis" {
   c.enforceNoReanalysis = true
  } else if arg == "--run-cipd-proxy-server" {
   c.runCIPDProxyServer = true
   c.runCIPDProxyServerControlledByFlags = true
  } else if arg == "--no-run-cipd-proxy-server" {
   c.runCIPDProxyServer = false
   c.runCIPDProxyServerControlledByFlags = true
   ctx.Verbosef("Disabling CIPD proxy due to --no-run-cipd-proxy-server")
  } else if len(arg) > 0 && arg[0] == '-' {
   parseArgNum := func(def int) int {
    if len(arg) > 2 {
     p, err := strconv.ParseUint(arg[2:], 1031)
     if err != nil {
      ctx.Fatalf("Failed to parse %q: %v", arg, err)
     }
     return int(p)
    } else if i+1 < len(args) {
     p, err := strconv.ParseUint(args[i+1], 1031)
     if err == nil {
      i++
      return int(p)
     }
    }
    return def
   }

   if len(arg) > 1 && arg[1] == 'j' {
    c.parallel = parseArgNum(c.parallel)
   } else if len(arg) > 1 && arg[1] == 'k' {
    c.keepGoing = parseArgNum(0)
   } else {
    ctx.Fatalln("Unknown option:", arg)
   }
  } else if k, v, ok := decodeKeyValue(arg); ok && len(k) > 0 {
   if k == "OUT_DIR" {
    ctx.Fatalln("OUT_DIR may only be set in the environment, not as a command line option.")
   }
   c.environ.Set(k, v)
  } else if arg == "dist" {
   c.dist = true
  } else if arg == "json-module-graph" {
   c.jsonModuleGraph = true
  } else if arg == "soong_docs" {
   c.soongDocs = true
  } else {
   if arg == "checkbuild" {
    c.checkbuild = true
   }
   c.arguments = append(c.arguments, arg)
  }
 }
}

func validateNinjaWeightList(weightListFilePath string) (err error) {
 data, err := os.ReadFile(weightListFilePath)
 if err != nil {
  return
 }
 lines := strings.Split(strings.TrimSpace(string(data)), "\n")
 for _, line := range lines {
  fields := strings.Split(line, ",")
  if len(fields) != 2 {
   return fmt.Errorf("wrong format, each line should have two fields, but '%s'", line)
  }
  _, err = strconv.Atoi(fields[1])
  if err != nil {
   return
  }
 }
 return
}

func (c *configImpl) configureLocale(ctx Context) {
 cmd := Command(ctx, Config{c}, nil, "locale", "locale", "-a")
 output, err := cmd.Output()

 var locales []string
 if err == nil {
  locales = strings.Split(string(output), "\n")
 } else {
  // If we're unable to list the locales, let's assume en_US.UTF-8
  locales = []string{"en_US.UTF-8"}
  ctx.Verbosef("Failed to list locales (%q), falling back to %q", err, locales)
 }

 // gettext uses LANGUAGE, which is passed directly through

 // For LANG and LC_*, only preserve the evaluated version of
 // LC_MESSAGES
 userLang := ""
 if lc_all, ok := c.environ.Get("LC_ALL"); ok {
  userLang = lc_all
 } else if lc_messages, ok := c.environ.Get("LC_MESSAGES"); ok {
  userLang = lc_messages
 } else if lang, ok := c.environ.Get("LANG"); ok {
  userLang = lang
 }

 c.environ.UnsetWithPrefix("LC_")

 if userLang != "" {
  c.environ.Set("LC_MESSAGES", userLang)
 }

 // The for LANG, use C.UTF-8 if it exists (Debian currently, proposed
 // for others)
 if inList("C.UTF-8", locales) {
  c.environ.Set("LANG", "C.UTF-8")
 } else if inList("C.utf8", locales) {
  // These normalize to the same thing
  c.environ.Set("LANG", "C.UTF-8")
 } else if inList("en_US.UTF-8", locales) {
  c.environ.Set("LANG", "en_US.UTF-8")
 } else if inList("en_US.utf8", locales) {
  // These normalize to the same thing
  c.environ.Set("LANG", "en_US.UTF-8")
 } else {
  ctx.Fatalln("System doesn't support either C.UTF-8 or en_US.UTF-8")
 }
}

func (c *configImpl) Environment() *Environment {
 return c.environ
}

func (c *configImpl) Arguments() []string {
 return c.arguments
}

func (c *configImpl) SoongBuildInvocationNeeded() bool {
 if len(c.Arguments()) > 0 {
  // Explicit targets requested that are not special targets like b2pbuild
  // or the JSON module graph
  return true
 }

 if !c.JsonModuleGraph() && !c.SoongDocs() {
  // Command line was empty, the default Ninja target is built
  return true
 }

 if c.Dist() {
  return true
 }

 // build.ninja doesn't need to be generated
 return false
}

func (c *configImpl) OutDir() string {
 if outDir, ok := c.environ.Get("OUT_DIR"); ok {
  return outDir
 }
 return "out"
}

func (c *configImpl) DistDir() string {
 return c.distDir
}

func (c *configImpl) RealDistDir() string {
 return c.distDir
}

func (c *configImpl) NinjaArgs() []string {
 if c.skipConfig {
  return append(c.arguments, c.ninjaArgs...)
 }
 return c.ninjaArgs
}

func (c *configImpl) SoongOutDir() string {
 return filepath.Join(c.OutDir(), "soong")
}

func (c *configImpl) PrebuiltOS() string {
 switch runtime.GOOS {
 case "linux":
  switch runtime.GOARCH {
  case "amd64":
   return "linux-x86"
  case "arm64":
   return "linux-arm64"
  default:
   panic(fmt.Errorf("Unknown GOARCH %s", runtime.GOARCH))
  }
 case "darwin":
  return "darwin-x86"
 default:
  panic(fmt.Errorf("Unknown GOOS %s", runtime.GOOS))
 }
}

func (c *configImpl) HostToolDir() string {
 return filepath.Join(c.OutDir(), "host", c.PrebuiltOS(), "bin")
}

func (c *configImpl) UsedEnvFile(tag string) string {
 if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
  return shared.JoinPath(c.SoongOutDir(), usedEnvFile+"."+v+c.CoverageSuffix()+"."+tag)
 }
 return shared.JoinPath(c.SoongOutDir(), usedEnvFile+"."+tag)
}

func (c *configImpl) SoongDocsHtml() string {
 return shared.JoinPath(c.SoongOutDir(), "docs/soong_build.html")
}

func (c *configImpl) ModuleGraphFile() string {
 return shared.JoinPath(c.SoongOutDir(), "module-graph.json")
}

func (c *configImpl) ModuleActionsFile() string {
 return shared.JoinPath(c.SoongOutDir(), "module-actions.json")
}

func (c *configImpl) TempDir() string {
 return shared.TempDirForOutDir(c.SoongOutDir())
}

func (c *configImpl) FileListDir() string {
 return filepath.Join(c.OutDir(), ".module_paths")
}

func (c *configImpl) KatiSuffix() string {
 if c.katiSuffix != "" {
  return c.katiSuffix
 }
 panic("SetKatiSuffix has not been called")
}

// Checkbuild returns true if "checkbuild" was one of the build goals, which means that the
// user is interested in additional checks at the expense of build time.
func (c *configImpl) Checkbuild() bool {
 return c.checkbuild
}

func (c *configImpl) Dist() bool {
 return c.dist
}

func (c *configImpl) JsonModuleGraph() bool {
 return c.jsonModuleGraph
}

func (c *configImpl) SoongDocs() bool {
 return c.soongDocs
}

func (c *configImpl) IsVerbose() bool {
 return c.verbose
}

func (c *configImpl) NinjaWeightListSource() NinjaWeightListSource {
 return c.ninjaWeightListSource
}

func (c *configImpl) SkipKati() bool {
 return c.skipKati
}

func (c *configImpl) SkipKatiNinja() bool {
 return c.skipKatiNinja
}

func (c *configImpl) SkipSoong() bool {
 return c.skipSoong
}

func (c *configImpl) SkipNinja() bool {
 return c.skipNinja
}

func (c *configImpl) SetSkipNinja(v bool) {
 c.skipNinja = v
}

func (c *configImpl) SkipConfig() bool {
 return c.skipConfig
}

func (c *configImpl) BuildFromTextStub() bool {
 return !c.buildFromSourceStub
}

func (c *configImpl) TargetProduct() string {
 if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
  return v
 }
 panic("TARGET_PRODUCT is not defined")
}

func (c *configImpl) TargetProductOrErr() (string, error) {
 if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
  return v, nil
 }
 return "", fmt.Errorf("TARGET_PRODUCT is not defined")
}

func (c *configImpl) CoverageSuffix() string {
 if v := c.environ.IsEnvTrue("EMMA_INSTRUMENT"); v {
  return ".coverage"
 }
 return ""
}

func (c *configImpl) TargetDevice() string {
 if c.targetDevice == "" && c.skipConfig {
  // Integration tests using build/soong/tests/lib.sh run with --skip-config,
  // don't call runMakeProductConfig, and can't query the product config to
  // determine the device name.  Assume it's the same as the product name
  // instead.
  if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
   return v
  }
 }
 return c.targetDevice
}

func (c *configImpl) SetTargetDevice(device string) {
 c.targetDevice = device
}

func (c *configImpl) TargetBuildVariant() string {
 if v, ok := c.environ.Get("TARGET_BUILD_VARIANT"); ok {
  return v
 }
 // By default, TARGET_BUILD_VARIANT=eng.
 return "eng"
}

func (c *configImpl) KatiArgs() []string {
 return c.katiArgs
}

func (c *configImpl) Parallel() int {
 return c.parallel
}

func (c *configImpl) GetSourceRootDirs() []string {
 return c.sourceRootDirs
}

func (c *configImpl) SetSourceRootDirs(i []string) {
 c.sourceRootDirs = i
}

func (c *configImpl) GetLogsPrefix() string {
 return c.logsPrefix
}

func (c *configImpl) SetLogsPrefix(prefix string) {
 c.logsPrefix = prefix
}

func (c *configImpl) HighmemParallel() int {
 if i, ok := c.environ.GetInt("NINJA_HIGHMEM_NUM_JOBS"); ok {
  return i
 }

 const minMemPerHighmemProcess = 8 * 1024 * 1024 * 1024
 parallel := c.Parallel()
 if c.UseRemoteBuild() {
  // Ninja doesn't support nested pools, and when remote builds are enabled the total ninja parallelism
  // is set very high (i.e. 500).  Using a large value here would cause the total number of running jobs
  // to be the sum of the sizes of the local and highmem pools, which will cause extra CPU contention.
  // Return 1/16th of the size of the local pool, rounding up.
  return (parallel + 15) / 16
 } else if c.totalRAM == 0 {
  // Couldn't detect the total RAM, don't restrict highmem processes.
  return parallel
 } else if c.totalRAM <= 16*1024*1024*1024 {
  // Less than 16GB of ram, restrict to 1 highmem processes
  return 1
 } else if c.totalRAM <= 32*1024*1024*1024 {
  // Less than 32GB of ram, restrict to 2 highmem processes
  return 2
 } else if p := int(c.totalRAM / minMemPerHighmemProcess); p < parallel {
  // If less than 8GB total RAM per process, reduce the number of highmem processes
  return p
 }
 // No restriction on highmem processes
 return parallel
}

func (c *configImpl) TotalRAM() uint64 {
 return c.totalRAM
}

func (c *configImpl) canSupportRBE() bool {
 // Only supported on linux
 if runtime.GOOS != "linux" {
  return false
 }

 // Do not use RBE with prod credentials in scenarios when stubby doesn't exist, since
 // its unlikely that we will be able to obtain necessary creds without stubby.
 authType, _ := c.rbeAuth()
 if !c.StubbyExists() && strings.Contains(authType, "use_google_prod_creds") {
  return false
 }
 if c.UseABFS() {
  return false
 }
 return true
}

func (c *configImpl) UseABFS() (useABFS bool) {
 c.useABFSMu.Lock()
 defer c.useABFSMu.Unlock()

 if c.useABFS != nil {
  return *c.useABFS
 }

 defer func() {
  c.useABFS = new(bool)
  *c.useABFS = useABFS
 }()

 if c.ninjaCommand == NINJA_NINJAGO {
  return true
 }

 if v, ok := c.environ.Get("NO_ABFS"); ok {
  v = strings.ToLower(strings.TrimSpace(v))
  if v == "true" || v == "1" {
   return false
  }
 }

 abfsBox := c.PrebuiltBuildTool("abfsbox")
 err := exec.Command(abfsBox, "hash", srcDirFileCheck).Run()
 return err == nil
}

func (c *configImpl) UseRBE() bool {
 // These alternate modes of running Soong do not use RBE / reclient.
 if c.JsonModuleGraph() {
  return false
 }

 if !c.canSupportRBE() {
  return false
 }

 v, _ := c.Environment().GetBool("USE_RBE")
 return v
}

func (c *configImpl) UseRewrapper() bool {
 if !c.UseRBE() {
  return false
 }

 // If specified, return the value.
 if v, ok := c.Environment().GetBool("USE_REWRAPPER"); ok {
  return v
 }
 // Until the native reapi implementation is the default, use rewrapper.
 // TODO(b/450248289): Eventually, Siso defaults to false, others default to true.
 return c.ninjaCommand == NINJA_SISO || true
}

func (c *configImpl) UseRBEproxy() bool {
 return c.UseRBE() && !c.UseRewrapper()
}

func (c *configImpl) StartReproxy() bool {
 // Only start reproxy if we are using rewrapper.
 if !c.UseRewrapper() {
  return false
 }

 if v, ok := c.environ.Get("NOSTART_RBE"); ok {
  v = strings.TrimSpace(v)
  if v != "" && v != "false" {
   return false
  }
 }
 return true
}

func (c *configImpl) StartRBEproxy() bool {
 if !c.UseRBEproxy() {
  return false
 }

 if v, ok := c.environ.Get("NOSTART_RBE"); ok {
  v = strings.TrimSpace(v)
  if v != "" && v != "false" {
   return false
  }
 }
 return true
}

func (c *configImpl) rbeProxyLogsDir() string {
 for _, f := range []string{"RBE_proxy_log_dir", "FLAG_output_dir"} {
  if v, ok := c.environ.Get(f); ok {
   return v
  }
 }
 return c.rbeTmpDir()
}

func (c *configImpl) rbePlatform() string {
 for _, f := range []string{"RBE_platform", "FLAG_platform"} {
  if v, ok := c.environ.Get(f); ok {
   return v
  }
 }
 return defaultRBEPlatform
}

func (c *configImpl) rbeDownloadTmpDir() string {
 for _, f := range []string{"RBE_download_tmp_dir", "FLAG_download_tmp_dir"} {
  if v, ok := c.environ.Get(f); ok {
   return v
  }
 }
 return c.rbeTmpDir()
}

func (c *configImpl) rbeTmpDir() string {
 return filepath.Join(c.SoongOutDir(), "rbe")
}

func (c *configImpl) rbeCacheDir() string {
 for _, f := range []string{"RBE_cache_dir", "FLAG_cache_dir"} {
  if v, ok := c.environ.Get(f); ok {
   return v
  }
 }
 return shared.JoinPath(c.SoongOutDir(), "rbe")
}

func (c *configImpl) shouldCleanupRBELogsDir() bool {
 // Perform a log directory cleanup only when the log directory
 // is auto created by the build rather than user-specified.
 for _, f := range []string{"RBE_proxy_log_dir", "FLAG_output_dir"} {
  if v, ok := c.environ.Get(f); ok {
   if v != c.rbeTmpDir() {
    return false
   }
  }
 }
 return true
}

func (c *configImpl) rbeExecRoot() string {
 for _, f := range []string{"RBE_exec_root", "FLAG_exec_root"} {
  if v, ok := c.environ.Get(f); ok {
   return v
  }
 }
 wd, err := os.Getwd()
 if err != nil {
  return ""
 }
 return wd
}

func (c *configImpl) rbeDir() string {
 if v, ok := c.environ.Get("RBE_DIR"); ok {
  return v
 }
 return "prebuilts/remoteexecution-client/live/"
}

func (c *configImpl) rbeReproxy() string {
 for _, f := range []string{"RBE_re_proxy", "FLAG_re_proxy"} {
  if v, ok := c.environ.Get(f); ok {
   return v
  }
 }
 return filepath.Join(c.rbeDir(), "reproxy")
}

func (c *configImpl) rbeAuth() (string, string) {
 credFlags := []string{
  "credentials_helper",
  "use_application_default_credentials",
  "use_gce_credentials",
  "credential_file",
  "use_google_prod_creds",
 }
 for _, cf := range credFlags {
  for _, f := range []string{"RBE_" + cf, "FLAG_" + cf} {
   if v, ok := c.environ.Get(f); ok {
    v = strings.TrimSpace(v)
    if v != "" && v != "false" && v != "0" {
     return "RBE_" + cf, v
    }
   }
  }
 }
 return "RBE_use_application_default_credentials", "true"
}

func (c *configImpl) rbeSockAddr(dir string) (string, error) {
 // Absolute path socket addresses have a prefix of //. This should
 // be included in the length limit.
 maxNameLen := len(syscall.RawSockaddrUnix{}.Path) - 2
 base := fmt.Sprintf("reproxy_%v.sock", rbeRandPrefix)

 name := filepath.Join(dir, base)
 if len(name) < maxNameLen {
  return name, nil
 }

 name = filepath.Join("/tmp", base)
 if len(name) < maxNameLen {
  return name, nil
 }

 return "", fmt.Errorf("cannot generate a proxy socket address shorter than the limit of %v", maxNameLen)
}

// IsGooglerEnvironment returns true if the current build is running
// on a Google developer machine and false otherwise.
func (c *configImpl) IsGooglerEnvironment() bool {
 cf := "ANDROID_BUILD_ENVIRONMENT_CONFIG"
 if v, ok := c.environ.Get(cf); ok {
  return v == "googler"
 }
 return false
}

// GoogleProdCredsExist determine whether credentials exist on the
// Googler machine to use remote execution.
func (c *configImpl) GoogleProdCredsExist() bool {
 if googleProdCredsExistCache {
  return googleProdCredsExistCache
 }
 if _, err := exec.Command("/usr/bin/gcertstatus", "-nocheck_ssh").Output(); err != nil {
  return false
 }
 googleProdCredsExistCache = true
 return true
}

// UseRemoteBuild indicates whether to use a remote build acceleration system
// to speed up the build.
func (c *configImpl) UseRemoteBuild() bool {
 return c.UseRBE()
}

// StubbyExists checks whether the stubby binary exists on the machine running
// the build.
func (c *configImpl) StubbyExists() bool {
 if _, err := exec.LookPath("stubby"); err != nil {
  return false
 }
 return true
}

// RemoteParallel controls how many remote jobs (i.e., commands which contain
// rewrapper) are run in parallel.  Note the parallelism of all other jobs is
// still limited by Parallel()
func (c *configImpl) RemoteParallel() int {
 if !c.UseRemoteBuild() {
  return 0
 }
 if i, ok := c.environ.GetInt("NINJA_REMOTE_NUM_JOBS"); ok {
  return i
 }
 return 500
}

func (c *configImpl) SetKatiArgs(args []string) {
 c.katiArgs = args
}

func (c *configImpl) SetNinjaArgs(args []string) {
 c.ninjaArgs = args
}

func (c *configImpl) SetKatiSuffix(suffix string) {
 c.katiSuffix = suffix
}

func (c *configImpl) LastKatiSuffixFile() string {
 return filepath.Join(c.OutDir(), "last_kati_suffix")
}

func (c *configImpl) HasKatiSuffix() bool {
 return c.katiSuffix != ""
}

func (c *configImpl) KatiEnvFile() string {
 return filepath.Join(c.OutDir(), "env"+c.KatiSuffix()+".sh")
}

func (c *configImpl) KatiBuildNinjaFile() string {
 return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+katiBuildSuffix+".ninja")
}

func (c *configImpl) KatiPackageNinjaFile() string {
 return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+katiPackageSuffix+".ninja")
}

func (c *configImpl) SoongVarsFile() string {
 targetProduct, err := c.TargetProductOrErr()
 if err != nil {
  return filepath.Join(c.SoongOutDir(), "soong.variables")
 } else {
  return filepath.Join(c.SoongOutDir(), "soong."+targetProduct+c.CoverageSuffix()+".variables")
 }
}

func (c *configImpl) SoongExtraVarsFile() string {
 targetProduct, err := c.TargetProductOrErr()
 if err != nil {
  return filepath.Join(c.SoongOutDir(), "soong.extra.variables")
 } else {
  return filepath.Join(c.SoongOutDir(), "soong."+targetProduct+c.CoverageSuffix()+".extra.variables")
 }
}

func (c *configImpl) SoongNinjaFile() string {
 targetProduct, err := c.TargetProductOrErr()
 if err != nil {
  return filepath.Join(c.SoongOutDir(), "build.ninja")
 } else {
  return filepath.Join(c.SoongOutDir(), "build."+targetProduct+c.CoverageSuffix()+".ninja")
 }
}

func (c *configImpl) SoongPhonyNinjaFile() string {
 return strings.TrimSuffix(c.SoongNinjaFile(), ".ninja") + ".phony.ninja"
}

func (c *configImpl) SoongDistNinjaFile() string {
 return strings.TrimSuffix(c.SoongNinjaFile(), ".ninja") + ".dist.ninja"
}

func (c *configImpl) SoongNoDistNinjaFile() string {
 return strings.TrimSuffix(c.SoongNinjaFile(), ".ninja") + ".nodist.ninja"
}

func (c *configImpl) CombinedNinjaFile() string {
 if c.katiSuffix == "" {
  return filepath.Join(c.OutDir(), "combined.ninja")
 }
 return filepath.Join(c.OutDir(), "combined"+c.KatiSuffix()+".ninja")
}

func (c *configImpl) SoongAndroidMk() string {
 return filepath.Join(c.SoongOutDir(), "Android-"+c.TargetProduct()+c.CoverageSuffix()+".mk")
}

func (c *configImpl) SoongMakeVarsMk() string {
 return filepath.Join(c.SoongOutDir(), "make_vars-"+c.TargetProduct()+c.CoverageSuffix()+".mk")
}

func (c *configImpl) SoongBuildMetrics() string {
 return filepath.Join(c.LogsDir(), "soong_build_metrics.pb")
}

func (c *configImpl) ProductOut() string {
 return filepath.Join(c.OutDir(), "target", "product", c.TargetDevice())
}

func (c *configImpl) DevicePreviousProductConfig() string {
 return filepath.Join(c.ProductOut(), "previous_build_config.mk")
}

// This will be sourced by rules that use SOONG_USE_PARTIAL_COMPILE.
func (c *configImpl) DeviceUsePartialCompile() string {
 return filepath.Join(c.SoongOutDir(), "use_partial_compile-"+c.TargetDevice()+".sh")
}

func (c *configImpl) DevicePreviousUsePartialCompile() string {
 return filepath.Join(c.ProductOut(), "previous_use_partial_compile.txt")
}

func (c *configImpl) KatiPackageMkDir() string {
 return filepath.Join(c.SoongOutDir(), "kati_packaging"+c.KatiSuffix())
}

func (c *configImpl) hostOutRoot() string {
 return filepath.Join(c.OutDir(), "host")
}

func (c *configImpl) HostOut() string {
 return filepath.Join(c.hostOutRoot(), c.HostPrebuiltTag())
}

// This probably needs to be multi-valued, so not exporting it for now
func (c *configImpl) hostCrossOut() string {
 if runtime.GOOS == "linux" {
  return filepath.Join(c.hostOutRoot(), "windows-x86")
 } else {
  return ""
 }
}

func (c *configImpl) HostPrebuiltTag() string {
 return c.PrebuiltOS()
}

func (c *configImpl) KatiBin() string {
 binName := "ckati"
 if c.useRkati {
  binName = "rkati"
 }
 if c.UseABFS() {
  binName += "-wrap"
 }

 return c.PrebuiltBuildTool(binName)
}

func (c *configImpl) NinjaBin() string {
 binName := "ninja"
 if c.UseABFS() {
  binName = "ninjago"
 }
 return c.PrebuiltBuildTool(binName)
}

func (c *configImpl) N2Bin() string {
 path := c.PrebuiltBuildTool("n2")
 // Use musl instead of glibc because glibc on the build server is old and has bugs
 return strings.ReplaceAll(path, "/linux-x86/", "/linux_musl-x86/")
}

func (c *configImpl) SisoBin() string {
 // TODO(b/374176257): remove this once Siso is built from source.
 return filepath.Join("prebuilts/siso", c.HostPrebuiltTag(), "siso")

 path := c.PrebuiltBuildTool("siso")
 // Use musl instead of glibc because glibc on the build server is old and has bugs
 return strings.ReplaceAll(path, "/linux-x86/", "/linux_musl-x86/")
}

func (c *configImpl) PrebuiltBuildTool(name string) string {
 if c.environ.IsEnvTrue("SANITIZE_BUILD_TOOL_PREBUILTS") {
  asan := filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "asan/bin", name)
  if _, err := os.Stat(asan); err == nil {
   return asan
  }
 }
 return filepath.Join("prebuilts/build-tools", c.HostPrebuiltTag(), "bin", name)
}

func (c *configImpl) SetBuildBrokenDupRules(val bool) {
 c.brokenDupRules = val
}

func (c *configImpl) BuildBrokenDupRules() bool {
 return c.brokenDupRules
}

func (c *configImpl) SetBuildBrokenUsesNetwork(val bool) {
 c.brokenUsesNetwork = val
}

func (c *configImpl) BuildBrokenUsesNetwork() bool {
 return c.brokenUsesNetwork
}

func (c *configImpl) SetBuildBrokenNinjaUsesEnvVars(val []string) {
 c.brokenNinjaEnvVars = val
}

func (c *configImpl) BuildBrokenNinjaUsesEnvVars() []string {
 return c.brokenNinjaEnvVars
}

func (c *configImpl) SetBuildBrokenMissingOutputs(val bool) {
 c.brokenMissingOutputs = val
}

func (c *configImpl) BuildBrokenMissingOutputs() bool {
 return c.brokenMissingOutputs
}

func (c *configImpl) SetTargetDeviceDir(dir string) {
 c.targetDeviceDir = dir
}

func (c *configImpl) TargetDeviceDir() string {
 return c.targetDeviceDir
}

func (c *configImpl) BuildDateTime() string {
 return c.buildDateTime
}

func (c *configImpl) MetricsUploaderApp() string {
 return c.metricsUploader
}

// LogsDir returns the absolute path to the logs directory where build log and
// metrics files are located. By default, the logs directory is the out
// directory. If the argument dist is specified, the logs directory
// is <dist_dir>/logs.
func (c *configImpl) LogsDir() string {
 dir := c.OutDir()
 if c.Dist() {
  // Always write logs to the real dist dir, even if Bazel is using a rigged dist dir for other files
  dir = filepath.Join(c.RealDistDir(), "logs")
 }
 absDir, err := filepath.Abs(dir)
 if err != nil {
  fmt.Fprintf(os.Stderr, "\nError making log dir '%s' absolute: %s\n", dir, err.Error())
  os.Exit(1)
 }
 return absDir
}

// MkFileMetrics returns the file path for make-related metrics.
func (c *configImpl) MkMetrics() string {
 return filepath.Join(c.LogsDir(), "mk_metrics.pb")
}

func (c *configImpl) SisoConfigDir() string {
 return c.sisoConfigDir
}

func (c *configImpl) SetEmptyNinjaFile(v bool) {
 c.emptyNinjaFile = v
}

func (c *configImpl) EmptyNinjaFile() bool {
 return c.emptyNinjaFile
}

func (c *configImpl) SkipMetricsUpload() bool {
 // b/362625275 - Metrics upload sometimes prevents abfs unmount
 if c.UseABFS() {
  return true
 }

 return c.skipMetricsUpload
}

func (c *configImpl) EnsureAllowlistIntegrity() bool {
 return c.ensureAllowlistIntegrity
}

func (c *configImpl) RunCIPDProxyServer() bool {
 return c.runCIPDProxyServer
}

// Siso logs and metrics files.
func (c *configImpl) SisoConfigFile(isBootstrap bool) string {
 if isBootstrap {
  return filepath.Join(c.SoongOutDir(), ".siso_config")
 }
 return filepath.Join(c.OutDir(), ".siso_config")
}

func (c *configImpl) SisoDepsFile(isBootstrap bool) string {
 if isBootstrap {
  return filepath.Join(c.SoongOutDir(), ".siso_deps")
 }
 return filepath.Join(c.OutDir(), ".siso_deps")
}

func (c *configImpl) SisoFsStateFile(isBootstrap bool) string {
 if isBootstrap {
  return filepath.Join(c.SoongOutDir(), ".siso_fs_state")
 }
 return filepath.Join(c.OutDir(), ".siso_fs_state")
}

func (c *configImpl) SisoFilegroupsFile(isBootstrap bool) string {
 if isBootstrap {
  return filepath.Join(c.SoongOutDir(), ".siso_filegroups")
 }
 return filepath.Join(c.OutDir(), ".siso_filegroups")
}

// Returns a Time object if one was passed via a command-line flag.
// Otherwise returns the passed default.
func (c *configImpl) BuildStartedTimeOrDefault(defaultTime time.Time) time.Time {
 if c.buildStartedTime == 0 {
  return defaultTime
 }
 return time.UnixMilli(c.buildStartedTime)
}

func (c *configImpl) BuildUUIDFile() string {
 suffix := ""
 if targetProduct, err := c.TargetProductOrErr(); err == nil {
  suffix = "-" + targetProduct
 }
 return filepath.Join(c.SoongOutDir(), "build_uuid"+suffix+".txt")
}

func (c *configImpl) BuildNumber() string {
 return c.buildNumber
}

func (c *configImpl) DistFileNameTag() string {
 return c.distFileNameTag
}

func (c *configImpl) IsActionSandboxedBuild() bool {
 sandboxing, ok := c.Environment().Get("SOONG_ACTION_SANDBOXING")
 return ok && sandboxing == "nsjail"
}

func (c *configImpl) ActionSandboxMetrics() *blueprint.SandboxMetrics {
 return nil
}

func GetMetricsUploader(topDir string, env *Environment) string {
 if p, ok := env.Get("METRICS_UPLOADER"); ok {
  metricsUploader := filepath.Join(topDir, p)
  if _, err := os.Stat(metricsUploader); err == nil {
   return metricsUploader
  }
 }

 return ""
}

var envVarFlagDirs = []string{
 "build/release",
 "vendor/google_shared/build/release",
 "vendor/google/release",
}

var resolveSoongEnvVarsMsgs []string

// Look for environment variable defaults for TARGET_RELEASE.
//
// This allows us to effectively guard changes to Soong where the value of the guard flag needs to
// be resolved before Soong runs product config.
//
// If multiple directories contain default values for environment variables, the last one listed in
// envVarFlagDirs will be used as the default for that environment variable. SOONG_ENVVAR_FLAG_DIRS
// can be used to add directories to the list.
func ResolveSoongEnvVars() error {
 targetRelease := os.Getenv("TARGET_RELEASE")
 if targetRelease == "" {
  // This build does not use TARGET_RELEASE.
  return nil
 }
 envMap := OsEnvironment().AsMap()
 flagDirs := slices.Concat(envVarFlagDirs, strings.Fields(os.Getenv("SOONG_ENVVAR_FLAG_DIRS")))

 // Starting with the last directory, look for variables for ${TARGET_RELEASE}
 for _, dir := range slices.Backward(flagDirs) {
  jsonPath := filepath.Join(dir, "build_config", "soong_env", targetRelease+".json")
  data, err := os.ReadFile(jsonPath)
  if err != nil {
   if os.IsNotExist(err) {
    continue
   }
   return fmt.Errorf("reading file %s: %w", jsonPath, err)
  }

  // Use map[string]interface{} to handle the arbitrary JSON structure.
  var config map[string]interface{}

  if err := json.Unmarshal(data, &config); err != nil {
   return fmt.Errorf("unmarshaling JSON from %s: %w", jsonPath, err)
  }

  // Look at the JSON object for "env_default".
  envDefault, ok := config["env_default"].(map[string]interface{})
  if !ok {
   // 'env_default' may not exist or may not be a map, which is fine, continue.
   continue
  }

  // For each key in env_default, set the environment variable if not defined.
  for key, value := range envDefault {
   switch value.(type) {
   case string:
   default:
    data, err := json.Marshal(value)
    if err != nil {
     return fmt.Errorf("[%s] failed to marshal `%v` for error: %w", jsonPath, value, err)
    }
    return fmt.Errorf("[%s] env_default[%q] must contain a string, but contains `%s`", jsonPath, key, string(data))
   }
   if _, exists := envMap[key]; !exists {
    v := fmt.Sprintf("%v", value)
    // These will get logged via `ctx.Verbosef` in newConfig.
    resolveSoongEnvVarsMsgs = append(resolveSoongEnvVarsMsgs, fmt.Sprintf("[%s] Setting %s=%v", jsonPath, key, v))
    os.Setenv(key, v)
    envMap[key] = v
   }
  }
 }
 return nil
}

[Dauer der Verarbeitung: 0.40 Sekunden, vorverarbeitet 2026-06-28]