Eine aufbereitete Darstellung der Quelle

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

Benutzer

Quelle  soong.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 (
 "errors"
 "fmt"
 "io/fs"
 "os"
 "path/filepath"
 "runtime"
 "strconv"
 "strings"
 "sync"
 "sync/atomic"
 "time"

 "android/soong/ui/tracer"

 "android/soong/ui/metrics"
 "android/soong/ui/metrics/metrics_proto"
 "android/soong/ui/status"

 "android/soong/shared"

 "github.com/google/blueprint"
 "github.com/google/blueprint/bootstrap"
 "github.com/google/blueprint/pathtools"

 "google.golang.org/protobuf/proto"
)

const (
 availableEnvFile = "soong.environment.available"
 usedEnvFile      = "soong.environment.used"

 soongBuildTag      = "build"
 jsonModuleGraphTag = "modulegraph"
 soongDocsTag       = "soong_docs"

 // bootstrapEpoch is used to determine if an incremental build is incompatible with the current
 // version of bootstrap and needs cleaning before continuing the build.  Increment this for
 // incompatible changes, for example when moving the location of a microfactory binary that is
 // executed during bootstrap before the primary builder has had a chance to update the path.
 bootstrapEpoch = 1
)

var (
 // Used during parallel update of symlinks in out directory to reflect new
 // TOP dir.
 symlinkWg            sync.WaitGroup
 numFound, numUpdated uint32
)

func writeEnvironmentFile(_ Context, envFile string, envDeps map[string]string) error {
 data, err := shared.EnvFileContents(envDeps)
 if err != nil {
  return err
 }

 return os.WriteFile(envFile, data, 0644)
}

// This uses Android.bp files and various tools to generate <builddir>/build.ninja.
//
// However, the execution of <builddir>/build.ninja happens later in
// build/soong/ui/build/build.go#Build()
//
// We want to rely on as few prebuilts as possible, so we need to bootstrap
// Soong. The process is as follows:
//
// 1. We use "Microfactory", a simple tool to compile Go code, to build
//    first itself, then soong_ui from soong_ui.bash. This binary contains
//    parts of soong_build that are needed to build itself.
// 2. This simplified version of soong_build then reads the Blueprint files
//    that describe itself and emits .bootstrap/build.ninja that describes
//    how to build its full version and use that to produce the final Ninja
//    file Soong emits.
// 3. soong_ui executes .bootstrap/build.ninja
//
// (After this, Kati is executed to parse the Makefiles, but that's not part of
// bootstrapping Soong)

// A tiny struct used to tell Blueprint that it's in bootstrap mode. It would
// probably be nicer to use a flag in bootstrap.Args instead.
type BlueprintConfig struct {
 toolDir                   string
 soongOutDir               string
 outDir                    string
 runGoTests                bool
 debugCompilation          bool
 subninjas                 []string
 primaryBuilderInvocations []bootstrap.PrimaryBuilderInvocation
 isActionSandboxedBuild    bool
}

func (c BlueprintConfig) HostToolDir() string {
 return c.toolDir
}

func (c BlueprintConfig) SoongOutDir() string {
 return c.soongOutDir
}

func (c BlueprintConfig) OutDir() string {
 return c.outDir
}

func (c BlueprintConfig) RunGoTests() bool {
 return c.runGoTests
}

func (c BlueprintConfig) DebugCompilation() bool {
 return c.debugCompilation
}

func (c BlueprintConfig) Subninjas() []string {
 return c.subninjas
}

func (c BlueprintConfig) PrimaryBuilderInvocations() []bootstrap.PrimaryBuilderInvocation {
 return c.primaryBuilderInvocations
}

func (c BlueprintConfig) IsBootstrap() bool {
 return true
}

func (c BlueprintConfig) IsActionSandboxedBuild() bool {
 return c.isActionSandboxedBuild
}

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

func environmentArgs(config Config, tag string) []string {
 return []string{
  "--available_env", shared.JoinPath(config.SoongOutDir(), availableEnvFile),
  "--used_env", config.UsedEnvFile(tag),
 }
}

func writeEmptyFile(ctx Context, path string) {
 err := os.MkdirAll(filepath.Dir(path), 0777)
 if err != nil {
  ctx.Fatalf("Failed to create parent directories of empty file '%s': %s", path, err)
 }

 if exists, err := fileExists(path); err != nil {
  ctx.Fatalf("Failed to check if file '%s' exists: %s", path, err)
 } else if !exists {
  err = os.WriteFile(path, nil, 0666)
  if err != nil {
   ctx.Fatalf("Failed to create empty file '%s': %s", path, err)
  }
 }
}

func fileExists(path string) (bool, error) {
 if _, err := os.Stat(path); os.IsNotExist(err) {
  return false, nil
 } else if err != nil {
  return false, err
 }
 return true, nil
}

type PrimaryBuilderFactory struct {
 name         string
 description  string
 config       Config
 output       string
 specificArgs []string
 debugPort    string
}

func (pb PrimaryBuilderFactory) primaryBuilderInvocation(config Config) bootstrap.PrimaryBuilderInvocation {
 commonArgs := make([]string, 00)

 commonArgs = append(commonArgs, "--kati_suffix", config.KatiSuffix())
 if !config.SkipKati() {
  commonArgs = append(commonArgs, "--kati_enabled")
 }

 if !pb.config.skipSoongTests {
  commonArgs = append(commonArgs, "-t")
 }

 if pb.config.buildFromSourceStub {
  commonArgs = append(commonArgs, "--build-from-source-stub")
 }

 if pb.config.moduleDebugFile != "" {
  commonArgs = append(commonArgs, "--soong_module_debug")
  commonArgs = append(commonArgs, pb.config.moduleDebugFile)
 }

 if pb.config.incrementalDebugFile != "" {
  commonArgs = append(commonArgs, "--incremental-debug-file")
  commonArgs = append(commonArgs, pb.config.incrementalDebugFile)
 }

 commonArgs = append(commonArgs, "-l", filepath.Join(pb.config.FileListDir(), "Android.bp.list"))
 invocationEnv := make(map[string]string)
 if pb.debugPort != "" {
  //debug mode
  commonArgs = append(commonArgs, "--delve_listen", pb.debugPort,
   "--delve_path", shared.ResolveDelveBinary())
  // GODEBUG=asyncpreemptoff=1 disables the preemption of goroutines. This
  // is useful because the preemption happens by sending SIGURG to the OS
  // thread hosting the goroutine in question and each signal results in
  // work that needs to be done by Delve; it uses ptrace to debug the Go
  // process and the tracer process must deal with every signal (it is not
  // possible to selectively ignore SIGURG). This makes debugging slower,
  // sometimes by an order of magnitude depending on luck.
  // The original reason for adding async preemption to Go is here:
  // https://github.com/golang/proposal/blob/master/design/24543-non-cooperative-preemption.md
  invocationEnv["GODEBUG"] = "asyncpreemptoff=1"
 }

 var allArgs []string
 allArgs = append(allArgs, pb.specificArgs...)

 allArgs = append(allArgs, commonArgs...)
 allArgs = append(allArgs, environmentArgs(pb.config, pb.name)...)
 if profileCpu := os.Getenv("SOONG_PROFILE_CPU"); profileCpu != "" {
  allArgs = append(allArgs, "--cpuprofile", profileCpu+"."+pb.name)
 }
 if profileMem := os.Getenv("SOONG_PROFILE_MEM"); profileMem != "" {
  allArgs = append(allArgs, "--memprofile", profileMem+"."+pb.name)
 }
 allArgs = append(allArgs, "Android.bp")

 return bootstrap.PrimaryBuilderInvocation{
  Implicits:   []string{pb.output + ".glob_results"},
  Outputs:     []string{pb.output},
  Args:        allArgs,
  Description: pb.description,
  // NB: Changing the value of this environment variable will not result in a
  // rebuild. The bootstrap Ninja file will change, but apparently Ninja does
  // not consider changing the pool specified in a statement a change that's
  // worth rebuilding for.
  Console: os.Getenv("SOONG_UNBUFFERED_OUTPUT") == "1",
  Env:     invocationEnv,
 }
}

