Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

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.45 Sekunden, vorverarbeitet 2026-06-28]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik