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

Quelle  dist.go   Sprache: unbekannt

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

// Copyright 2025 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 android

import (
 "cmp"
 "fmt"
 "io"
 "maps"
 "path/filepath"
 "slices"
 "strings"
 "sync"

 "github.com/google/blueprint"
 "github.com/google/blueprint/proptools"
)

//go:generate go run ../../blueprint/gobtools/codegen

// The contributions to the dist.
type distContributions struct {
 // Path to license metadata file.
 licenseMetadataFile Path
 // List of goals and the dist copy instructions.
 copiesForGoals []*copiesForGoals
}

// getCopiesForGoals returns a copiesForGoals into which copy instructions that
// must be processed when building one or more of those goals can be added.
func (d *distContributions) getCopiesForGoals(goals []string) *copiesForGoals {
 copiesForGoals := &copiesForGoals{goals: goals}
 d.copiesForGoals = append(d.copiesForGoals, copiesForGoals)
 return copiesForGoals
}

// Associates a list of dist copy instructions with a set of goals for which they
// should be run.
type copiesForGoals struct {
 // goals are build targets that will trigger the copy instructions.
 goals []string

 // A list of instructions to copy a module's output files to somewhere in the
 // dist directory.
 copies []distCopy
}

// Adds a copy instruction.
func (d *copiesForGoals) addCopyInstruction(from Path, dest string) {
 d.copies = append(d.copies, distCopy{from, dest})
}

// Instruction on a path that must be copied into the dist.
// @auto-generate: gob
type distCopy struct {
 // The path to copy from.
 from Path

 // The destination within the dist directory to copy to.
 dest string
}

func (d *distCopy) String() string {
 if len(d.dest) == 0 {
  return d.from.String()
 }
 return fmt.Sprintf("%s:%s", d.from.String(), d.dest)
}

type distCopies []distCopy

func (d *distCopies) Strings() (ret []string) {
 if d == nil {
  return
 }
 for _, dist := range *d {
  ret = append(ret, dist.String())
 }
 return
}

// This gets the dist contributions from the given module that were specified in the Android.bp
// file using the dist: property. It does not include contributions that the module's
// implementation may have defined with ctx.DistForGoals(), for that, see DistProvider.
func getDistContributions(ctx ConfigAndOtherModuleProviderContext, mod ModuleOrProxy) *distContributions {
 name := mod.Name()

 commonInfo := OtherModulePointerProviderOrDefault(ctx, mod, CommonModuleInfoProvider)

 info := GetInstallFilesCommon(commonInfo)
 availableTaggedDists := info.DistFiles

 if len(availableTaggedDists) == 0 {
  // Nothing dist-able for this module.
  return nil
 }

 // Collate the contributions this module makes to the dist.
 distContributions := &distContributions{}

 if !exemptFromRequiredApplicableLicensesProperty(mod) {
  distContributions.licenseMetadataFile = info.LicenseMetadataFile
 }

 // Iterate over this module's dist structs, merged from the dist and dists properties.
 for _, dist := range commonInfo.Dists {
  // Get the list of goals this dist should be enabled for. e.g. sdk, droidcore
  goals := dist.Targets

  // Get the tag representing the output files to be dist'd. e.g. ".jar", ".proguard_map"
  var tag string
  if dist.Tag == nil {
   // If the dist struct does not specify a tag, use the default output files tag.
   tag = DefaultDistTag
  } else {
   tag = *dist.Tag
  }

  // Get the paths of the output files to be dist'd, represented by the tag.
  // Can be an empty list.
  tagPaths := availableTaggedDists[tag]
  if len(tagPaths) == 0 {
   // Nothing to dist for this tag, continue to the next dist.
   continue
  }

  if len(tagPaths) > 1 && (dist.Dest != nil || dist.Suffix != nil) {
   errorMessage := "%s: Cannot apply dest/suffix for more than one dist " +
    "file for %q goals tag %q in module %s. The list of dist files, " +
    "which should have a single element, is:\n%s"
   panic(fmt.Errorf(errorMessage, mod, goals, tag, name, tagPaths))
  }

  copiesForGoals := distContributions.getCopiesForGoals(goals)

  // Iterate over each path adding a copy instruction to copiesForGoals
  for _, path := range tagPaths {
   // It's possible that the Path is nil from errant modules. Be defensive here.
   if path == nil {
    tagName := "default" // for error message readability
    if dist.Tag != nil {
     tagName = *dist.Tag
    }
    panic(fmt.Errorf("Dist file should not be nil for the %s tag in %s", tagName, name))
   }

   dest := filepath.Base(path.String())

   if dist.Dest != nil {
    var err error
    if dest, err = validateSafePath(*dist.Dest); err != nil {
     // This was checked in ModuleBase.GenerateBuildActions
     panic(err)
    }
   }

   ext := filepath.Ext(dest)
   suffix := ""
   if dist.Suffix != nil {
    suffix = *dist.Suffix
   }

   prependProductString := ""
   if proptools.Bool(dist.Prepend_artifact_with_product) {
    prependProductString = fmt.Sprintf("%s-", ctx.Config().DeviceProduct())
   }

   appendProductString := ""
   if proptools.Bool(dist.Append_artifact_with_product) {
    appendProductString = fmt.Sprintf("_%s", ctx.Config().DeviceProduct())
   }

   if suffix != "" || appendProductString != "" || prependProductString != "" {
    dest = prependProductString + strings.TrimSuffix(dest, ext) + suffix + appendProductString + ext
   }

   if dist.Dir != nil {
    var err error
    if dest, err = validateSafePath(*dist.Dir, dest); err != nil {
     // This was checked in ModuleBase.GenerateBuildActions
     panic(err)
    }
   }

   copiesForGoals.addCopyInstruction(path, dest)
  }
 }

 return distContributions
}

// soongDist implements collecting and writing out dist rules for Soong-only builds.
//
// Any phony target that is used as a dist goal gets an extra intermediate phony target
// added as a dependency.
//
// An extra ninja file is written out to be used when dist is enabled, which contains copy
// rules for all the dist files, and the intermediate phony targets with dependencies on
// the copy rules.
//
// Another extra ninja file is written out to be used when dist is not enabled, which contains
// empty phony rules for the intermediate phony targets.
type soongDist struct {
 // copies is the list of copy pairs from all dist rules.
 copies []distCopy
 // goals is the list of goals that have dist rules attached.
 goals []string
 // goalToDests maps dist goals to the list of copy destinations for that goal.
 goalToDests map[string][]string
 // conflicts is the list of copy pairs that have conflicting destinations.  If any
 // conflicts are found then all copy rules will be replaced with error messages.
 conflicts [][2]distCopy
}

const distIntermediateGoalSuffix = "__dist"

// collectDists visits all singletons and modules to collect the dist goals and copies.
func (s *soongDist) collectDists(ctx SingletonContext) {
 var allDistContributions []distContributions
 ctx.VisitAllSingletons(func(s blueprint.SingletonProxy) {
  if info, ok := OtherSingletonProvider(ctx, s, SingletonDistInfoProvider); ok {
   if contribution := distsToDistContributions(info.Dists); contribution != nil {
    allDistContributions = append(allDistContributions, *contribution)
   }
  }
 })

 ctx.VisitAllModuleProxies(func(mod ModuleProxy) {
  if distInfo, ok := OtherModuleProvider(ctx, mod, DistProvider); ok {
   if contribution := distsToDistContributions(distInfo.Dists); contribution != nil {
    allDistContributions = append(allDistContributions, *contribution)
   }
  }

  commonInfo := OtherModulePointerProviderOrDefault(ctx, mod, CommonModuleInfoProvider)
  if commonInfo.SkipAndroidMkProcessing {
   return
  }
  if contribution := getDistContributions(ctx, mod); contribution != nil {
   allDistContributions = append(allDistContributions, *contribution)
  }
 })

 s.parseDistContributions(allDistContributions)
}

func distsToDistContributions(dists []dist) *distContributions {
 if len(dists) == 0 {
  return nil
 }

 copyGoals := []*copiesForGoals{}
 for _, dist := range dists {
  copyGoals = append(copyGoals, &copiesForGoals{
   goals:  dist.goals,
   copies: dist.paths,
  })
 }

 return &distContributions{
  copiesForGoals: copyGoals,
 }
}

