|
|
|
|
Quelle context.go
Sprache: unbekannt
|
|
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
// Copyright 2014 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 blueprint
import (
"bufio"
"bytes"
"cmp"
"context"
"encoding/json"
"errors"
"fmt"
"hash/fnv"
"io"
"io/ioutil"
"iter"
"maps"
"math"
"os"
"path/filepath"
"reflect"
"runtime"
"runtime/pprof"
"slices"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"text/scanner"
"text/template"
"unsafe"
"github.com/google/blueprint/gobtools"
"github.com/google/blueprint/metrics"
"github.com/google/blueprint/parser"
"github.com/google/blueprint/pathtools"
"github.com/google/blueprint/pool"
"github.com/google/blueprint/proptools"
"github.com/google/blueprint/syncmap"
"github.com/google/blueprint/uniquelist"
)
//go:generate go run ./gobtools/codegen
var ErrBuildActionsNotReady = errors.New("build actions are not ready")
const maxErrors = 10
const MockModuleListFile = "bplist"
const OutFilePermissions = 0666
const BuildActionsCacheFile = "build_actions.gob"
const OrderOnlyStringsCacheFile = "order_only_strings.gob"
// sandboxConfig is an interface for config objects that can report if the
// build is action sandboxed.
type sandboxConfig interface {
IsActionSandboxedBuild() bool
ActionSandboxMetrics() *SandboxMetrics
}
type SandboxedAction struct {
Name string
Sandboxed bool
}
// SandboxMetrics tracks the total number of rules and action sandboxing disabled
// (i.e. opted-out) rules in action sandboxed builds
type SandboxMetrics struct {
deduper sync.Map
totalRules int64
disabledRules int64
actions []SandboxedAction
}
func (s *SandboxMetrics) updateSandboxMetrics(name string, source string, isSandboxDisa bled bool) {
if source != "" {
if _, ok := s.deduper.LoadOrStore(source, true); ok {
return
}
name = source
}
s.totalRules += 1
if isSandboxDisabled {
s.disabledRules += 1
}
s.actions = append(s.actions, SandboxedAction{
Name: name,
Sandboxed: !isSandboxDisabled,
})
}
func (s *SandboxMetrics) TotalRules() int64 {
return s.totalRules
}
func (s *SandboxMetrics) DisabledRules() int64 {
return s.disabledRules
}
func (s *SandboxMetrics) Actions() []SandboxedAction {
return s.actions
}
// A Context contains all the state needed to parse a set of Blueprints files
// and generate a Ninja file. The process of generating a Ninja file proceeds
// through a series of four phases. Each phase corresponds with a some methods
// on the Context object
//
// Phase Methods
// ------------ -------------------------------------------
// 1. Registration RegisterModuleType, RegisterSingletonType
//
// 2. Parse ParseBlueprintsFiles, Parse
//
// 3. Generate ResolveDependencies, PrepareBuildActions
//
// 4. Write WriteBuildFile
//
// The registration phase prepares the context to process Blueprints files
// containing various types of modules. The parse phase reads in one or more
// Blueprints files and validates their contents against the module types that
// have been registered. The generate phase then analyzes the parsed Blueprints
// contents to create an internal representation for the build actions that must
// be performed. This phase also performs validation of the module dependencies
// and property values defined in the parsed Blueprints files. Finally, the
// write phase generates the Ninja manifest text based on the generated build
// actions.
type Context struct {
context.Context
// Used for metrics-related event logging.
EventHandler *metrics.EventHandler
BeforePrepareBuildActionsHook func() error
moduleFactories map[string]ModuleFactory
nameInterface NameInterface
moduleGroups []*moduleGroup
singletonInfo []*singletonInfo
mutatorInfo []*mutatorInfo
variantMutatorNames []string
completedTransitionMutators int
transitionMutators []*transitionMutatorImpl
transitionMutatorNames []string
needsUpdateDependencies uint32 // positive if a mutator modified the dependencies
dependenciesReady bool // set to true on a successful ResolveDependencies
buildActionsReady bool // set to true on a successful PrepareBuildActions
// set by SetIgnoreUnknownModuleTypes
ignoreUnknownModuleTypes bool
// set by SetAllowMissingDependencies
allowMissingDependencies bool
// set during PrepareBuildActions
nameTracker *nameTracker
liveGlobals *liveTracker
globalVariables map[Variable]*ninjaString
globalPools map[Pool]*poolDef
globalRules map[Rule]*ruleDef
// set during PrepareBuildActions
outDir *ninjaString // The builddir special Ninja variable
requiredNinjaMajor int // For the ninja_required_version variable
requiredNinjaMinor int // For the ninja_required_version variable
requiredNinjaMicro int // For the ninja_required_version variable
subninjas []string
// set lazily by sortedModuleGroups
cachedSortedModuleGroups []*moduleGroup
// cache deps modified to determine whether cachedSortedModuleGroups needs to be recalculated
cachedDepsModified bool
globs syncmap.SyncMap[globKey, globsMapEntry]
restoredGlobsFromCache map[globKey]pathtools.GlobResult
restoredGlobMetrics pathtools.RestoredGlobsMetrics
srcDir string
incrementalDBDir string
fs pathtools.FileSystem
moduleListFile string
// Mutators indexed by the ID of the provider associated with them. Not all mutators will
// have providers, and not all providers will have a mutator, or if they do the mutator may
// not be registered in this Context.
providerMutators []*mutatorInfo
// True for the index of any mutators that have already run over all modules
finishedMutators []bool
// If true, RunBlueprint will skip cloning modules at the end of RunBlueprint.
// Cloning modules intentionally invalidates some Module values after
// mutators run (to ensure that mutators don't set such Module values in a way
// which ruins the integrity of the graph). However, keeping Module values
// changed by mutators may be a desirable outcome (such as for tooling or tests).
SkipCloneModulesAfterMutators bool
// String values that can be used to gate build graph traversal
includeTags *IncludeTags
sourceRootDirs *SourceRootDirs
// True if an incremental analysis can be attempted, i.e., there is no Soong
// code changes, no environmental variable changes and no product config
// variable changes.
incrementalAnalysis bool
// True if the flag --incremental-build-actions is set, in which case Soong
// will try to do a incremental build. Mainly two tasks will involve here:
// caching the providers of all the participating modules, and restoring the
// providers and skip the build action generations if there is a cache hit.
// Enabling this flag will only guarantee the former task to be performed, the
// latter will depend on the flag above.
incrementalEnabled bool
incrementalProviderTest bool
keyValueStoreCache *KeyValueStoreCache
buildActionsToCacheLock sync.Mutex
orderOnlyStringsCache OrderOnlyStringsCache
orderOnlyStrings syncmap.SyncMap[uniquelist.UniqueList[string], *orderOnlyStringsInfo]
incrementalDebugFile string
EncContext gobtools.EncContext
providerValueHashes []proptools.Hash
moduleDebugDataChannel chan []byte
captureBuildParams bool
// index of the last mutator which uses `CreateModule`.
mutatorIndexAfterLastCreateModule int
// If splitAllVariants is true, all variants will be created upfront rather than on-demand.
splitAllVariants bool
// index of the first mutator that supports partial analysis.
mutatorIndexPartialAnalysis int
partialAnalysisTargets []string
}
type orderOnlyStringsInfo struct {
dedup bool
incremental bool
dedupName string
}
// A container for String keys. The keys can be used to gate build graph traversal
type SourceRootDirs struct {
dirs []string
}
func (dirs *SourceRootDirs) Add(names ...string) {
dirs.dirs = append(dirs.dirs, names...)
}
func (dirs *SourceRootDirs) SourceRootDirAllowed(path string) (bool, string) {
sort.Slice(dirs.dirs, func(i, j int) bool {
return len(dirs.dirs[i]) < len(dirs.dirs[j])
})
last := len(dirs.dirs)
for i := range dirs.dirs {
// iterate from longest paths (most specific)
prefix := dirs.dirs[last-i-1]
disallowedPrefix := false
if len(prefix) >= 1 && prefix[0] == '-' {
prefix = prefix[1:]
disallowedPrefix = true
}
if strings.HasPrefix(path, prefix) {
if disallowedPrefix {
return false, prefix
} else {
return true, prefix
}
}
}
return true, ""
}
func (c *Context) AddSourceRootDirs(dirs ...string) {
c.sourceRootDirs.Add(dirs...)
}
// A container for String keys. The keys can be used to gate build graph traversal
type IncludeTags map[string]bool
func (tags *IncludeTags) Add(names ...string) {
for _, name := range names {
(*tags)[name] = true
}
}
func (tags *IncludeTags) Contains(tag string) bool {
_, exists := (*tags)[tag]
return exists
}
func (c *Context) AddIncludeTags(names ...string) {
c.includeTags.Add(names...)
}
func (c *Context) ContainsIncludeTag(name string) bool {
return c.includeTags.Contains(name)
}
// iterateAllVariants returns an iter.Seq that iterates over every variant of every module.
func (c *Context) iterateAllVariants() iter.Seq[*moduleInfo] {
return func(yield func(*moduleInfo) bool) {
for _, group := range c.moduleGroups {
for _, module := range group.modules {
if !yield(module) {
return
}
}
}
}
}
// An Error describes a problem that was encountered that is related to a
// particular location in a Blueprints file.
type BlueprintError struct {
Err error // the error that occurred
Pos scanner.Position // the relevant Blueprints file location
}
// A ModuleError describes a problem that was encountered that is related to a
// particular module in a Blueprints file
type ModuleError struct {
BlueprintError
module *moduleInfo
}
// A PropertyError describes a problem that was encountered that is related to a
// particular property in a Blueprints file
type PropertyError struct {
ModuleError
property string
}
func (e *BlueprintError) Error() string {
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
}
func (e *ModuleError) Error() string {
return fmt.Sprintf("%s: %s: %s", e.Pos, e.module, e.Err)
}
func (e *PropertyError) Error() string {
return fmt.Sprintf("%s: %s: %s: %s", e.Pos, e.module, e.property, e.Err)
}
type localBuildActions struct {
variables []*localVariable
rules []*localRule
buildDefs []*buildDef
}
type moduleList []*moduleInfo
func (l moduleList) firstModule() *moduleInfo {
if len(l) > 0 {
return l[0]
}
panic(fmt.Errorf("no first module!"))
}
func (l moduleList) lastModule() *moduleInfo {
if len(l) > 0 {
return l[len(l)-1]
}
panic(fmt.Errorf("no last module!"))
}
type moduleGroup struct {
name string
modules moduleList
namespace Namespace
// A partial copy of moduleInfo at the end of defaults mutator.
// common across all variants.
coreModuleInfo moduleInfo
// used for creating on demand variants.
variantOnDemandLock sync.Mutex
// Additional variations supported by this module group that were not
// created by Split.
supportedVariantsOnDemand map[string]TransitionInfos
// Map of requested variation map to on-demand variants.
// Set to empty at the end of mutator.
cachedVariantsOnDemand map[string]*moduleInfo
// Indicates the module group is not yet in the build graph.
passive bool
}
func (group *moduleGroup) moduleByVariantName(name string) *moduleInfo {
for _, module := range group.modules {
if module.variant.name == name {
return module
}
}
return nil
}
// registerSupportedVariants registers the supported variants, but does not create them
func (group *moduleGroup) registerSupportedVariants(mutatorName string, infos TransitionInfos) {
if len(infos) == 0 {
return
}
group.variantOnDemandLock.Lock()
defer group.variantOnDemandLock.Unlock()
if group.supportedVariantsOnDemand == nil {
group.supportedVariantsOnDemand = map[string]TransitionInfos{}
}
if _, exists := group.supportedVariantsOnDemand[mutatorName]; exists {
return
}
group.supportedVariantsOnDemand[mutatorName] = append(group.supportedVariantsOnDemand[mutatorName], infos...)
}
// searchOnDemandVariant returns true if an on-demand variant creation should be attempted.
// It uses the following heuristics.
// 1. SplitOnDemand is non-nil for any mutator that precedes the Add.*Dependency request.
// 2. No transition has occurred yet
//
// Feasibility of on-demand variant request will be determined subsequently by using `applyTransitions`
// across a sliding window on the on-demand variant.
func (group *moduleGroup) searchOnDemandVariant(onDemandVariants variationMap, far bool, atMutatorIndex int, transitionMutators []*transitionMutatorImpl) bool {
for _, mutator := range transitionMutators {
if atMutatorIndex >= mutator.mutatorIndex {
if _, exists := group.supportedVariantsOnDemand[mutator.name]; exists {
return true
}
}
}
if len(transitionMutators) > 0 && atMutatorIndex < transitionMutators[0].mutatorIndex {
return true
}
return false
}
// loadOrCreateVariantOnDemand returns an on-demand variant (if supported) or nil.
// It reruns the completed transitions, applying Outgoing/Incoming transition to resolve the requested variant to a final variant.
//
// To reduce duplicate reruns, it stores the results in a map.
func (c *Context) loadOrCreateVariantOnDemand(config any, module *moduleInfo, depTag DependencyTag, possibleDeps *moduleGroup, variant variationMap,
requestedVariations []Variation, far bool, moduleOutgoingTransitionInfos []TransitionInfo) (*moduleInfo, []error) {
createVariantOnDemand := func() (*moduleInfo, []error) {
var variantOnDemand *moduleInfo
var errs []error
onDemandVariantForIncomingTransition := c.createVariantOnDemand(possibleDeps, variant)
_, _, errs = c.applyTransitions(config, module, depTag, possibleDeps, variant, requestedVariations, far, onDemandVariantForIncomingTransition)
if len(errs) > 0 || onDemandVariantForIncomingTransition.createdOnDemandIncompatible {
variantOnDemand = nil
} else {
variantOnDemand = c.createVariantOnDemand(possibleDeps, onDemandVariantForIncomingTransition.requestedOnDemandVariant.clone())
}
return variantOnDemand, errs
}
if module.group == possibleDeps {
// inter-variant dep can cause a deadlock, so always compute.
return createVariantOnDemand()
}
var variantSb strings.Builder
for _, mutator := range slices.Sorted(maps.Keys(variant.variations)) {
variantSb.WriteString("-")
variantSb.WriteString(variant.variations[mutator])
}
for _, mutator := range slices.Sorted(maps.Keys(module.requestedOnDemandVariant.variations)) {
variantSb.WriteString("-")
variantSb.WriteString(variant.variations[mutator])
}
for _, oti := range moduleOutgoingTransitionInfos {
if oti != nil && oti.Variation() != "" {
variantSb.WriteString("-")
variantSb.WriteString(oti.Variation())
}
}
possibleDeps.variantOnDemandLock.Lock()
defer possibleDeps.variantOnDemandLock.Unlock()
if possibleDeps.cachedVariantsOnDemand == nil {
possibleDeps.cachedVariantsOnDemand = make(map[string]*moduleInfo)
}
// Match found
if cached, exists := possibleDeps.cachedVariantsOnDemand[variantSb.String()]; exists {
return cached, nil
}
// No match found
variantOnDemand, errs := createVariantOnDemand()
possibleDeps.cachedVariantsOnDemand[variantSb.String()] = variantOnDemand
return variantOnDemand, errs
}
// createVariantOnDemand uses group.coreModuleInfo.factory() to create an "empty" variant on demand.
// `runMutator` will be responsible for populating this variant using cloneLogicModule
// and creating the dependency edges to this on demand variant.
func (c *Context) createVariantOnDemand(group *moduleGroup, onDemandVariants variationMap) *moduleInfo {
newlogicmodule, newproperties := group.coreModuleInfo.factory()
newmodule := moduleInfo{
logicModule: newlogicmodule,
properties: newproperties,
factory: group.coreModuleInfo.factory,
group: group,
createdOnDemand: true,
requestedOnDemandVariant: onDemandVariants,
relBlueprintsFile: group.coreModuleInfo.relBlueprintsFile,
pos: group.coreModuleInfo.pos,
}
newmodule.createdOnDemandReplaceWith = &newmodule
newlogicmodule.setInfo(&newmodule)
return &newmodule
}
// Initialize the properties of the on demand variant and
// re-run the completed mutators on this newly created module.
func (c *Context) rerunMutatorsOnVariantOnDemand(newmodule *moduleInfo, fromMutatorIndex, tillMutatorIndex int, p pauseFunc, config interface{}) {
pause := func(dep *moduleInfo) {
if dep.createdOnDemand {
// Transitive variant on demand.
// Set `requestedOnDemandVariant` on this transitive dep.
// This will be used in the main coordinator goroutine to create the correct transition for this variant.
for _, transitionMutator := range c.transitionMutators[:c.completedTransitionMutators] {
if transitionMutator.mutatorIndex >=
dep.finishedMutator+3 { // TODO (b/448182248): Revisit partially transitioned on-demand variants.
// Copy requestedOnDemandVariant from rdep to dep on-demand modules.
// This will be done only for the transition mutators that have not yet
// been completed in rerunMutator.
// variation map for earlier transition mutators will be resolved by `findVariant`.
//
// TODO (spandandas): Does this handle transition mutators between
// newmodule.finishedMutator and c.completedTransitionMutators?
dep.requestedOnDemandVariant.set(transitionMutator.name, newmodule.requestedOnDemandVariant.get(transitionMutator.name))
}
}
}
if p != nil {
p(dep)
}
}
// initialize the properties from coreModuleInfo.
if fromMutatorIndex == c.mutatorIndexAfterLastCreateModule {
newlogicmodule, newproperties := c.cloneLogicModule(&newmodule.group.coreModuleInfo)
newlogicmodule.setInfo(newmodule)
newmodule.logicModule = newlogicmodule
newmodule.properties = newproperties
}
for index, mi := range c.mutatorInfo {
if index < fromMutatorIndex {
continue
}
newmodule.startedMutator = index
mctx := &mutatorContext{
baseModuleContext: baseModuleContext{
context: c,
config: config,
module: newmodule,
},
mutator: mi,
pauseFunc: pause,
}
if mi.transitionPropagateMutator != nil {
mi.transitionPropagateMutator(mctx)
} else {
mctx.mutator = mi
mi.bottomUpMutator(mctx)
}
newmodule.finishedMutator = index
if len(mctx.reverseDeps) > 0 || len(mctx.replace) > 0 || len(mctx.rename) > 0 {
panic("TODO (b/448182009): Add support for AddReverseDependency, ReplaceDependency, Rename")
}
// The module has been mutated till the current mutator.
// Do not mutate further.
if index == tillMutatorIndex {
break
}
// Err
if newmodule.createdOnDemandIncompatible {
break
}
}
}
type moduleInfo struct {
// set during Parse
typeName string
factory ModuleFactory
relBlueprintsFile string
pos scanner.Position
propertyPos map[string]scanner.Position
createdBy *moduleInfo
variant variant
logicModule Module
group *moduleGroup
properties []interface{}
// set during ResolveDependencies
missingDeps []string
newDirectDeps []*moduleInfo
newOnDemandReverseDeps []reverseDep
// set during updateDependencies
reverseDeps []*moduleInfo
forwardDeps []*moduleInfo
directDeps []depInfo
// used by parallelVisit
waitingCount atomic.Int32
// set during each runMutator
splitModules moduleList
obsoletedByNewVariants bool
// Used by TransitionMutator implementations
// incomingTransitionInfos stores the map from variation to TransitionInfo object for transitions that were
// requested by reverse dependencies. It is updated by reverse dependencies and protected by
// incomingTransitionInfosLock. It is invalid after the TransitionMutator top down mutator has run on
// this module.
incomingTransitionInfos map[string]TransitionInfo
incomingTransitionInfosLock sync.Mutex
// splitTransitionInfos and splitTransitionVariations stores the list of TransitionInfo objects, and their
// corresponding variations, returned by Split or requested by reverse dependencies. They are valid after the
// TransitionMutator top down mutator has run on this module, and invalid after the bottom up mutator has run.
splitTransitionInfos []TransitionInfo
splitTransitionVariations []string
currentTransitionMutator string
// transitionInfos stores the final TransitionInfo for this module indexed by transitionMutatorImpl.index
transitionInfos []TransitionInfo
// outgoingTransitionCache stores the final variation for each dependency, indexed by the source variation
// index in splitTransitionInfos and then by the index of the dependency in directDeps
outgoingTransitionCache [][]string
// set during PrepareBuildActions
actionDefs localBuildActions
buildParams *[]BuildParams
startedMutator int
finishedMutator int
startedGenerateBuildActions bool
finishedGenerateBuildActions bool
// freeAfterGenerateBuildActions is set if the module called ModuleContext.FreeModuleAfterGenerateBuildActions,
// allowing the Module to be freed after GenerateBuildActions complete, and requiring all future accesses
// to go through ModuleProxy instead of the Module.
freeAfterGenerateBuildActions bool
// cachedName stores the result of Module.Name() after the end of GenerateBuildActions for use in ModuleProxy.Name()
cachedName string
// cachedString stores the result of Module.String() after the end of GenerateBuildActions for use in ModuleProxy.String().
cachedString string
// cachedUniqueName stores the result of UniqueName after the end of GenerateBuildActions for use when sorting
// modules.
cachedUniqueName string
moduleIncrementalInfo
createdOnDemand bool
createdOnDemandReplaceWith *moduleInfo
requestedOnDemandVariant variationMap
// Properties used to determine whether a requested on-demand variant can be created.
createdOnDemandIncompatible bool
createdOnDemandSupportedSplits []TransitionInfo
// Index used for sorting primary and on-demand variants. Special-cased to android's image mutator for now.
sortIndex int
}
type providerInfo struct {
providers []interface{}
providerInitialValueHashes []proptools.Hash
}
// @auto-generate: gob
type globResultCache struct {
Pattern string
Excludes []string
Result proptools.Hash
}
func (g *globResultCache) equal(other globResultCache) bool {
return g.Result == other.Result && g.Pattern == other.Pattern && slices.Equal(g.Excludes, other.Excludes)
}
type moduleIncrementalInfo struct {
commonIncrementalInfo
buildActionInputHash proptools.Hash
orderOnlyStrings []string
incrementalDebugInfo []byte
// providersHash is the hash of the providers set by this module
providersHash proptools.Hash
// transitiveProvidersHash is the hash of the providers set by
// this module and all transitive dependencies of this module.
transitiveProvidersHash proptools.Hash
}
type commonIncrementalInfo struct {
incrementalRestored bool
// hasUnrestoredProvider is true when the module has been restored from the cache and the provider
// (indexed in the same order as providerRegistry) exists in the cache but has not yet been
// restored.
hasUnrestoredProvider []bool
providerRestoreLock sync.Mutex
buildActionCacheKey *DataCacheKey
globCache []globResultCache
providerInfo
}
type variant struct {
name string
variations variationMap
}
type depInfo struct {
module *moduleInfo
tag DependencyTag
}
func (module *moduleInfo) Name() string {
// If this is called from a LoadHook (which is run before the module has been registered)
// then group will not be set and so the name is retrieved from logicModule.Name().
// Usually, using that method is not safe as it does not track renames (group.name does).
// However, when called from LoadHook it is safe as there is no way to rename a module
// until after the LoadHook has run and the module has been registered.
if module.group != nil {
return module.group.name
} else {
return module.logicModule.Name()
}
}
func (module *moduleInfo) String() string {
s := fmt.Sprintf("module %q", module.Name())
if module.variant.name != "" {
s += fmt.Sprintf(" variant %q", module.variant.name)
}
if module.createdBy != nil {
s += fmt.Sprintf(" (created by %s)", module.createdBy)
}
return s
}
func (module *moduleInfo) namespace() Namespace {
return module.group.namespace
}
func (module *moduleInfo) moduleCacheKey() string {
variant := module.variant.name
if variant == "" {
variant = "none"
}
return calculateFileNameHash(fmt.Sprintf("%s-%s-%s-%s",
filepath.Dir(module.relBlueprintsFile), module.cachedUniqueName, variant, module.typeName))
}
// @auto-generate: gob
type stringHash struct {
string
}
func calculateFileNameHash(name string) string {
hash, err := proptools.CalculateHash(stringHash{name})
if err != nil {
panic(newPanicErrorf(err, "failed to calculate hash for file name: %s", name))
}
return hash.FormatUint(16)
}
func (c *Context) setModuleTransitionInfo(module *moduleInfo, t *transitionMutatorImpl, info TransitionInfo) {
if len(module.transitionInfos) == 0 {
module.transitionInfos = make([]TransitionInfo, len(c.transitionMutators))
}
module.transitionInfos[t.index] = info
}
// A Variation is a way that a variant of a module differs from other variants of the same module.
// For example, two variants of the same module might have Variation{"arch","arm"} and
// Variation{"arch","arm64"}
// @auto-generate: gob
type Variation struct {
// Mutator is the axis on which this variation applies, i.e. "arch" or "link"
Mutator string
// Variation is the name of the variation on the axis, i.e. "arm" or "arm64" for arch, or
// "shared" or "static" for link.
Variation string
}
// A variationMap stores a map of Mutator to Variation to specify a variant of a module.
type variationMap struct {
variations map[string]string
}
func (vm variationMap) clone() variationMap {
return variationMap{
variations: maps.Clone(vm.variations),
}
}
func (vm variationMap) cloneMatching(mutators []string) variationMap {
newVariations := make(map[string]string)
for _, mutator := range mutators {
if variation, ok := vm.variations[mutator]; ok {
newVariations[mutator] = variation
}
}
return variationMap{
variations: newVariations,
}
}
// Compare this variationMap to another one. Returns true if the every entry in this map
// exists and has the same value in the other map.
func (vm variationMap) subsetOf(other variationMap) bool {
for k, v1 := range vm.variations {
if v2, ok := other.variations[k]; !ok || v1 != v2 {
return false
}
}
return true
}
func (vm variationMap) equal(other variationMap) bool {
return maps.Equal(vm.variations, other.variations)
}
func (vm *variationMap) set(mutator, variation string) {
if variation == "" {
if vm.variations != nil {
delete(vm.variations, mutator)
}
} else {
if vm.variations == nil {
vm.variations = make(map[string]string)
}
vm.variations[mutator] = variation
}
}
func (vm variationMap) get(mutator string) string {
return vm.variations[mutator]
}
func (vm variationMap) delete(mutator string) {
delete(vm.variations, mutator)
}
func (vm variationMap) empty() bool {
return len(vm.variations) == 0
}
// differenceKeysCount returns the count of keys that exist in this variationMap that don't exist in the argument. It
// ignores the values.
func (vm variationMap) differenceKeysCount(other variationMap) int {
divergence := 0
for mutator, _ := range vm.variations {
if _, exists := other.variations[mutator]; !exists {
divergence += 1
}
}
return divergence
}
type singletonInfo struct {
// set during RegisterSingletonType
factory SingletonFactory
singleton Singleton
name string
parallel bool
// set during PrepareBuildActions
actionDefs localBuildActions
subninjas []string
startedGenerateBuildActions bool
finishedGenerateBuildActions bool
commonIncrementalInfo
// Whether this singleton supports incremental build.
incrementalSupported bool
// The provider hashes of all the singletons that this singleton might depend on.
// These values are calculated before calling the GenerateBuildAction of the current
// singleton, and combined with the hashes of all the module providers that this
// singleton might depend on, we can decide if the input of the GenerateBuildAction
// has any change, and skip the execution of it if there is no change.
providerValueHashes []proptools.Hash
}
type mutatorInfo struct {
// set during RegisterMutator
transitionPropagateMutator func(BaseModuleContext)
bottomUpMutator BottomUpMutator
name string
index int
transitionMutator *transitionMutatorImpl
usesRename bool
usesReverseDependencies bool
usesReplaceDependencies bool
usesCreateModule bool
mutatesDependencies bool
mutatesGlobalState bool
prePartial bool
}
func newContext() *Context {
eventHandler := metrics.EventHandler{}
return &Context{
Context: context.Background(),
EventHandler: &eventHandler,
moduleFactories: make(map[string]ModuleFactory),
nameInterface: NewSimpleNameInterface(),
fs: pathtools.OsFs,
includeTags: &IncludeTags{},
sourceRootDirs: &SourceRootDirs{},
outDir: nil,
requiredNinjaMajor: 1,
requiredNinjaMinor: 7,
requiredNinjaMicro: 0,
orderOnlyStringsCache: make(OrderOnlyStringsCache),
orderOnlyStrings: syncmap.SyncMap[uniquelist.UniqueList[string], *orderOnlyStringsInfo]{},
}
}
// NewContext creates a new Context object. The created context initially has
// no module or singleton factories registered, so the RegisterModuleFactory and
// RegisterSingletonFactory methods must be called before it can do anything
// useful.
func NewContext() *Context {
ctx := newContext()
ctx.RegisterBottomUpMutator("blueprint_deps", blueprintDepsMutator)
return ctx
}
// A ModuleFactory function creates a new Module object. See the
// Context.RegisterModuleType method for details about how a registered
// ModuleFactory is used by a Context.
type ModuleFactory func() (m Module, propertyStructs []interface{})
// RegisterModuleType associates a module type name (which can appear in a
// Blueprints file) with a Module factory function. When the given module type
// name is encountered in a Blueprints file during parsing, the Module factory
// is invoked to instantiate a new Module object to handle the build action
// generation for the module. If a Mutator splits a module into multiple variants,
// the factory is invoked again to create a new Module for each variant.
//
// The module type names given here must be unique for the context. The factory
// function should be a named function so that its package and name can be
// included in the generated Ninja file for debugging purposes.
//
// The factory function returns two values. The first is the newly created
// Module object. The second is a slice of pointers to that Module object's
// properties structs. Each properties struct is examined when parsing a module
// definition of this type in a Blueprints file. Exported fields of the
// properties structs are automatically set to the property values specified in
// the Blueprints file. The properties struct field names determine the name of
// the Blueprints file properties that are used - the Blueprints property name
// matches that of the properties struct field name with the first letter
// converted to lower-case.
//
// The fields of the properties struct must be either []string, a string, or
// bool. The Context will panic if a Module gets instantiated with a properties
// struct containing a field that is not one these supported types.
//
// Any properties that appear in the Blueprints files that are not built-in
// module properties (such as "name" and "deps") and do not have a corresponding
// field in the returned module properties struct result in an error during the
// Context's parse phase.
//
// As an example, the follow code:
//
// type myModule struct {
// properties struct {
// Foo string
// Bar []string
// }
// }
//
// func NewMyModule() (blueprint.Module, []interface{}) {
// module := new(myModule)
// properties := &module.properties
// return module, []interface{}{properties}
// }
//
// func main() {
// ctx := blueprint.NewContext()
// ctx.RegisterModuleType("my_module", NewMyModule)
// // ...
// }
//
// would support parsing a module defined in a Blueprints file as follows:
//
// my_module {
// name: "myName",
// foo: "my foo string",
// bar: ["my", "bar", "strings"],
// }
//
// The factory function may be called from multiple goroutines. Any accesses
// to global variables must be synchronized.
func (c *Context) RegisterModuleType(name string, factory ModuleFactory) {
if _, present := c.moduleFactories[name]; present {
panic(fmt.Errorf("module type %q is already registered", name))
}
c.moduleFactories[name] = factory
}
// A SingletonFactory function creates a new Singleton object. See the
// Context.RegisterSingletonType method for details about how a registered
// SingletonFactory is used by a Context.
type SingletonFactory func() Singleton
// RegisterSingletonType registers a singleton type that will be invoked to
// generate build actions. Each registered singleton type is instantiated
// and invoked exactly once as part of the generate phase.
//
// Those singletons registered with parallel=true are run in parallel, after
// which the other registered singletons are run in registration order.
//
// The singleton type names given here must be unique for the context. The
// factory function should be a named function so that its package and name can
// be included in the generated Ninja file for debugging purposes.
func (c *Context) RegisterSingletonType(name string, factory SingletonFactory, parallel bool) {
for _, s := range c.singletonInfo {
if s.name == name {
panic(fmt.Errorf("singleton %q is already registered", name))
}
}
c.singletonInfo = append(c.singletonInfo, &singletonInfo{
factory: factory,
singleton: factory(),
name: name,
parallel: parallel,
})
}
func (c *Context) SetNameInterface(i NameInterface) {
c.nameInterface = i
}
func (c *Context) SetIncrementalAnalysis(incremental bool) {
c.incrementalAnalysis = incremental
}
func (c *Context) GetIncrementalAnalysis() bool {
return c.incrementalAnalysis
}
func (c *Context) SetIncrementalEnabled(incremental bool) {
c.incrementalEnabled = incremental
}
func (c *Context) SetIncrementalProviderTest(test bool) {
c.incrementalProviderTest = test
}
func (c *Context) GetIncrementalEnabled() bool {
return c.incrementalEnabled
}
func (c *Context) SetIncrementalDebugFile(file string) {
c.incrementalDebugFile = file
}
func (c *Context) SetPartialAnalysisTargets(targets string) {
rawSlice := strings.Split(strings.TrimSpace(targets), ",")
for _, item := range rawSlice {
cleanItem := strings.TrimSpace(item)
if cleanItem != "" {
c.partialAnalysisTargets = append(c.partialAnalysisTargets, cleanItem)
}
}
}
func (c *Context) GetPartialAnalysisTargets() []string {
return c.partialAnalysisTargets
}
func (c *Context) GetSplitAllVariants() bool {
return c.splitAllVariants
}
func (c *Context) SetSplitAllVariants(s bool) {
c.splitAllVariants = s
}
func (c *Context) CacheAllBuildActions(soongOutDir string) (err error) {
if err := cacheEncData(c, soongOutDir, OrderOnlyStringsCacheFile, &c.orderOnlyStringsCache); err != nil {
return err
}
defer func() {
err = errors.Join(err, c.keyValueStoreCache.close())
}()
err = c.EncContext.EncodeReferences()
return err
}
func cacheEncData(ctx *Context, soongOutDir string, fileName string, data gobtools.CustomEnc) error {
buf := new(bytes.Buffer)
if err := data.Encode(ctx.EncContext, buf); err != nil {
return err
}
return writeToCache(ctx, soongOutDir, fileName, buf)
}
func writeToCache(ctx *Context, soongOutDir string, fileName string, buf *bytes.Buffer) error {
file, err := pathtools.OpenWithTruncateOnClose(ctx.fs, filepath.Join(ctx.SrcDir(), soongOutDir, fileName))
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(buf.Bytes())
return err
}
func (c *Context) RestoreAllBuildActions(soongOutDir string) error {
return restoreEncData(c, soongOutDir, OrderOnlyStringsCacheFile, &c.orderOnlyStringsCache)
}
func restoreEncData(ctx *Context, soongOutDir string, fileName string, data gobtools.CustomDec) error {
if stream, err := restoreFromCache(ctx, soongOutDir, fileName); err == nil && stream != nil {
return data.Decode(ctx.EncContext, bytes.NewReader(stream))
} else {
return err
}
}
func restoreFromCache(ctx *Context, soongOutDir string, fileName string) ([]byte, error) {
file := filepath.Join(ctx.SrcDir(), soongOutDir, fileName)
if _, err := os.Stat(file); os.IsNotExist(err) {
return nil, nil
}
return os.ReadFile(file)
}
func (c *Context) SetSrcDir(path string) {
c.srcDir = path
c.fs = pathtools.NewOsFs(path)
}
func (c *Context) SrcDir() string {
return c.srcDir
}
func (c *Context) SetIncrementalDBDir(path string) {
c.incrementalDBDir = path
}
func (c *Context) IncrementalDBDir() string {
return c.incrementalDBDir
}
func singletonPkgPath(singleton Singleton) string {
typ := reflect.TypeOf(singleton)
for typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
return typ.PkgPath()
}
func singletonTypeName(singleton Singleton) string {
typ := reflect.TypeOf(singleton)
for typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
return typ.PkgPath() + "." + typ.Name()
}
// registerTransitionPropagateMutator registers a mutator that will be invoked to propagate transition mutator
// configuration info top-down between Modules.
func (c *Context) registerTransitionPropagateMutator(name string, mutator func(mctx BaseModuleContext)) MutatorHandle {
for _, m := range c.mutatorInfo {
if m.name == name && m.transitionPropagateMutator != nil {
panic(fmt.Errorf("mutator %q is already registered", name))
}
}
info := &mutatorInfo{
transitionPropagateMutator: mutator,
name: name,
index: len(c.mutatorInfo),
}
c.mutatorInfo = append(c.mutatorInfo, info)
return info
}
// RegisterBottomUpMutator registers a mutator that will be invoked to split Modules into variants.
// Each registered mutator is invoked in registration order once per Module, and will not be invoked on a
// module until the invocations on all of the modules dependencies have returned.
//
// The mutator type names given here must be unique to all bottom up or early
// mutators in the Context.
func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator) MutatorHandle {
for _, m := range c.variantMutatorNames {
if m == name {
panic(fmt.Errorf("mutator %q is already registered", name))
}
}
info := &mutatorInfo{
bottomUpMutator: mutator,
name: name,
index: len(c.mutatorInfo),
}
c.mutatorInfo = append(c.mutatorInfo, info)
c.variantMutatorNames = append(c.variantMutatorNames, name)
return info
}
// RegisterFirstBottomUpMutator registers a mutator that will be invoked to split Modules into variants.
// The registered mutator is placed at the front of the list.
//
// The mutator type names given here must be unique to all bottom up mutators in the Context.
func (c *Context) RegisterFirstBottomUpMutator(name string, mutator BottomUpMutator) MutatorHandle {
for _, m := range c.variantMutatorNames {
if m == name {
panic(fmt.Errorf("mutator %q is already registered", name))
}
}
info := &mutatorInfo{
bottomUpMutator: mutator,
name: name,
index: 0,
}
c.mutatorInfo = append([]*mutatorInfo{info}, c.mutatorInfo...)
c.variantMutatorNames = append([]string{name}, c.variantMutatorNames...)
for i := range c.mutatorInfo {
c.mutatorInfo[i].index = i
}
return info
}
// HasMutatorFinished returns true if the given mutator has finished running.
// It will panic if given an invalid mutator name.
func (c *Context) HasMutatorFinished(mutatorName string) bool {
for _, mutator := range c.mutatorInfo {
if mutator.name == mutatorName {
return len(c.finishedMutators) > mutator.index && c.finishedMutators[mutator.index]
}
}
panic(fmt.Sprintf("unknown mutator %q", mutatorName))
}
type MutatorHandle interface {
// UsesRename marks the mutator as using the BottomUpMutatorContext.Rename method, which prevents
// coalescing adjacent mutators into a single mutator pass.
UsesRename() MutatorHandle
// UsesReverseDependencies marks the mutator as using the BottomUpMutatorContext.AddReverseDependency
// method, which prevents coalescing adjacent mutators into a single mutator pass.
UsesReverseDependencies() MutatorHandle
// UsesReplaceDependencies marks the mutator as using the BottomUpMutatorContext.ReplaceDependencies
// method, which prevents coalescing adjacent mutators into a single mutator pass.
UsesReplaceDependencies() MutatorHandle
// UsesCreateModule marks the mutator as using the BottomUpMutatorContext.CreateModule method,
// which prevents coalescing adjacent mutators into a single mutator pass.
UsesCreateModule() MutatorHandle
// MutatesDependencies marks the mutator as modifying properties in dependencies, which prevents
// coalescing adjacent mutators into a single mutator pass.
MutatesDependencies() MutatorHandle
// MutatesGlobalState marks the mutator as modifying global state, which prevents coalescing
// adjacent mutators into a single mutator pass.
MutatesGlobalState() MutatorHandle
PrePartial() MutatorHandle
setTransitionMutator(impl *transitionMutatorImpl) MutatorHandle
}
func (mutator *mutatorInfo) UsesRename() MutatorHandle {
mutator.usesRename = true
return mutator
}
func (mutator *mutatorInfo) UsesReverseDependencies() MutatorHandle {
mutator.usesReverseDependencies = true
return mutator
}
func (mutator *mutatorInfo) UsesReplaceDependencies() MutatorHandle {
mutator.usesReplaceDependencies = true
return mutator
}
func (mutator *mutatorInfo) UsesCreateModule() MutatorHandle {
mutator.usesCreateModule = true
return mutator
}
func (mutator *mutatorInfo) MutatesDependencies() MutatorHandle {
mutator.mutatesDependencies = true
return mutator
}
func (mutator *mutatorInfo) MutatesGlobalState() MutatorHandle {
mutator.mutatesGlobalState = true
return mutator
}
func (mutator *mutatorInfo) PrePartial() MutatorHandle {
mutator.prePartial = true
return mutator
}
func (mutator *mutatorInfo) setTransitionMutator(impl *transitionMutatorImpl) MutatorHandle {
mutator.transitionMutator = impl
return mutator
}
// SetIgnoreUnknownModuleTypes sets the behavior of the context in the case
// where it encounters an unknown module type while parsing Blueprints files. By
// default, the context will report unknown module types as an error. If this
// method is called with ignoreUnknownModuleTypes set to true then the context
// will silently ignore unknown module types.
//
// This method should generally not be used. It exists to facilitate the
// bootstrapping process.
func (c *Context) SetIgnoreUnknownModuleTypes(ignoreUnknownModuleTypes bool) {
c.ignoreUnknownModuleTypes = ignoreUnknownModuleTypes
}
// SetAllowMissingDependencies changes the behavior of Blueprint to ignore
// unresolved dependencies. If the module's GenerateBuildActions calls
// ModuleContext.GetMissingDependencies Blueprint will not emit any errors
// for missing dependencies.
func (c *Context) SetAllowMissingDependencies(allowMissingDependencies bool) {
c.allowMissingDependencies = allowMissingDependencies
}
func (c *Context) SetModuleListFile(listFile string) {
c.moduleListFile = listFile
}
func (c *Context) ListModulePaths(baseDir string) (paths []string, err error) {
reader, err := c.fs.Open(c.moduleListFile)
if err != nil {
return nil, err
}
defer reader.Close()
bytes, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
text := string(bytes)
text = strings.Trim(text, "\n")
lines := strings.Split(text, "\n")
for i := range lines {
lines[i] = filepath.Join(baseDir, lines[i])
}
return lines, nil
}
// a fileParseContext tells the status of parsing a particular file
type fileParseContext struct {
// name of file
fileName string
// scope to use when resolving variables
Scope *parser.Scope
// pointer to the one in the parent directory
parent *fileParseContext
// is closed once FileHandler has completed for this file
doneVisiting chan struct{}
}
// ParseBlueprintsFiles parses a set of Blueprints files starting with the file
// at rootFile. When it encounters a Blueprints file with a set of subdirs
// listed it recursively parses any Blueprints files found in those
// subdirectories.
//
// If no errors are encountered while parsing the files, the list of paths on
// which the future output will depend is returned. This list will include both
// Blueprints file paths as well as directory paths for cases where wildcard
// subdirs are found.
func (c *Context) ParseBlueprintsFiles(rootFile string,
config interface{}) (deps []string, errs []error) {
baseDir := filepath.Dir(rootFile)
pathsToParse, err := c.ListModulePaths(baseDir)
if err != nil {
return nil, []error{err}
}
return c.ParseFileList(baseDir, pathsToParse, config)
}
type shouldVisitFileInfo struct {
shouldVisitFile bool
skippedModules []string
reasonForSkip string
errs []error
}
// Returns a boolean for whether this file should be analyzed
// Evaluates to true if the file either
// 1. does not contain a blueprint_package_includes
// 2. contains a blueprint_package_includes and all requested tags are set
// This should be processed before adding any modules to the build graph
func shouldVisitFile(c *Context, file *parser.File) shouldVisitFileInfo {
skippedModules := []string{}
for _, def := range file.Defs {
switch def := def.(type) {
case *parser.Module:
skippedModules = append(skippedModules, def.Name())
}
}
shouldVisit, invalidatingPrefix := c.sourceRootDirs.SourceRootDirAllowed(file.Name)
if !shouldVisit {
return shouldVisitFileInfo{
shouldVisitFile: shouldVisit,
skippedModules: skippedModules,
reasonForSkip: fmt.Sprintf(
"%q is a descendant of %q, and that path prefix was not included in PRODUCT_SOURCE_ROOT_DIRS",
file.Name,
invalidatingPrefix,
),
}
}
return shouldVisitFileInfo{shouldVisitFile: true}
}
func (c *Context) ParseFileList(rootDir string, filePaths []string,
config interface{}) (deps []string, errs []error) {
if len(filePaths) < 1 {
return nil, []error{fmt.Errorf("no paths provided to parse")}
}
c.dependenciesReady = false
type newModuleInfo struct {
*moduleInfo
deps []string
added chan<- struct{}
}
type newSkipInfo struct {
shouldVisitFileInfo
file string
}
moduleCh := make(chan newModuleInfo)
errsCh := make(chan []error)
doneCh := make(chan struct{})
skipCh := make(chan newSkipInfo)
var numErrs uint32
var numGoroutines int32
// handler must be reentrant
handleOneFile := func(file *parser.File) {
if atomic.LoadUint32(&numErrs) > maxErrors {
return
}
addedCh := make(chan struct{})
var scopedModuleFactories map[string]ModuleFactory
var addModule func(module *moduleInfo) []error
addModule = func(module *moduleInfo) []error {
// Run any load hooks immediately before it is sent to the moduleCh and is
// registered by name. This allows load hooks to set and/or modify any aspect
// of the module (including names) using information that is not available when
// the module factory is called.
newModules, newDeps, errs := runAndRemoveLoadHooks(c, config, module, &scopedModuleFactories)
if len(errs) > 0 {
return errs
}
moduleCh <- newModuleInfo{module, newDeps, addedCh}
<-addedCh
for _, n := range newModules {
errs = addModule(n)
if len(errs) > 0 {
return errs
}
}
return nil
}
shouldVisitInfo := shouldVisitFile(c, file)
errs := shouldVisitInfo.errs
if len(errs) > 0 {
atomic.AddUint32(&numErrs, uint32(len(errs)))
errsCh <- errs
}
if !shouldVisitInfo.shouldVisitFile {
skipCh <- newSkipInfo{
file: file.Name,
shouldVisitFileInfo: shouldVisitInfo,
}
// TODO: Write a file that lists the skipped bp files
return
}
for _, def := range file.Defs {
switch def := def.(type) {
case *parser.Module:
module, errs := processModuleDef(def, file.Name, c.moduleFactories, scopedModuleFactories, c.ignoreUnknownModuleTypes)
if len(errs) == 0 && module != nil {
errs = addModule(module)
}
if len(errs) > 0 {
atomic.AddUint32(&numErrs, uint32(len(errs)))
errsCh <- errs
}
case *parser.Assignment:
// Already handled via Scope object
default:
panic("unknown definition type")
}
}
}
atomic.AddInt32(&numGoroutines, 1)
go func() {
var errs []error
deps, errs = c.WalkBlueprintsFiles(rootDir, filePaths, handleOneFile)
if len(errs) > 0 {
errsCh <- errs
}
doneCh <- struct{}{}
}()
var hookDeps []string
loop:
for {
select {
case newErrs := <-errsCh:
errs = append(errs, newErrs...)
case module := <-moduleCh:
newErrs := c.addModule(module.moduleInfo)
hookDeps = append(hookDeps, module.deps...)
if module.added != nil {
module.added <- struct{}{}
}
if len(newErrs) > 0 {
errs = append(errs, newErrs...)
}
case <-doneCh:
n := atomic.AddInt32(&numGoroutines, -1)
if n == 0 {
break loop
}
case skipped := <-skipCh:
nctx := newNamespaceContextFromFilename(skipped.file)
for _, name := range skipped.skippedModules {
c.nameInterface.NewSkippedModule(nctx, name, SkippedModuleInfo{
filename: skipped.file,
reason: skipped.reasonForSkip,
})
}
}
}
sort.Strings(hookDeps)
deps = append(deps, hookDeps...)
return deps, errs
}
type FileHandler func(*parser.File)
// WalkBlueprintsFiles walks a set of Blueprints files starting with the given filepaths,
// calling the given file handler on each
//
// When WalkBlueprintsFiles encounters a Blueprints file with a set of subdirs listed,
// it recursively parses any Blueprints files found in those subdirectories.
//
// If any of the file paths is an ancestor directory of any other of file path, the ancestor
// will be parsed and visited first.
//
// the file handler will be called from a goroutine, so it must be reentrant.
//
// If no errors are encountered while parsing the files, the list of paths on
// which the future output will depend is returned. This list will include both
// Blueprints file paths as well as directory paths for cases where wildcard
// subdirs are found.
//
// visitor will be called asynchronously, and will only be called once visitor for each
// ancestor directory has completed.
//
// WalkBlueprintsFiles will not return until all calls to visitor have returned.
func (c *Context) WalkBlueprintsFiles(rootDir string, filePaths []string,
visitor FileHandler) (deps []string, errs []error) {
// make a mapping from ancestors to their descendants to facilitate parsing ancestors first
descendantsMap, err := findBlueprintDescendants(filePaths)
if err != nil {
panic(err.Error())
}
blueprintsSet := make(map[string]bool)
// Channels to receive data back from openAndParse goroutines
blueprintsCh := make(chan fileParseContext)
errsCh := make(chan []error)
depsCh := make(chan string)
// Channel to notify main loop that a openAndParse goroutine has finished
doneParsingCh := make(chan fileParseContext)
// Number of outstanding goroutines to wait for
activeCount := 0
var pending []fileParseContext
tooManyErrors := false
// Limit concurrent calls to parseBlueprintFiles to 200
// Darwin has a default limit of 256 open files
maxActiveCount := 200
// count the number of pending calls to visitor()
visitorWaitGroup := sync.WaitGroup{}
startParseBlueprintsFile := func(blueprint fileParseContext) {
if blueprintsSet[blueprint.fileName] {
return
}
blueprintsSet[blueprint.fileName] = true
activeCount++
deps = append(deps, blueprint.fileName)
visitorWaitGroup.Add(1)
go func() {
file, blueprints, deps, errs := c.openAndParse(blueprint.fileName, blueprint.Scope, rootDir,
&blueprint)
if len(errs) > 0 {
errsCh <- errs
}
for _, blueprint := range blueprints {
blueprintsCh <- blueprint
}
for _, dep := range deps {
depsCh <- dep
}
doneParsingCh <- blueprint
if blueprint.parent != nil && blueprint.parent.doneVisiting != nil {
// wait for visitor() of parent to complete
<-blueprint.parent.doneVisiting
}
if len(errs) == 0 {
// process this file
visitor(file)
}
if blueprint.doneVisiting != nil {
close(blueprint.doneVisiting)
}
visitorWaitGroup.Done()
}()
}
foundParseableBlueprint := func(blueprint fileParseContext) {
if activeCount >= maxActiveCount {
pending = append(pending, blueprint)
} else {
startParseBlueprintsFile(blueprint)
}
}
startParseDescendants := func(blueprint fileParseContext) {
descendants, hasDescendants := descendantsMap[blueprint.fileName]
if hasDescendants {
for _, descendant := range descendants {
foundParseableBlueprint(fileParseContext{descendant, parser.NewScope(blueprint.Scope), &blueprint, make(chan struct{})})
}
}
}
// begin parsing any files that have no ancestors
startParseDescendants(fileParseContext{"", parser.NewScope(nil), nil, nil})
loop:
for {
if len(errs) > maxErrors {
tooManyErrors = true
}
select {
case newErrs := <-errsCh:
errs = append(errs, newErrs...)
case dep := <-depsCh:
deps = append(deps, dep)
case blueprint := <-blueprintsCh:
if tooManyErrors {
continue
}
foundParseableBlueprint(blueprint)
case blueprint := <-doneParsingCh:
activeCount--
if !tooManyErrors {
startParseDescendants(blueprint)
}
if activeCount < maxActiveCount && len(pending) > 0 {
// start to process the next one from the queue
next := pending[len(pending)-1]
pending = pending[:len(pending)-1]
startParseBlueprintsFile(next)
}
if activeCount == 0 {
break loop
}
}
}
sort.Strings(deps)
// wait for every visitor() to complete
visitorWaitGroup.Wait()
return
}
// MockFileSystem causes the Context to replace all reads with accesses to the provided map of
// filenames to contents stored as a byte slice.
func (c *Context) MockFileSystem(files map[string][]byte) {
// look for a module list file
_, ok := files[MockModuleListFile]
if !ok {
// no module list file specified; find every file named Blueprints
pathsToParse := []string{}
for candidate := range files {
if filepath.Base(candidate) == "Android.bp" {
pathsToParse = append(pathsToParse, candidate)
}
}
if len(pathsToParse) < 1 {
panic(fmt.Sprintf("No Blueprints files found in mock filesystem: %v\n", files))
}
// put the list of Blueprints files into a list file
files[MockModuleListFile] = []byte(strings.Join(pathsToParse, "\n"))
}
c.SetModuleListFile(MockModuleListFile)
// mock the filesystem
c.fs = pathtools.MockFs(files)
}
func (c *Context) SetFs(fs pathtools.FileSystem) {
c.fs = fs
}
// openAndParse opens and parses a single Blueprints file, and returns the results
func (c *Context) openAndParse(filename string, scope *parser.Scope, rootDir string,
parent *fileParseContext) (file *parser.File,
subBlueprints []fileParseContext, deps []string, errs []error) {
f, err := c.fs.Open(filename)
if err != nil {
// couldn't open the file; see if we can provide a clearer error than "could not open file"
stats, statErr := c.fs.Lstat(filename)
if statErr == nil {
isSymlink := stats.Mode()&os.ModeSymlink != 0
if isSymlink {
err = fmt.Errorf("could not open symlink %v : %v", filename, err)
target, readlinkErr := os.Readlink(filename)
if readlinkErr == nil {
_, targetStatsErr := c.fs.Lstat(target)
if targetStatsErr != nil {
err = fmt.Errorf("could not open symlink %v; its target (%v) cannot be opened", filename, target)
}
}
} else {
err = fmt.Errorf("%v exists but could not be opened: %v", filename, err)
}
}
return nil, nil, nil, []error{err}
}
func() {
defer func() {
err = f.Close()
if err != nil {
errs = append(errs, err)
}
}()
file, subBlueprints, errs = c.parseOne(rootDir, filename, f, scope, parent)
}()
if len(errs) > 0 {
return nil, nil, nil, errs
}
for _, b := range subBlueprints {
deps = append(deps, b.fileName)
}
return file, subBlueprints, deps, nil
}
// parseOne parses a single Blueprints file from the given reader, creating Module
// objects for each of the module definitions encountered. If the Blueprints
// file contains an assignment to the "subdirs" variable, then the
// subdirectories listed are searched for Blueprints files returned in the
// subBlueprints return value. If the Blueprints file contains an assignment
// to the "build" variable, then the file listed are returned in the
// subBlueprints return value.
//
// rootDir specifies the path to the root directory of the source tree, while
// filename specifies the path to the Blueprints file. These paths are used for
// error reporting and for determining the module's directory.
func (c *Context) parseOne(rootDir, filename string, reader io.Reader,
scope *parser.Scope, parent *fileParseContext) (file *parser.File, subBlueprints []fileParseContext, errs []error) {
relBlueprintsFile, err := filepath.Rel(rootDir, filename)
if err != nil {
return nil, nil, []error{err}
}
scope.DontInherit("subdirs")
scope.DontInherit("optional_subdirs")
scope.DontInherit("build")
file, errs = parser.ParseAndEval(filename, reader, scope)
if len(errs) > 0 {
for i, err := range errs {
if parseErr, ok := err.(*parser.ParseError); ok {
err = &BlueprintError{
Err: parseErr.Err,
Pos: parseErr.Pos,
}
errs[i] = err
}
}
// If there were any parse errors don't bother trying to interpret the
// result.
return nil, nil, errs
}
file.Name = relBlueprintsFile
build, buildPos, err := getLocalStringListFromScope(scope, "build")
if err != nil {
errs = append(errs, err)
}
for _, buildEntry := range build {
if strings.Contains(buildEntry, "/") {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("illegal value %v. The '/' character is not permitted", buildEntry),
Pos: buildPos,
})
}
}
if err != nil {
errs = append(errs, err)
}
var blueprints []string
newBlueprints, newErrs := c.findBuildBlueprints(filepath.Dir(filename), build, buildPos)
blueprints = append(blueprints, newBlueprints...)
errs = append(errs, newErrs...)
subBlueprintsAndScope := make([]fileParseContext, len(blueprints))
for i, b := range blueprints {
subBlueprintsAndScope[i] = fileParseContext{b, parser.NewScope(scope), parent, make(chan struct{})}
}
return file, subBlueprintsAndScope, errs
}
func (c *Context) findBuildBlueprints(dir string, build []string,
buildPos scanner.Position) ([]string, []error) {
var blueprints []string
var errs []error
for _, file := range build {
pattern := filepath.Join(dir, file)
var matches []string
var err error
matches, err = c.glob(pattern, nil)
if err != nil {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("%q: %s", pattern, err.Error()),
Pos: buildPos,
})
continue
}
if len(matches) == 0 {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("%q: not found", pattern),
Pos: buildPos,
})
}
for _, foundBlueprints := range matches {
if strings.HasSuffix(foundBlueprints, "/") {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("%q: is a directory", foundBlueprints),
Pos: buildPos,
})
}
blueprints = append(blueprints, foundBlueprints)
}
}
return blueprints, errs
}
func (c *Context) findSubdirBlueprints(dir string, subdirs []string, subdirsPos scanner.Position,
subBlueprintsName string, optional bool) ([]string, []error) {
var blueprints []string
var errs []error
for _, subdir := range subdirs {
pattern := filepath.Join(dir, subdir, subBlueprintsName)
var matches []string
var err error
matches, err = c.glob(pattern, nil)
if err != nil {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("%q: %s", pattern, err.Error()),
Pos: subdirsPos,
})
continue
}
if len(matches) == 0 && !optional {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("%q: not found", pattern),
Pos: subdirsPos,
})
}
for _, subBlueprints := range matches {
if strings.HasSuffix(subBlueprints, "/") {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("%q: is a directory", subBlueprints),
Pos: subdirsPos,
})
}
blueprints = append(blueprints, subBlueprints)
}
}
return blueprints, errs
}
func getLocalStringListFromScope(scope *parser.Scope, v string) ([]string, scanner.Position, error) {
if assignment := scope.GetLocal(v); assignment == nil {
return nil, scanner.Position{}, nil
} else {
switch value := assignment.Value.(type) {
case *parser.List:
ret := make([]string, 0, len(value.Values))
for _, listValue := range value.Values {
s, ok := listValue.(*parser.String)
if !ok {
// The parser should not produce this.
panic("non-string value found in list")
}
ret = append(ret, s.Value)
}
return ret, assignment.EqualsPos, nil
case *parser.Bool, *parser.String:
return nil, scanner.Position{}, &BlueprintError{
Err: fmt.Errorf("%q must be a list of strings", v),
Pos: assignment.EqualsPos,
}
default:
panic(fmt.Errorf("unknown value type: %d", assignment.Value.Type()))
}
}
}
// Clones a build logic module by calling the factory method for its module type, and then cloning
// property values. Any values stored in the module object that are not stored in properties
// structs will be lost.
func (c *Context) cloneLogicModule(origModule *moduleInfo) (Module, []interface{}) {
newLogicModule, newProperties := origModule.factory()
if len(newProperties) != len(origModule.properties) {
panic("mismatched properties array length in " + origModule.Name())
}
for i := range newProperties {
dst := reflect.ValueOf(newProperties[i])
src := reflect.ValueOf(origModule.properties[i])
proptools.CopyProperties(dst, src)
}
return newLogicModule, newProperties
}
func newVariant(module *moduleInfo, mutatorName string, variationName string) variant {
newVariantName := module.variant.name
if variationName != "" {
if newVariantName == "" {
newVariantName = variationName
} else {
newVariantName += "_" + variationName
}
}
newVariations := module.variant.variations.clone()
newVariations.set(mutatorName, variationName)
return variant{newVariantName, newVariations}
}
func (c *Context) createVariations(origModule *moduleInfo, mutator *mutatorInfo,
depChooser depChooser, variationNames []string) (moduleList, []error) {
if mutator.transitionMutator == nil {
panic(fmt.Errorf("method createVariations called from mutator that was not a TransitionMutator"))
}
if len(variationNames) == 0 {
panic(fmt.Errorf("mutator %q passed zero-length variation list for module %q",
mutator.name, origModule.Name()))
}
var newModules moduleList
var errs []error
for i, variationName := range variationNames {
var newLogicModule Module
var newProperties []interface{}
if i == 0 {
// Reuse the existing module for the first new variant
// This both saves creating a new module, and causes the insertion in c.moduleInfo below
// with logicModule as the key to replace the original entry in c.moduleInfo
newLogicModule, newProperties = origModule.logicModule, origModule.properties
} else {
newLogicModule, newProperties = c.cloneLogicModule(origModule)
}
m := *origModule
newModule := &m
newLogicModule.setInfo(newModule)
newModule.directDeps = slices.Clone(origModule.directDeps)
newModule.reverseDeps = nil
newModule.forwardDeps = nil
newModule.logicModule = newLogicModule
newModule.variant = newVariant(origModule, mutator.name, variationName)
newModule.properties = newProperties
newModule.providers = slices.Clone(origModule.providers)
newModule.providerInitialValueHashes = slices.Clone(origModule.providerInitialValueHashes)
newModule.transitionInfos = slices.Clone(origModule.transitionInfos)
newModules = append(newModules, newModule)
newErrs := c.convertDepsToVariation(newModule, i, depChooser)
if len(newErrs) > 0 {
errs = append(errs, newErrs...)
}
}
// Mark original variant as invalid. Modules that depend on this module will still
// depend on origModule, but we'll fix it when the mutator is called on them.
origModule.obsoletedByNewVariants = true
origModule.splitModules = newModules
atomic.AddUint32(&c.needsUpdateDependencies, 1)
return newModules, errs
}
type depChooser func(source *moduleInfo, variationIndex, depIndex int, dep depInfo) (*moduleInfo, string)
func chooseDep(candidates moduleList, mutatorName, variationName string, defaultVariationName *string) (*moduleInfo, string) {
for _, m := range candidates {
if m.variant.variations.get(mutatorName) == variationName {
return m, ""
}
}
if defaultVariationName != nil {
// give it a second chance; match with defaultVariationName
for _, m := range candidates {
if m.variant.variations.get(mutatorName) == *defaultVariationName {
return m, ""
}
}
}
return nil, variationName
}
func chooseDepByIndexes(mutatorName string, variations [][]string) depChooser {
return func(source *moduleInfo, variationIndex, depIndex int, dep depInfo) (*moduleInfo, string) {
desiredVariation := variations[variationIndex][depIndex]
return chooseDep(dep.module.splitModules, mutatorName, desiredVariation, nil)
}
}
func (c *Context) convertDepsToVariation(module *moduleInfo, variationIndex int, depChooser depChooser) (errs []error) {
for i, dep := range module.directDeps {
if dep.module.obsoletedByNewVariants {
newDep, missingVariation := depChooser(module, variationIndex, i, dep)
if newDep == nil {
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("failed to find variation %q for module %q needed by %q",
missingVariation, dep.module.Name(), module.Name()),
Pos: module.pos,
})
continue
}
module.directDeps[i].module = newDep
}
}
return errs
}
func (c *Context) prettyPrintVariant(variations variationMap) string {
var names []string
for _, m := range c.variantMutatorNames {
if v := variations.get(m); v != "" {
names = append(names, m+":"+v)
}
}
if len(names) == 0 {
return "<empty variant>"
}
return strings.Join(names, ",")
}
func (c *Context) prettyPrintGroupVariants(group *moduleGroup) string {
var variants []string
for _, module := range group.modules {
variants = append(variants, c.prettyPrintVariant(module.variant.variations))
}
return strings.Join(variants, "\n ")
}
func newModule(factory ModuleFactory) *moduleInfo {
logicModule, properties := factory()
moduleInfo := &moduleInfo{
logicModule: logicModule,
factory: factory,
properties: properties,
startedMutator: -1,
finishedMutator: -1,
}
logicModule.setInfo(moduleInfo)
return moduleInfo
}
func processModuleDef(moduleDef *parser.Module,
relBlueprintsFile string, moduleFactories, scopedModuleFactories map[string]ModuleFactory, ignoreUnknownModuleTypes bool) (*moduleInfo, []error) {
factory, ok := moduleFactories[moduleDef.Type]
if !ok && scopedModuleFactories != nil {
factory, ok = scopedModuleFactories[moduleDef.Type]
}
if !ok {
if ignoreUnknownModuleTypes {
return nil, nil
}
return nil, []error{
&BlueprintError{
Err: fmt.Errorf("unrecognized module type %q", moduleDef.Type),
Pos: moduleDef.TypePos,
},
}
}
module := newModule(factory)
module.typeName = moduleDef.Type
module.relBlueprintsFile = relBlueprintsFile
propertyMap, errs := proptools.UnpackProperties(moduleDef.Properties, module.properties...)
if len(errs) > 0 {
for i, err := range errs {
if unpackErr, ok := err.(*proptools.UnpackError); ok {
err = &BlueprintError{
Err: unpackErr.Err,
Pos: unpackErr.Pos,
}
errs[i] = err
}
}
return nil, errs
}
module.pos = moduleDef.TypePos
module.propertyPos = make(map[string]scanner.Position)
for name, propertyDef := range propertyMap {
module.propertyPos[name] = propertyDef.ColonPos
}
return module, nil
}
func (c *Context) addModule(module *moduleInfo) []error {
name := module.logicModule.Name()
if name == "" {
return []error{
&BlueprintError{
Err: fmt.Errorf("property 'name' is missing from a module"),
Pos: module.pos,
},
}
}
group := &moduleGroup{
name: name,
modules: moduleList{module},
}
module.group = group
namespace, errs := c.nameInterface.NewModule(
newNamespaceContext(module),
ModuleGroup{moduleGroup: group},
module.logicModule)
if len(errs) > 0 {
for i := range errs {
errs[i] = &BlueprintError{Err: errs[i], Pos: module.pos}
}
return errs
}
group.namespace = namespace
c.moduleGroups = append(c.moduleGroups, group)
return nil
}
// ResolveDependencies checks that the dependencies specified by all of the
// modules defined in the parsed Blueprints files are valid. This means that
// the modules depended upon are defined and that no circular dependencies
// exist.
func (c *Context) ResolveDependencies(config interface{}) (deps []string, errs []error) {
c.BeginEvent("resolve_deps")
defer c.EndEvent("resolve_deps")
return c.resolveDependencies(c.Context, config)
}
// coalesceMutators takes the list of mutators and returns a list of lists of mutators,
// where sublist is a compatible group of mutators that can be run with relaxed
// intra-mutator ordering.
func coalesceMutators(mutators []*mutatorInfo) [][]*mutatorInfo {
var coalescedMutators [][]*mutatorInfo
var last *mutatorInfo
// Returns true if the mutator can be coalesced with other mutators that
// also return true.
coalescable := func(m *mutatorInfo) bool {
return m.bottomUpMutator != nil &&
m.transitionMutator == nil &&
!m.usesCreateModule &&
!m.usesReplaceDependencies &&
!m.usesReverseDependencies &&
!m.usesRename &&
!m.mutatesGlobalState &&
!m.mutatesDependencies &&
// No mix of pre partial marker mutator with others.
!m.prePartial
}
for _, mutator := range mutators {
if last != nil && coalescable(last) && coalescable(mutator) {
lastGroup := &coalescedMutators[len(coalescedMutators)-1]
*lastGroup = append(*lastGroup, mutator)
} else {
coalescedMutators = append(coalescedMutators, []*mutatorInfo{mutator})
last = mutator
}
}
return coalescedMutators
}
func (c *Context) resolveDependencies(ctx context.Context, config interface{}) (deps []string, errs []error) {
pprof.Do(ctx, pprof.Labels("blueprint", "ResolveDependencies"), func(ctx context.Context) {
c.initProviders()
errs = c.updateDependencies()
if len(errs) > 0 {
return
}
mutatorGroups := coalesceMutators(c.mutatorInfo)
deps, errs = c.runMutators(ctx, config, mutatorGroups)
if len(errs) > 0 {
return
}
c.dependenciesReady = true
})
if len(errs) > 0 {
return nil, errs
}
return deps, nil
}
// Default dependencies handling. If the module implements the (deprecated)
// DynamicDependerModule interface then this set consists of the union of those
// module names returned by its DynamicDependencies method and those added by calling
// AddDependencies or AddVariationDependencies on DynamicDependencyModuleContext.
func blueprintDepsMutator(ctx BottomUpMutatorContext) {
if dynamicDepender, ok := ctx.Module().(DynamicDependerModule); ok {
func() {
defer func() {
if r := recover(); r != nil {
ctx.error(newPanicErrorf(r, "DynamicDependencies for %s", ctx.moduleInfo()))
}
}()
dynamicDeps := dynamicDepender.DynamicDependencies(ctx)
if ctx.Failed() {
return
}
ctx.AddDependency(ctx.Module(), nil, dynamicDeps...)
}()
}
}
// applyTransitions takes a variationMap being used to add a dependency on a module in a moduleGroup
// and applies the OutgoingTransition and IncomingTransition methods of each completed TransitionMutator to
// modify the requested variation. It finds a variant that existed before the TransitionMutator ran that is
// a subset of the requested variant to use as the module context for IncomingTransition.
//
// for onDemand: true, a copy of the dependency will be created on demand and used as the context
// for IncomingTransition.
func (c *Context) applyTransitions(config any, module *moduleInfo, depTag DependencyTag, group *moduleGroup, variant variationMap,
requestedVariations []Variation, far bool, onDemandMatchingVariant *moduleInfo) (variationMap, []TransitionInfo, []error) {
// Initialize some variables that will be used to manage IncomingTransition context of on demand variants.
onDemandFromMutatorIndex := c.mutatorIndexAfterLastCreateModule
var moduleOutgoingTransitionInfos []TransitionInfo
for _, transitionMutator := range c.transitionMutators[:c.completedTransitionMutators] {
explicitlyRequested := slices.ContainsFunc(requestedVariations, func(variation Variation) bool {
return variation.Mutator == transitionMutator.name
})
var outgoingTransitionInfo TransitionInfo
if explicitlyRequested {
sourceVariation := variant.get(transitionMutator.name)
outgoingTransitionInfo = transitionMutator.mutator.TransitionInfoFromVariation(sourceVariation)
} else {
// Apply the outgoing transition if it was not explicitly requested.
var srcTransitionInfo TransitionInfo
if (!far || transitionMutator.neverFar) && len(module.transitionInfos) > transitionMutator.index {
srcTransitionInfo = module.transitionInfos[transitionMutator.index]
}
ctx := outgoingTransitionContextPool.Get()
*ctx = outgoingTransitionContextImpl{
transitionContextImpl{context: c, source: module, dep: nil,
depTag: depTag, postMutator: true, config: config},
}
outgoingTransitionInfo = transitionMutator.mutator.OutgoingTransition(ctx, srcTransitionInfo)
errs := ctx.errs
outgoingTransitionContextPool.Put(ctx)
ctx = nil
if len(errs) > 0 {
return variationMap{}, moduleOutgoingTransitionInfos, errs
}
moduleOutgoingTransitionInfos = append(moduleOutgoingTransitionInfos, outgoingTransitionInfo)
}
earlierVariantCreatingMutators := c.transitionMutatorNames[:transitionMutator.index]
filteredVariant := variant.cloneMatching(earlierVariantCreatingMutators)
// Find an appropriate module to use as the context for the IncomingTransition.
var matchingInputVariant *moduleInfo
for _, module := range group.modules {
filteredInputVariant := module.variant.variations.cloneMatching(earlierVariantCreatingMutators)
if filteredInputVariant.equal(filteredVariant) {
matchingInputVariant = module
break
}
}
if onDemandMatchingVariant != nil {
// Mutate the onDemand variant through a moving window,
// from the previous transition mutator to the current transition mutator.
// This variant can then be used as the context for the IncomingTransition.
matchingInputVariant = onDemandMatchingVariant
if outgoingTransitionInfo != nil {
onDemandMatchingVariant.requestedOnDemandVariant.variations[transitionMutator.name] = outgoingTransitionInfo.Variation()
}
// Rerun till propagate mutator handle of the Transition mutator.
// transitionMutator.mutatorIndex corresponds to mutate.
// transitionMutator.mutatorIndex-1 corresponds to bottomUp handle of the transition mutator
// transitionMutator.mutatorIndex-2 (inclusive) corresponds to propagate mutator handle of the transition mutator
c.rerunMutatorsOnVariantOnDemand(onDemandMatchingVariant, onDemandFromMutatorIndex, transitionMutator.mutatorIndex-2, nil, config)
onDemandFromMutatorIndex = transitionMutator.mutatorIndex - 1
}
if matchingInputVariant != nil {
// Apply the incoming transition.
ctx := incomingTransitionContextPool.Get()
*ctx = incomingTransitionContextImpl{
transitionContextImpl{context: c, source: nil, dep: matchingInputVariant,
depTag: depTag, postMutator: true, config: config},
}
finalTransitionInfo := transitionMutator.mutator.IncomingTransition(ctx, outgoingTransitionInfo)
errs := ctx.errs
incomingTransitionContextPool.Put(ctx)
ctx = nil
if len(errs) > 0 {
return variationMap{}, moduleOutgoingTransitionInfos, errs
}
variation := ""
if finalTransitionInfo != nil {
variation = finalTransitionInfo.Variation()
}
variant.set(transitionMutator.name, variation)
// For on-demand variants, check if requested variation can be supported by `SplitOnDemand`.
if onDemandMatchingVariant != nil && variation == "" {
onDemandMatchingVariant.requestedOnDemandVariant.delete(transitionMutator.name)
} else if onDemandMatchingVariant != nil {
// Rerun till Mutate of the Transition mutator
onDemandMatchingVariant.requestedOnDemandVariant.variations[transitionMutator.name] = variation
c.rerunMutatorsOnVariantOnDemand(onDemandMatchingVariant, onDemandFromMutatorIndex, transitionMutator.mutatorIndex, nil, config)
onDemandFromMutatorIndex = transitionMutator.mutatorIndex + 1
found := false
for _, split := range onDemandMatchingVariant.createdOnDemandSupportedSplits {
if split.Variation() == variation {
found = true
}
}
if !found {
onDemandMatchingVariant.createdOnDemandIncompatible = true
return variant, moduleOutgoingTransitionInfos, nil
}
onDemandMatchingVariant.createdOnDemandSupportedSplits = nil // reset for next transition mutator check.
}
}
if (matchingInputVariant == nil && !explicitlyRequested) || variant.get(transitionMutator.name) == "" {
// The transition mutator didn't apply anything to the target variant, remove the variation unless it
// was explicitly requested when adding the dependency.
variant.delete(transitionMutator.name)
}
}
return variant, moduleOutgoingTransitionInfos, nil
}
func (c *Context) findVariant(config any, module *moduleInfo, depTag DependencyTag,
possibleDeps *moduleGroup, requestedVariations []Variation, far bool, reverse bool, mutatorIndex int) (*moduleInfo, variationMap, []error) {
// We can't just append variant.Variant to module.dependencyVariant.variantName and
// compare the strings because the result won't be in mutator registration order.
// Create a new map instead, and then deep compare the maps.
var newVariant variationMap
if !far {
newVariant = module.variant.variations.clone()
} else {
for _, transitionMutator := range c.transitionMutators {
if transitionMutator.neverFar {
newVariant.set(transitionMutator.name, module.variant.variations.get(transitionMutator.name))
}
}
}
for _, v := range requestedVariations {
newVariant.set(v.Mutator, v.Variation)
}
newVariantBeforeTransitions := newVariant.clone()
var moduleOutgoingTransitionInfos []TransitionInfo
if !reverse {
var errs []error
newVariant, moduleOutgoingTransitionInfos, errs = c.applyTransitions(config, module, depTag, possibleDeps, newVariant, requestedVariations, far, nil)
if len(errs) > 0 {
return nil, variationMap{}, errs
}
}
// check returns a bool for whether the requested newVariant matches the given variant from possibleDeps, and a
// divergence score. A score of 0 is best match, and a positive integer is a worse match.
// For a non-far search, the score is always 0 as the match must always be exact. For a far search,
// the score is the number of variants that are present in the given variant but not newVariant.
check := func(variant variationMap) (bool, int) {
if far {
if newVariant.subsetOf(variant) {
return true, variant.differenceKeysCount(newVariant)
}
} else {
if variant.equal(newVariant) {
return true, 0
}
}
return false, math.MaxInt
}
var foundDep *moduleInfo
bestDivergence := math.MaxInt
for _, m := range possibleDeps.modules {
if match, divergence := check(m.variant.variations); match && divergence < bestDivergence {
foundDep = m
bestDivergence = divergence
if !far {
// non-far dependencies use equality, so only the first match needs to be checked.
break
}
}
}
if foundDep == nil &&
!c.allowMissingDependencies && // TODO (b/448182009): Skip checking for possible on-demand variant if allowMissingDependencies is set.
possibleDeps.searchOnDemandVariant(newVariantBeforeTransitions, far, mutatorIndex, c.transitionMutators) {
if variantOnDemand, errs := c.loadOrCreateVariantOnDemand(config, module, depTag, possibleDeps, newVariantBeforeTransitions, requestedVariations, far, moduleOutgoingTransitionInfos); variantOnDemand != nil {
foundDep = c.createVariantOnDemand(possibleDeps, variantOnDemand.requestedOnDemandVariant.clone())
foundDep.finishedMutator = mutatorIndex
newVariant = variantOnDemand.requestedOnDemandVariant.clone()
} else {
return nil, newVariant, errs
}
}
return foundDep, newVariant, nil
}
func (c *Context) addVariationDependency(module *moduleInfo, mutator *mutatorInfo, config any, variations []Variation,
tag DependencyTag, depName string, far bool) (*moduleInfo, []error) {
if _, ok := tag.(BaseDependencyTag); ok {
panic("BaseDependencyTag is not allowed to be used directly!")
}
possibleDeps := c.moduleGroupFromName(depName, module.namespace())
if possibleDeps == nil {
return nil, c.discoveredMissingDependencies(module, depName, variationMap{})
}
foundDep, newVariant, errs := c.findVariant(config, module, tag, possibleDeps, variations, far, false, mutator.index)
if errs != nil {
return nil, errs
}
if foundDep == nil {
if c.allowMissingDependencies {
// Allow missing variants.
return nil, c.discoveredMissingDependencies(module, depName, newVariant)
}
return nil, []error{&BlueprintError{
Err: fmt.Errorf("dependency %q of %q missing variant:\n %s\navailable variants:\n %s",
depName, module.Name(),
c.prettyPrintVariant(newVariant),
c.prettyPrintGroupVariants(possibleDeps)),
Pos: module.pos,
}}
}
if module == foundDep {
return nil, []error{&BlueprintError{
Err: fmt.Errorf("%q depends on itself", depName),
Pos: module.pos,
}}
}
if foundDep.createdOnDemand {
// `runMutator` will create the dependency edges, deduping on demand variants if necessary.
// Also, defer the beforeInModuleList check to the end of runMutator.
return foundDep, nil
}
// AddVariationDependency allows adding a dependency on itself, but only if
// that module is earlier in the module list than this one, since we always
// run GenerateBuildActions in order for the variants of a module
if foundDep.group == module.group && beforeInModuleList(module, foundDep, module.group.modules) {
return nil, []error{&BlueprintError{
Err: fmt.Errorf("%q depends on later version of itself", depName),
Pos: module.pos,
}}
}
// The mutator will pause until the newly added dependency has finished running the current mutator,
// so it is safe to add the new dependency directly to directDeps and forwardDeps where it will be visible
// to future calls to VisitDirectDeps. Set newDirectDeps so that at the end of the mutator the reverseDeps
// of the dependencies can be updated to point to this module without running a full c.updateDependencies()
module.directDeps = append(module.directDeps, depInfo{foundDep, tag})
module.forwardDeps = append(module.forwardDeps, foundDep)
module.newDirectDeps = append(module.newDirectDeps, foundDep)
return foundDep, nil
}
// findBlueprintDescendants returns a map linking parent Blueprint files to child Blueprints files
// For example, if paths = []string{"a/b/c/Android.bp", "a/Android.bp"},
// then descendants = {"":[]string{"a/Android.bp"}, "a/Android.bp":[]string{"a/b/c/Android.bp"}}
func findBlueprintDescendants(paths []string) (descendants map[string][]string, err error) {
// make mapping from dir path to file path
filesByDir := make(map[string]string, len(paths))
for _, path := range paths {
dir := filepath.Dir(path)
_, alreadyFound := filesByDir[dir]
if alreadyFound {
return nil, fmt.Errorf("Found two Blueprint files in directory %v : %v and %v", dir, filesByDir[dir], path)
}
filesByDir[dir] = path
}
findAncestor := func(childFile string) (ancestor string) {
prevAncestorDir := filepath.Dir(childFile)
for {
ancestorDir := filepath.Dir(prevAncestorDir)
if ancestorDir == prevAncestorDir {
// reached the root dir without any matches; assign this as a descendant of ""
return ""
}
ancestorFile, ancestorExists := filesByDir[ancestorDir]
if ancestorExists {
return ancestorFile
}
prevAncestorDir = ancestorDir
}
}
// generate the descendants map
descendants = make(map[string][]string, len(filesByDir))
for _, childFile := range filesByDir {
ancestorFile := findAncestor(childFile)
descendants[ancestorFile] = append(descendants[ancestorFile], childFile)
}
return descendants, nil
}
type visitOrderer interface {
// returns the number of modules that this module needs to wait for
waitCount(module *moduleInfo) int
// returns the list of modules that are waiting for this module
propagate(module *moduleInfo) []*moduleInfo
}
type unorderedVisitorImpl struct{}
func (unorderedVisitorImpl) waitCount(module *moduleInfo) int {
return 0
}
func (unorderedVisitorImpl) propagate(module *moduleInfo) []*moduleInfo {
return nil
}
type bottomUpVisitorImpl struct{}
func (bottomUpVisitorImpl) waitCount(module *moduleInfo) int {
return len(module.forwardDeps)
}
func (bottomUpVisitorImpl) propagate(module *moduleInfo) []*moduleInfo {
return module.reverseDeps
}
type topDownVisitorImpl struct{}
func (topDownVisitorImpl) waitCount(module *moduleInfo) int {
return len(module.reverseDeps)
}
func (topDownVisitorImpl) propagate(module *moduleInfo) []*moduleInfo {
return module.forwardDeps
}
func (topDownVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
for i := 0; i < len(modules); i++ {
module := modules[len(modules)-1-i]
if visit(module, nil) {
return
}
}
}
var (
bottomUpVisitor bottomUpVisitorImpl
topDownVisitor topDownVisitorImpl
)
// unpause is a channel that will be closed when the paused module should resume.
type unpause chan struct{}
// pauseFunc is a function a visitor function can call to pause execution until the visitor
// function on the given module is completed.
type pauseFunc func(until *moduleInfo)
// parallelVisitWorker is an individual worker that will call visitor functions on behalf of
// a call to parallelVisit. It's run method loops until the input queueCh is closed, receiving
// batches of modules on which to call visit method, and posting responses to the responseCh
// channel.
type parallelVisitWorker struct {
// queue is the slice of modules that are currently being processed.
queue []*moduleInfo
// currentQueueIndex is the index into the queue of the module currently being visited.
currentQueueIndex int
// done is the slice of modules that have had the visit method called on them has returned.
done []*moduleInfo
// remaining is the slice of modules that have not had the visit method called on them.
remaining []*moduleInfo
// current is the module that is currently running the visit method.
current *moduleInfo
// unblocked is the slice of modules that are now runnable after visit returned on a module
// in this batch.
// TODO: can these modules just be handled in this worker? Use some heuristic to decide
// whether to handle them or send them back to the orchestrator?
unblocked []*moduleInfo
// visit is the method that is called on each module.
visit func(module *moduleInfo, pause pauseFunc) bool
// order is the interface that describes which modules will be ready next once the current module
// has had visit called on it.
order visitOrderer
// queueCh is the input channel that provides batches of modules to call visit on. It is closed
// when there is no more work to do.
queueCh <-chan []*moduleInfo
// responseCh is the output channel where a parallelVisitWorkerResponse is sent after processing
// each batch.
responseCh chan<- parallelVisitWorkerResponse
}
// pauseSpec describes a pause that a module needs to occur until another module has been visited,
// at which point the unpause channel will be closed.
type pauseSpec struct {
paused *moduleInfo
until *moduleInfo
unpause unpause
}
// parallelVisitWorkerResponse is sent after a parallelVisitWorker processes a batch of modules.
type parallelVisitWorkerResponse struct {
// done is the slice of modules that have had the visit method called on.
done []*moduleInfo
// returned is the slice of modules that the worker did not call the visit method on,
// either because the visit method returned an error, or because the visit method called
// the pause function, which may be waiting (directly or transitively) on a module that
// is later in the batch.
returned []*moduleInfo
// unblocked is the slice of modules that became ready (i.e. waitingCount is now zero)
// after the modules that the worker ran finished.
unblocked []*moduleInfo
// error is set when a visit method returned an error, signalling that parallelVisit should
// abort.
error bool
// pause is set when the visit method called pause, signalling that the worker is waiting
// indefinitely on the unpause channel, which should be closed when the requested module
// has completed.
pause pauseSpec
}
// run is the main loop method of a parallelVisitWorker. It receives batches of modules to
// call the visit method on from queueCh, and then calls runQueue on them.
func (worker *parallelVisitWorker) run() {
for queued := range worker.queueCh {
if len(worker.queue) > 0 {
panic(fmt.Errorf("already have queued work"))
}
errored := worker.runQueue(queued)
if errored {
return
}
}
}
// runQueue processes a single batch of modules.
func (worker *parallelVisitWorker) runQueue(queue []*moduleInfo) bool {
worker.queue = queue
worker.done = nil
worker.unblocked = nil
// Use a loop on worker.currentQueueIndex so that the pause method can update it when it sends
// the done and remaining modules back to the orchestrator.
for worker.currentQueueIndex = 0; worker.currentQueueIndex < len(worker.queue); worker.currentQueueIndex++ {
worker.current = worker.queue[worker.currentQueueIndex]
worker.remaining = worker.queue[worker.currentQueueIndex+1:]
ret := worker.visit(worker.current, worker.pause)
worker.done = worker.queue[:worker.currentQueueIndex+1]
if ret {
// An error occurred. Send the completed and uncompleted modules back to the orchestrator with
// the error flag set.
worker.responseCh <- parallelVisitWorkerResponse{
done: worker.done,
error: true,
returned: worker.remaining,
unblocked: worker.unblocked,
}
return true
}
// Decrement waitingCount on the next modules in the tree based
// on propagation order, and add them to the queue if they are
// ready to start.
for _, module := range worker.order.propagate(worker.current) {
if module.waitingCount.Add(-1) == 0 {
worker.unblocked = append(worker.unblocked, module)
}
}
}
// The batch is complete. Send the completed modules back to the orchestrator.
worker.responseCh <- parallelVisitWorkerResponse{
done: worker.done,
unblocked: worker.unblocked,
}
worker.queue = nil
return false
}
// pause is called by visitors (via the function pointer passed into the visit function) in order to
// signal that the visitor needs to wait until the visitor has completed on the target module.
func (worker *parallelVisitWorker) pause(until *moduleInfo) {
// If the target module is already done there is no need to pause. This is safe because waitingCount
// will never change once it has reached -1.
if until.waitingCount.Load() == -1 {
return
}
unpause := make(chan struct{})
// This visitor needs to pause. Send the completed and uncompleted modules back to the orchestrator
// with a pauseSpec that describes how and when to unpause this module. The uncompleted modules are
// returned to the orchestrator in case the pause is waiting (directly or transitively) for one of
// the uncompleted modules.
worker.responseCh <- parallelVisitWorkerResponse{
done: worker.done,
returned: worker.remaining,
unblocked: worker.unblocked,
pause: pauseSpec{
paused: worker.current,
until: until,
unpause: unpause,
},
}
// Reset the queue to contain only the current module, as everything else has already been returned
// to the orchestrator.
worker.done = nil
worker.queue = []*moduleInfo{worker.current}
worker.currentQueueIndex = 0
worker.remaining = nil
worker.unblocked = nil
// Wait for the orchestrator to close the unpause channel to signal that the requested module has
// finished.
<-unpause
}
// parallelVisitLimit is the maximum number of visitors that can be simultaneously active in parallelVisit.
var parallelVisitLimit = runtime.NumCPU() * 2
// parallelVisitBatchSize is the number of visitors that will be batched together and passed to a single
// parallelVisitWorker in order to reduce channel communication overhead. Setting this value higher
// amortizes the synchronization and coordination costs across more modules, but setting it too high
// risks having a single worker left processing modules after all the other workers have finished if it
// has too many long visitors in a single batch.
const parallelVisitBatchSize = 100
// parallelVisit calls visit on each module, guaranteeing that visit is not called on a module until
// visit on all of its dependencies (as determined by the visitOrderer) has finished. A visit function
// can call the pause function to wait for another dependency to be visited before continuing.
// If a visit function returns true to cancel while another visitor is paused, the paused visitor will
// never be resumed and its goroutine will stay paused forever.
// The limit argument sets the maximum number of workers that can be running visit functions simultaneously.
// The total number of workers can be higher than limit if some of them are paused, but the paused workers
// won't be unpaused until the number of active workers drops below the limit.
func parallelVisit(moduleIter iter.Seq[*moduleInfo], order visitOrderer, limit int,
visit func(module *moduleInfo, pause pauseFunc) bool) []error {
// queueCh sends batches of modules to process from the orchestrator to the workers.
queueCh := make(chan []*moduleInfo, limit)
// responseCh receives responses from the workers when a batch has finished processing.
responseCh := make(chan parallelVisitWorkerResponse, limit)
// Closing queueCh when parallelVisit finishes signals the workers to exit.
defer close(queueCh)
activeWorkers := 0 // Number of workers running, not counting paused visitors.
activeModules := 0 // Number of modules that have been sent to workers.
visited := 0 // Number of modules whose visitors have finished.
pausedWorkers := 0 // Number of workers that are waiting on an unpause channel
workers := 0 // Total number of spawned workers.
hung := make(map[*moduleInfo]bool)
cancel := false // will be set when any worker returns an error.
var queue []*moduleInfo // The list of modules that are ready to be sent to workers.
var returnedQueue [][]*moduleInfo // The list of modules that were sent to workers and then returned and need to be resent.
var unpauseQueue []pauseSpec // Visitors that are ready to unpause but backlogged due to limit.
queuedModules := 0 // The number of modules that are queued in queue, returnedQueue or unpauseQueue.
// pauseMap holds the map from modules that are being waited on to the list of pauseSpecs that are waiting on them.
pauseMap := make(map[*moduleInfo][]pauseSpec)
// newWorker spawns a new worker goroutine.
newWorker := func() {
worker := ¶llelVisitWorker{
visit: visit,
order: order,
queueCh: queueCh,
responseCh: responseCh,
}
workers++
go worker.run()
}
toVisit := 0
// Initialize waitingCount on each module with the number of modules that need to complete before it can run.
// Add any modules whose waitingCount is 0 to the initial queue of ready modules.
for module := range moduleIter {
hung[module] = true
toVisit++
waitingCount := order.waitCount(module)
module.waitingCount.Store(int32(waitingCount))
if waitingCount == 0 {
queue = append(queue, module)
queuedModules++
}
}
queue = slices.Grow(queue, toVisit-len(queue))
// queueWork is called to send work to any available workers, including spawning new workers if there is work
// to do and the number of active workers is below the limit.
queueWork := func() {
for queuedModules > 0 && activeWorkers < limit {
// First priority: unpause any paused workers that are ready, as the visitor functions may already
// be holding resources.
if len(unpauseQueue) > 0 {
unpause := unpauseQueue[0]
unpauseQueue = unpauseQueue[1:]
pausedWorkers--
queuedModules--
activeModules++
close(unpause.unpause)
} else {
// If there are worker slots available and no idle workers, spawn a new worker.
if activeWorkers < limit && activeWorkers+pausedWorkers == workers {
newWorker()
}
var batch []*moduleInfo
// Second priority: re-send any returned work back to a worker.
if len(returnedQueue) > 0 {
batch = returnedQueue[0]
returnedQueue = returnedQueue[1:]
} else {
// Send a batch of work from the queue. Limit the size of the batch to the size of the queue
// divided by the number of available workers to avoid sending a big batch of work to a single
// worker when other workers are available and to parallelVisitBatchSize.
availableWorkersSlots := limit - activeWorkers
queueSizePerAvailableWorker := (len(queue) + availableWorkersSlots - 1) / availableWorkersSlots
batchSize := min(parallelVisitBatchSize, queueSizePerAvailableWorker)
batch = queue[:batchSize]
queue = queue[batchSize:]
}
activeModules += len(batch)
queuedModules -= len(batch)
if len(batch) == 0 {
panic("zero length batch")
}
queueCh <- batch
}
activeWorkers++
}
}
// Call queueWork before starting the loop so that activeModules is nonzero.
queueWork()
// The main orchestrator loop, which runs until there are no workers doing work.
for activeModules > 0 {
// Wait for a response from a worker.
response := <-responseCh
activeWorkers--
if response.error {
// Once cancel is set no more work will be sent to workers.
cancel = true
}
// Process finished modules.
visited += len(response.done)
activeModules -= len(response.done)
for _, doneModule := range response.done {
delete(hung, doneModule)
// Mark this module as done. Nothing else should be updating waitingCount, so a single attempt
// at CompareAndSwap should always succeed. This is the only location that will ever update
// waitingCount from 0 to -1, and once it is -1 it will never be changed for the rest of this
// call to parallelVisit.
if !doneModule.waitingCount.CompareAndSwap(0, -1) {
panic(fmt.Errorf("failed to atomically mark module %s as done", doneModule))
}
// Add any modules that were paused on this module to the unpause queue.
if unpauses, ok := pauseMap[doneModule]; ok {
delete(pauseMap, doneModule)
queuedModules += len(unpauses)
unpauseQueue = append(unpauseQueue, unpauses...)
}
}
if len(response.unblocked) > 0 {
// Add any modules that were made ready to the queue.
queuedModules += len(response.unblocked)
queue = append(queue, response.unblocked...)
}
// Re-queue any returned modules.
if len(response.returned) > 0 {
queuedModules += len(response.returned)
activeModules -= len(response.returned)
returnedQueue = append(returnedQueue, response.returned)
}
// Handle a requested pause.
if response.pause.paused != nil {
// This goroutine is the only one that can set waitingCount to -1, so reading it here does not
// race with updating pauseMap if the value is not yet -1.
if response.pause.until.waitingCount.Load() == -1 {
// Module being paused for is already finished, resume immediately.
// activeWorkers was decremented above when the response was received,
// re-increment it as it is going to resume.
activeWorkers++
close(response.pause.unpause)
} else {
// Register for unpausing.
pauseMap[response.pause.until] = append(pauseMap[response.pause.until], response.pause)
pausedWorkers++
activeModules--
}
// If a dependency is created on demand, add it to the queue for processing.
// The main coordinator goroutine will be responsible for deduping on demand variant
// requested from multiple rdeps.
if response.pause.until.createdOnDemand {
queue = append(queue, response.pause.until)
toVisit++
hung[response.pause.until] = true
queuedModules++
}
}
// Each time a response has been handled check if there is work that can now be queued.
if !cancel {
queueWork()
}
}
// The orchestrator loop has finished because there are no modules being processed. In the normal case all
// the modules should have been visited. If an error occurred there may be queued or paused modules.
// If a deadlock occurred and all remaining modules are not ready or paused then there is newly added
// cyclic dependency.
if !cancel {
// Invariant checks: no queued, returned or unpaused modules. These weren't waiting on anything except
// the parallelism limit so they should have run.
if len(queue) > 0 {
panic(fmt.Errorf("parallelVisit finished with %d queued visitors", len(queue)))
}
if len(returnedQueue) > 0 {
panic(fmt.Errorf("parallelVisit finished with %d returned queued visitors", len(returnedQueue)))
}
if len(unpauseQueue) > 0 {
panic(fmt.Errorf("parallelVisit finished with %d queued unpaused visitors", len(unpauseQueue)))
}
if visited != toVisit || len(pauseMap) > 0 {
// Probably a deadlock due to a dependency cycle. Start from each module in the order
// of the input modules list and perform a depth-first search for any module that is
// in the walk path twice. Note this traverses from modules to the modules that would
// have been unblocked when that module finished, i.e. the reverse of the visitOrderer.
// This search takes into account both the pre-existing dependencies and any newly
// added dependencies that are still in the pauseMap.
// In order to reduce duplicated work, once a module has been checked and determined
// not to be part of a cycle add it and everything that depends on it to the checked
// map.
checked := make(map[*moduleInfo]bool, toVisit) // modules that were already checked
checking := make(map[*moduleInfo]bool) // modules actively being checked
var errs []error
var check func(group *moduleInfo) []*moduleInfo
check = func(module *moduleInfo) []*moduleInfo {
if checking[module] {
// This is a cycle.
return []*moduleInfo{module}
}
if checked[module] {
return nil
}
checked[module] = true
checking[module] = true
defer delete(checking, module)
// Create an iterator that yields the existing dependencies of this module (via order.propagate),
// followed by any newly added dependencies stored in pauseMap.
dependencyIter := func(yield func(*moduleInfo) bool) {
for _, dep := range order.propagate(module) {
if !yield(dep) {
return
}
}
for _, pauseSpec := range pauseMap[module] {
if !yield(pauseSpec.paused) {
return
}
}
}
var cycle []*moduleInfo
for dep := range dependencyIter {
cycle = check(dep)
if cycle != nil {
break
}
}
if cycle != nil {
if cycle[0] == module {
// We are the "start" of the cycle, so we're responsible
// for generating the errors.
slices.Reverse(cycle)
errs = append(errs, cycleError(cycle)...)
// We can continue processing this module's children to
// find more cycles. Since all the modules that were
// part of the found cycle were marked as visited we
// won't run into that cycle again.
} else {
// We're not the "start" of the cycle, so we just append
// our module to the list and return it.
return append(cycle, module)
}
}
return nil
}
for module := range moduleIter {
check(module)
}
if len(errs) > 0 {
return errs
}
}
// Invariant check: if there was no dependency cycle and no cancellation every module
// should have been visited, so there is nothing left to be paused on.
if len(pauseMap) > 0 {
panic(fmt.Errorf("parallelVisit finished with %d paused visitors", len(pauseMap)))
}
// Invariant check: if there was no dependency cycle and no cancellation every module
// should have been visited.
if visited != toVisit {
panic(fmt.Errorf("parallelVisit ran %d visitors, expected %d. Unvisited modules map: %v. Try rebuilding with SOONG_SPLIT_ALL_VARIANTS=true. Please file a go/soong-bug if build is successful with the environment variable.", visited, toVisit, hung))
}
}
return nil
}
func cycleError(cycle []*moduleInfo) (errs []error) {
// The cycle list is in reverse order because all the 'check' calls append
// their own module to the list.
errs = append(errs, &BlueprintError{
Err: fmt.Errorf("encountered dependency cycle:"),
Pos: cycle[len(cycle)-1].pos,
})
// Iterate backwards through the cycle list.
curModule := cycle[0]
for i := len(cycle) - 1; i >= 0; i-- {
nextModule := cycle[i]
errs = append(errs, &BlueprintError{
Err: fmt.Errorf(" %s depends on %s",
curModule, nextModule),
Pos: curModule.pos,
})
curModule = nextModule
}
return errs
}
// updateDependencies recursively walks the module dependency graph and updates
// additional fields based on the dependencies. It builds a sorted list of modules
// such that dependencies of a module always appear first, and populates reverse
// dependency links and counts of total dependencies. It also reports errors when
// it encounters dependency cycles. This should be called after resolveDependencies,
// as well as after any mutator pass has called addDependency
func (c *Context) updateDependencies() (errs []error) {
c.cachedDepsModified = true
for module := range c.iterateAllVariants() {
// Reset the forward and reverse deps without reducing their capacity to avoid reallocation.
module.reverseDeps = module.reverseDeps[:0]
module.forwardDeps = module.forwardDeps[:0]
}
for module := range c.iterateAllVariants() {
// Add an implicit dependency ordering on all earlier modules in the same module group
selfIndex := slices.Index(module.group.modules, module)
module.forwardDeps = slices.Grow(module.forwardDeps, selfIndex+len(module.directDeps))
module.forwardDeps = append(module.forwardDeps, module.group.modules[:selfIndex]...)
for _, dep := range module.directDeps {
module.forwardDeps = append(module.forwardDeps, dep.module)
}
for _, dep := range module.forwardDeps {
dep.reverseDeps = append(dep.reverseDeps, module)
}
}
return
}
// Gets a list of strings from the given list of ninjaStrings by invoking ninjaString.Value on each.
func getNinjaStrings(nStrs []*ninjaString, nameTracker *nameTracker) []string {
var strs []string
for _, nstr := range nStrs {
strs = append(strs, nstr.Value(nameTracker))
}
return strs
}
type WeightedOutputsModuleInfo struct {
Type string
DepsCount int
SrcsCount int
Outputs []string
}
func (c *Context) GetWeightedOutputsFromPredicate(predicate func(*WeightedOutputsModuleInfo) (bool, int)) map[string]int {
outputToWeight := make(map[string]int)
for m := range c.iterateAllVariants() {
info := WeightedOutputsModuleInfo{
Type: m.typeName,
DepsCount: len(m.directDeps),
SrcsCount: 0,
}
for _, bDef := range m.actionDefs.buildDefs {
info.SrcsCount += len(bDef.InputStrings) + len(bDef.Inputs) + len(bDef.ImplicitStrings) + len(bDef.Implicits)
info.Outputs = append(info.Outputs, bDef.OutputStrings...)
info.Outputs = append(info.Outputs, bDef.ImplicitOutputStrings...)
info.Outputs = append(info.Outputs, getNinjaStrings(bDef.Outputs, c.nameTracker)...)
info.Outputs = append(info.Outputs, getNinjaStrings(bDef.ImplicitOutputs, c.nameTracker)...)
}
if ok, weight := predicate(&info); ok {
for _, o := range info.Outputs {
if val, ok := outputToWeight[o]; ok {
if val > weight {
continue
}
}
outputToWeight[o] = weight
}
}
}
return outputToWeight
}
// PrepareBuildActions generates an internal representation of all the build
// actions that need to be performed. This process involves invoking the
// GenerateBuildActions method on each of the Module objects created during the
// parse phase and then on each of the registered Singleton objects.
//
// If the ResolveDependencies method has not already been called it is called
// automatically by this method.
//
// The config argument is made available to all of the Module and Singleton
// objects via the Config method on the ModuleContext and SingletonContext
// objects passed to GenerateBuildActions. It is also passed to the functions
// specified via PoolFunc, RuleFunc, and VariableFunc so that they can compute
// config-specific values.
//
// The returned deps is a list of the ninja files dependencies that were added
// by the modules and singletons via the ModuleContext.AddNinjaFileDeps(),
// SingletonContext.AddNinjaFileDeps(), and PackageContext.AddNinjaFileDeps()
// methods.
func (c *Context) PrepareBuildActions(config interface{}) (deps []string, errs []error) {
c.BeginEvent("prepare_build_actions")
defer c.EndEvent("prepare_build_actions")
pprof.Do(c.Context, pprof.Labels("blueprint", "PrepareBuildActions"), func(ctx context.Context) {
c.buildActionsReady = false
c.liveGlobals = newLiveTracker(c, config)
// Add all the global rules/variable/pools here because when we restore from
// cache we don't have the build defs available to build the globals.
// TODO(b/356414070): Revisit this logic once we have a clearer picture about
// how the incremental build pieces fit together.
if c.GetIncrementalEnabled() {
if c.keyValueStoreCache == nil {
c.keyValueStoreCache = &KeyValueStoreCache{}
dbPath := filepath.Join(c.SrcDir(), c.IncrementalDBDir())
// Remove gob files and all the cached data from the key-value store for a full build.
if !c.GetIncrementalAnalysis() {
err := errors.Join(c.keyValueStoreCache.reset(c, dbPath), c.fs.Remove(filepath.Join(dbPath, OrderOnlyStringsCacheFile)))
if err != nil {
panic(fmt.Errorf("error resetting incremental db: %w", err))
}
}
if err := c.keyValueStoreCache.open(dbPath); err != nil {
panic(fmt.Errorf("error opening incremental db: %w", err))
}
c.EncContext = gobtools.NewEncContext(c.keyValueStoreCache.referencesDb)
}
for _, p := range packageContexts {
for _, v := range p.scope.variables {
err := c.liveGlobals.addVariable(v)
if err != nil {
errs = []error{err}
return
}
}
for _, v := range p.scope.rules {
_, err := c.liveGlobals.addRule(v)
if err != nil {
errs = []error{err}
return
}
}
for _, v := range p.scope.pools {
err := c.liveGlobals.addPool(v)
if err != nil {
errs = []error{err}
return
}
}
}
}
if !c.dependenciesReady {
var extraDeps []string
extraDeps, errs = c.resolveDependencies(ctx, config)
if len(errs) > 0 {
return
}
deps = append(deps, extraDeps...)
}
var depsModules []string
depsModules, errs = c.generateModuleBuildActions(config, c.liveGlobals)
if len(errs) > 0 {
return
}
pprof.Do(c.Context, pprof.Labels("blueprint", "GC"), func(ctx context.Context) {
runtime.GC()
})
var depsSingletons []string
depsSingletons, errs = c.generateSingletonBuildActions(config, c.singletonInfo, c.liveGlobals)
if len(errs) > 0 {
return
}
deps = append(deps, depsModules...)
deps = append(deps, depsSingletons...)
if c.outDir != nil {
err := c.liveGlobals.addNinjaStringDeps(c.outDir)
if err != nil {
errs = []error{err}
return
}
}
pkgNames, depsPackages := c.makeUniquePackageNames(c.liveGlobals)
deps = append(deps, depsPackages...)
nameTracker := c.memoizeFullNames(c.liveGlobals, pkgNames)
// This will panic if it finds a problem since it's a programming error.
c.checkForVariableReferenceCycles(c.liveGlobals.variables, nameTracker)
c.nameTracker = nameTracker
c.globalVariables = c.liveGlobals.variables
c.globalPools = c.liveGlobals.pools
c.globalRules = c.liveGlobals.rules
c.buildActionsReady = true
})
if len(errs) > 0 {
return nil, errs
}
return deps, nil
}
func (c *Context) runMutators(ctx context.Context, config interface{}, mutatorGroups [][]*mutatorInfo) (deps []string, errs []error) {
c.finishedMutators = make([]bool, len(c.mutatorInfo))
pprof.Do(ctx, pprof.Labels("blueprint", "runMutators"), func(ctx context.Context) {
mutatorIndexAfterLastCreateModule := -1
mutatorIndexPartialAnalysis := -1
for i := len(mutatorGroups) - 1; i >= 0; i-- {
if mutatorIndexAfterLastCreateModule == -1 && mutatorGroups[i][0].usesCreateModule {
mutatorIndexAfterLastCreateModule = mutatorGroups[i][0].index
}
if mutatorIndexPartialAnalysis == -1 && mutatorGroups[i][0].prePartial {
mutatorIndexPartialAnalysis = mutatorGroups[i][0].index
}
}
c.mutatorIndexAfterLastCreateModule = mutatorIndexAfterLastCreateModule + 1
c.mutatorIndexPartialAnalysis = mutatorIndexPartialAnalysis + 1
for _, mutatorGroup := range mutatorGroups {
if mutatorGroup[0].index == c.mutatorIndexPartialAnalysis && len(c.partialAnalysisTargets) > 0 {
targetMap := make(map[string]bool, len(c.partialAnalysisTargets))
for _, t := range c.partialAnalysisTargets {
targetMap[t] = false
}
foundCount := 0
for _, m := range c.moduleGroups {
if alreadyFound, isTarget := targetMap[m.name]; isTarget {
m.passive = false
if !alreadyFound {
targetMap[m.name] = true
foundCount++
}
} else {
m.passive = true
}
}
if foundCount < len(targetMap) {
notFound := make([]string, 0, len(targetMap)-foundCount)
for target, found := range targetMap {
if !found {
notFound = append(notFound, target)
}
}
panic(fmt.Sprintf("the requested targets are not found: %v", notFound))
}
}
name := mutatorGroup[0].name
if len(mutatorGroup) > 1 {
name += "_plus_" + strconv.Itoa(len(mutatorGroup)-1)
}
pprof.Do(ctx, pprof.Labels("mutator", name), func(context.Context) {
c.BeginEvent(name)
defer c.EndEvent(name)
var newDeps []string
if mutatorGroup[0].transitionPropagateMutator != nil {
newDeps, errs = c.runMutator(config, mutatorGroup, topDownMutator)
} else if mutatorGroup[0].bottomUpMutator != nil {
newDeps, errs = c.runMutator(config, mutatorGroup, bottomUpMutator)
} else {
panic("no mutator set on " + mutatorGroup[0].name)
}
if len(errs) > 0 {
return
}
deps = append(deps, newDeps...)
})
if len(errs) > 0 {
return
}
}
})
if len(errs) > 0 {
return nil, errs
}
return deps, nil
}
type mutatorDirection interface {
run(mutator []*mutatorInfo, ctx *mutatorContext)
orderer() visitOrderer
fmt.Stringer
}
type bottomUpMutatorImpl struct{}
func (bottomUpMutatorImpl) run(mutatorGroup []*mutatorInfo, ctx *mutatorContext) {
for _, mutator := range mutatorGroup {
ctx.mutator = mutator
ctx.module.startedMutator = mutator.index
mutator.bottomUpMutator(ctx)
ctx.module.finishedMutator = mutator.index
}
}
func (bottomUpMutatorImpl) orderer() visitOrderer {
return bottomUpVisitor
}
func (bottomUpMutatorImpl) String() string {
return "bottom up mutator"
}
type topDownMutatorImpl struct{}
func (topDownMutatorImpl) run(mutatorGroup []*mutatorInfo, ctx *mutatorContext) {
if len(mutatorGroup) > 1 {
panic(fmt.Errorf("top down mutator group %s must only have 1 mutator, found %d", mutatorGroup[0].name, len(mutatorGroup)))
}
mutatorGroup[0].transitionPropagateMutator(ctx)
}
func (topDownMutatorImpl) orderer() visitOrderer {
return topDownVisitor
}
func (topDownMutatorImpl) String() string {
return "top down mutator"
}
var (
topDownMutator topDownMutatorImpl
bottomUpMutator bottomUpMutatorImpl
)
type reverseDep struct {
module *moduleInfo
dep depInfo
}
var mutatorContextPool = pool.New[mutatorContext]()
func (c *Context) runMutator(config interface{}, mutatorGroup []*mutatorInfo,
direction mutatorDirection) (deps []string, errs []error) {
type globalStateChange struct {
reverse []reverseDep
rename []rename
replace []replace
newModules []*moduleInfo
onDemandModules []*moduleInfo
onDemandReverseDeps []reverseDep
deps []string
}
type createdOnDemandStateChange struct {
module *moduleInfo
moduleReplaceWithCh chan *moduleInfo
}
reverseDeps := make(map[*moduleInfo][]depInfo)
onDemandReverseDeps := make(map[*moduleInfo]map[*moduleInfo]bool)
var rename []rename
var replace []replace
var newModules []*moduleInfo
var onDemandModules []*moduleInfo
errsCh := make(chan []error)
globalStateCh := make(chan globalStateChange)
createdOnDemandStateCh := make(chan createdOnDemandStateChange)
done := make(chan bool)
c.needsUpdateDependencies = 0
visit := func(module *moduleInfo, pause pauseFunc) bool {
if module.splitModules != nil {
panic("split module found in sorted module list")
}
if module.createdOnDemand {
// Send a request to the main coordinator goroutine to check
// if the previous mutators need to be run on this on demand variant.
// This allows the coordinator goroutine to dedupe processing if necessary.
moduleReplaceWithCh := make(chan *moduleInfo)
createdOnDemandStateCh <- createdOnDemandStateChange{
module,
moduleReplaceWithCh,
}
moduleReplaceWith := <-moduleReplaceWithCh
if module == moduleReplaceWith {
c.rerunMutatorsOnVariantOnDemand(module, c.mutatorIndexAfterLastCreateModule, mutatorGroup[len(mutatorGroup)-1].index, pause, config) // Mutate till the current mutator.
globalStateCh <- globalStateChange{
onDemandModules: []*moduleInfo{module},
onDemandReverseDeps: module.newOnDemandReverseDeps,
}
}
module.createdOnDemandReplaceWith = moduleReplaceWith
return false
}
mctx := mutatorContextPool.Get()
*mctx = mutatorContext{
baseModuleContext: baseModuleContext{
context: c,
config: config,
module: module,
},
mutator: mutatorGroup[0],
pauseFunc: pause,
}
module.startedMutator = mutatorGroup[0].index
func() {
defer func() {
if r := recover(); r != nil {
in := fmt.Sprintf("%s %q for %s", direction, mutatorGroup[0].name, module)
if err, ok := r.(panicError); ok {
err.addIn(in)
mctx.error(err)
} else {
mctx.error(newPanicErrorf(r, in))
}
}
}()
direction.run(mutatorGroup, mctx)
}()
module.finishedMutator = mutatorGroup[len(mutatorGroup)-1].index
hasErrors := false
if len(mctx.errs) > 0 {
errsCh <- mctx.errs
hasErrors = true
} else {
if len(mctx.reverseDeps) > 0 || len(mctx.replace) > 0 || len(mctx.rename) > 0 || len(mctx.newModules) > 0 || len(mctx.ninjaFileDeps) > 0 || len(mctx.module.newOnDemandReverseDeps) > 0 {
globalStateCh <- globalStateChange{
reverse: mctx.reverseDeps,
replace: mctx.replace,
rename: mctx.rename,
newModules: mctx.newModules,
deps: mctx.ninjaFileDeps,
onDemandReverseDeps: module.newOnDemandReverseDeps,
}
}
}
if module.startedMutator == c.mutatorIndexAfterLastCreateModule {
// Store core module info after the final CreateModule mutator.
// This ensures that storeCoreModuleInfo is called on modules
// created using CreateModule.
mctx.storeCoreModuleInfo()
}
mutatorContextPool.Put(mctx)
mctx = nil
return hasErrors
}
type onDemandModuleUniqueKey struct {
group *moduleGroup
variant string
}
variantToOnDemandModule := map[onDemandModuleUniqueKey]*moduleInfo{}
// Process errs and reverseDeps in a single goroutine
go func() {
for {
select {
case newErrs := <-errsCh:
errs = append(errs, newErrs...)
case globalStateChange := <-globalStateCh:
for _, r := range globalStateChange.reverse {
reverseDeps[r.module] = append(reverseDeps[r.module], r.dep)
}
for _, r := range globalStateChange.onDemandReverseDeps {
if _, exists := onDemandReverseDeps[r.module]; !exists {
onDemandReverseDeps[r.module] = make(map[*moduleInfo]bool)
}
onDemandReverseDeps[r.module][r.dep.module] = true
}
replace = append(replace, globalStateChange.replace...)
rename = append(rename, globalStateChange.rename...)
newModules = append(newModules, globalStateChange.newModules...)
onDemandModules = append(onDemandModules, globalStateChange.onDemandModules...)
deps = append(deps, globalStateChange.deps...)
case createdOnDemandStateChange := <-createdOnDemandStateCh:
var variantSb strings.Builder
mod := createdOnDemandStateChange.module
for _, mutator := range slices.Sorted(maps.Keys(mod.requestedOnDemandVariant.variations)) {
variantSb.WriteString("-")
variantSb.WriteString(mod.requestedOnDemandVariant.variations[mutator])
}
uniqueDepKey := onDemandModuleUniqueKey{
mod.group,
variantSb.String(),
}
if existing, exists := variantToOnDemandModule[uniqueDepKey]; exists {
createdOnDemandStateChange.moduleReplaceWithCh <- existing
} else {
createdOnDemandStateChange.moduleReplaceWithCh <- createdOnDemandStateChange.module
variantToOnDemandModule[uniqueDepKey] = createdOnDemandStateChange.module
}
case <-done:
return
}
}
}()
visitErrs := parallelVisit(c.iterateAllVariants(), direction.orderer(), parallelVisitLimit, visit)
if len(visitErrs) > 0 {
return nil, visitErrs
}
for _, mutator := range mutatorGroup {
c.finishedMutators[mutator.index] = true
}
done <- true
if len(errs) > 0 {
return nil, errs
}
transitionMutator := mutatorGroup[0].transitionMutator
if transitionMutator != nil {
for _, group := range c.moduleGroups {
for i := 0; i < len(group.modules); i++ {
module := group.modules[i]
// Update module group to contain newly split variants
if module.splitModules != nil {
group.modules, i = spliceModules(group.modules, i, module.splitModules)
}
// Fix up any remaining dependencies on modules that were split into variants
// by replacing them with the first variant
for j, dep := range module.directDeps {
if dep.module.obsoletedByNewVariants {
module.directDeps[j].module = dep.module.splitModules.firstModule()
}
}
if module.createdBy != nil && module.createdBy.obsoletedByNewVariants {
module.createdBy = module.createdBy.splitModules.firstModule()
}
}
}
c.completedTransitionMutators = transitionMutator.index + 1
} else {
for _, group := range c.moduleGroups {
for _, module := range group.modules {
// Add any new forward dependencies to the reverse dependencies of the dependency to avoid
// having to call a full c.updateDependencies().
for _, m := range module.newDirectDeps {
m.reverseDeps = append(m.reverseDeps, module)
}
module.newDirectDeps = nil
}
}
}
// Add in any new reverse dependencies that were added by the mutator
for module, deps := range reverseDeps {
sort.Sort(depSorter{deps, c.nameInterface})
module.directDeps = append(module.directDeps, deps...)
for _, dep := range deps {
module.forwardDeps = append(module.forwardDeps, dep.module)
dep.module.reverseDeps = append(dep.module.reverseDeps, module)
}
}
// Set forward/reverseDeps of on-demand variants.
// Sort the deps to ensure deterministic orderding.
for module, depsAsKeys := range onDemandReverseDeps {
deps := slices.Collect(maps.Keys(depsAsKeys))
slices.SortFunc(deps, func(a, b *moduleInfo) int {
return cmp.Compare(a.variant.name, b.variant.name)
})
for _, dep := range deps {
module.forwardDeps = append(module.forwardDeps, dep)
dep.reverseDeps = append(dep.reverseDeps, module)
}
module.newOnDemandReverseDeps = nil
}
for _, module := range newModules {
errs = c.addModule(module)
if len(errs) > 0 {
return nil, errs
}
}
errs = c.handleRenames(rename)
if len(errs) > 0 {
return nil, errs
}
errs = c.handleReplacements(replace)
if len(errs) > 0 {
return nil, errs
}
if c.needsUpdateDependencies > 0 {
errs = c.updateDependencies()
if len(errs) > 0 {
return nil, errs
}
}
// Add the on-demand variant into its module group.
// Since there can be inter-variant deps, it needs to be inserted before its inter-variant rdep.
insertIntoModuleGroup := func(module *moduleInfo) {
isInterVariantDep := false
// Check reverseDeps to see if inter-variant rdep exists.
for _, rdep := range module.reverseDeps {
if rdep.group == module.group {
isInterVariantDep = true
break
}
}
insertIndex := len(module.group.modules)
if isInterVariantDep {
insertIndex = slices.IndexFunc(module.group.modules, func(groupModule *moduleInfo) bool {
return slices.ContainsFunc(groupModule.directDeps, func(directDep depInfo) bool {
return directDep.module == module
})
})
if insertIndex == -1 {
insertIndex = len(module.group.modules)
}
}
module.group.modules = slices.Insert(module.group.modules, insertIndex, module)
}
moduleGroupsWithOnDemandModules := make(map[*moduleGroup]bool)
for _, module := range onDemandModules {
moduleGroupsWithOnDemandModules[module.group] = true
insertIntoModuleGroup(module)
for _, fd := range module.newDirectDeps {
fd.reverseDeps = append(fd.reverseDeps, module)
// AddVariationDependency allows adding a dependency on itself, but only if
// that module is earlier in the module list than this one, since we always
// run GenerateBuildActions in order for the variants of a module
if fd.group == module.group && beforeInModuleList(module, fd, module.group.modules) {
return nil, []error{&BlueprintError{
Err: fmt.Errorf("%q depends on later version of itself", module.Name()),
Pos: module.pos,
}}
}
}
// The module has been created and mutated.
// For future mutators, this variant is the same as normal variants.
// Set this flag to false so that we do not try to call `rerunMutatorsOnVariantOnDemand` again.
module.createdOnDemand = false
module.createdOnDemandReplaceWith = nil
module.newDirectDeps = nil
module.newOnDemandReverseDeps = nil
module.createdOnDemandSupportedSplits = nil
module.group.cachedVariantsOnDemand = nil
}
for g := range moduleGroupsWithOnDemandModules {
slices.SortStableFunc(g.modules, func(a, b *moduleInfo) int {
return cmp.Compare(a.sortIndex, b.sortIndex)
})
}
return deps, errs
}
// Removes modules[i] from the list and inserts newModules... where it was located, returning
// the new slice and the index of the last inserted element
func spliceModules(modules moduleList, i int, newModules moduleList) (moduleList, int) {
spliceSize := len(newModules)
newLen := len(modules) + spliceSize - 1
var dest moduleList
if cap(modules) >= len(modules)-1+len(newModules) {
// We can fit the splice in the existing capacity, do everything in place
dest = modules[:newLen]
} else {
dest = make(moduleList, newLen)
copy(dest, modules[:i])
}
// Move the end of the slice over by spliceSize-1
copy(dest[i+spliceSize:], modules[i+1:])
// Copy the new modules into the slice
copy(dest[i:], newModules)
return dest, i + spliceSize - 1
}
func (c *Context) generateModuleBuildActions(config interface{},
liveGlobals *liveTracker) ([]string, []error) {
c.BeginEvent("generateModuleBuildActions")
defer c.EndEvent("generateModuleBuildActions")
var deps []string
var errs []error
cancelCh := make(chan struct{})
errsCh := make(chan []error)
depsCh := make(chan []string)
go func() {
for {
select {
case <-cancelCh:
close(cancelCh)
return
case newErrs := <-errsCh:
errs = append(errs, newErrs...)
case newDeps := <-depsCh:
deps = append(deps, newDeps...)
}
}
}()
visitErrs := parallelVisit(c.iterateAllVariants(), bottomUpVisitor, parallelVisitLimit,
func(module *moduleInfo, pause pauseFunc) bool {
module.cachedUniqueName = c.nameInterface.UniqueName(newNamespaceContext(module), module.group.name)
sanitizedName := toNinjaName(module.cachedUniqueName)
sanitizedVariant := toNinjaName(module.variant.name)
prefix := moduleNamespacePrefix(sanitizedName + "_" + sanitizedVariant)
// The parent scope of the moduleContext's local scope gets overridden to be that of the
// calling Go package on a per-call basis. Since the initial parent scope doesn't matter we
// just set it to nil.
scope := newLocalScope(nil, prefix)
mctx := &moduleContext{
baseModuleContext: baseModuleContext{
context: c,
config: config,
module: module,
},
scope: scope,
handledMissingDeps: module.missingDeps == nil,
}
// Use a deferred call for this, to avoid errors from trying to evaluate the select()
// expressions in the configurable values that mctx.evaluator encounters too early.
defer func() {
if c.moduleDebugDataChannel != nil {
c.moduleDebugDataChannel <- getModuleDebugJson(mctx.evaluator, module)
}
// The evaluator isn't needed anymore. Avoid possibly cyclic ref that may increase gc load.
mctx.evaluator = nil
}()
module.startedGenerateBuildActions = true
func() {
defer func() {
if r := recover(); r != nil {
in := fmt.Sprintf("GenerateBuildActions for %s", module)
if err, ok := r.(panicError); ok {
err.addIn(in)
mctx.error(err)
} else {
mctx.error(newPanicErrorf(r, in))
}
}
}()
if !module.restoreModuleBuildActions(c) || c.incrementalProviderTest {
if !c.SkipCloneModulesAfterMutators {
// Replaces every build logic module with a clone of itself. Prevents introducing problems where
// a mutator sets a non-property member variable on a module, which works until a later mutator
// creates variants of that module.
module.logicModule, module.properties = c.cloneLogicModule(module)
module.logicModule.setInfo(module)
}
module.logicModule.GenerateBuildActions(mctx)
}
module.calculateProviderHash()
}()
module.finishedGenerateBuildActions = true
if len(mctx.errs) > 0 {
errsCh <- mctx.errs
return true
}
if module.missingDeps != nil && !mctx.handledMissingDeps && !module.incrementalRestored {
var errs []error
for _, depName := range module.missingDeps {
errs = append(errs, c.missingDependencyError(module, depName))
}
errsCh <- errs
return true
}
depsCh <- mctx.ninjaFileDeps
if module.freeAfterGenerateBuildActions {
// This module is freed after GenerateBuildActions complete, requiring all future accesses
// to go through ModuleProxy instead of the Module.
// Cache Module.Name() and Module.String() for future use in ModuleProxy.Name() and ModuleProxy.String()
module.cachedName = module.logicModule.Name()
module.cachedString = module.logicModule.String()
// When soong debug data is requested, don't remove these info, they will show up in soong-debug-info.json.
if c.moduleDebugDataChannel == nil {
// TODO: logicModule is needed to evaluate configurable properties, we should figure out an alternative.
module.logicModule = nil
module.properties = nil
module.propertyPos = nil
}
}
newErrs := c.processLocalBuildActions(&module.actionDefs,
&mctx.actionDefs, liveGlobals)
if len(newErrs) > 0 {
errsCh <- newErrs
return true
}
return false
})
cancelCh <- struct{}{}
<-cancelCh
errs = append(errs, visitErrs...)
return deps, errs
}
func (c *Context) WriteIncrementalDebugInfo(filename string, modules []*moduleInfo) {
f, err := os.Create(filename)
if err != nil {
// We expect this to be writable
panic(fmt.Sprintf("couldn't create incremental module debug file %s: %s", filename, err))
}
defer f.Close()
needComma := false
f.WriteString("{\n\"modules\": [\n")
for _, module := range modules {
if module.incrementalDebugInfo == nil {
continue
}
if needComma {
f.WriteString(",\n")
} else {
needComma = true
}
f.Write(module.incrementalDebugInfo)
}
f.WriteString("\n]\n}")
}
func (c *Context) generateOneSingletonBuildActions(config interface{},
info *singletonInfo, liveGlobals *liveTracker) ([]string, []error) {
var deps []string
var errs []error
// The parent scope of the singletonContext's local scope gets overridden to be that of the
// calling Go package on a per-call basis. Since the initial parent scope doesn't matter we
// just set it to nil.
scope := newLocalScope(nil, singletonNamespacePrefix(info.name))
sctx := &singletonContext{
singleton: info,
context: c,
config: config,
scope: scope,
globals: liveGlobals,
depProviders: make(map[int]bool),
}
info.startedGenerateBuildActions = true
func() {
defer func() {
if r := recover(); r != nil {
in := fmt.Sprintf("GenerateBuildActions for singleton %s", info.name)
if err, ok := r.(panicError); ok {
err.addIn(in)
sctx.error(err)
} else {
sctx.error(newPanicErrorf(r, in))
}
}
}()
c.restoreSingleton(info)
if !info.incrementalRestored || c.incrementalProviderTest {
info.singleton.GenerateBuildActions(sctx)
}
// A caching entry for singletons is only written if the singleton
// depends on at least one provider. If zero providers are consumed,
// the cache restore process would fail to locate
// a cache entry, forcing the singleton to re-execute on every build.
//
// An edge case where a coding change removes a provider dependency is safely handled
// because any detected code change automatically invalidates the entire global cache,
// ensuring system consistency.
if info.buildActionCacheKey != nil && !info.incrementalRestored {
cache := make(map[int]proptools.Hash)
for k, _ := range sctx.depProviders {
// A singleton might depend on both module providers and singleton providers, and
// the hashes of all the former are stored in context globally, and the hashes of
// the latter are stored inside the current singletonInfo.
if providerRegistry[k].mutator == singletonTag {
cache[k] = info.providerValueHashes[k]
} else {
cache[k] = c.providerValueHashes[k]
}
}
var providerHashes []ProviderHash
for i, p := range info.providers {
if p == nil {
continue
}
err := c.keyValueStoreCache.writeProvider(c.EncContext, info.providerInitialValueHashes[i], CachedProvider{
Id: providerRegistry[i],
Value: p,
})
if err != nil {
panic(err)
}
providerHashes = append(providerHashes,
ProviderHash{
Id: providerRegistry[i],
Hash: info.providerInitialValueHashes[i],
})
}
if err := c.keyValueStoreCache.writeSingletonBuildAction(c.EncContext, info.buildActionCacheKey,
&SingletonActionCachedData{
ProviderHashes: providerHashes,
DependencyProviderHashes: cache,
GlobCache: info.globCache,
}); err != nil {
panic(err)
}
}
}()
info.finishedGenerateBuildActions = true
if len(sctx.errs) > 0 {
errs = append(errs, sctx.errs...)
return deps, errs
}
deps = append(deps, sctx.ninjaFileDeps...)
info.subninjas = append(info.subninjas, sctx.subninjas...)
newErrs := c.processLocalBuildActions(&info.actionDefs,
&sctx.actionDefs, liveGlobals)
errs = append(errs, newErrs...)
return deps, errs
}
func (c *Context) restoreSingleton(info *singletonInfo) {
info.incrementalSupported = c.GetIncrementalEnabled() && info.singleton.IncrementalSupported()
if !info.incrementalSupported {
return
}
info.buildActionCacheKey = &DataCacheKey{
Id: info.name,
}
// We can pursue a incremental analysis because there is no soong coding change, no
// product config and env variable changes.
if !c.GetIncrementalAnalysis() {
return
}
data, err := c.keyValueStoreCache.readSingletonBuildAction(c.EncContext, info.buildActionCacheKey)
if err != nil {
panic(err)
}
if data == nil {
return
}
// This logic here assumes a singleton's behavior is a pure function of its providers.
// Conditional access to certain providers must also be based on other provider
// values, ensuring that any behavioral change is captured by the input providers hashes.
for k, v := range data.DependencyProviderHashes {
var hash proptools.Hash
if providerRegistry[k].mutator == singletonTag {
hash = info.providerValueHashes[k]
} else {
hash = c.providerValueHashes[k]
}
if hash != v {
return
}
}
for _, glob := range data.GlobCache {
result, err := c.glob(glob.Pattern, glob.Excludes)
if err != nil {
panic(newPanicErrorf(err, "failed to glob for cached singleton: %s %s %v", info.name, glob.Pattern, glob.Excludes))
}
hash, err := proptools.CalculateHash(stringList(result))
if err != nil {
panic(newPanicErrorf(err, "failed to calculate hash for cached glob result: %s", info.name))
}
if hash != glob.Result {
// Don't restore, a glob result has changed.
return
}
}
info.incrementalRestored = true
info.providerInitialValueHashes = make([]proptools.Hash, len(providerRegistry))
info.hasUnrestoredProvider = make([]bool, len(providerRegistry))
for _, provider := range data.ProviderHashes {
info.hasUnrestoredProvider[provider.Id.id] = true
info.providerInitialValueHashes[provider.Id.id] = provider.Hash
}
}
func (c *Context) generateParallelSingletonBuildActions(config interface{},
singletons []*singletonInfo, liveGlobals *liveTracker) ([]string, []error) {
c.BeginEvent("generateParallelSingletonBuildActions")
defer c.EndEvent("generateParallelSingletonBuildActions")
var deps []string
var errs []error
wg := sync.WaitGroup{}
cancelCh := make(chan struct{})
depsCh := make(chan []string)
errsCh := make(chan []error)
go func() {
for {
select {
case <-cancelCh:
close(cancelCh)
return
case dep := <-depsCh:
deps = append(deps, dep...)
case newErrs := <-errsCh:
if len(errs) <= maxErrors {
errs = append(errs, newErrs...)
}
}
}
}()
for _, info := range singletons {
if !info.parallel {
// Skip any singletons registered with parallel=false.
continue
}
wg.Add(1)
go func(inf *singletonInfo) {
defer wg.Done()
newDeps, newErrs := c.generateOneSingletonBuildActions(config, inf, liveGlobals)
depsCh <- newDeps
errsCh <- newErrs
}(info)
}
wg.Wait()
cancelCh <- struct{}{}
<-cancelCh
return deps, errs
}
func (c *Context) calculateProvidersHashes() {
c.providerValueHashes = make([]proptools.Hash, len(providerRegistry))
var wg sync.WaitGroup
for i := range len(providerRegistry) {
wg.Add(1)
go func() {
defer wg.Done()
var providerHashes []proptools.Hash
// For singleton providers we collect provider hashes from singletons.
if providerRegistry[i].mutator != singletonTag {
c.visitAllModuleInfos(func(m *moduleInfo) {
if m.providerInitialValueHashes != nil {
providerHashes = append(providerHashes, m.providerInitialValueHashes[i])
}
})
}
var err error
c.providerValueHashes[i], err = proptools.CalculateHash(hashList(providerHashes))
if err != nil {
panic(err)
}
}()
}
wg.Wait()
}
func (c *Context) generateSingletonBuildActions(config interface{},
singletons []*singletonInfo, liveGlobals *liveTracker) ([]string, []error) {
c.BeginEvent("generateSingletonBuildActions")
defer c.EndEvent("generateSingletonBuildActions")
var deps []string
var errs []error
// Run one singleton. Use a variable to simplify manual validation testing.
var runSingleton = func(info *singletonInfo) {
c.BeginEvent("singleton:" + info.name)
defer c.EndEvent("singleton:" + info.name)
newDeps, newErrs := c.generateOneSingletonBuildActions(config, info, liveGlobals)
deps = append(deps, newDeps...)
errs = append(errs, newErrs...)
}
// Force a resort of the module groups before running singletons so that two singletons running in parallel
// don't cause a data race when they trigger a resort in VisitAllModules.
c.sortedModuleGroups()
if c.GetIncrementalEnabled() {
c.calculateProvidersHashes()
}
// First, take care of any singletons that want to run in parallel.
deps, errs = c.generateParallelSingletonBuildActions(config, singletons, liveGlobals)
for _, info := range singletons {
if !info.parallel {
if c.GetIncrementalEnabled() {
// A singleton might depend on modules and other singletons that run before it.
// We already captured the provider hashes of all the modules in calculateProvidersHashes,
// now we calculate the hashes of all singletons that the current one might depend on.
info.providerValueHashes = make([]proptools.Hash, len(providerRegistry))
for i := range len(providerRegistry) {
var providerHashes []proptools.Hash
if providerRegistry[i].mutator == singletonTag {
c.VisitAllSingletons(func(s SingletonProxy) {
hash := proptools.ZeroHash
if s.singleton.providerInitialValueHashes != nil {
hash = s.singleton.providerInitialValueHashes[i]
}
providerHashes = append(providerHashes, hash)
})
}
var err error
info.providerValueHashes[i], err = proptools.CalculateHash(hashList(providerHashes))
if err != nil {
panic(err)
}
}
}
runSingleton(info)
if len(errs) > maxErrors {
break
}
}
}
return deps, errs
}
func (c *Context) processLocalBuildActions(out, in *localBuildActions,
liveGlobals *liveTracker) []error {
var errs []error
// First we go through and add everything referenced by the module's
// buildDefs to the live globals set. This will end up adding the live
// locals to the set as well, but we'll take them out after.
for _, def := range in.buildDefs {
err := liveGlobals.AddBuildDefDeps(def)
if err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errs
}
out.buildDefs = append(out.buildDefs, in.buildDefs...)
// We use the now-incorrect set of live "globals" to determine which local
// definitions are live. As we go through copying those live locals to the
// moduleGroup we remove them from the live globals set.
for _, v := range in.variables {
isLive := liveGlobals.RemoveVariableIfLive(v)
if isLive {
out.variables = append(out.variables, v)
}
}
for _, r := range in.rules {
isLive := liveGlobals.RemoveRuleIfLive(r)
if isLive {
out.rules = append(out.rules, r)
}
}
return nil
}
func (c *Context) walkDeps(topModule *moduleInfo, allowDuplicates bool,
visitDown func(depInfo, *moduleInfo) bool, visitUp func(depInfo, *moduleInfo)) {
visited := make(map[*moduleInfo]bool)
var visiting *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "WalkDeps(%s, %s, %s) for dependency %s",
topModule, funcName(visitDown), funcName(visitUp), visiting))
}
}()
var walk func(module *moduleInfo)
walk = func(module *moduleInfo) {
for _, dep := range module.directDeps {
if allowDuplicates || !visited[dep.module] {
visiting = dep.module
recurse := true
if visitDown != nil {
recurse = visitDown(dep, module)
}
if recurse && !visited[dep.module] {
walk(dep.module)
visited[dep.module] = true
}
if visitUp != nil {
visitUp(dep, module)
}
}
}
}
walk(topModule)
}
type replace struct {
from, to *moduleInfo
predicate ReplaceDependencyPredicate
}
type rename struct {
group *moduleGroup
name string
}
type onDemandDep struct {
from, to *moduleInfo
tag DependencyTag
}
// moduleVariantsThatDependOn takes the name of a module and a dependency and returns the all the variants of the
// module that depends on the dependency.
func (c *Context) moduleVariantsThatDependOn(name string, dep *moduleInfo) []*moduleInfo {
group := c.moduleGroupFromName(name, dep.namespace())
var variants []*moduleInfo
if group == nil {
return nil
}
for _, m := range group.modules {
for _, moduleDep := range m.directDeps {
if moduleDep.module == dep {
variants = append(variants, m)
}
}
}
return variants
}
func (c *Context) handleRenames(renames []rename) []error {
var errs []error
for _, rename := range renames {
group, name := rename.group, rename.name
if name == group.name || len(group.modules) < 1 {
continue
}
errs = append(errs, c.nameInterface.Rename(group.name, rename.name, group.namespace)...)
}
return errs
}
func (c *Context) handleReplacements(replacements []replace) []error {
var errs []error
changedDeps := false
for _, replace := range replacements {
for _, m := range replace.from.reverseDeps {
for i, d := range m.directDeps {
if d.module == replace.from {
// If the replacement has a predicate then check it.
if replace.predicate == nil || replace.predicate(m.logicModule, d.tag, d.module.logicModule) {
m.directDeps[i].module = replace.to
changedDeps = true
}
}
}
}
}
if changedDeps {
c.needsUpdateDependencies++
}
return errs
}
func (c *Context) discoveredMissingDependencies(module *moduleInfo, depName string, depVariations variationMap) (errs []error) {
if !depVariations.empty() {
depName = depName + "{" + c.prettyPrintVariant(depVariations) + "}"
}
if c.allowMissingDependencies {
module.missingDeps = append(module.missingDeps, depName)
return nil
}
return []error{c.missingDependencyError(module, depName)}
}
func (c *Context) missingDependencyError(module *moduleInfo, depName string) (errs error) {
guess := namesLike(depName, module.Name(), c.moduleGroups)
err := c.nameInterface.MissingDependencyError(module.Name(), module.namespace(), depName, guess)
return &BlueprintError{
Err: err,
Pos: module.pos,
}
}
func (c *Context) moduleGroupFromName(name string, namespace Namespace) *moduleGroup {
group, exists := c.nameInterface.ModuleFromName(name, namespace)
if exists {
return group.moduleGroup
}
return nil
}
func (c *Context) sortedModuleGroups() []*moduleGroup {
moduleLess := func(a, b *moduleInfo) int {
return cmp.Compare(a.variant.name, b.variant.name)
}
if c.cachedSortedModuleGroups == nil || c.cachedDepsModified {
unwrap := func(wrappers []ModuleGroup) []*moduleGroup {
result := make([]*moduleGroup, 0, len(wrappers))
for _, group := range wrappers {
// Order the module variants in-place
slices.SortFunc(group.moduleGroup.modules, moduleLess)
result = append(result, group.moduleGroup)
}
return result
}
c.cachedSortedModuleGroups = unwrap(c.nameInterface.AllModules())
c.cachedDepsModified = false
}
return c.cachedSortedModuleGroups
}
func (c *Context) visitAllModuleVariants(module *moduleInfo,
visit func(*moduleInfo)) {
for _, module := range module.group.modules {
visit(module)
}
}
func (c *Context) visitAllModuleInfos(visit func(*moduleInfo)) {
for _, moduleGroup := range c.sortedModuleGroups() {
for _, module := range moduleGroup.modules {
visit(module)
}
}
}
func (c *Context) requireNinjaVersion(major, minor, micro int) {
if major != 1 {
panic("ninja version with major version != 1 not supported")
}
if c.requiredNinjaMinor < minor {
c.requiredNinjaMinor = minor
c.requiredNinjaMicro = micro
}
if c.requiredNinjaMinor == minor && c.requiredNinjaMicro < micro {
c.requiredNinjaMicro = micro
}
}
func (c *Context) setOutDir(value *ninjaString) {
if c.outDir == nil {
c.outDir = value
}
}
func (c *Context) makeUniquePackageNames(
liveGlobals *liveTracker) (map[*packageContext]string, []string) {
pkgs := make(map[string]*packageContext)
pkgNames := make(map[*packageContext]string)
longPkgNames := make(map[*packageContext]bool)
processPackage := func(pctx *packageContext) {
if pctx == nil {
// This is a built-in rule and has no package.
return
}
if _, ok := pkgNames[pctx]; ok {
// We've already processed this package.
return
}
otherPkg, present := pkgs[pctx.shortName]
if present {
// Short name collision. Both this package and the one that's
// already there need to use their full names. We leave the short
// name in pkgNames for now so future collisions still get caught.
longPkgNames[pctx] = true
longPkgNames[otherPkg] = true
} else {
// No collision so far. Tentatively set the package's name to be
// its short name.
pkgNames[pctx] = pctx.shortName
pkgs[pctx.shortName] = pctx
}
}
// We try to give all packages their short name, but when we get collisions
// we need to use the full unique package name.
for v, _ := range liveGlobals.variables {
processPackage(v.packageContext())
}
for p, _ := range liveGlobals.pools {
processPackage(p.packageContext())
}
for r, _ := range liveGlobals.rules {
processPackage(r.packageContext())
}
// Add the packages that had collisions using their full unique names. This
// will overwrite any short names that were added in the previous step.
for pctx := range longPkgNames {
pkgNames[pctx] = pctx.fullName
}
// Create deps list from calls to PackageContext.AddNinjaFileDeps
deps := []string{}
for _, pkg := range pkgs {
deps = append(deps, pkg.ninjaFileDeps...)
}
return pkgNames, deps
}
// memoizeFullNames stores the full name of each live global variable, rule and pool since each is
// guaranteed to be used at least twice, once in the definition and once for each usage, and many
// are used much more than once.
func (c *Context) memoizeFullNames(liveGlobals *liveTracker, pkgNames map[*packageContext]string) *nameTracker {
nameTracker := &nameTracker{
pkgNames: pkgNames,
variables: make(map[Variable]string),
rules: make(map[Rule]string),
pools: make(map[Pool]string),
}
for v := range liveGlobals.variables {
nameTracker.variables[v] = v.fullName(pkgNames)
}
for r := range liveGlobals.rules {
nameTracker.rules[r] = r.fullName(pkgNames)
}
for p := range liveGlobals.pools {
nameTracker.pools[p] = p.fullName(pkgNames)
}
return nameTracker
}
func (c *Context) checkForVariableReferenceCycles(
variables map[Variable]*ninjaString, nameTracker *nameTracker) {
visited := make(map[Variable]bool) // variables that were already checked
checking := make(map[Variable]bool) // variables actively being checked
var check func(v Variable) []Variable
check = func(v Variable) []Variable {
visited[v] = true
checking[v] = true
defer delete(checking, v)
value := variables[v]
for _, dep := range value.Variables() {
if checking[dep] {
// This is a cycle.
return []Variable{dep, v}
}
if !visited[dep] {
cycle := check(dep)
if cycle != nil {
if cycle[0] == v {
// We are the "start" of the cycle, so we're responsible
// for generating the errors. The cycle list is in
// reverse order because all the 'check' calls append
// their own module to the list.
msgs := []string{"detected variable reference cycle:"}
// Iterate backwards through the cycle list.
curName := nameTracker.Variable(v)
curValue := value.Value(nameTracker)
for i := len(cycle) - 1; i >= 0; i-- {
next := cycle[i]
nextName := nameTracker.Variable(next)
nextValue := variables[next].Value(nameTracker)
msgs = append(msgs, fmt.Sprintf(
" %q depends on %q", curName, nextName))
msgs = append(msgs, fmt.Sprintf(
" [%s = %s]", curName, curValue))
curName = nextName
curValue = nextValue
}
// Variable reference cycles are a programming error,
// not the fault of the Blueprint file authors.
panic(strings.Join(msgs, "\n"))
} else {
// We're not the "start" of the cycle, so we just append
// our module to the list and return it.
return append(cycle, v)
}
}
}
}
return nil
}
for v := range variables {
if !visited[v] {
cycle := check(v)
if cycle != nil {
panic("inconceivable!")
}
}
}
}
// ModuleTypePropertyStructs returns a mapping from module type name to a list of pointers to
// property structs returned by the factory for that module type.
func (c *Context) ModuleTypePropertyStructs() map[string][]interface{} {
ret := make(map[string][]interface{}, len(c.moduleFactories))
for moduleType, factory := range c.moduleFactories {
_, ret[moduleType] = factory()
}
return ret
}
func (c *Context) ModuleTypeFactories() map[string]ModuleFactory {
return maps.Clone(c.moduleFactories)
}
func (c *Context) ModuleName(logicModule ModuleOrProxy) string {
return logicModule.info().Name()
}
func (c *Context) ModuleDir(logicModule ModuleOrProxy) string {
return filepath.Dir(c.BlueprintFile(logicModule))
}
func (c *Context) ModuleSubDir(logicModule ModuleOrProxy) string {
return logicModule.info().variant.name
}
func (c *Context) ModuleType(logicModule ModuleOrProxy) string {
return logicModule.info().typeName
}
// ModuleProvider returns the value, if any, for the provider for a module. If the value for the
// provider was not set it returns nil and false. The return value should always be considered read-only.
// It panics if called before the appropriate mutator or GenerateBuildActions pass for the provider on the
// module. The value returned may be a deep copy of the value originally passed to SetProvider.
func (c *Context) ModuleProvider(logicModule ModuleOrProxy, provider AnyProviderKey) (any, bool) {
return c.provider(logicModule.info(), provider.provider())
}
func (c *Context) BlueprintFile(logicModule ModuleOrProxy) string {
return logicModule.info().relBlueprintsFile
}
func (c *Context) moduleErrorf(module *moduleInfo, format string,
args ...interface{}) error {
if module == nil {
// This can happen if ModuleErrorf is called from a load hook
return &BlueprintError{
Err: fmt.Errorf(format, args...),
}
}
return &ModuleError{
BlueprintError: BlueprintError{
Err: fmt.Errorf(format, args...),
Pos: module.pos,
},
module: module,
}
}
func (c *Context) ModuleErrorf(logicModule ModuleOrProxy, format string,
args ...interface{}) error {
return c.moduleErrorf(logicModule.info(), format, args...)
}
func (c *Context) PropertyErrorf(logicModule ModuleOrProxy, property string, format string,
args ...interface{}) error {
module := logicModule.info()
if module == nil {
// This can happen if PropertyErrorf is called from a load hook
return &BlueprintError{
Err: fmt.Errorf(format, args...),
}
}
pos := module.propertyPos[property]
if !pos.IsValid() {
pos = module.pos
}
return &PropertyError{
ModuleError: ModuleError{
BlueprintError: BlueprintError{
Err: fmt.Errorf(format, args...),
Pos: pos,
},
module: module,
},
property: property,
}
}
func (c *Context) VisitAllModules(visit func(Module)) {
var visitingModule *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitAllModules(%s) for %s",
funcName(visit), visitingModule))
}
}()
c.visitAllModuleInfos(func(module *moduleInfo) {
visitingModule = module
if module.logicModule == nil {
panic(fmt.Errorf("VisitAllModules visited module %s that called FreeAfterGenerateBuildActions()", module))
}
visit(module.logicModule)
})
}
func (c *Context) VisitAllModulesIf(pred func(Module) bool, visit func(Module)) {
var visitingModule *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitAllModulesIf(%s, %s) for %s",
funcName(pred), funcName(visit), visitingModule))
}
}()
c.visitAllModuleInfos(func(module *moduleInfo) {
visitingModule = module
if module.logicModule == nil {
panic(fmt.Errorf("VisitAllModulesIf visited module %s that called FreeAfterGenerateBuildActions()", module))
}
if pred(module.logicModule) {
visit(module.logicModule)
}
})
}
func (c *Context) VisitAllModulesProxies(visit func(ModuleProxy)) {
var visitingModule *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitAllModules(%s) for %s",
funcName(visit), visitingModule))
}
}()
c.visitAllModuleInfos(func(module *moduleInfo) {
visitingModule = module
visit(ModuleProxy{module})
})
}
func (c *Context) VisitAllModulesOrProxies(visit func(ModuleOrProxy)) {
var visitingModule *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitAllModules(%s) for %s",
funcName(visit), visitingModule))
}
}()
c.visitAllModuleInfos(func(module *moduleInfo) {
visitingModule = module
if module.logicModule != nil {
visit(module.logicModule)
} else {
visit(ModuleProxy{module})
}
})
}
func (c *Context) VisitDirectDeps(module Module, visit func(Module)) {
c.VisitDirectDepsWithTags(module, func(m Module, _ DependencyTag) {
visit(m)
})
}
func (c *Context) VisitDirectDepsProxies(module ModuleOrProxy, visit func(ModuleProxy)) {
topModule := module.info()
var visiting *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitDirectDepsProxies(%s, %s) for dependency %s",
topModule, funcName(visit), visiting))
}
}()
for _, dep := range topModule.directDeps {
visiting = dep.module
visit(ModuleProxy{dep.module})
}
}
func (c *Context) VisitDirectDepsProxiesWithTags(module ModuleOrProxy, visit func(ModuleProxy, DependencyTag)) {
topModule := module.info()
var visiting *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitDirectDepsProxies(%s, %s) for dependency %s",
topModule, funcName(visit), visiting))
}
}()
for _, dep := range topModule.directDeps {
visiting = dep.module
visit(ModuleProxy{dep.module}, dep.tag)
}
}
func (c *Context) VisitDirectDepsWithTags(module Module, visit func(Module, DependencyTag)) {
topModule := module.info()
var visiting *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitDirectDeps(%s, %s) for dependency %s",
topModule, funcName(visit), visiting))
}
}()
for _, dep := range topModule.directDeps {
visiting = dep.module
if dep.module.logicModule == nil {
panic(fmt.Errorf("VisitDirectDepsWithTags visited module %s that called FreeAfterGenerateBuildActions()", dep.module))
}
visit(dep.module.logicModule, dep.tag)
}
}
func (c *Context) VisitDirectDepsIf(module Module, pred func(Module) bool, visit func(Module)) {
topModule := module.info()
var visiting *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitDirectDepsIf(%s, %s, %s) for dependency %s",
topModule, funcName(pred), funcName(visit), visiting))
}
}()
for _, dep := range topModule.directDeps {
visiting = dep.module
if dep.module.logicModule == nil {
panic(fmt.Errorf("VisitDirectDepsIf visited module %s that called FreeAfterGenerateBuildActions()", dep.module))
}
if pred(dep.module.logicModule) {
visit(dep.module.logicModule)
}
}
}
func (c *Context) VisitDepsDepthFirst(module Module, visit func(Module)) {
topModule := module.info()
var visiting *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitDepsDepthFirst(%s, %s) for dependency %s",
topModule, funcName(visit), visiting))
}
}()
c.walkDeps(topModule, false, nil, func(dep depInfo, parent *moduleInfo) {
visiting = dep.module
if dep.module.logicModule == nil {
panic(fmt.Errorf("VisitDepsDepthFirst visited module %s that called FreeAfterGenerateBuildActions()", dep.module))
}
visit(dep.module.logicModule)
})
}
func (c *Context) VisitDepsDepthFirstIf(module Module, pred func(Module) bool, visit func(Module)) {
topModule := module.info()
var visiting *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitDepsDepthFirstIf(%s, %s, %s) for dependency %s",
topModule, funcName(pred), funcName(visit), visiting))
}
}()
c.walkDeps(topModule, false, nil, func(dep depInfo, parent *moduleInfo) {
if dep.module.logicModule == nil {
panic(fmt.Errorf("VisitDepsDepthFirstIf visited module %s that called FreeAfterGenerateBuildActions()", dep.module))
}
if pred(dep.module.logicModule) {
visiting = dep.module
visit(dep.module.logicModule)
}
})
}
func (c *Context) PrimaryModule(module Module) Module {
return c.primaryModule(module.info()).logicModule
}
func (c *Context) primaryModule(moduleInfo *moduleInfo) *moduleInfo {
return moduleInfo.group.modules.firstModule()
}
func (c *Context) IsPrimaryModule(module ModuleOrProxy) bool {
return module.info().group.modules.firstModule() == module.info()
}
func (c *Context) IsFinalModule(module ModuleOrProxy) bool {
return module.info().group.modules.lastModule() == module.info()
}
func (c *Context) VisitAllModuleVariants(module ModuleOrProxy, visit func(Module)) {
var visitingModule *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitAllModuleVariants(%s) for %s",
funcName(visit), visitingModule))
}
}()
c.visitAllModuleVariants(module.info(), func(module *moduleInfo) {
visitingModule = module
if module.logicModule == nil {
panic(fmt.Errorf("VisitAllModuleVariants visited module %s that called FreeAfterGenerateBuildActions()", module))
}
visit(module.logicModule)
})
}
func (c *Context) VisitAllModuleVariantProxies(module ModuleProxy, visit func(ModuleProxy)) {
var visitingModule *moduleInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitAllModuleVariantProxies(%s) for %s",
funcName(visit), visitingModule))
}
}()
c.visitAllModuleVariants(module.info(), func(module *moduleInfo) {
visitingModule = module
visit(ModuleProxy{module})
})
}
func (c *Context) VisitAllSingletons(visit func(singleton SingletonProxy)) {
var singleton *singletonInfo
defer func() {
if r := recover(); r != nil {
panic(newPanicErrorf(r, "VisitAllSingletons(%s) for %s",
funcName(visit), singleton.name))
}
}()
for _, singleton = range c.singletonInfo {
// Only return the finished ones
if singleton.finishedGenerateBuildActions {
visit(SingletonProxy{singleton: singleton})
}
}
}
func (c *Context) ModuleToProxy(module ModuleOrProxy) ModuleProxy {
return ModuleProxy{module.info()}
}
func (c *Context) CaptureBuildParams() {
c.captureBuildParams = true
}
func (c *Context) BuildParamsForModule(module ModuleOrProxy) []BuildParams {
if p := module.info().buildParams; p != nil {
return *p
}
return nil
}
// Singletons returns a list of all registered Singletons.
func (c *Context) Singletons() []Singleton {
var ret []Singleton
for _, s := range c.singletonInfo {
ret = append(ret, s.singleton)
}
return ret
}
// SingletonName returns the name that the given singleton was registered with.
func (c *Context) SingletonName(singleton Singleton) string {
for _, s := range c.singletonInfo {
if s.singleton == singleton {
return s.name
}
}
return ""
}
func (c *Context) singletonByName(name string) *singletonInfo {
for _, s := range c.singletonInfo {
if s.name == name {
return s
}
}
return nil
}
// Checks that the hashes of all the providers match the hashes from when they were first set.
// Does nothing on success, returns a list of errors otherwise. It's recommended to run this
// in a goroutine.
func (c *Context) VerifyProvidersWereUnchanged() []error {
if !c.buildActionsReady {
return []error{ErrBuildActionsNotReady}
}
errors := parallelVisitSimple(c.iterateAllVariants(), 1000, func(m *moduleInfo, _ int) []error {
var errors []error
for i, provider := range m.providers {
if provider != nil {
hash, err := proptools.CalculateHashReflection(provider)
if err != nil {
errors = append(errors, fmt.Errorf("provider %q on module %q was modified after being set, and no longer hashable afterwards: %s", providerRegistry[i].typ, m.Name(), err.Error()))
continue
}
if m.providerInitialValueHashes[i] != hash {
errors = append(errors, fmt.Errorf("provider %q on module %q was modified after being set", providerRegistry[i].typ, m.Name()))
}
} else if m.providerInitialValueHashes[i] != proptools.ZeroHash && !m.hasUnrestoredProvider[i] {
// This should be unreachable, because in setProvider we check if the provider has already been set.
errors = append(errors, fmt.Errorf("provider %q on module %q was unset somehow, this is an internal error", providerRegistry[i].typ, m.Name()))
}
}
return errors
})
return errors
}
// WriteBuildFile writes the Ninja manifest text for the generated build
// actions to w. If this is called before PrepareBuildActions successfully
// completes then ErrBuildActionsNotReady is returned.
func (c *Context) WriteBuildFile(w StringWriterWriter, shardNinja bool, ninjaFileName string) error {
var err error
pprof.Do(c.Context, pprof.Labels("blueprint", "WriteBuildFile"), func(ctx context.Context) {
if !c.buildActionsReady {
err = ErrBuildActionsNotReady
return
}
nw := newNinjaWriter(w)
if err = c.writeBuildFileHeader(nw); err != nil {
return
}
if err = c.writeNinjaRequiredVersion(nw); err != nil {
return
}
if err = c.writeSubninjas(nw, c.subninjas); err != nil {
return
}
// TODO: Group the globals by package.
if err = c.writeGlobalVariables(nw); err != nil {
return
}
if err = c.writeGlobalPools(nw); err != nil {
return
}
if err = c.writeBuildDir(nw); err != nil {
return
}
if err = c.writeGlobalRules(nw); err != nil {
return
}
if err = c.writeAllModuleActions(nw, shardNinja, ninjaFileName); err != nil {
return
}
if err = c.writeAllSingletonActions(nw); err != nil {
return
}
})
return err
}
type pkgAssociation struct {
PkgName string
PkgPath string
}
type pkgAssociationSorter struct {
pkgs []pkgAssociation
}
func (s *pkgAssociationSorter) Len() int {
return len(s.pkgs)
}
func (s *pkgAssociationSorter) Less(i, j int) bool {
iName := s.pkgs[i].PkgName
jName := s.pkgs[j].PkgName
return iName < jName
}
func (s *pkgAssociationSorter) Swap(i, j int) {
s.pkgs[i], s.pkgs[j] = s.pkgs[j], s.pkgs[i]
}
func (c *Context) writeBuildFileHeader(nw *ninjaWriter) error {
var pkgs []pkgAssociation
maxNameLen := 0
for pkg, name := range c.nameTracker.pkgNames {
pkgs = append(pkgs, pkgAssociation{
PkgName: name,
PkgPath: pkg.pkgPath,
})
if len(name) > maxNameLen {
maxNameLen = len(name)
}
}
for i := range pkgs {
pkgs[i].PkgName += strings.Repeat(" ", maxNameLen-len(pkgs[i].PkgName))
}
sort.Sort(&pkgAssociationSorter{pkgs})
params := map[string]interface{}{
"Pkgs": pkgs,
}
buf := bytes.NewBuffer(nil)
err := fileHeaderTemplate.Execute(buf, params)
if err != nil {
return err
}
return nw.Comment(buf.String())
}
func (c *Context) writeNinjaRequiredVersion(nw *ninjaWriter) error {
value := fmt.Sprintf("%d.%d.%d", c.requiredNinjaMajor, c.requiredNinjaMinor,
c.requiredNinjaMicro)
err := nw.Assign("ninja_required_version", value)
if err != nil {
return err
}
return nw.BlankLine()
}
func (c *Context) writeSubninjas(nw *ninjaWriter, subninjas []string) error {
for _, subninja := range subninjas {
err := nw.Subninja(subninja)
if err != nil {
return err
}
}
return nw.BlankLine()
}
func (c *Context) writeBuildDir(nw *ninjaWriter) error {
if c.outDir != nil {
err := nw.Assign("builddir", c.outDir.Value(c.nameTracker))
if err != nil {
return err
}
err = nw.BlankLine()
if err != nil {
return err
}
}
return nil
}
func (c *Context) writeGlobalVariables(nw *ninjaWriter) error {
visited := make(map[Variable]bool)
var walk func(v Variable) error
walk = func(v Variable) error {
visited[v] = true
// First visit variables on which this variable depends.
value := c.globalVariables[v]
for _, dep := range value.Variables() {
if !visited[dep] {
err := walk(dep)
if err != nil {
return err
}
}
}
err := nw.Assign(c.nameTracker.Variable(v), value.Value(c.nameTracker))
if err != nil {
return err
}
err = nw.BlankLine()
if err != nil {
return err
}
return nil
}
globalVariables := make([]Variable, 0, len(c.globalVariables))
for variable := range c.globalVariables {
globalVariables = append(globalVariables, variable)
}
slices.SortFunc(globalVariables, func(a, b Variable) int {
return cmp.Compare(c.nameTracker.Variable(a), c.nameTracker.Variable(b))
})
for _, v := range globalVariables {
if !visited[v] {
err := walk(v)
if err != nil {
return nil
}
}
}
return nil
}
func (c *Context) writeGlobalPools(nw *ninjaWriter) error {
globalPools := make([]Pool, 0, len(c.globalPools))
for pool := range c.globalPools {
globalPools = append(globalPools, pool)
}
slices.SortFunc(globalPools, func(a, b Pool) int {
return cmp.Compare(c.nameTracker.Pool(a), c.nameTracker.Pool(b))
})
for _, pool := range globalPools {
name := c.nameTracker.Pool(pool)
def := c.globalPools[pool]
err := def.WriteTo(nw, name)
if err != nil {
return err
}
err = nw.BlankLine()
if err != nil {
return err
}
}
return nil
}
func (c *Context) writeGlobalRules(nw *ninjaWriter) error {
globalRules := make([]Rule, 0, len(c.globalRules))
for rule := range c.globalRules {
globalRules = append(globalRules, rule)
}
slices.SortFunc(globalRules, func(a, b Rule) int {
return cmp.Compare(c.nameTracker.Rule(a), c.nameTracker.Rule(b))
})
for _, rule := range globalRules {
name := c.nameTracker.Rule(rule)
def := c.globalRules[rule]
err := def.WriteTo(nw, name, c.nameTracker)
if err != nil {
return err
}
err = nw.BlankLine()
if err != nil {
return err
}
}
return nil
}
type depSorter struct {
deps []depInfo
nameInterface NameInterface
}
func (s depSorter) Len() int {
return len(s.deps)
}
func (s depSorter) Less(i, j int) bool {
return moduleLess(s.deps[i].module, s.deps[j].module, s.nameInterface)
}
func (s depSorter) Swap(i, j int) {
s.deps[i], s.deps[j] = s.deps[j], s.deps[i]
}
func moduleLess(iMod, jMod *moduleInfo, nameInterface NameInterface) bool {
iName := nameInterface.UniqueName(newNamespaceContext(iMod), iMod.group.name)
jName := nameInterface.UniqueName(newNamespaceContext(jMod), jMod.group.name)
if iName == jName {
iVariantName := iMod.variant.name
jVariantName := jMod.variant.name
if iVariantName == jVariantName {
panic(fmt.Sprintf("duplicate module name: %s %s: %#v and %#v\n",
iName, iVariantName, iMod.variant.variations, jMod.variant.variations))
} else {
return iVariantName < jVariantName
}
} else {
return iName < jName
}
}
func GetNinjaShardFiles(ninjaFile string) []string {
suffix := ".ninja"
if !strings.HasSuffix(ninjaFile, suffix) {
panic(fmt.Errorf("ninja file name in wrong format : %s", ninjaFile))
}
base := strings.TrimSuffix(ninjaFile, suffix)
ninjaShardCnt := 10
fileNames := make([]string, ninjaShardCnt)
for i := 0; i < ninjaShardCnt; i++ {
fileNames[i] = fmt.Sprintf("%s.%d%s", base, i, suffix)
}
return fileNames
}
func (c *Context) writeAllModuleActions(nw *ninjaWriter, shardNinja bool, ninjaFileName string) error {
c.BeginEvent("modules")
defer c.EndEvent("modules")
var modules []*moduleInfo
for module := range c.iterateAllVariants() {
modules = append(modules, module)
}
// cachedNameModuleLess is similar to moduleLess, but uses the namespaced name cached in
// moduleInfo.uniqueName instead of recomputing it each time.
cachedNameModuleLess := func(a, b *moduleInfo) int {
if a.cachedUniqueName == b.cachedUniqueName {
if a.variant.name == b.variant.name {
panic(fmt.Sprintf("duplicate module name: %s %s: %#v and %#v\n",
a.cachedUniqueName, a.variant.name, a.variant.variations, a.variant.variations))
} else {
return cmp.Compare(a.variant.name, b.variant.name)
}
} else {
return cmp.Compare(a.cachedUniqueName, b.cachedUniqueName)
}
}
slices.SortFunc(modules, cachedNameModuleLess)
phonys := c.deduplicateOrderOnlyDeps(modules)
c.EventHandler.Do("sort_phony_builddefs", func() {
// sorting for determinism, the phony output names are stable
sort.Slice(phonys.buildDefs, func(i int, j int) bool {
return phonys.buildDefs[i].OutputStrings[0] < phonys.buildDefs[j].OutputStrings[0]
})
})
if err := c.writeLocalBuildActions(nw, phonys); err != nil {
return err
}
if shardNinja {
var wg sync.WaitGroup
errorCh := make(chan error)
files := GetNinjaShardFiles(ninjaFileName)
shardedModules := proptools.ShardByCount(modules, len(files))
for i, batchModules := range shardedModules {
file := files[i]
wg.Add(1)
go func(file string, batchModules []*moduleInfo) {
defer wg.Done()
f, err := pathtools.OpenWithTruncateOnClose(c.fs, JoinPath(c.SrcDir(), file))
if err != nil {
errorCh <- fmt.Errorf("error opening Ninja file shard: %s", err)
return
}
defer func() {
err := f.Close()
if err != nil {
errorCh <- err
}
}()
buf := bufio.NewWriterSize(f, 16*1024*1024)
defer func() {
err := buf.Flush()
if err != nil {
errorCh <- err
}
}()
writer := newNinjaWriter(buf)
err = c.writeIncrementalModules(batchModules, writer)
if err != nil {
errorCh <- err
}
}(file, batchModules)
nw.Subninja(file)
}
if c.GetIncrementalEnabled() {
wg.Add(1)
go func() {
defer wg.Done()
parallelVisitSimple(slices.Values(modules), parallelVisitLimit,
func(m *moduleInfo, _ int) []error {
if !m.incrementalRestored {
m.cacheModuleBuildActions(c.EncContext, c.keyValueStoreCache)
}
return nil
})
}()
}
go func() {
wg.Wait()
close(errorCh)
}()
if c.incrementalDebugFile != "" {
c.WriteIncrementalDebugInfo(c.incrementalDebugFile, modules)
}
var errors []error
for newErrors := range errorCh {
errors = append(errors, newErrors)
}
if len(errors) > 0 {
return proptools.MergeErrors(errors)
}
return nil
} else {
return c.writeModuleAction(modules, nw)
}
}
// A simplified version of parallelVisit where multiple calls to it can be run at the same time.
func parallelVisitSimple(moduleIter iter.Seq[*moduleInfo], limit int, visit func(module *moduleInfo, idx int) []error) []error {
type moduleToProcess struct {
module *moduleInfo
index int
}
toProcess := make(chan moduleToProcess)
errorCh := make(chan []error)
var wg sync.WaitGroup
go func() {
idx := 0
for m := range moduleIter {
toProcess <- moduleToProcess{m, idx}
idx++
}
close(toProcess)
}()
for i := 0; i < limit; i++ {
wg.Add(1)
go func() {
var errors []error
for m := range toProcess {
errors = append(errors, visit(m.module, m.index)...)
}
if len(errors) > 0 {
errorCh <- errors
}
wg.Done()
}()
}
go func() {
wg.Wait()
close(errorCh)
}()
var errors []error
for newErrors := range errorCh {
errors = append(errors, newErrors...)
}
return errors
}
func (c *Context) writeIncrementalModules(modules []*moduleInfo, baseWriter *ninjaWriter) error {
// Use a sync.Pool to reuse buffers and reduce memory allocations.
bufferPool := sync.Pool{
New: func() any {
// Pre-allocate a reasonably sized buffer for each new writer.
return bytes.NewBuffer(make([]byte, 0, 128*1024))
},
}
headerBufPool := sync.Pool{New: func() any { return new(bytes.Buffer) }}
writeSignals := make([]chan struct{}, len(modules))
for i := range writeSignals {
writeSignals[i] = make(chan struct{})
}
errs := parallelVisitSimple(slices.Values(modules), parallelVisitLimit, func(m *moduleInfo, idx int) []error {
var moduleBytes []byte
var err error
if m.incrementalRestored && !c.incrementalProviderTest {
// Read from the cache if the module is restored.
moduleBytes, err = c.keyValueStoreCache.readNinjaStatements(m.buildActionCacheKey)
} else {
// Generate statements for dirty modules.
inMemoryWriter := bufferPool.Get().(*bytes.Buffer)
inMemoryWriter.Reset()
defer bufferPool.Put(inMemoryWriter)
headerBuf := headerBufPool.Get().(*bytes.Buffer)
headerBuf.Reset()
defer headerBufPool.Put(headerBuf)
mWriter := newNinjaWriter(inMemoryWriter)
err = c.writeOneModuleAction(m, mWriter, headerBuf)
if err == nil {
// Make a copy of the bytes, as the buffer will be reused.
moduleBytes = slices.Clone(inMemoryWriter.Bytes())
// Write the newly generated statements back to the cache for incremental module.
if m.buildActionCacheKey != nil {
err = c.keyValueStoreCache.writeNinjaStatements(m.buildActionCacheKey, moduleBytes)
}
}
}
// Wait for the signal from the previous thread before writing even when an
// error happens to ensure no concurrently writing of the ninja file.
if idx > 0 {
<-writeSignals[idx-1]
}
if err == nil {
baseWriter.writer.Write(moduleBytes)
}
// Signal the next thread that it's clear to write.
close(writeSignals[idx])
if err != nil {
return []error{err}
}
return nil
})
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func (c *Context) writeModuleAction(modules []*moduleInfo, nw *ninjaWriter) error {
buf := bytes.NewBuffer(nil)
for _, module := range modules {
if err := c.writeOneModuleAction(module, nw, buf); err != nil {
return err
}
}
return nil
}
func (c *Context) writeOneModuleAction(module *moduleInfo, nw *ninjaWriter, buf *bytes.Buffer) error {
if len(module.actionDefs.variables)+len(module.actionDefs.rules)+len(module.actionDefs.buildDefs) == 0 {
return nil
}
buf.Reset()
// In order to make the bootstrap build manifest independent of the
// build dir we need to output the Blueprints file locations in the
// comments as paths relative to the source directory.
relPos := module.pos
relPos.Filename = module.relBlueprintsFile
// Get the name and location of the factory function for the module.
factoryFunc := runtime.FuncForPC(reflect.ValueOf(module.factory).Pointer())
factoryName := factoryFunc.Name()
infoMap := map[string]interface{}{
"name": module.Name(),
"typeName": module.typeName,
"goFactory": factoryName,
"pos": relPos,
"variant": module.variant.name,
}
if err := moduleHeaderTemplate.Execute(buf, infoMap); err != nil {
return err
}
if err := nw.Comment(buf.String()); err != nil {
return err
}
if err := nw.BlankLine(); err != nil {
return err
}
if err := c.writeLocalBuildActions(nw, &module.actionDefs); err != nil {
return err
}
if err := nw.BlankLine(); err != nil {
return err
}
return nil
}
func (c *Context) writeAllSingletonActions(nw *ninjaWriter) error {
c.BeginEvent("singletons")
defer c.EndEvent("singletons")
buf := bytes.NewBuffer(nil)
var ninjaBytes []byte
var err error
inMemoryWriter := bytes.NewBuffer(nil)
for _, info := range c.singletonInfo {
if info.incrementalRestored && !c.incrementalProviderTest {
// Read from the cache if the singleton is restored.
ninjaBytes, err = c.keyValueStoreCache.readNinjaStatements(info.buildActionCacheKey) | | |