// bootstrapEpochCleanup deletes files used by bootstrap during incremental builds across
// incompatible changes.  Incompatible changes are marked by incrementing the bootstrapEpoch
// constant.  A tree is considered out of date for the current epoch of the
// .soong.bootstrap.epoch.<epoch> file doesn't exist.
func bootstrapEpochCleanup(ctx Context, config Config) {
 epochFile := fmt.Sprintf(".soong.bootstrap.epoch.%d", bootstrapEpoch)
 epochPath := filepath.Join(config.SoongOutDir(), epochFile)
 if exists, err := fileExists(epochPath); err != nil {
  ctx.Fatalf("failed to check if bootstrap epoch file %q exists: %q", epochPath, err)
 } else if !exists {
  // The tree is out of date for the current epoch, delete files used by bootstrap
  // and force the primary builder to rerun.
  soongNinjaFile := config.SoongNinjaFile()
  os.Remove(soongNinjaFile)
  for _, file := range blueprint.GetNinjaShardFiles(soongNinjaFile) {
   if ok, _ := fileExists(file); ok {
    os.Remove(file)
   }
  }
  os.Remove(soongNinjaFile + ".globs")
  os.Remove(soongNinjaFile + ".globs_time")
  os.Remove(soongNinjaFile + ".glob_results")

  // Mark the tree as up to date with the current epoch by writing the epoch marker file.
  writeEmptyFile(ctx, epochPath)
 }
}

func bootstrapBlueprint(ctx Context, config Config) {
 e := ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
 defer e.End()

 st := ctx.Status.StartTool()
 defer st.Finish()
 st.SetTotalActions(1)
 action := &status.Action{
  Description: "bootstrap blueprint",
  Outputs:     []string{"bootstrap blueprint"},
 }
 st.StartAction(action)

 // Clean up some files for incremental builds across incompatible changes.
 bootstrapEpochCleanup(ctx, config)

 baseArgs := []string{"--soong_variables", config.SoongVarsFile()}

 mainSoongBuildExtraArgs := append(baseArgs, "-o", config.SoongNinjaFile())
 if config.EmptyNinjaFile() {
  mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--empty-ninja-file")
 }
 if config.buildFromSourceStub {
  mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--build-from-source-stub")
 }
 if config.ensureAllowlistIntegrity {
  mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--ensure-allowlist-integrity")
 }
 if config.incrementalBuildActions {
  mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--incremental-build-actions")
 }
 if len(config.partialAnalysisTargets) > 0 {
  mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, fmt.Sprintf("%s=\"%s\"", "--partial-analysis-targets", config.partialAnalysisTargets))
 }
 if config.incrementalProviderTest {
  mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--incremental-provider-test")
 }

 pbfs := []PrimaryBuilderFactory{
  {
   name:         soongBuildTag,
   description:  fmt.Sprintf("analyzing Android.bp files and generating ninja file at %s", config.SoongNinjaFile()),
   config:       config,
   output:       config.SoongNinjaFile(),
   specificArgs: mainSoongBuildExtraArgs,
  },
  {
   name:        jsonModuleGraphTag,
   description: fmt.Sprintf("generating the Soong module graph at %s", config.ModuleGraphFile()),
   config:      config,
   output:      config.ModuleGraphFile(),
   specificArgs: append(baseArgs,
    "--module_graph_file", config.ModuleGraphFile(),
    "--module_actions_file", config.ModuleActionsFile(),
   ),
  },
  {
   name:        soongDocsTag,
   description: fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()),
   config:      config,
   output:      config.SoongDocsHtml(),
   specificArgs: append(baseArgs,
    "--soong_docs", config.SoongDocsHtml(),
   ),
  },
 }

 // Figure out which invocations will be run under the debugger:
 //   * SOONG_DELVE if set specifies listening port
 //   * SOONG_DELVE_STEPS if set specifies specific invocations to be debugged, otherwise all are
 debuggedInvocations := make(map[string]bool)
 delvePort := os.Getenv("SOONG_DELVE")
 if delvePort != "" {
  if steps := os.Getenv("SOONG_DELVE_STEPS"); steps != "" {
   var validSteps []string
   for _, pbf := range pbfs {
    debuggedInvocations[pbf.name] = false
    validSteps = append(validSteps, pbf.name)

   }
   for _, step := range strings.Split(steps, ",") {
    if _, ok := debuggedInvocations[step]; ok {
     debuggedInvocations[step] = true
    } else {
     ctx.Fatalf("SOONG_DELVE_STEPS contains unknown soong_build step %s\n"+
      "Valid steps are %v", step, validSteps)
    }
   }
  } else {
   //  SOONG_DELVE_STEPS is not set, run all steps in the debugger
   for _, pbf := range pbfs {
    debuggedInvocations[pbf.name] = true
   }
  }
 }

 var invocations []bootstrap.PrimaryBuilderInvocation
 for _, pbf := range pbfs {
  if debuggedInvocations[pbf.name] {
   pbf.debugPort = delvePort
  }
  pbi := pbf.primaryBuilderInvocation(config)
  invocations = append(invocations, pbi)
 }

 blueprintArgs := bootstrap.Args{
  ModuleListFile: filepath.Join(config.FileListDir(), "Android.bp.list"),
  OutFile:        shared.JoinPath(config.SoongOutDir(), "bootstrap.ninja"),
  EmptyNinjaFile: false,
 }

 blueprintCtx := blueprint.NewContext()
 blueprintCtx.AddSourceRootDirs(config.GetSourceRootDirs()...)
 blueprintCtx.SetIgnoreUnknownModuleTypes(true)
 blueprintConfig := BlueprintConfig{
  soongOutDir: config.SoongOutDir(),
  toolDir:     config.HostToolDir(),
  outDir:      config.OutDir(),
  runGoTests:  !config.skipSoongTests,
  // If we want to debug soong_build, we need to compile it for debugging
  debugCompilation:          delvePort != "",
  primaryBuilderInvocations: invocations,
  isActionSandboxedBuild:    config.IsActionSandboxedBuild(),
 }

 // since `bootstrap.ninja` is regenerated unconditionally, we ignore the deps, i.e. little
 // reason to write a `bootstrap.ninja.d` file
 _, err := bootstrap.RunBlueprint(blueprintArgs, bootstrap.DoEverything, blueprintCtx, blueprintConfig)

 result := status.ActionResult{
  Action: action,
 }
 if err != nil {
  result.Error = err
  result.Output = err.Error()
 }
 st.FinishAction(result)
 if err != nil {
  ctx.Fatalf("bootstrap failed")
 }
}