func (s *soongDist) parseDistContributions(allDistContributions []distContributions) {
 s.goalToDests = map[string][]string{}
 for _, dist := range allDistContributions {
  for _, distCopy := range dist.copiesForGoals {
   for _, goal := range distCopy.goals {
    for _, dest := range distCopy.copies {
     s.goalToDests[goal] = append(s.goalToDests[goal], dest.dest)
    }
   }
   s.copies = append(s.copies, distCopy.copies...)
  }
 }

 // Kick off some work that can be done in parallel.
 wg := sync.WaitGroup{}

 // Collect all the dist goals and sort them.
 wg.Add(1)
 go func() {
  defer wg.Done()
  s.goals = slices.Grow(s.goals, len(s.goalToDests))
  s.goals = slices.AppendSeq(s.goals, maps.Keys(s.goalToDests))
  slices.Sort(s.goals)
 }()

 // Sort the copy rules and detect duplicates.
 wg.Add(1)
 go func() {
  defer wg.Done()
  slices.SortFunc(s.copies, func(a, b distCopy) int {
   return cmp.Or(cmp.Compare(a.dest, b.dest), cmp.Compare(a.from.String(), b.from.String()))
  })
  s.copies = slices.CompactFunc(s.copies, func(a, b distCopy) bool {
   if a.dest == b.dest {
    if as, bs := a.from.String(), b.from.String(); as != bs {
     s.conflicts = append(s.conflicts, [2]distCopy{a, b})
    }
    return true
   }
   return false
  })
 }()

 // Wait for the parallel work to finish.  distGoals, distCopies and ctx.Errorf must not be used
 // until the parallel work finishes.
 wg.Wait()
}

// addDistsToPhonyMap adds the intermediate phony targets as dependencies of the dist goals.
func (s *soongDist) addDistsToPhonyMap(ctx SingletonContext, phonyMap phonyMap) {
 for _, goal := range s.goals {
  phonyMap[goal] = append(phonyMap[goal], PathForPhony(ctx, goal+distIntermediateGoalSuffix))
 }
}

// writeNinjaFiles writes the two extra ninja files for use when dist is and is not enabled.
func (s *soongDist) writeNinjaFiles(soongDistFile, soongNoDistFile Path, rewrapperRemoteBuild bool) []error {
 wg := WaitGroupWithErrorCollector{}

 wg.Go(func() error {
  f, err := openBufferedFile(absolutePath(soongDistFile.String()))
  if err != nil {
   return err
  }
  defer f.Close()
  if len(s.conflicts) == 0 {
   return s.generateSoongDistNinja(f, rewrapperRemoteBuild)
  } else {
   return s.generateSoongConflictDistNinja(f, rewrapperRemoteBuild)
  }
 })

 wg.Go(func() error {
  f, err := openBufferedFile(absolutePath(soongNoDistFile.String()))
  if err != nil {
   return err
  }
  defer f.Close()
  return s.generateSoongNoDistNinja(f)
 })

 wg.Wait()
 return wg.Errors()
}

// generateSoongDistNinja generates a ninja file to be used when dist is enabled which contains
// copy rules for all the dist files, and the intermediate phony targets with dependencies
// on the copy rules.
func (s *soongDist) generateSoongDistNinja(w io.StringWriter, rewrapperRemoteBuild bool) error {
 var err error
 write := func(s string) {
  if err == nil {
   _, err = w.WriteString(s)
  }
 }
 write("" +
  "rule distCp\n" +
  " description = Dist $out\n" +
  " command = rm -f $out && cp $in $out\n" +
  " sandbox_disabled = true\n")
 if rewrapperRemoteBuild {
  write(" pool = local_pool\n")
 }
 write("\n")

 insertFileNameTagReplacer := strings.NewReplacer("FILE_NAME_TAG_PLACEHOLDER", "$distFileNameTag")

 for _, distCopy := range s.copies {
  write("build $distdir/")
  write(insertFileNameTagReplacer.Replace(distCopy.dest))
  write(": distCp ")
  write(distCopy.from.String())
  write("\n")
 }

 for _, goal := range s.goals {
  intermediateGoal := goal + distIntermediateGoalSuffix
  write("build ")
  write(intermediateGoal)
  write(": phony")
  for _, dest := range s.goalToDests[goal] {
   write(" $distdir/")
   write(insertFileNameTagReplacer.Replace(dest))
  }
  write("\n phony_output = true\n")
 }
 return err
}