func checkEnvironmentFile(ctx Context, config Config, currentEnv *Environment, envFile string) {
 getenv := func(k string) string {
  v, _ := currentEnv.Get(k)
  return v
 }

 // Log the changed environment variables to ChangedEnvironmentVariable field

 if stale, changedEnvironmentVariableList, err := shared.StaleEnvFile(envFile, getenv); stale {
  if config.EnforceNoReanalysis() {
   if exists, _ := fileExists(envFile); exists {
    if err != nil {
     msg := fmt.Sprintf("Reanalysis will run due to missing or invalid environment file: %s. Changed TARGET_PRODUCT?", err)
     // Both Error and Fatalf are used because Fatalf cannot write the error message to
     // error.log.
     ctx.Status.Error(msg)
     ctx.Fatalf(msg)
    }
    msg := fmt.Sprintf("Reanalysis will run due to environment change. Changed environment variables: %v", changedEnvironmentVariableList)
    // Both Error and Fatalf are used because Fatalf cannot write the error message to
    // error.log.
    ctx.Status.Error(msg)
    ctx.Fatalf(msg)
   }
  }
  for _, changedEnvironmentVariable := range changedEnvironmentVariableList {
   ctx.Metrics.AddChangedEnvironmentVariable(changedEnvironmentVariable)
  }
  os.Remove(envFile)
 }
}

func updateSymlinks(ctx Context, dir, prevCWD, cwd string, updateSemaphore chan struct{}) error {
 defer symlinkWg.Done()

 visit := func(path string, d fs.DirEntry, err error) error {
  if d.IsDir() && path != dir {
   symlinkWg.Add(1)
   go updateSymlinks(ctx, path, prevCWD, cwd, updateSemaphore)
   return filepath.SkipDir
  }
  f, err := d.Info()
  if err != nil {
   return err
  }
  // If the file is not a symlink, we don't have to update it.
  if f.Mode()&os.ModeSymlink != os.ModeSymlink {
   return nil
  }

  atomic.AddUint32(&numFound, 1)
  target, err := os.Readlink(path)
  if err != nil {
   return err
  }
  if strings.HasPrefix(target, prevCWD) &&
   (len(target) == len(prevCWD) || target[len(prevCWD)] == '/') {
   target = filepath.Join(cwd, target[len(prevCWD):])
   if err := os.Remove(path); err != nil {
    return err
   }
   if err := os.Symlink(target, path); err != nil {
    return err
   }
   atomic.AddUint32(&numUpdated, 1)
  }
  return nil
 }

 <-updateSemaphore
 defer func() { updateSemaphore <- struct{}{} }()
 if err := filepath.WalkDir(dir, visit); err != nil {
  return err
 }
 return nil
}

// b/376466642: If the concurrency of updateSymlinks is unbounded, Go's runtime spawns a
// theoretically unbounded number of threads to handle blocking syscalls. This causes the runtime to
// panic due to hitting thread limits in rare cases. Limiting to GOMAXPROCS concurrent symlink
// updates should make this a non-issue.
func newUpdateSemaphore() chan struct{} {
 numPermits := runtime.GOMAXPROCS(0)
 c := make(chan struct{}, numPermits)
 for i := 0; i < numPermits; i++ {
  c <- struct{}{}
 }
 return c
}

func fixOutDirSymlinks(ctx Context, config Config, outDir string) error {
 cwd, err := os.Getwd()
 if err != nil {
  return err
 }

 // Record the .top as the very last thing in the function.
 tf := filepath.Join(outDir, ".top")
 defer func() {
  if err := os.WriteFile(tf, []byte(cwd), 0644); err != nil {
   fmt.Fprintf(os.Stderr, "Unable to log CWD: %v", err)
  }
 }()

 // Find the previous working directory if it was recorded.
 var prevCWD string
 pcwd, err := os.ReadFile(tf)
 if err != nil {
  if os.IsNotExist(err) {
   // No previous working directory recorded, nothing to do.
   return nil
  }
  ctx.Println(fmt.Sprintf("Failed to read pcwd: %v", err))
  return err
 }

 prevCWD = strings.Trim(string(pcwd), "\n")

 if prevCWD == cwd || prevCWD == "" {
  // We are in the same source dir, or prevCWD came up empty for some reason,
  // so nothing to update.
  return nil
 }

 ctx.Println(fmt.Sprintf("CWD directory changed from %v to %v, updating output symlinks", prevCWD, cwd))

 symlinkWg.Add(1)
 if err := updateSymlinks(ctx, outDir, prevCWD, cwd, newUpdateSemaphore()); err != nil {
  return err
 }
 symlinkWg.Wait()
 ctx.Println(fmt.Sprintf("Updated %d/%d symlinks in dir %v", numUpdated, numFound, outDir))

 return nil
}

func migrateOutputSymlinks(ctx Context, config Config) error {
 // Figure out the real out directory ("out" could be a symlink).
 outDir := config.OutDir()
 s, err := os.Lstat(outDir)
 if err != nil {
  if os.IsNotExist(err) {
   // No out dir exists, no symlinks to migrate.
   return nil
  }
  return err
 }
 if s.Mode()&os.ModeSymlink == os.ModeSymlink {
  target, err := filepath.EvalSymlinks(outDir)
  if err != nil {
   return err
  }
  outDir = target
 }
 return fixOutDirSymlinks(ctx, config, outDir)
}