// generateSoongConflictDistNinja generates a ninja file to be used when dist is enabled
// but conflicting copying rules are present.  The ninja file will contain errors for all
// intermediate phony targets, causing any build with `dist` on the command line to fail.
// This matches the behavior of old builds with Make-based dist rules that were only
// generated when `dist` was on the command line, and is necessary because mainline builds
// that configure multiple targets with conflicting multilib values (e.g. arm64 and x86_64)
// exist but don't use `dist.
func (s *soongDist) generateSoongConflictDistNinja(w io.StringWriter, rewrapperRemoteBuild bool) error {
 var err error
 write := func(s string) {
  if err == nil {
   _, err = w.WriteString(s)
  }
 }
 write("" +
  "rule distError\n" +
  " description = Error $out\n" +
  " command = echo $distErrorMsg; false\n" +
  " sandbox_disabled = true\n" +
  " phony_output = true\n")
 if rewrapperRemoteBuild {
  write(" pool = local_pool\n")
 }

 conflictA := s.conflicts[0][0]
 conflictB := s.conflicts[0][1]
 errorMsg := fmt.Sprintf("conflicting dist sources %q and %q for target %q",
  conflictA.from.String(), conflictB.from.String(), conflictA.dest)

 write("distErrorMsg = ")
 write(proptools.NinjaAndShellEscape(errorMsg))
 write("\n")

 for _, goal := range s.goals {
  intermediateGoal := goal + distIntermediateGoalSuffix
  write("build ")
  write(intermediateGoal)
  write(": distError\n")
 }
 return err
}

// writeSoongNoDistNinja generates a ninja file to be used when dist is not enabled, which contains
// empty phony rules for the intermediate phony targets.
func (s *soongDist) generateSoongNoDistNinja(w io.StringWriter) error {
 var err error
 write := func(s string) {
  if err == nil {
   _, err = w.WriteString(s)
  }
 }
 for _, goal := range s.goals {
  intermediateGoal := goal + distIntermediateGoalSuffix
  write("build ")
  write(intermediateGoal)
  write(": phony\n phony_output = true\n")
 }
 return err
}

// WaitGroupWithErrorCollector wraps sync.WaitGroup with helpers to collect errors
// from the parallel tasks.
type WaitGroupWithErrorCollector struct {
 sync.WaitGroup
 lock   sync.Mutex
 errors []error
 done   bool
}

// Error reports an error from a parallel task.
func (wg *WaitGroupWithErrorCollector) Error(err error) {
 wg.lock.Lock()
 defer wg.lock.Unlock()
 wg.errors = append(wg.errors, err)
}

// Go runs a task, calling Add, Done and Error as necessary.
func (wg *WaitGroupWithErrorCollector) Go(f func() error) {
 wg.Add(1)
 go func() {
  defer wg.Done()
  err := f()
  if err != nil {
   wg.Error(err)
  }
 }()
}

// GoWithMultipleErrors runs a task, calling Add, Done and Error as necessary.
func (wg *WaitGroupWithErrorCollector) GoWithMultipleErrors(f func() []error) {
 wg.Add(1)
 go func() {
  defer wg.Done()
  errs := f()
  for _, err := range errs {
   wg.Error(err)
  }
 }()
}

// Wait waits for all parallel tasks to finish and makes any errors visible to Errors.
func (wg *WaitGroupWithErrorCollector) Wait() {
 wg.WaitGroup.Wait()
 wg.done = true
}

// Errors returns any errors reported by the parallel tasks.  Calling Errors before Wait
// returns will panic.
func (wg *WaitGroupWithErrorCollector) Errors() []error {
 if !wg.done {
  panic(fmt.Errorf("Errors called before Wait"))
 }
 return wg.errors
}

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