func runSoong(ctx Context, config Config, enforceNoSoongOutput bool) {
 e := ctx.BeginTrace(metrics.RunSoong, "soong")
 defer e.End()

 if err := migrateOutputSymlinks(ctx, config); err != nil {
  ctx.Fatalf("failed to migrate output directory to current TOP dir: %v", err)
 }

 // We have two environment files: .available is the one with every variable,
 // .used with the ones that were actually used. The latter is used to
 // determine whether Soong needs to be re-run since why re-run it if only
 // unused variables were changed?
 envFile := filepath.Join(config.SoongOutDir(), availableEnvFile)

 // This is done unconditionally, but does not take a measurable amount of time
 bootstrapBlueprint(ctx, config)

 soongBuildEnv := config.Environment().Copy()
 soongBuildEnv.Set("TOP", os.Getenv("TOP"))
 soongBuildEnv.Set("LOG_DIR", config.LogsDir())

 // For Soong bootstrapping tests
 if os.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
  soongBuildEnv.Set("ALLOW_MISSING_DEPENDENCIES", "true")
 }

 // Only inject the SOONG_ENFORCE_NO_REANALYSIS variable if it's not a clean build.
 // Clean build should not fail even if SOONG_ENFORCE_NO_REANALYSIS is set.
 cleanBuild := false
 if exists, _ := fileExists(config.UsedEnvFile(soongBuildTag)); !exists {
  cleanBuild = true
 }

 if config.EnforceNoReanalysis() && !cleanBuild {
  soongBuildEnv.Set("SOONG_ENFORCE_NO_REANALYSIS", "true")
 } else {
  soongBuildEnv.Unset("SOONG_ENFORCE_NO_REANALYSIS")
 }

 err := writeEnvironmentFile(ctx, envFile, soongBuildEnv.AsMap())
 if err != nil {
  ctx.Fatalf("failed to write environment file %s: %s", envFile, err)
 }

 func() {
  e := ctx.BeginTrace(metrics.RunSoong, "environment check")
  defer e.End()

  checkEnvironmentFile(ctx, config, soongBuildEnv, config.UsedEnvFile(soongBuildTag))

  if config.JsonModuleGraph() {
   checkEnvironmentFile(ctx, config, soongBuildEnv, config.UsedEnvFile(jsonModuleGraphTag))
  }

  if config.SoongDocs() {
   checkEnvironmentFile(ctx, config, soongBuildEnv, config.UsedEnvFile(soongDocsTag))
  }
 }()

 ninja := func(targets ...string) {
  e := ctx.BeginTrace(metrics.RunSoong, "bootstrap")
  defer e.End()

  fifo := filepath.Join(config.OutDir(), ".ninja_fifo")
  nr := status.NewNinjaReader(ctx, ctx.Status.StartTool(), fifo, ctx.SigNumFunc)
  func() {
   defer nr.Close()
   var ninjaEnv Environment
   var ninjaCmd string
   var ninjaArgs []string
   switch config.ninjaCommand {
   case NINJA_N2:
    ninjaCmd = config.N2Bin()
    ninjaArgs = []string{
     // TODO: implement these features, or remove them.
     //"-d", "keepdepfile",
     //"-d", "stats",
     //"-o", "usesphonyoutputs=yes",
     //"-o", "preremoveoutputs=yes",
     //"-w", "dupbuild=err",
     //"-w", "outputdir=err",
     //"-w", "missingoutfile=err",
     "-v",
     "-j", strconv.Itoa(config.Parallel()),
     "--frontend-file", fifo,
     "-f", filepath.Join(config.SoongOutDir(), "bootstrap.ninja"),
    }
   case NINJA_SISO:
    sisoLogDir := filepath.Join(config.LogsDir(), "bootstrap")
    // create log dir, otherwise glog will use /tmp instead.
    err := os.MkdirAll(sisoLogDir, 0777)
    if err != nil {
     ctx.Fatalf("Failed to create siso log dir: %v\n", err)
    }
    ninjaCmd = config.SisoBin()
    ninjaArgs = []string{
     "--log_dir", sisoLogDir, // for glog, e.g. siso.*INFO*
     "ninja",
     "-d", "keepdepfile",
     // TODO: implement these features, or remove them.
     //"-d", "stats",
     //"-o", "usesphonyoutputs=yes",
     //"-o", "preremoveoutputs=yes",
     //"-w", "dupbuild=err",
     //"-w", "outputdir=err",
     //"-w", "missingoutfile=err",
     "--local_jobs", strconv.Itoa(config.Parallel()),
     //"--remote_jobs", strconv.Itoa(config.RemoteParallel()),
     "--frontend_file", fifo,
     "-f", filepath.Join(config.SoongOutDir(), "bootstrap.ninja"),
    }
    if value := config.SisoConfigDir(); value != "" {
     value = createSisoConfigDir(ctx, config, value)
     ninjaArgs = append(ninjaArgs, fmt.Sprintf("--config_repo_dir=%s", value))
    }
    sisoExperiments := []string{
     "ignore-missing-out-in-depfile",
     "fallback-on-exec-error",
    }
    if exps, ok := ninjaEnv.Get("SISO_EXPERIMENTS"); ok {
     sisoExperiments = append(sisoExperiments, exps)
    }
    ninjaEnv.Set("SISO_EXPERIMENTS", strings.Join(sisoExperiments, ","))

    if config.IsVerbose() {
     // Output `siso version`.
     vcmd := Command(ctx, config, nil, "siso version",
      config.SisoBin(), "version")
     versionOutput, err := vcmd.CombinedOutput()
     if err != nil {
      ctx.Fatalf("Failed to run siso version: %s\n", err)
     }
     ctx.Verbosef("%s", versionOutput)
    }
   default:
    // NINJA_NINJA is the default.
    ninjaCmd = config.NinjaBin()
    ninjaArgs = []string{
     "-d", "keepdepfile",
     "-d", "stats",
     "-o", "usesphonyoutputs=yes",
     "-o", "preremoveoutputs=yes",
     "-w", "dupbuild=err",
     "-w", "outputdir=err",
     "-w", "missingoutfile=err",
     "-j", strconv.Itoa(config.Parallel()),
     "--frontend_file", fifo,
     "-f", filepath.Join(config.SoongOutDir(), "bootstrap.ninja"),
    }
   }

   if extra, ok := config.Environment().Get("SOONG_UI_NINJA_ARGS"); ok {
    ctx.Printf(`CAUTION: arguments in $SOONG_UI_NINJA_ARGS=%q, e.g. "-n", can make soong_build FAIL or INCORRECT`, extra)
    ninjaArgs = append(ninjaArgs, strings.Fields(extra)...)
   }

   ninjaArgs = append(ninjaArgs, targets...)

   cmd := Command(ctx, config, e, "soong bootstrap",
    ninjaCmd, ninjaArgs...)

   // This is currently how the command line to invoke soong_build finds the
   // root of the source tree and the output root
   ninjaEnv.Set("TOP", os.Getenv("TOP"))
   SetupLitePath(ctx, config, "")
   ninjaPath, _ := config.Environment().Get("PATH")
   ninjaEnv.Set("PATH", ninjaPath)

   cmd.Environment = &ninjaEnv
   cmd.Sandbox = soongSandbox
   cmd.RunAndStreamOrFatal()
  }()

  if enforceNoSoongOutput && nr.HasAnyOutput() {
   ctx.Fatalf("Soong must not output anything to stdout/stderr on a successful build, please remove the prints")
  }
 }

 targets := make([]string, 00)

 if config.JsonModuleGraph() {
  targets = append(targets, config.ModuleGraphFile())
 }

 if config.SoongDocs() {
  targets = append(targets, config.SoongDocsHtml())
 }

 if config.SoongBuildInvocationNeeded() {
  // This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
  targets = append(targets, config.SoongNinjaFile())
 }

 for _, target := range targets {
  if err := checkGlobs(ctx, config, target); err != nil {
   ctx.Fatalf("Error checking globs: %s", err.Error())
  }
 }

 beforeSoongTimestamp := time.Now()

 ninja(targets...)

 loadSoongBuildMetrics(ctx, config, beforeSoongTimestamp)

 soongNinjaFile := config.SoongNinjaFile()
 distGzipFile(ctx, config, soongNinjaFile, "soong_ui/soong")
 for _, file := range blueprint.GetNinjaShardFiles(soongNinjaFile) {
  if ok, _ := fileExists(file); ok {
   distGzipFile(ctx, config, file, "soong_ui/soong")
  }
 }
 distFile(ctx, config, config.SoongVarsFile(), "soong_ui/soong")
 distFile(ctx, config, config.SoongExtraVarsFile(), "soong_ui/soong")

 if !config.SkipKati() {
  distGzipFile(ctx, config, config.SoongAndroidMk(), "soong_ui/soong")
  distGzipFile(ctx, config, config.SoongMakeVarsMk(), "soong_ui/soong")
 } else {
  distGzipFile(ctx, config, config.SoongPhonyNinjaFile(), "soong_ui/soong")
  distGzipFile(ctx, config, config.SoongDistNinjaFile(), "soong_ui/soong")
  distGzipFile(ctx, config, config.SoongNoDistNinjaFile(), "soong_ui/soong")
 }

 if config.JsonModuleGraph() {
  distGzipFile(ctx, config, config.ModuleGraphFile(), "soong_ui/soong")
 }

 if config.ninjaCommand == NINJA_SISO {
  distFile(ctx, config, config.SisoConfigFile(true), "soong_ui/bootstrap")
  distFile(ctx, config, config.SisoDepsFile(true), "soong_ui/bootstrap")
  distFile(ctx, config, config.SisoFsStateFile(true), "soong_ui/bootstrap")
  distFile(ctx, config, config.SisoFilegroupsFile(true), "soong_ui/bootstrap")
 }
}

// checkGlobs manages the globs that cause soong to rerun.
//
// When soong_build runs, it will run globs. It will write all the globs
// it ran into the "{finalOutFile}.globs" file. Then every build,
// soong_ui will check that file, rerun the globs, and if they changed
// from the results that soong_build got, update the ".glob_results"
// file, causing soong_build to rerun. The ".glob_results" file will
// be empty on the first run of soong_build, because we don't know
// what the globs are yet, but also remain empty until the globs change
// so that we don't run soong_build a second time unnecessarily.
// Both soong_build and soong_ui will also update a ".globs_time" file
// with the time that they ran at every build. When soong_ui checks
// globs, it only reruns globs whose dependencies are newer than the
// time in the ".globs_time" file.
func checkGlobs(ctx Context, config Config, finalOutFile string) error {
 e := ctx.BeginTrace(metrics.RunSoong, "check_globs")
 defer e.End()
 st := ctx.Status.StartTool()
 st.Status("Running globs...")
 defer st.Finish()

 globsFile, err := os.Open(finalOutFile + ".globs")
 if errors.Is(err, os.ErrNotExist) {
  // if the glob file doesn't exist, make sure the glob_results file exists and is empty.
  if err := os.MkdirAll(filepath.Dir(finalOutFile), 0777); err != nil {
   return err
  }
  return os.WriteFile(finalOutFile+".glob_results", nil, 0666)
 } else if err != nil {
  return err
 }
 defer globsFile.Close()

 globsTimeBytes, err := os.ReadFile(finalOutFile + ".globs_time")
 if errors.Is(err, os.ErrNotExist) {
  globsTimeBytes = []byte("0")
 } else if err != nil {
  return err
 }

 globsTimeMicros, err := strconv.ParseInt(strings.TrimSpace(string(globsTimeBytes)), 1064)
 if err != nil {
  return err
 }
 globCheckStartTime := time.Now().UnixMicro()

 hasChangedGlobs, err := pathtools.CheckForChangedGlobs(pathtools.OsFs, globsFile, globsTimeMicros)
 if err != nil {
  if config.EnforceNoReanalysis() {
   msg := fmt.Sprintf("Reanalysis will run due to glob error: %s", err.Error())
   // Both Error and Fatalf are used because Fatalf cannot write the error message to
   // error.log.
   ctx.Status.Error(msg)
   ctx.Fatalf(msg)
  }
  fmt.Fprintf(os.Stdout, "\nGlobs changed, rerunning soong...\n")
  fmt.Fprintf(os.Stdout, "%s\n", err.Error())
 }

 if hasChangedGlobs {
  // Write the current time to the glob_results file. We just need
  // some unique value to trigger a rerun, it doesn't matter what it is.
  err = os.WriteFile(
   finalOutFile+".glob_results",
   []byte(fmt.Sprintf("%d\n", globCheckStartTime)),
   0666,
  )
  if err != nil {
   return err
  }
 } else {
  // Update the globs_time file if there were no changed globs so that we don't
  // rerun globs in the future that we just saw didn't change.
  err = os.WriteFile(
   finalOutFile+".globs_time",
   []byte(fmt.Sprintf("%d\n", globCheckStartTime)),
   0666,
  )
  if err != nil {
   return err
  }
 }
 return nil
}

// loadSoongBuildMetrics reads out/soong_build_metrics.pb if it was generated by soong_build and copies the
// events stored in it into the soong_ui trace to provide introspection into how long the different phases of
// soong_build are taking.
func loadSoongBuildMetrics(ctx Context, config Config, oldTimestamp time.Time) {
 soongBuildMetricsFile := config.SoongBuildMetrics()
 if metricsStat, err := os.Stat(soongBuildMetricsFile); err != nil {
  ctx.Verbosef("Failed to stat %s: %s", soongBuildMetricsFile, err)
  return
 } else if !metricsStat.ModTime().After(oldTimestamp) {
  ctx.Verbosef("%s timestamp not later after running soong, expected %s > %s",
   soongBuildMetricsFile, metricsStat.ModTime(), oldTimestamp)
  return
 }

 metricsData, err := os.ReadFile(soongBuildMetricsFile)
 if err != nil {
  ctx.Verbosef("Failed to read %s: %s", soongBuildMetricsFile, err)
  return
 }

 soongBuildMetrics := metrics_proto.SoongBuildMetrics{}
 err = proto.Unmarshal(metricsData, &soongBuildMetrics)
 if err != nil {
  ctx.Verbosef("Failed to unmarshal %s: %s", soongBuildMetricsFile, err)
  return
 }
 for _, event := range soongBuildMetrics.Events {
  desc := event.GetDescription()
  if dot := strings.LastIndexByte(desc, '.'); dot >= 0 {
   desc = desc[dot+1:]
  }
  ctx.Tracer.Complete(desc, ctx.Thread,
   event.GetStartTime(), event.GetStartTime()+event.GetRealTime())
 }
 for _, event := range soongBuildMetrics.PerfCounters {
  timestamp := event.GetTime()
  for _, group := range event.Groups {
   counters := make([]tracer.Counter, 0, len(group.Counters))
   for _, counter := range group.Counters {
    counters = append(counters, tracer.Counter{
     Name:  counter.GetName(),
     Value: counter.GetValue(),
    })
   }
   ctx.Tracer.CountersAtTime(group.GetName(), ctx.Thread, timestamp, counters)
  }
 }
}

[Dauer der Verarbeitung: 0.26 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