95bec3331c
Storing every string without ninja variable references through simpleNinjaString costs 24 bytes and a heap allocation. 16 bytes is used for the ninjaString.str string, 8 bytes for the ninjaString.variables *[]variableReference. An additional 8 bytes is used for the resulting pointer into the heap. The vast majority of calls to simpleNinjaString originate in blueprint.parseBuildParams, which converts all of the parameters passed to ctx.Build into ninjaStrings. All together this was allocating 1.575 GB of *ninjaString objects. Add a parseNinjaOrSimpleStrings function that converts input strings into ninjaStrings if they have ninja variable references, but also returns a slice of plain strings for input strings without any ninja variable references. That still results in 1.39 GB of allocations just for the output string slice, so also add an optimization that reuses the input string slice as the output slice if all of the strings had no variable references. Plumb the resulting strings through everywhere that the []*ninjaStrings were used. This reduces the total memory allocations inside blueprint.parseBuildParams in my AOSP aosp_cf_x86_64_phone-userdebug build from 3.337 GB to 1.786 GB. Test: ninja_strings_test.go Change-Id: I51bc138a2a6b1cc7383c7df0a483ccb067ffa02b
4978 lines
139 KiB
Go
4978 lines
139 KiB
Go
// 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 (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"text/scanner"
|
|
"text/template"
|
|
|
|
"github.com/google/blueprint/metrics"
|
|
"github.com/google/blueprint/parser"
|
|
"github.com/google/blueprint/pathtools"
|
|
"github.com/google/blueprint/proptools"
|
|
)
|
|
|
|
var ErrBuildActionsNotReady = errors.New("build actions are not ready")
|
|
|
|
const maxErrors = 10
|
|
const MockModuleListFile = "bplist"
|
|
|
|
// 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
|
|
moduleInfo map[Module]*moduleInfo
|
|
modulesSorted []*moduleInfo
|
|
preSingletonInfo []*singletonInfo
|
|
singletonInfo []*singletonInfo
|
|
mutatorInfo []*mutatorInfo
|
|
variantMutatorNames []string
|
|
|
|
depsModified 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
|
|
pkgNames map[*packageContext]string
|
|
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 map[globKey]pathtools.GlobResult
|
|
globLock sync.Mutex
|
|
|
|
srcDir 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
|
|
|
|
// The currently running mutator
|
|
startedMutator *mutatorInfo
|
|
// True for any mutators that have already run over all modules
|
|
finishedMutators map[*mutatorInfo]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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// 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 moduleAlias struct {
|
|
variant variant
|
|
target *moduleInfo
|
|
}
|
|
|
|
func (m *moduleAlias) alias() *moduleAlias { return m }
|
|
func (m *moduleAlias) module() *moduleInfo { return nil }
|
|
func (m *moduleAlias) moduleOrAliasTarget() *moduleInfo { return m.target }
|
|
func (m *moduleAlias) moduleOrAliasVariant() variant { return m.variant }
|
|
|
|
func (m *moduleInfo) alias() *moduleAlias { return nil }
|
|
func (m *moduleInfo) module() *moduleInfo { return m }
|
|
func (m *moduleInfo) moduleOrAliasTarget() *moduleInfo { return m }
|
|
func (m *moduleInfo) moduleOrAliasVariant() variant { return m.variant }
|
|
|
|
type moduleOrAlias interface {
|
|
alias() *moduleAlias
|
|
module() *moduleInfo
|
|
moduleOrAliasTarget() *moduleInfo
|
|
moduleOrAliasVariant() variant
|
|
}
|
|
|
|
type modulesOrAliases []moduleOrAlias
|
|
|
|
func (l modulesOrAliases) firstModule() *moduleInfo {
|
|
for _, moduleOrAlias := range l {
|
|
if m := moduleOrAlias.module(); m != nil {
|
|
return m
|
|
}
|
|
}
|
|
panic(fmt.Errorf("no first module!"))
|
|
}
|
|
|
|
func (l modulesOrAliases) lastModule() *moduleInfo {
|
|
for i := range l {
|
|
if m := l[len(l)-1-i].module(); m != nil {
|
|
return m
|
|
}
|
|
}
|
|
panic(fmt.Errorf("no last module!"))
|
|
}
|
|
|
|
type moduleGroup struct {
|
|
name string
|
|
ninjaName string
|
|
|
|
modules modulesOrAliases
|
|
|
|
namespace Namespace
|
|
}
|
|
|
|
func (group *moduleGroup) moduleOrAliasByVariantName(name string) moduleOrAlias {
|
|
for _, module := range group.modules {
|
|
if module.moduleOrAliasVariant().name == name {
|
|
return module
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (group *moduleGroup) moduleByVariantName(name string) *moduleInfo {
|
|
return group.moduleOrAliasByVariantName(name).module()
|
|
}
|
|
|
|
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 []depInfo
|
|
|
|
// set during updateDependencies
|
|
reverseDeps []*moduleInfo
|
|
forwardDeps []*moduleInfo
|
|
directDeps []depInfo
|
|
|
|
// used by parallelVisit
|
|
waitingCount int
|
|
|
|
// set during each runMutator
|
|
splitModules modulesOrAliases
|
|
|
|
// Used by TransitionMutator implementations
|
|
transitionVariations []string
|
|
currentTransitionMutator string
|
|
requiredVariationsLock sync.Mutex
|
|
|
|
// set during PrepareBuildActions
|
|
actionDefs localBuildActions
|
|
|
|
providers []interface{}
|
|
|
|
startedMutator *mutatorInfo
|
|
finishedMutator *mutatorInfo
|
|
|
|
startedGenerateBuildActions bool
|
|
finishedGenerateBuildActions bool
|
|
}
|
|
|
|
type variant struct {
|
|
name string
|
|
variations variationMap
|
|
dependencyVariations 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
|
|
}
|
|
|
|
// 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"}
|
|
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 map[string]string
|
|
|
|
func (vm variationMap) clone() variationMap {
|
|
if vm == nil {
|
|
return nil
|
|
}
|
|
newVm := make(variationMap)
|
|
for k, v := range vm {
|
|
newVm[k] = v
|
|
}
|
|
|
|
return newVm
|
|
}
|
|
|
|
// 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 {
|
|
if v2, ok := other[k]; !ok || v1 != v2 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (vm variationMap) equal(other variationMap) bool {
|
|
return reflect.DeepEqual(vm, other)
|
|
}
|
|
|
|
type singletonInfo struct {
|
|
// set during RegisterSingletonType
|
|
factory SingletonFactory
|
|
singleton Singleton
|
|
name string
|
|
parallel bool
|
|
|
|
// set during PrepareBuildActions
|
|
actionDefs localBuildActions
|
|
}
|
|
|
|
type mutatorInfo struct {
|
|
// set during RegisterMutator
|
|
topDownMutator TopDownMutator
|
|
bottomUpMutator BottomUpMutator
|
|
name string
|
|
parallel bool
|
|
}
|
|
|
|
func newContext() *Context {
|
|
eventHandler := metrics.EventHandler{}
|
|
return &Context{
|
|
Context: context.Background(),
|
|
EventHandler: &eventHandler,
|
|
moduleFactories: make(map[string]ModuleFactory),
|
|
nameInterface: NewSimpleNameInterface(),
|
|
moduleInfo: make(map[Module]*moduleInfo),
|
|
globs: make(map[globKey]pathtools.GlobResult),
|
|
fs: pathtools.OsFs,
|
|
finishedMutators: make(map[*mutatorInfo]bool),
|
|
includeTags: &IncludeTags{},
|
|
sourceRootDirs: &SourceRootDirs{},
|
|
outDir: nil,
|
|
requiredNinjaMajor: 1,
|
|
requiredNinjaMinor: 7,
|
|
requiredNinjaMicro: 0,
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
|
|
// RegisterPreSingletonType registers a presingleton type that will be invoked to
|
|
// generate build actions before any Blueprint files have been read. Each registered
|
|
// presingleton type is instantiated and invoked exactly once at the beginning of the
|
|
// parse phase. Each registered presingleton is invoked in registration order.
|
|
//
|
|
// The presingleton 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) RegisterPreSingletonType(name string, factory SingletonFactory) {
|
|
for _, s := range c.preSingletonInfo {
|
|
if s.name == name {
|
|
panic(fmt.Errorf("presingleton %q is already registered", name))
|
|
}
|
|
}
|
|
|
|
c.preSingletonInfo = append(c.preSingletonInfo, &singletonInfo{
|
|
factory: factory,
|
|
singleton: factory(),
|
|
name: name,
|
|
parallel: false,
|
|
})
|
|
}
|
|
|
|
func (c *Context) SetNameInterface(i NameInterface) {
|
|
c.nameInterface = i
|
|
}
|
|
|
|
func (c *Context) SetSrcDir(path string) {
|
|
c.srcDir = path
|
|
c.fs = pathtools.NewOsFs(path)
|
|
}
|
|
|
|
func (c *Context) SrcDir() string {
|
|
return c.srcDir
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
// RegisterTopDownMutator registers a mutator that will be invoked to propagate dependency info
|
|
// top-down between Modules. Each registered mutator is invoked in registration order (mixing
|
|
// TopDownMutators and BottomUpMutators) once per Module, and the invocation on any module will
|
|
// have returned before it is in invoked on any of its dependencies.
|
|
//
|
|
// The mutator type names given here must be unique to all top down mutators in
|
|
// the Context.
|
|
//
|
|
// Returns a MutatorHandle, on which Parallel can be called to set the mutator to visit modules in
|
|
// parallel while maintaining ordering.
|
|
func (c *Context) RegisterTopDownMutator(name string, mutator TopDownMutator) MutatorHandle {
|
|
for _, m := range c.mutatorInfo {
|
|
if m.name == name && m.topDownMutator != nil {
|
|
panic(fmt.Errorf("mutator %q is already registered", name))
|
|
}
|
|
}
|
|
|
|
info := &mutatorInfo{
|
|
topDownMutator: mutator,
|
|
name: name,
|
|
}
|
|
|
|
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 (mixing TopDownMutators and
|
|
// BottomUpMutators) once per Module, 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.
|
|
//
|
|
// Returns a MutatorHandle, on which Parallel can be called to set the mutator to visit modules in
|
|
// parallel while maintaining ordering.
|
|
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,
|
|
}
|
|
c.mutatorInfo = append(c.mutatorInfo, info)
|
|
|
|
c.variantMutatorNames = append(c.variantMutatorNames, name)
|
|
|
|
return info
|
|
}
|
|
|
|
type IncomingTransitionContext interface {
|
|
// Module returns the target of the dependency edge for which the transition
|
|
// is being computed
|
|
Module() Module
|
|
|
|
// Config returns the config object that was passed to
|
|
// Context.PrepareBuildActions.
|
|
Config() interface{}
|
|
}
|
|
|
|
type OutgoingTransitionContext interface {
|
|
// Module returns the target of the dependency edge for which the transition
|
|
// is being computed
|
|
Module() Module
|
|
|
|
// DepTag() Returns the dependency tag through which this dependency is
|
|
// reached
|
|
DepTag() DependencyTag
|
|
}
|
|
|
|
// Transition mutators implement a top-down mechanism where a module tells its
|
|
// direct dependencies what variation they should be built in but the dependency
|
|
// has the final say.
|
|
//
|
|
// When implementing a transition mutator, one needs to implement four methods:
|
|
// - Split() that tells what variations a module has by itself
|
|
// - OutgoingTransition() where a module tells what it wants from its
|
|
// dependency
|
|
// - IncomingTransition() where a module has the final say about its own
|
|
// variation
|
|
// - Mutate() that changes the state of a module depending on its variation
|
|
//
|
|
// That the effective variation of module B when depended on by module A is the
|
|
// composition the outgoing transition of module A and the incoming transition
|
|
// of module B.
|
|
//
|
|
// the outgoing transition should not take the properties of the dependency into
|
|
// account, only those of the module that depends on it. For this reason, the
|
|
// dependency is not even passed into it as an argument. Likewise, the incoming
|
|
// transition should not take the properties of the depending module into
|
|
// account and is thus not informed about it. This makes for a nice
|
|
// decomposition of the decision logic.
|
|
//
|
|
// A given transition mutator only affects its own variation; other variations
|
|
// stay unchanged along the dependency edges.
|
|
//
|
|
// Soong makes sure that all modules are created in the desired variations and
|
|
// that dependency edges are set up correctly. This ensures that "missing
|
|
// variation" errors do not happen and allows for more flexible changes in the
|
|
// value of the variation among dependency edges (as oppposed to bottom-up
|
|
// mutators where if module A in variation X depends on module B and module B
|
|
// has that variation X, A must depend on variation X of B)
|
|
//
|
|
// The limited power of the context objects passed to individual mutators
|
|
// methods also makes it more difficult to shoot oneself in the foot. Complete
|
|
// safety is not guaranteed because no one prevents individual transition
|
|
// mutators from mutating modules in illegal ways and for e.g. Split() or
|
|
// Mutate() to run their own visitations of the transitive dependency of the
|
|
// module and both of these are bad ideas, but it's better than no guardrails at
|
|
// all.
|
|
//
|
|
// This model is pretty close to Bazel's configuration transitions. The mapping
|
|
// between concepts in Soong and Bazel is as follows:
|
|
// - Module == configured target
|
|
// - Variant == configuration
|
|
// - Variation name == configuration flag
|
|
// - Variation == configuration flag value
|
|
// - Outgoing transition == attribute transition
|
|
// - Incoming transition == rule transition
|
|
//
|
|
// The Split() method does not have a Bazel equivalent and Bazel split
|
|
// transitions do not have a Soong equivalent.
|
|
//
|
|
// Mutate() does not make sense in Bazel due to the different models of the
|
|
// two systems: when creating new variations, Soong clones the old module and
|
|
// thus some way is needed to change it state whereas Bazel creates each
|
|
// configuration of a given configured target anew.
|
|
type TransitionMutator interface {
|
|
// Returns the set of variations that should be created for a module no matter
|
|
// who depends on it. Used when Make depends on a particular variation or when
|
|
// the module knows its variations just based on information given to it in
|
|
// the Blueprint file. This method should not mutate the module it is called
|
|
// on.
|
|
Split(ctx BaseModuleContext) []string
|
|
|
|
// Called on a module to determine which variation it wants from its direct
|
|
// dependencies. The dependency itself can override this decision. This method
|
|
// should not mutate the module itself.
|
|
OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
|
|
|
|
// Called on a module to determine which variation it should be in based on
|
|
// the variation modules that depend on it want. This gives the module a final
|
|
// say about its own variations. This method should not mutate the module
|
|
// itself.
|
|
IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
|
|
|
|
// Called after a module was split into multiple variations on each variation.
|
|
// It should not split the module any further but adding new dependencies is
|
|
// fine. Unlike all the other methods on TransitionMutator, this method is
|
|
// allowed to mutate the module.
|
|
Mutate(ctx BottomUpMutatorContext, variation string)
|
|
}
|
|
|
|
type transitionMutatorImpl struct {
|
|
name string
|
|
mutator TransitionMutator
|
|
}
|
|
|
|
// Adds each argument in items to l if it's not already there.
|
|
func addToStringListIfNotPresent(l []string, items ...string) []string {
|
|
OUTER:
|
|
for _, i := range items {
|
|
for _, existing := range l {
|
|
if existing == i {
|
|
continue OUTER
|
|
}
|
|
}
|
|
|
|
l = append(l, i)
|
|
}
|
|
|
|
return l
|
|
}
|
|
|
|
func (t *transitionMutatorImpl) addRequiredVariation(m *moduleInfo, variation string) {
|
|
m.requiredVariationsLock.Lock()
|
|
defer m.requiredVariationsLock.Unlock()
|
|
|
|
// This is only a consistency check. Leaking the variations of a transition
|
|
// mutator to another one could well lead to issues that are difficult to
|
|
// track down.
|
|
if m.currentTransitionMutator != "" && m.currentTransitionMutator != t.name {
|
|
panic(fmt.Errorf("transition mutator is %s in mutator %s", m.currentTransitionMutator, t.name))
|
|
}
|
|
|
|
m.currentTransitionMutator = t.name
|
|
m.transitionVariations = addToStringListIfNotPresent(m.transitionVariations, variation)
|
|
}
|
|
|
|
func (t *transitionMutatorImpl) topDownMutator(mctx TopDownMutatorContext) {
|
|
module := mctx.(*mutatorContext).module
|
|
mutatorSplits := t.mutator.Split(mctx)
|
|
if mutatorSplits == nil || len(mutatorSplits) == 0 {
|
|
panic(fmt.Errorf("transition mutator %s returned no splits for module %s", t.name, mctx.ModuleName()))
|
|
}
|
|
|
|
// transitionVariations for given a module can be mutated by the module itself
|
|
// and modules that directly depend on it. Since this is a top-down mutator,
|
|
// all modules that directly depend on this module have already been processed
|
|
// so no locking is necessary.
|
|
module.transitionVariations = addToStringListIfNotPresent(module.transitionVariations, mutatorSplits...)
|
|
sort.Strings(module.transitionVariations)
|
|
|
|
for _, srcVariation := range module.transitionVariations {
|
|
for _, dep := range module.directDeps {
|
|
finalVariation := t.transition(mctx)(mctx.Module(), srcVariation, dep.module.logicModule, dep.tag)
|
|
t.addRequiredVariation(dep.module, finalVariation)
|
|
}
|
|
}
|
|
}
|
|
|
|
type transitionContextImpl struct {
|
|
module Module
|
|
depTag DependencyTag
|
|
config interface{}
|
|
}
|
|
|
|
func (c *transitionContextImpl) Module() Module {
|
|
return c.module
|
|
}
|
|
|
|
func (c *transitionContextImpl) DepTag() DependencyTag {
|
|
return c.depTag
|
|
}
|
|
|
|
func (c *transitionContextImpl) Config() interface{} {
|
|
return c.config
|
|
}
|
|
|
|
func (t *transitionMutatorImpl) transition(mctx BaseMutatorContext) Transition {
|
|
return func(source Module, sourceVariation string, dep Module, depTag DependencyTag) string {
|
|
tc := &transitionContextImpl{module: dep, depTag: depTag, config: mctx.Config()}
|
|
outgoingVariation := t.mutator.OutgoingTransition(tc, sourceVariation)
|
|
finalVariation := t.mutator.IncomingTransition(tc, outgoingVariation)
|
|
return finalVariation
|
|
}
|
|
}
|
|
|
|
func (t *transitionMutatorImpl) bottomUpMutator(mctx BottomUpMutatorContext) {
|
|
mc := mctx.(*mutatorContext)
|
|
// Fetch and clean up transition mutator state. No locking needed since the
|
|
// only time interaction between multiple modules is required is during the
|
|
// computation of the variations required by a given module.
|
|
variations := mc.module.transitionVariations
|
|
mc.module.transitionVariations = nil
|
|
mc.module.currentTransitionMutator = ""
|
|
|
|
if len(variations) < 1 {
|
|
panic(fmt.Errorf("no variations found for module %s by mutator %s",
|
|
mctx.ModuleName(), t.name))
|
|
}
|
|
|
|
if len(variations) == 1 && variations[0] == "" {
|
|
// Module is not split, just apply the transition
|
|
mc.applyTransition(t.transition(mctx))
|
|
} else {
|
|
mc.createVariationsWithTransition(t.transition(mctx), variations...)
|
|
}
|
|
}
|
|
|
|
func (t *transitionMutatorImpl) mutateMutator(mctx BottomUpMutatorContext) {
|
|
module := mctx.(*mutatorContext).module
|
|
currentVariation := module.variant.variations[t.name]
|
|
t.mutator.Mutate(mctx, currentVariation)
|
|
}
|
|
|
|
func (c *Context) RegisterTransitionMutator(name string, mutator TransitionMutator) {
|
|
impl := &transitionMutatorImpl{name: name, mutator: mutator}
|
|
|
|
c.RegisterTopDownMutator(name+"_deps", impl.topDownMutator).Parallel()
|
|
c.RegisterBottomUpMutator(name, impl.bottomUpMutator).Parallel()
|
|
c.RegisterBottomUpMutator(name+"_mutate", impl.mutateMutator).Parallel()
|
|
}
|
|
|
|
type MutatorHandle interface {
|
|
// Set the mutator to visit modules in parallel while maintaining ordering. Calling any
|
|
// method on the mutator context is thread-safe, but the mutator must handle synchronization
|
|
// for any modifications to global state or any modules outside the one it was invoked on.
|
|
Parallel() MutatorHandle
|
|
}
|
|
|
|
func (mutator *mutatorInfo) Parallel() MutatorHandle {
|
|
mutator.parallel = true
|
|
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{}
|
|
var blueprintPackageIncludes *PackageIncludes
|
|
for _, def := range file.Defs {
|
|
switch def := def.(type) {
|
|
case *parser.Module:
|
|
skippedModules = append(skippedModules, def.Name())
|
|
if def.Type != "blueprint_package_includes" {
|
|
continue
|
|
}
|
|
module, errs := processModuleDef(def, file.Name, c.moduleFactories, nil, c.ignoreUnknownModuleTypes)
|
|
if len(errs) > 0 {
|
|
// This file contains errors in blueprint_package_includes
|
|
// Visit anyways so that we can report errors on other modules in the file
|
|
return shouldVisitFileInfo{
|
|
shouldVisitFile: true,
|
|
errs: errs,
|
|
}
|
|
}
|
|
logicModule, _ := c.cloneLogicModule(module)
|
|
blueprintPackageIncludes = logicModule.(*PackageIncludes)
|
|
}
|
|
}
|
|
|
|
if blueprintPackageIncludes != nil {
|
|
packageMatches := blueprintPackageIncludes.MatchesIncludeTags(c)
|
|
if !packageMatches {
|
|
return shouldVisitFileInfo{
|
|
shouldVisitFile: false,
|
|
skippedModules: skippedModules,
|
|
reasonForSkip: fmt.Sprintf(
|
|
"module is defined in %q which contains a blueprint_package_includes module with unsatisfied tags",
|
|
file.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,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
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.Remove("subdirs")
|
|
scope.Remove("optional_subdirs")
|
|
scope.Remove("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, local := scope.Get(v); assignment == nil || !local {
|
|
return nil, scanner.Position{}, nil
|
|
} else {
|
|
switch value := assignment.Value.Eval().(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()))
|
|
}
|
|
}
|
|
}
|
|
|
|
func getStringFromScope(scope *parser.Scope, v string) (string, scanner.Position, error) {
|
|
if assignment, _ := scope.Get(v); assignment == nil {
|
|
return "", scanner.Position{}, nil
|
|
} else {
|
|
switch value := assignment.Value.Eval().(type) {
|
|
case *parser.String:
|
|
return value.Value, assignment.EqualsPos, nil
|
|
case *parser.Bool, *parser.List:
|
|
return "", scanner.Position{}, &BlueprintError{
|
|
Err: fmt.Errorf("%q must be a string", 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,
|
|
local bool) variant {
|
|
|
|
newVariantName := module.variant.name
|
|
if variationName != "" {
|
|
if newVariantName == "" {
|
|
newVariantName = variationName
|
|
} else {
|
|
newVariantName += "_" + variationName
|
|
}
|
|
}
|
|
|
|
newVariations := module.variant.variations.clone()
|
|
if newVariations == nil {
|
|
newVariations = make(variationMap)
|
|
}
|
|
newVariations[mutatorName] = variationName
|
|
|
|
newDependencyVariations := module.variant.dependencyVariations.clone()
|
|
if !local {
|
|
if newDependencyVariations == nil {
|
|
newDependencyVariations = make(variationMap)
|
|
}
|
|
newDependencyVariations[mutatorName] = variationName
|
|
}
|
|
|
|
return variant{newVariantName, newVariations, newDependencyVariations}
|
|
}
|
|
|
|
func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
|
|
depChooser depChooser, variationNames []string, local bool) (modulesOrAliases, []error) {
|
|
|
|
if len(variationNames) == 0 {
|
|
panic(fmt.Errorf("mutator %q passed zero-length variation list for module %q",
|
|
mutatorName, origModule.Name()))
|
|
}
|
|
|
|
var newModules modulesOrAliases
|
|
|
|
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
|
|
newModule.directDeps = append([]depInfo(nil), origModule.directDeps...)
|
|
newModule.reverseDeps = nil
|
|
newModule.forwardDeps = nil
|
|
newModule.logicModule = newLogicModule
|
|
newModule.variant = newVariant(origModule, mutatorName, variationName, local)
|
|
newModule.properties = newProperties
|
|
newModule.providers = append([]interface{}(nil), origModule.providers...)
|
|
|
|
newModules = append(newModules, newModule)
|
|
|
|
newErrs := c.convertDepsToVariation(newModule, 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.logicModule = nil
|
|
origModule.splitModules = newModules
|
|
|
|
atomic.AddUint32(&c.depsModified, 1)
|
|
|
|
return newModules, errs
|
|
}
|
|
|
|
type depChooser func(source *moduleInfo, dep depInfo) (*moduleInfo, string)
|
|
|
|
// This function is called for every dependency edge to determine which
|
|
// variation of the dependency is needed. Its inputs are the depending module,
|
|
// its variation, the dependency and the dependency tag.
|
|
type Transition func(source Module, sourceVariation string, dep Module, depTag DependencyTag) string
|
|
|
|
func chooseDepByTransition(mutatorName string, transition Transition) depChooser {
|
|
return func(source *moduleInfo, dep depInfo) (*moduleInfo, string) {
|
|
sourceVariation := source.variant.variations[mutatorName]
|
|
depLogicModule := dep.module.logicModule
|
|
if depLogicModule == nil {
|
|
// This is really a lie because the original dependency before the split
|
|
// went away when it was split. We choose an arbitrary split module
|
|
// instead and hope that whatever information the transition wants from it
|
|
// is the same as in the original one
|
|
// TODO(lberki): this can be fixed by calling transition() once and saving
|
|
// its results somewhere
|
|
depLogicModule = dep.module.splitModules[0].moduleOrAliasTarget().logicModule
|
|
}
|
|
|
|
desiredVariation := transition(source.logicModule, sourceVariation, depLogicModule, dep.tag)
|
|
for _, m := range dep.module.splitModules {
|
|
if m.moduleOrAliasVariant().variations[mutatorName] == desiredVariation {
|
|
return m.moduleOrAliasTarget(), ""
|
|
}
|
|
}
|
|
|
|
return nil, desiredVariation
|
|
}
|
|
}
|
|
|
|
func chooseDep(candidates modulesOrAliases, mutatorName, variationName string, defaultVariationName *string) (*moduleInfo, string) {
|
|
for _, m := range candidates {
|
|
if m.moduleOrAliasVariant().variations[mutatorName] == variationName {
|
|
return m.moduleOrAliasTarget(), ""
|
|
}
|
|
}
|
|
|
|
if defaultVariationName != nil {
|
|
// give it a second chance; match with defaultVariationName
|
|
for _, m := range candidates {
|
|
if m.moduleOrAliasVariant().variations[mutatorName] == *defaultVariationName {
|
|
return m.moduleOrAliasTarget(), ""
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, variationName
|
|
}
|
|
|
|
func chooseDepExplicit(mutatorName string,
|
|
variationName string, defaultVariationName *string) depChooser {
|
|
return func(source *moduleInfo, dep depInfo) (*moduleInfo, string) {
|
|
return chooseDep(dep.module.splitModules, mutatorName, variationName, defaultVariationName)
|
|
}
|
|
}
|
|
|
|
func chooseDepInherit(mutatorName string, defaultVariationName *string) depChooser {
|
|
return func(source *moduleInfo, dep depInfo) (*moduleInfo, string) {
|
|
sourceVariation := source.variant.variations[mutatorName]
|
|
return chooseDep(dep.module.splitModules, mutatorName, sourceVariation, defaultVariationName)
|
|
}
|
|
}
|
|
|
|
func (c *Context) convertDepsToVariation(module *moduleInfo, depChooser depChooser) (errs []error) {
|
|
for i, dep := range module.directDeps {
|
|
if dep.module.logicModule == nil {
|
|
newDep, missingVariation := depChooser(module, 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 {
|
|
names := make([]string, 0, len(variations))
|
|
for _, m := range c.variantMutatorNames {
|
|
if v, ok := variations[m]; ok {
|
|
names = append(names, m+":"+v)
|
|
}
|
|
}
|
|
|
|
return strings.Join(names, ",")
|
|
}
|
|
|
|
func (c *Context) prettyPrintGroupVariants(group *moduleGroup) string {
|
|
var variants []string
|
|
for _, moduleOrAlias := range group.modules {
|
|
if mod := moduleOrAlias.module(); mod != nil {
|
|
variants = append(variants, c.prettyPrintVariant(mod.variant.variations))
|
|
} else if alias := moduleOrAlias.alias(); alias != nil {
|
|
variants = append(variants, c.prettyPrintVariant(alias.variant.variations)+
|
|
" (alias to "+c.prettyPrintVariant(alias.target.variant.variations)+")")
|
|
}
|
|
}
|
|
return strings.Join(variants, "\n ")
|
|
}
|
|
|
|
func newModule(factory ModuleFactory) *moduleInfo {
|
|
logicModule, properties := factory()
|
|
|
|
return &moduleInfo{
|
|
logicModule: logicModule,
|
|
factory: factory,
|
|
properties: properties,
|
|
}
|
|
}
|
|
|
|
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,
|
|
},
|
|
}
|
|
}
|
|
c.moduleInfo[module.logicModule] = module
|
|
|
|
group := &moduleGroup{
|
|
name: name,
|
|
modules: modulesOrAliases{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)
|
|
}
|
|
|
|
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()
|
|
|
|
c.liveGlobals = newLiveTracker(c, config)
|
|
|
|
deps, errs = c.generateSingletonBuildActions(config, c.preSingletonInfo, c.liveGlobals)
|
|
if len(errs) > 0 {
|
|
return
|
|
}
|
|
|
|
errs = c.updateDependencies()
|
|
if len(errs) > 0 {
|
|
return
|
|
}
|
|
|
|
var mutatorDeps []string
|
|
mutatorDeps, errs = c.runMutators(ctx, config)
|
|
if len(errs) > 0 {
|
|
return
|
|
}
|
|
deps = append(deps, mutatorDeps...)
|
|
|
|
c.BeginEvent("clone_modules")
|
|
if !c.SkipCloneModulesAfterMutators {
|
|
c.cloneModules()
|
|
}
|
|
defer c.EndEvent("clone_modules")
|
|
|
|
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...)
|
|
}()
|
|
}
|
|
}
|
|
|
|
// findExactVariantOrSingle searches the moduleGroup for a module with the same variant as module,
|
|
// and returns the matching module, or nil if one is not found. A group with exactly one module
|
|
// is always considered matching.
|
|
func findExactVariantOrSingle(module *moduleInfo, possible *moduleGroup, reverse bool) *moduleInfo {
|
|
found, _ := findVariant(module, possible, nil, false, reverse)
|
|
if found == nil {
|
|
for _, moduleOrAlias := range possible.modules {
|
|
if m := moduleOrAlias.module(); m != nil {
|
|
if found != nil {
|
|
// more than one possible match, give up
|
|
return nil
|
|
}
|
|
found = m
|
|
}
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
func (c *Context) addDependency(module *moduleInfo, tag DependencyTag, depName string) (*moduleInfo, []error) {
|
|
if _, ok := tag.(BaseDependencyTag); ok {
|
|
panic("BaseDependencyTag is not allowed to be used directly!")
|
|
}
|
|
|
|
if depName == module.Name() {
|
|
return nil, []error{&BlueprintError{
|
|
Err: fmt.Errorf("%q depends on itself", depName),
|
|
Pos: module.pos,
|
|
}}
|
|
}
|
|
|
|
possibleDeps := c.moduleGroupFromName(depName, module.namespace())
|
|
if possibleDeps == nil {
|
|
return nil, c.discoveredMissingDependencies(module, depName, nil)
|
|
}
|
|
|
|
if m := findExactVariantOrSingle(module, possibleDeps, false); m != nil {
|
|
module.newDirectDeps = append(module.newDirectDeps, depInfo{m, tag})
|
|
atomic.AddUint32(&c.depsModified, 1)
|
|
return m, nil
|
|
}
|
|
|
|
if c.allowMissingDependencies {
|
|
// Allow missing variants.
|
|
return nil, c.discoveredMissingDependencies(module, depName, module.variant.dependencyVariations)
|
|
}
|
|
|
|
return nil, []error{&BlueprintError{
|
|
Err: fmt.Errorf("dependency %q of %q missing variant:\n %s\navailable variants:\n %s",
|
|
depName, module.Name(),
|
|
c.prettyPrintVariant(module.variant.dependencyVariations),
|
|
c.prettyPrintGroupVariants(possibleDeps)),
|
|
Pos: module.pos,
|
|
}}
|
|
}
|
|
|
|
func (c *Context) findReverseDependency(module *moduleInfo, destName string) (*moduleInfo, []error) {
|
|
if destName == module.Name() {
|
|
return nil, []error{&BlueprintError{
|
|
Err: fmt.Errorf("%q depends on itself", destName),
|
|
Pos: module.pos,
|
|
}}
|
|
}
|
|
|
|
possibleDeps := c.moduleGroupFromName(destName, module.namespace())
|
|
if possibleDeps == nil {
|
|
return nil, []error{&BlueprintError{
|
|
Err: fmt.Errorf("%q has a reverse dependency on undefined module %q",
|
|
module.Name(), destName),
|
|
Pos: module.pos,
|
|
}}
|
|
}
|
|
|
|
if m := findExactVariantOrSingle(module, possibleDeps, true); m != nil {
|
|
return m, nil
|
|
}
|
|
|
|
if c.allowMissingDependencies {
|
|
// Allow missing variants.
|
|
return module, c.discoveredMissingDependencies(module, destName, module.variant.dependencyVariations)
|
|
}
|
|
|
|
return nil, []error{&BlueprintError{
|
|
Err: fmt.Errorf("reverse dependency %q of %q missing variant:\n %s\navailable variants:\n %s",
|
|
destName, module.Name(),
|
|
c.prettyPrintVariant(module.variant.dependencyVariations),
|
|
c.prettyPrintGroupVariants(possibleDeps)),
|
|
Pos: module.pos,
|
|
}}
|
|
}
|
|
|
|
func findVariant(module *moduleInfo, possibleDeps *moduleGroup, variations []Variation, far bool, reverse bool) (*moduleInfo, variationMap) {
|
|
// 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 {
|
|
if !reverse {
|
|
// For forward dependency, ignore local variants by matching against
|
|
// dependencyVariant which doesn't have the local variants
|
|
newVariant = module.variant.dependencyVariations.clone()
|
|
} else {
|
|
// For reverse dependency, use all the variants
|
|
newVariant = module.variant.variations.clone()
|
|
}
|
|
}
|
|
for _, v := range variations {
|
|
if newVariant == nil {
|
|
newVariant = make(variationMap)
|
|
}
|
|
newVariant[v.Mutator] = v.Variation
|
|
}
|
|
|
|
check := func(variant variationMap) bool {
|
|
if far {
|
|
return newVariant.subsetOf(variant)
|
|
} else {
|
|
return variant.equal(newVariant)
|
|
}
|
|
}
|
|
|
|
var foundDep *moduleInfo
|
|
for _, m := range possibleDeps.modules {
|
|
if check(m.moduleOrAliasVariant().variations) {
|
|
foundDep = m.moduleOrAliasTarget()
|
|
break
|
|
}
|
|
}
|
|
|
|
return foundDep, newVariant
|
|
}
|
|
|
|
func (c *Context) addVariationDependency(module *moduleInfo, 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, nil)
|
|
}
|
|
|
|
foundDep, newVariant := findVariant(module, possibleDeps, variations, far, false)
|
|
|
|
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,
|
|
}}
|
|
}
|
|
// 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,
|
|
}}
|
|
}
|
|
module.newDirectDeps = append(module.newDirectDeps, depInfo{foundDep, tag})
|
|
atomic.AddUint32(&c.depsModified, 1)
|
|
return foundDep, nil
|
|
}
|
|
|
|
func (c *Context) addInterVariantDependency(origModule *moduleInfo, tag DependencyTag,
|
|
from, to Module) *moduleInfo {
|
|
if _, ok := tag.(BaseDependencyTag); ok {
|
|
panic("BaseDependencyTag is not allowed to be used directly!")
|
|
}
|
|
|
|
var fromInfo, toInfo *moduleInfo
|
|
for _, moduleOrAlias := range origModule.splitModules {
|
|
if m := moduleOrAlias.module(); m != nil {
|
|
if m.logicModule == from {
|
|
fromInfo = m
|
|
}
|
|
if m.logicModule == to {
|
|
toInfo = m
|
|
if fromInfo != nil {
|
|
panic(fmt.Errorf("%q depends on later version of itself", origModule.Name()))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if fromInfo == nil || toInfo == nil {
|
|
panic(fmt.Errorf("AddInterVariantDependency called for module %q on invalid variant",
|
|
origModule.Name()))
|
|
}
|
|
|
|
fromInfo.newDirectDeps = append(fromInfo.newDirectDeps, depInfo{toInfo, tag})
|
|
atomic.AddUint32(&c.depsModified, 1)
|
|
return toInfo
|
|
}
|
|
|
|
// 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
|
|
// visit modules in order
|
|
visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool)
|
|
}
|
|
|
|
type unorderedVisitorImpl struct{}
|
|
|
|
func (unorderedVisitorImpl) waitCount(module *moduleInfo) int {
|
|
return 0
|
|
}
|
|
|
|
func (unorderedVisitorImpl) propagate(module *moduleInfo) []*moduleInfo {
|
|
return nil
|
|
}
|
|
|
|
func (unorderedVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
|
|
for _, module := range modules {
|
|
if visit(module, nil) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
type bottomUpVisitorImpl struct{}
|
|
|
|
func (bottomUpVisitorImpl) waitCount(module *moduleInfo) int {
|
|
return len(module.forwardDeps)
|
|
}
|
|
|
|
func (bottomUpVisitorImpl) propagate(module *moduleInfo) []*moduleInfo {
|
|
return module.reverseDeps
|
|
}
|
|
|
|
func (bottomUpVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
|
|
for _, module := range modules {
|
|
if visit(module, nil) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
type unpause chan struct{}
|
|
|
|
const parallelVisitLimit = 1000
|
|
|
|
// Calls visit on each module, guaranteeing that visit is not called on a module until visit on all
|
|
// of its dependencies has finished. A visit function can write a pauseSpec to the pause channel
|
|
// to wait for another dependency to be visited. 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.
|
|
func parallelVisit(modules []*moduleInfo, order visitOrderer, limit int,
|
|
visit func(module *moduleInfo, pause chan<- pauseSpec) bool) []error {
|
|
|
|
doneCh := make(chan *moduleInfo)
|
|
cancelCh := make(chan bool)
|
|
pauseCh := make(chan pauseSpec)
|
|
cancel := false
|
|
|
|
var backlog []*moduleInfo // Visitors that are ready to start but backlogged due to limit.
|
|
var unpauseBacklog []pauseSpec // Visitors that are ready to unpause but backlogged due to limit.
|
|
|
|
active := 0 // Number of visitors running, not counting paused visitors.
|
|
visited := 0 // Number of finished visitors.
|
|
|
|
pauseMap := make(map[*moduleInfo][]pauseSpec)
|
|
|
|
for _, module := range modules {
|
|
module.waitingCount = order.waitCount(module)
|
|
}
|
|
|
|
// Call the visitor on a module if there are fewer active visitors than the parallelism
|
|
// limit, otherwise add it to the backlog.
|
|
startOrBacklog := func(module *moduleInfo) {
|
|
if active < limit {
|
|
active++
|
|
go func() {
|
|
ret := visit(module, pauseCh)
|
|
if ret {
|
|
cancelCh <- true
|
|
}
|
|
doneCh <- module
|
|
}()
|
|
} else {
|
|
backlog = append(backlog, module)
|
|
}
|
|
}
|
|
|
|
// Unpause the already-started but paused visitor on a module if there are fewer active
|
|
// visitors than the parallelism limit, otherwise add it to the backlog.
|
|
unpauseOrBacklog := func(pauseSpec pauseSpec) {
|
|
if active < limit {
|
|
active++
|
|
close(pauseSpec.unpause)
|
|
} else {
|
|
unpauseBacklog = append(unpauseBacklog, pauseSpec)
|
|
}
|
|
}
|
|
|
|
// Start any modules in the backlog up to the parallelism limit. Unpause paused modules first
|
|
// since they may already be holding resources.
|
|
unpauseOrStartFromBacklog := func() {
|
|
for active < limit && len(unpauseBacklog) > 0 {
|
|
unpause := unpauseBacklog[0]
|
|
unpauseBacklog = unpauseBacklog[1:]
|
|
unpauseOrBacklog(unpause)
|
|
}
|
|
for active < limit && len(backlog) > 0 {
|
|
toVisit := backlog[0]
|
|
backlog = backlog[1:]
|
|
startOrBacklog(toVisit)
|
|
}
|
|
}
|
|
|
|
toVisit := len(modules)
|
|
|
|
// Start or backlog any modules that are not waiting for any other modules.
|
|
for _, module := range modules {
|
|
if module.waitingCount == 0 {
|
|
startOrBacklog(module)
|
|
}
|
|
}
|
|
|
|
for active > 0 {
|
|
select {
|
|
case <-cancelCh:
|
|
cancel = true
|
|
backlog = nil
|
|
case doneModule := <-doneCh:
|
|
active--
|
|
if !cancel {
|
|
// Mark this module as done.
|
|
doneModule.waitingCount = -1
|
|
visited++
|
|
|
|
// Unpause or backlog any modules that were waiting for this one.
|
|
if unpauses, ok := pauseMap[doneModule]; ok {
|
|
delete(pauseMap, doneModule)
|
|
for _, unpause := range unpauses {
|
|
unpauseOrBacklog(unpause)
|
|
}
|
|
}
|
|
|
|
// Start any backlogged modules up to limit.
|
|
unpauseOrStartFromBacklog()
|
|
|
|
// Decrement waitingCount on the next modules in the tree based
|
|
// on propagation order, and start or backlog them if they are
|
|
// ready to start.
|
|
for _, module := range order.propagate(doneModule) {
|
|
module.waitingCount--
|
|
if module.waitingCount == 0 {
|
|
startOrBacklog(module)
|
|
}
|
|
}
|
|
}
|
|
case pauseSpec := <-pauseCh:
|
|
if pauseSpec.until.waitingCount == -1 {
|
|
// Module being paused for is already finished, resume immediately.
|
|
close(pauseSpec.unpause)
|
|
} else {
|
|
// Register for unpausing.
|
|
pauseMap[pauseSpec.until] = append(pauseMap[pauseSpec.until], pauseSpec)
|
|
|
|
// Don't count paused visitors as active so that this can't deadlock
|
|
// if 1000 visitors are paused simultaneously.
|
|
active--
|
|
unpauseOrStartFromBacklog()
|
|
}
|
|
}
|
|
}
|
|
|
|
if !cancel {
|
|
// Invariant check: no backlogged modules, these weren't waiting on anything except
|
|
// the parallelism limit so they should have run.
|
|
if len(backlog) > 0 {
|
|
panic(fmt.Errorf("parallelVisit finished with %d backlogged visitors", len(backlog)))
|
|
}
|
|
|
|
// Invariant check: no backlogged paused modules, these weren't waiting on anything
|
|
// except the parallelism limit so they should have run.
|
|
if len(unpauseBacklog) > 0 {
|
|
panic(fmt.Errorf("parallelVisit finished with %d backlogged unpaused visitors", len(unpauseBacklog)))
|
|
}
|
|
|
|
if len(pauseMap) > 0 {
|
|
// Probably a deadlock due to a newly added dependency cycle. Start from each module in
|
|
// the order of the input modules list and perform a depth-first search for the module
|
|
// it is paused on, ignoring modules that are marked as done. Note this traverses from
|
|
// modules to the modules that would have been unblocked when that module finished, i.e
|
|
// the reverse of the visitOrderer.
|
|
|
|
// 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]struct{})
|
|
|
|
var check func(module, end *moduleInfo) []*moduleInfo
|
|
check = func(module, end *moduleInfo) []*moduleInfo {
|
|
if module.waitingCount == -1 {
|
|
// This module was finished, it can't be part of a loop.
|
|
return nil
|
|
}
|
|
if module == end {
|
|
// This module is the end of the loop, start rolling up the cycle.
|
|
return []*moduleInfo{module}
|
|
}
|
|
|
|
if _, alreadyChecked := checked[module]; alreadyChecked {
|
|
return nil
|
|
}
|
|
|
|
for _, dep := range order.propagate(module) {
|
|
cycle := check(dep, end)
|
|
if cycle != nil {
|
|
return append([]*moduleInfo{module}, cycle...)
|
|
}
|
|
}
|
|
for _, depPauseSpec := range pauseMap[module] {
|
|
cycle := check(depPauseSpec.paused, end)
|
|
if cycle != nil {
|
|
return append([]*moduleInfo{module}, cycle...)
|
|
}
|
|
}
|
|
|
|
checked[module] = struct{}{}
|
|
return nil
|
|
}
|
|
|
|
// Iterate over the modules list instead of pauseMap to provide deterministic ordering.
|
|
for _, module := range modules {
|
|
for _, pauseSpec := range pauseMap[module] {
|
|
cycle := check(pauseSpec.paused, pauseSpec.until)
|
|
if len(cycle) > 0 {
|
|
return cycleError(cycle)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invariant check: if there was no deadlock and no cancellation every module
|
|
// should have been visited.
|
|
if visited != toVisit {
|
|
panic(fmt.Errorf("parallelVisit ran %d visitors, expected %d", visited, toVisit))
|
|
}
|
|
|
|
// Invariant check: if there was no deadlock 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)))
|
|
}
|
|
}
|
|
|
|
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
|
|
visited := make(map[*moduleInfo]bool) // modules that were already checked
|
|
checking := make(map[*moduleInfo]bool) // modules actively being checked
|
|
|
|
sorted := make([]*moduleInfo, 0, len(c.moduleInfo))
|
|
|
|
var check func(group *moduleInfo) []*moduleInfo
|
|
|
|
check = func(module *moduleInfo) []*moduleInfo {
|
|
visited[module] = true
|
|
checking[module] = true
|
|
defer delete(checking, module)
|
|
|
|
// Reset the forward and reverse deps without reducing their capacity to avoid reallocation.
|
|
module.reverseDeps = module.reverseDeps[:0]
|
|
module.forwardDeps = module.forwardDeps[:0]
|
|
|
|
// Add an implicit dependency ordering on all earlier modules in the same module group
|
|
for _, dep := range module.group.modules {
|
|
if dep == module {
|
|
break
|
|
}
|
|
if depModule := dep.module(); depModule != nil {
|
|
module.forwardDeps = append(module.forwardDeps, depModule)
|
|
}
|
|
}
|
|
|
|
outer:
|
|
for _, dep := range module.directDeps {
|
|
// use a loop to check for duplicates, average number of directDeps measured to be 9.5.
|
|
for _, exists := range module.forwardDeps {
|
|
if dep.module == exists {
|
|
continue outer
|
|
}
|
|
}
|
|
module.forwardDeps = append(module.forwardDeps, dep.module)
|
|
}
|
|
|
|
for _, dep := range module.forwardDeps {
|
|
if checking[dep] {
|
|
// This is a cycle.
|
|
return []*moduleInfo{dep, module}
|
|
}
|
|
|
|
if !visited[dep] {
|
|
cycle := check(dep)
|
|
if cycle != nil {
|
|
if cycle[0] == module {
|
|
// We are the "start" of the cycle, so we're responsible
|
|
// for generating the errors.
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
dep.reverseDeps = append(dep.reverseDeps, module)
|
|
}
|
|
|
|
sorted = append(sorted, module)
|
|
|
|
return nil
|
|
}
|
|
|
|
for _, module := range c.moduleInfo {
|
|
if !visited[module] {
|
|
cycle := check(module)
|
|
if cycle != nil {
|
|
if cycle[len(cycle)-1] != module {
|
|
panic("inconceivable!")
|
|
}
|
|
errs = append(errs, cycleError(cycle)...)
|
|
}
|
|
}
|
|
}
|
|
|
|
c.modulesSorted = sorted
|
|
|
|
return
|
|
}
|
|
|
|
type jsonVariations []Variation
|
|
|
|
type jsonModuleName struct {
|
|
Name string
|
|
Variant string
|
|
Variations jsonVariations
|
|
DependencyVariations jsonVariations
|
|
}
|
|
|
|
type jsonDep struct {
|
|
jsonModuleName
|
|
Tag string
|
|
}
|
|
|
|
type JsonModule struct {
|
|
jsonModuleName
|
|
Deps []jsonDep
|
|
Type string
|
|
Blueprint string
|
|
CreatedBy *string
|
|
Module map[string]interface{}
|
|
}
|
|
|
|
func toJsonVariationMap(vm variationMap) jsonVariations {
|
|
m := make(jsonVariations, 0, len(vm))
|
|
for k, v := range vm {
|
|
m = append(m, Variation{k, v})
|
|
}
|
|
sort.Slice(m, func(i, j int) bool {
|
|
if m[i].Mutator != m[j].Mutator {
|
|
return m[i].Mutator < m[j].Mutator
|
|
}
|
|
return m[i].Variation < m[j].Variation
|
|
})
|
|
return m
|
|
}
|
|
|
|
func jsonModuleNameFromModuleInfo(m *moduleInfo) *jsonModuleName {
|
|
return &jsonModuleName{
|
|
Name: m.Name(),
|
|
Variant: m.variant.name,
|
|
Variations: toJsonVariationMap(m.variant.variations),
|
|
DependencyVariations: toJsonVariationMap(m.variant.dependencyVariations),
|
|
}
|
|
}
|
|
|
|
type JSONDataSupplier interface {
|
|
AddJSONData(d *map[string]interface{})
|
|
}
|
|
|
|
// JSONAction contains the action-related info we expose to json module graph
|
|
type JSONAction struct {
|
|
Inputs []string
|
|
Outputs []string
|
|
Desc string
|
|
}
|
|
|
|
// JSONActionSupplier allows JSON representation of additional actions that are not registered in
|
|
// Ninja
|
|
type JSONActionSupplier interface {
|
|
JSONActions() []JSONAction
|
|
}
|
|
|
|
func jsonModuleFromModuleInfo(m *moduleInfo) *JsonModule {
|
|
result := &JsonModule{
|
|
jsonModuleName: *jsonModuleNameFromModuleInfo(m),
|
|
Deps: make([]jsonDep, 0),
|
|
Type: m.typeName,
|
|
Blueprint: m.relBlueprintsFile,
|
|
Module: make(map[string]interface{}),
|
|
}
|
|
if m.createdBy != nil {
|
|
n := m.createdBy.Name()
|
|
result.CreatedBy = &n
|
|
}
|
|
if j, ok := m.logicModule.(JSONDataSupplier); ok {
|
|
j.AddJSONData(&result.Module)
|
|
}
|
|
for _, p := range m.providers {
|
|
if j, ok := p.(JSONDataSupplier); ok {
|
|
j.AddJSONData(&result.Module)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func jsonModuleWithActionsFromModuleInfo(m *moduleInfo) *JsonModule {
|
|
result := &JsonModule{
|
|
jsonModuleName: jsonModuleName{
|
|
Name: m.Name(),
|
|
Variant: m.variant.name,
|
|
},
|
|
Deps: make([]jsonDep, 0),
|
|
Type: m.typeName,
|
|
Blueprint: m.relBlueprintsFile,
|
|
Module: make(map[string]interface{}),
|
|
}
|
|
var actions []JSONAction
|
|
for _, bDef := range m.actionDefs.buildDefs {
|
|
a := JSONAction{
|
|
Inputs: append(append(append(
|
|
bDef.InputStrings,
|
|
bDef.ImplicitStrings...),
|
|
getNinjaStringsWithNilPkgNames(bDef.Inputs)...),
|
|
getNinjaStringsWithNilPkgNames(bDef.Implicits)...),
|
|
|
|
Outputs: append(append(append(
|
|
bDef.OutputStrings,
|
|
bDef.ImplicitOutputStrings...),
|
|
getNinjaStringsWithNilPkgNames(bDef.Outputs)...),
|
|
getNinjaStringsWithNilPkgNames(bDef.ImplicitOutputs)...),
|
|
}
|
|
if d, ok := bDef.Variables["description"]; ok {
|
|
a.Desc = d.Value(nil)
|
|
}
|
|
actions = append(actions, a)
|
|
}
|
|
|
|
if j, ok := m.logicModule.(JSONActionSupplier); ok {
|
|
actions = append(actions, j.JSONActions()...)
|
|
}
|
|
for _, p := range m.providers {
|
|
if j, ok := p.(JSONActionSupplier); ok {
|
|
actions = append(actions, j.JSONActions()...)
|
|
}
|
|
}
|
|
|
|
result.Module["Actions"] = actions
|
|
return result
|
|
}
|
|
|
|
// Gets a list of strings from the given list of ninjaStrings by invoking ninjaString.Value with
|
|
// nil pkgNames on each of the input ninjaStrings.
|
|
func getNinjaStringsWithNilPkgNames(nStrs []*ninjaString) []string {
|
|
var strs []string
|
|
for _, nstr := range nStrs {
|
|
strs = append(strs, nstr.Value(nil))
|
|
}
|
|
return strs
|
|
}
|
|
|
|
func (c *Context) GetWeightedOutputsFromPredicate(predicate func(*JsonModule) (bool, int)) map[string]int {
|
|
outputToWeight := make(map[string]int)
|
|
for _, m := range c.modulesSorted {
|
|
jmWithActions := jsonModuleWithActionsFromModuleInfo(m)
|
|
if ok, weight := predicate(jmWithActions); ok {
|
|
for _, a := range jmWithActions.Module["Actions"].([]JSONAction) {
|
|
for _, o := range a.Outputs {
|
|
if val, ok := outputToWeight[o]; ok {
|
|
if val > weight {
|
|
continue
|
|
}
|
|
}
|
|
outputToWeight[o] = weight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return outputToWeight
|
|
}
|
|
|
|
func inList(s string, l []string) bool {
|
|
for _, element := range l {
|
|
if s == element {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// PrintJSONGraph prints info of modules in a JSON file.
|
|
func (c *Context) PrintJSONGraphAndActions(wGraph io.Writer, wActions io.Writer) {
|
|
modulesToGraph := make([]*JsonModule, 0)
|
|
modulesToActions := make([]*JsonModule, 0)
|
|
for _, m := range c.modulesSorted {
|
|
jm := jsonModuleFromModuleInfo(m)
|
|
jmWithActions := jsonModuleWithActionsFromModuleInfo(m)
|
|
for _, d := range m.directDeps {
|
|
jm.Deps = append(jm.Deps, jsonDep{
|
|
jsonModuleName: *jsonModuleNameFromModuleInfo(d.module),
|
|
Tag: fmt.Sprintf("%T %+v", d.tag, d.tag),
|
|
})
|
|
jmWithActions.Deps = append(jmWithActions.Deps, jsonDep{
|
|
jsonModuleName: jsonModuleName{
|
|
Name: d.module.Name(),
|
|
},
|
|
})
|
|
|
|
}
|
|
modulesToGraph = append(modulesToGraph, jm)
|
|
modulesToActions = append(modulesToActions, jmWithActions)
|
|
}
|
|
writeJson(wGraph, modulesToGraph)
|
|
writeJson(wActions, modulesToActions)
|
|
}
|
|
|
|
func writeJson(w io.Writer, modules []*JsonModule) {
|
|
e := json.NewEncoder(w)
|
|
e.SetIndent("", "\t")
|
|
e.Encode(modules)
|
|
}
|
|
|
|
// 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
|
|
|
|
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
|
|
}
|
|
|
|
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...)
|
|
|
|
c.memoizeFullNames(c.liveGlobals, pkgNames)
|
|
|
|
// This will panic if it finds a problem since it's a programming error.
|
|
c.checkForVariableReferenceCycles(c.liveGlobals.variables, pkgNames)
|
|
|
|
c.pkgNames = pkgNames
|
|
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{}) (deps []string, errs []error) {
|
|
pprof.Do(ctx, pprof.Labels("blueprint", "runMutators"), func(ctx context.Context) {
|
|
for _, mutator := range c.mutatorInfo {
|
|
pprof.Do(ctx, pprof.Labels("mutator", mutator.name), func(context.Context) {
|
|
c.BeginEvent(mutator.name)
|
|
defer c.EndEvent(mutator.name)
|
|
var newDeps []string
|
|
if mutator.topDownMutator != nil {
|
|
newDeps, errs = c.runMutator(config, mutator, topDownMutator)
|
|
} else if mutator.bottomUpMutator != nil {
|
|
newDeps, errs = c.runMutator(config, mutator, bottomUpMutator)
|
|
} else {
|
|
panic("no mutator set on " + mutator.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(mutator *mutatorInfo, ctx *mutatorContext) {
|
|
mutator.bottomUpMutator(ctx)
|
|
}
|
|
|
|
func (bottomUpMutatorImpl) orderer() visitOrderer {
|
|
return bottomUpVisitor
|
|
}
|
|
|
|
func (bottomUpMutatorImpl) String() string {
|
|
return "bottom up mutator"
|
|
}
|
|
|
|
type topDownMutatorImpl struct{}
|
|
|
|
func (topDownMutatorImpl) run(mutator *mutatorInfo, ctx *mutatorContext) {
|
|
mutator.topDownMutator(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
|
|
}
|
|
|
|
func (c *Context) runMutator(config interface{}, mutator *mutatorInfo,
|
|
direction mutatorDirection) (deps []string, errs []error) {
|
|
|
|
newModuleInfo := make(map[Module]*moduleInfo)
|
|
for k, v := range c.moduleInfo {
|
|
newModuleInfo[k] = v
|
|
}
|
|
|
|
type globalStateChange struct {
|
|
reverse []reverseDep
|
|
rename []rename
|
|
replace []replace
|
|
newModules []*moduleInfo
|
|
deps []string
|
|
}
|
|
|
|
reverseDeps := make(map[*moduleInfo][]depInfo)
|
|
var rename []rename
|
|
var replace []replace
|
|
var newModules []*moduleInfo
|
|
|
|
errsCh := make(chan []error)
|
|
globalStateCh := make(chan globalStateChange)
|
|
newVariationsCh := make(chan modulesOrAliases)
|
|
done := make(chan bool)
|
|
|
|
c.depsModified = 0
|
|
|
|
visit := func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
if module.splitModules != nil {
|
|
panic("split module found in sorted module list")
|
|
}
|
|
|
|
mctx := &mutatorContext{
|
|
baseModuleContext: baseModuleContext{
|
|
context: c,
|
|
config: config,
|
|
module: module,
|
|
},
|
|
name: mutator.name,
|
|
pauseCh: pause,
|
|
}
|
|
|
|
module.startedMutator = mutator
|
|
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
in := fmt.Sprintf("%s %q for %s", direction, mutator.name, module)
|
|
if err, ok := r.(panicError); ok {
|
|
err.addIn(in)
|
|
mctx.error(err)
|
|
} else {
|
|
mctx.error(newPanicErrorf(r, in))
|
|
}
|
|
}
|
|
}()
|
|
direction.run(mutator, mctx)
|
|
}()
|
|
|
|
module.finishedMutator = mutator
|
|
|
|
if len(mctx.errs) > 0 {
|
|
errsCh <- mctx.errs
|
|
return true
|
|
}
|
|
|
|
if len(mctx.newVariations) > 0 {
|
|
newVariationsCh <- mctx.newVariations
|
|
}
|
|
|
|
if len(mctx.reverseDeps) > 0 || len(mctx.replace) > 0 || len(mctx.rename) > 0 || len(mctx.newModules) > 0 || len(mctx.ninjaFileDeps) > 0 {
|
|
globalStateCh <- globalStateChange{
|
|
reverse: mctx.reverseDeps,
|
|
replace: mctx.replace,
|
|
rename: mctx.rename,
|
|
newModules: mctx.newModules,
|
|
deps: mctx.ninjaFileDeps,
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
replace = append(replace, globalStateChange.replace...)
|
|
rename = append(rename, globalStateChange.rename...)
|
|
newModules = append(newModules, globalStateChange.newModules...)
|
|
deps = append(deps, globalStateChange.deps...)
|
|
case newVariations := <-newVariationsCh:
|
|
for _, moduleOrAlias := range newVariations {
|
|
if m := moduleOrAlias.module(); m != nil {
|
|
newModuleInfo[m.logicModule] = m
|
|
}
|
|
}
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
c.startedMutator = mutator
|
|
|
|
var visitErrs []error
|
|
if mutator.parallel {
|
|
visitErrs = parallelVisit(c.modulesSorted, direction.orderer(), parallelVisitLimit, visit)
|
|
} else {
|
|
direction.orderer().visit(c.modulesSorted, visit)
|
|
}
|
|
|
|
if len(visitErrs) > 0 {
|
|
return nil, visitErrs
|
|
}
|
|
|
|
c.finishedMutators[mutator] = true
|
|
|
|
done <- true
|
|
|
|
if len(errs) > 0 {
|
|
return nil, errs
|
|
}
|
|
|
|
c.moduleInfo = newModuleInfo
|
|
|
|
for _, group := range c.moduleGroups {
|
|
for i := 0; i < len(group.modules); i++ {
|
|
module := group.modules[i].module()
|
|
if module == nil {
|
|
// Existing alias, skip it
|
|
continue
|
|
}
|
|
|
|
// 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.logicModule == nil {
|
|
module.directDeps[j].module = dep.module.splitModules.firstModule()
|
|
}
|
|
}
|
|
|
|
if module.createdBy != nil && module.createdBy.logicModule == nil {
|
|
module.createdBy = module.createdBy.splitModules.firstModule()
|
|
}
|
|
|
|
// Add in any new direct dependencies that were added by the mutator
|
|
module.directDeps = append(module.directDeps, module.newDirectDeps...)
|
|
module.newDirectDeps = nil
|
|
}
|
|
|
|
findAliasTarget := func(variant variant) *moduleInfo {
|
|
for _, moduleOrAlias := range group.modules {
|
|
if alias := moduleOrAlias.alias(); alias != nil {
|
|
if alias.variant.variations.equal(variant.variations) {
|
|
return alias.target
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Forward or delete any dangling aliases.
|
|
// Use a manual loop instead of range because len(group.modules) can
|
|
// change inside the loop
|
|
for i := 0; i < len(group.modules); i++ {
|
|
if alias := group.modules[i].alias(); alias != nil {
|
|
if alias.target.logicModule == nil {
|
|
newTarget := findAliasTarget(alias.target.variant)
|
|
if newTarget != nil {
|
|
alias.target = newTarget
|
|
} else {
|
|
// The alias was left dangling, remove it.
|
|
group.modules = append(group.modules[:i], group.modules[i+1:]...)
|
|
i--
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add in any new reverse dependencies that were added by the mutator
|
|
for module, deps := range reverseDeps {
|
|
sort.Sort(depSorter(deps))
|
|
module.directDeps = append(module.directDeps, deps...)
|
|
c.depsModified++
|
|
}
|
|
|
|
for _, module := range newModules {
|
|
errs = c.addModule(module)
|
|
if len(errs) > 0 {
|
|
return nil, errs
|
|
}
|
|
atomic.AddUint32(&c.depsModified, 1)
|
|
}
|
|
|
|
errs = c.handleRenames(rename)
|
|
if len(errs) > 0 {
|
|
return nil, errs
|
|
}
|
|
|
|
errs = c.handleReplacements(replace)
|
|
if len(errs) > 0 {
|
|
return nil, errs
|
|
}
|
|
|
|
if c.depsModified > 0 {
|
|
errs = c.updateDependencies()
|
|
if len(errs) > 0 {
|
|
return nil, errs
|
|
}
|
|
}
|
|
|
|
return deps, errs
|
|
}
|
|
|
|
// 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.
|
|
func (c *Context) cloneModules() {
|
|
type update struct {
|
|
orig Module
|
|
clone *moduleInfo
|
|
}
|
|
ch := make(chan update)
|
|
doneCh := make(chan bool)
|
|
go func() {
|
|
errs := parallelVisit(c.modulesSorted, unorderedVisitorImpl{}, parallelVisitLimit,
|
|
func(m *moduleInfo, pause chan<- pauseSpec) bool {
|
|
origLogicModule := m.logicModule
|
|
m.logicModule, m.properties = c.cloneLogicModule(m)
|
|
ch <- update{origLogicModule, m}
|
|
return false
|
|
})
|
|
if len(errs) > 0 {
|
|
panic(errs)
|
|
}
|
|
doneCh <- true
|
|
}()
|
|
|
|
done := false
|
|
for !done {
|
|
select {
|
|
case <-doneCh:
|
|
done = true
|
|
case update := <-ch:
|
|
delete(c.moduleInfo, update.orig)
|
|
c.moduleInfo[update.clone.logicModule] = update.clone
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 modulesOrAliases, i int, newModules modulesOrAliases) (modulesOrAliases, int) {
|
|
spliceSize := len(newModules)
|
|
newLen := len(modules) + spliceSize - 1
|
|
var dest modulesOrAliases
|
|
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(modulesOrAliases, 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.modulesSorted, bottomUpVisitor, parallelVisitLimit,
|
|
func(module *moduleInfo, pause chan<- pauseSpec) bool {
|
|
uniqueName := c.nameInterface.UniqueName(newNamespaceContext(module), module.group.name)
|
|
sanitizedName := toNinjaName(uniqueName)
|
|
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,
|
|
}
|
|
|
|
mctx.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))
|
|
}
|
|
}
|
|
}()
|
|
mctx.module.logicModule.GenerateBuildActions(mctx)
|
|
}()
|
|
|
|
mctx.module.finishedGenerateBuildActions = true
|
|
|
|
if len(mctx.errs) > 0 {
|
|
errsCh <- mctx.errs
|
|
return true
|
|
}
|
|
|
|
if module.missingDeps != nil && !mctx.handledMissingDeps {
|
|
var errs []error
|
|
for _, depName := range module.missingDeps {
|
|
errs = append(errs, c.missingDependencyError(module, depName))
|
|
}
|
|
errsCh <- errs
|
|
return true
|
|
}
|
|
|
|
depsCh <- mctx.ninjaFileDeps
|
|
|
|
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) 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{
|
|
name: info.name,
|
|
context: c,
|
|
config: config,
|
|
scope: scope,
|
|
globals: liveGlobals,
|
|
}
|
|
|
|
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))
|
|
}
|
|
}
|
|
}()
|
|
info.singleton.GenerateBuildActions(sctx)
|
|
}()
|
|
|
|
if len(sctx.errs) > 0 {
|
|
errs = append(errs, sctx.errs...)
|
|
return deps, errs
|
|
}
|
|
|
|
deps = append(deps, sctx.ninjaFileDeps...)
|
|
|
|
newErrs := c.processLocalBuildActions(&info.actionDefs,
|
|
&sctx.actionDefs, liveGlobals)
|
|
errs = append(errs, newErrs...)
|
|
return deps, errs
|
|
}
|
|
|
|
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) 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...)
|
|
}
|
|
|
|
// 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 {
|
|
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
|
|
}
|
|
|
|
func (c *Context) moduleMatchingVariant(module *moduleInfo, name string) *moduleInfo {
|
|
group := c.moduleGroupFromName(name, module.namespace())
|
|
|
|
if group == nil {
|
|
return nil
|
|
}
|
|
|
|
for _, m := range group.modules {
|
|
if module.variant.name == m.moduleOrAliasVariant().name {
|
|
return m.moduleOrAliasTarget()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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 {
|
|
atomic.AddUint32(&c.depsModified, 1)
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func (c *Context) discoveredMissingDependencies(module *moduleInfo, depName string, depVariations variationMap) (errs []error) {
|
|
if depVariations != nil {
|
|
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 {
|
|
if c.cachedSortedModuleGroups == nil || c.cachedDepsModified {
|
|
unwrap := func(wrappers []ModuleGroup) []*moduleGroup {
|
|
result := make([]*moduleGroup, 0, len(wrappers))
|
|
for _, group := range wrappers {
|
|
result = append(result, group.moduleGroup)
|
|
}
|
|
return result
|
|
}
|
|
|
|
c.cachedSortedModuleGroups = unwrap(c.nameInterface.AllModules())
|
|
c.cachedDepsModified = false
|
|
}
|
|
|
|
return c.cachedSortedModuleGroups
|
|
}
|
|
|
|
func (c *Context) visitAllModules(visit func(Module)) {
|
|
var module *moduleInfo
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
panic(newPanicErrorf(r, "VisitAllModules(%s) for %s",
|
|
funcName(visit), module))
|
|
}
|
|
}()
|
|
|
|
for _, moduleGroup := range c.sortedModuleGroups() {
|
|
for _, moduleOrAlias := range moduleGroup.modules {
|
|
if module = moduleOrAlias.module(); module != nil {
|
|
visit(module.logicModule)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Context) visitAllModulesIf(pred func(Module) bool,
|
|
visit func(Module)) {
|
|
|
|
var module *moduleInfo
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
panic(newPanicErrorf(r, "VisitAllModulesIf(%s, %s) for %s",
|
|
funcName(pred), funcName(visit), module))
|
|
}
|
|
}()
|
|
|
|
for _, moduleGroup := range c.sortedModuleGroups() {
|
|
for _, moduleOrAlias := range moduleGroup.modules {
|
|
if module = moduleOrAlias.module(); module != nil {
|
|
if pred(module.logicModule) {
|
|
visit(module.logicModule)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Context) visitAllModuleVariants(module *moduleInfo,
|
|
visit func(Module)) {
|
|
|
|
var variant *moduleInfo
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
panic(newPanicErrorf(r, "VisitAllModuleVariants(%s, %s) for %s",
|
|
module, funcName(visit), variant))
|
|
}
|
|
}()
|
|
|
|
for _, moduleOrAlias := range module.group.modules {
|
|
if variant = moduleOrAlias.module(); variant != nil {
|
|
visit(variant.logicModule)
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
for v := range liveGlobals.variables {
|
|
v.memoizeFullName(pkgNames)
|
|
}
|
|
for r := range liveGlobals.rules {
|
|
r.memoizeFullName(pkgNames)
|
|
}
|
|
for p := range liveGlobals.pools {
|
|
p.memoizeFullName(pkgNames)
|
|
}
|
|
}
|
|
|
|
func (c *Context) checkForVariableReferenceCycles(
|
|
variables map[Variable]*ninjaString, pkgNames map[*packageContext]string) {
|
|
|
|
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 := v.fullName(pkgNames)
|
|
curValue := value.Value(pkgNames)
|
|
for i := len(cycle) - 1; i >= 0; i-- {
|
|
next := cycle[i]
|
|
nextName := next.fullName(pkgNames)
|
|
nextValue := variables[next].Value(pkgNames)
|
|
|
|
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!")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// AllTargets returns a map all the build target names to the rule used to build
|
|
// them. This is the same information that is output by running 'ninja -t
|
|
// targets all'. If this is called before PrepareBuildActions successfully
|
|
// completes then ErrbuildActionsNotReady is returned.
|
|
func (c *Context) AllTargets() (map[string]string, error) {
|
|
if !c.buildActionsReady {
|
|
return nil, ErrBuildActionsNotReady
|
|
}
|
|
|
|
targets := map[string]string{}
|
|
var collectTargets = func(actionDefs localBuildActions) error {
|
|
for _, buildDef := range actionDefs.buildDefs {
|
|
ruleName := buildDef.Rule.fullName(c.pkgNames)
|
|
for _, output := range append(buildDef.Outputs, buildDef.ImplicitOutputs...) {
|
|
outputValue, err := output.Eval(c.globalVariables)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
targets[outputValue] = ruleName
|
|
}
|
|
for _, output := range append(buildDef.OutputStrings, buildDef.ImplicitOutputStrings...) {
|
|
targets[output] = ruleName
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
// Collect all the module build targets.
|
|
for _, module := range c.moduleInfo {
|
|
if err := collectTargets(module.actionDefs); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Collect all the singleton build targets.
|
|
for _, info := range c.singletonInfo {
|
|
if err := collectTargets(info.actionDefs); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return targets, nil
|
|
}
|
|
|
|
func (c *Context) OutDir() (string, error) {
|
|
if c.outDir != nil {
|
|
return c.outDir.Eval(c.globalVariables)
|
|
} else {
|
|
return "", nil
|
|
}
|
|
}
|
|
|
|
// 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{})
|
|
for moduleType, factory := range c.moduleFactories {
|
|
_, ret[moduleType] = factory()
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (c *Context) ModuleTypeFactories() map[string]ModuleFactory {
|
|
ret := make(map[string]ModuleFactory)
|
|
for k, v := range c.moduleFactories {
|
|
ret[k] = v
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (c *Context) ModuleName(logicModule Module) string {
|
|
module := c.moduleInfo[logicModule]
|
|
return module.Name()
|
|
}
|
|
|
|
func (c *Context) ModuleDir(logicModule Module) string {
|
|
return filepath.Dir(c.BlueprintFile(logicModule))
|
|
}
|
|
|
|
func (c *Context) ModuleSubDir(logicModule Module) string {
|
|
module := c.moduleInfo[logicModule]
|
|
return module.variant.name
|
|
}
|
|
|
|
func (c *Context) ModuleType(logicModule Module) string {
|
|
module := c.moduleInfo[logicModule]
|
|
return module.typeName
|
|
}
|
|
|
|
// ModuleProvider returns the value, if any, for the provider for a module. If the value for the
|
|
// provider was not set it returns the zero value of the type of the provider, which means the
|
|
// return value can always be type-asserted to the type of the provider. 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 Module, provider ProviderKey) interface{} {
|
|
module := c.moduleInfo[logicModule]
|
|
value, _ := c.provider(module, provider)
|
|
return value
|
|
}
|
|
|
|
// ModuleHasProvider returns true if the provider for the given module has been set.
|
|
func (c *Context) ModuleHasProvider(logicModule Module, provider ProviderKey) bool {
|
|
module := c.moduleInfo[logicModule]
|
|
_, ok := c.provider(module, provider)
|
|
return ok
|
|
}
|
|
|
|
func (c *Context) BlueprintFile(logicModule Module) string {
|
|
module := c.moduleInfo[logicModule]
|
|
return module.relBlueprintsFile
|
|
}
|
|
|
|
func (c *Context) ModuleErrorf(logicModule Module, format string,
|
|
args ...interface{}) error {
|
|
|
|
module := c.moduleInfo[logicModule]
|
|
return &BlueprintError{
|
|
Err: fmt.Errorf(format, args...),
|
|
Pos: module.pos,
|
|
}
|
|
}
|
|
|
|
func (c *Context) VisitAllModules(visit func(Module)) {
|
|
c.visitAllModules(visit)
|
|
}
|
|
|
|
func (c *Context) VisitAllModulesIf(pred func(Module) bool,
|
|
visit func(Module)) {
|
|
|
|
c.visitAllModulesIf(pred, visit)
|
|
}
|
|
|
|
func (c *Context) VisitDirectDeps(module Module, visit func(Module)) {
|
|
topModule := c.moduleInfo[module]
|
|
|
|
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
|
|
visit(dep.module.logicModule)
|
|
}
|
|
}
|
|
|
|
func (c *Context) VisitDirectDepsIf(module Module, pred func(Module) bool, visit func(Module)) {
|
|
topModule := c.moduleInfo[module]
|
|
|
|
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 pred(dep.module.logicModule) {
|
|
visit(dep.module.logicModule)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Context) VisitDepsDepthFirst(module Module, visit func(Module)) {
|
|
topModule := c.moduleInfo[module]
|
|
|
|
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
|
|
visit(dep.module.logicModule)
|
|
})
|
|
}
|
|
|
|
func (c *Context) VisitDepsDepthFirstIf(module Module, pred func(Module) bool, visit func(Module)) {
|
|
topModule := c.moduleInfo[module]
|
|
|
|
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 pred(dep.module.logicModule) {
|
|
visiting = dep.module
|
|
visit(dep.module.logicModule)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (c *Context) PrimaryModule(module Module) Module {
|
|
return c.moduleInfo[module].group.modules.firstModule().logicModule
|
|
}
|
|
|
|
func (c *Context) FinalModule(module Module) Module {
|
|
return c.moduleInfo[module].group.modules.lastModule().logicModule
|
|
}
|
|
|
|
func (c *Context) VisitAllModuleVariants(module Module,
|
|
visit func(Module)) {
|
|
|
|
c.visitAllModuleVariants(c.moduleInfo[module], visit)
|
|
}
|
|
|
|
// 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 ""
|
|
}
|
|
|
|
// 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) 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); 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); 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 {
|
|
headerTemplate := template.New("fileHeader")
|
|
_, err := headerTemplate.Parse(fileHeaderTemplate)
|
|
if err != nil {
|
|
// This is a programming error.
|
|
panic(err)
|
|
}
|
|
|
|
var pkgs []pkgAssociation
|
|
maxNameLen := 0
|
|
for pkg, name := range c.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 = headerTemplate.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) error {
|
|
for _, subninja := range c.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.pkgNames))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = nw.BlankLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type globalEntity interface {
|
|
fullName(pkgNames map[*packageContext]string) string
|
|
}
|
|
|
|
type globalEntitySorter struct {
|
|
pkgNames map[*packageContext]string
|
|
entities []globalEntity
|
|
}
|
|
|
|
func (s *globalEntitySorter) Len() int {
|
|
return len(s.entities)
|
|
}
|
|
|
|
func (s *globalEntitySorter) Less(i, j int) bool {
|
|
iName := s.entities[i].fullName(s.pkgNames)
|
|
jName := s.entities[j].fullName(s.pkgNames)
|
|
return iName < jName
|
|
}
|
|
|
|
func (s *globalEntitySorter) Swap(i, j int) {
|
|
s.entities[i], s.entities[j] = s.entities[j], s.entities[i]
|
|
}
|
|
|
|
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(v.fullName(c.pkgNames), value.Value(c.pkgNames))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = nw.BlankLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
globalVariables := make([]globalEntity, 0, len(c.globalVariables))
|
|
for variable := range c.globalVariables {
|
|
globalVariables = append(globalVariables, variable)
|
|
}
|
|
|
|
sort.Sort(&globalEntitySorter{c.pkgNames, globalVariables})
|
|
|
|
for _, entity := range globalVariables {
|
|
v := entity.(Variable)
|
|
if !visited[v] {
|
|
err := walk(v)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Context) writeGlobalPools(nw *ninjaWriter) error {
|
|
globalPools := make([]globalEntity, 0, len(c.globalPools))
|
|
for pool := range c.globalPools {
|
|
globalPools = append(globalPools, pool)
|
|
}
|
|
|
|
sort.Sort(&globalEntitySorter{c.pkgNames, globalPools})
|
|
|
|
for _, entity := range globalPools {
|
|
pool := entity.(Pool)
|
|
name := pool.fullName(c.pkgNames)
|
|
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([]globalEntity, 0, len(c.globalRules))
|
|
for rule := range c.globalRules {
|
|
globalRules = append(globalRules, rule)
|
|
}
|
|
|
|
sort.Sort(&globalEntitySorter{c.pkgNames, globalRules})
|
|
|
|
for _, entity := range globalRules {
|
|
rule := entity.(Rule)
|
|
name := rule.fullName(c.pkgNames)
|
|
def := c.globalRules[rule]
|
|
err := def.WriteTo(nw, name, c.pkgNames)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = nw.BlankLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type depSorter []depInfo
|
|
|
|
func (s depSorter) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s depSorter) Less(i, j int) bool {
|
|
iName := s[i].module.Name()
|
|
jName := s[j].module.Name()
|
|
if iName == jName {
|
|
iName = s[i].module.variant.name
|
|
jName = s[j].module.variant.name
|
|
}
|
|
return iName < jName
|
|
}
|
|
|
|
func (s depSorter) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
type moduleSorter struct {
|
|
modules []*moduleInfo
|
|
nameInterface NameInterface
|
|
}
|
|
|
|
func (s moduleSorter) Len() int {
|
|
return len(s.modules)
|
|
}
|
|
|
|
func (s moduleSorter) Less(i, j int) bool {
|
|
iMod := s.modules[i]
|
|
jMod := s.modules[j]
|
|
iName := s.nameInterface.UniqueName(newNamespaceContext(iMod), iMod.group.name)
|
|
jName := s.nameInterface.UniqueName(newNamespaceContext(jMod), jMod.group.name)
|
|
if iName == jName {
|
|
iVariantName := s.modules[i].variant.name
|
|
jVariantName := s.modules[j].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 (s moduleSorter) Swap(i, j int) {
|
|
s.modules[i], s.modules[j] = s.modules[j], s.modules[i]
|
|
}
|
|
|
|
func (c *Context) writeAllModuleActions(nw *ninjaWriter) error {
|
|
c.BeginEvent("modules")
|
|
defer c.EndEvent("modules")
|
|
headerTemplate := template.New("moduleHeader")
|
|
if _, err := headerTemplate.Parse(moduleHeaderTemplate); err != nil {
|
|
// This is a programming error.
|
|
panic(err)
|
|
}
|
|
|
|
modules := make([]*moduleInfo, 0, len(c.moduleInfo))
|
|
for _, module := range c.moduleInfo {
|
|
modules = append(modules, module)
|
|
}
|
|
sort.Sort(moduleSorter{modules, c.nameInterface})
|
|
|
|
phonys := c.deduplicateOrderOnlyDeps(modules)
|
|
if err := c.writeLocalBuildActions(nw, phonys); err != nil {
|
|
return err
|
|
}
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
for _, module := range modules {
|
|
if len(module.actionDefs.variables)+len(module.actionDefs.rules)+len(module.actionDefs.buildDefs) == 0 {
|
|
continue
|
|
}
|
|
|
|
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 := headerTemplate.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")
|
|
headerTemplate := template.New("singletonHeader")
|
|
_, err := headerTemplate.Parse(singletonHeaderTemplate)
|
|
if err != nil {
|
|
// This is a programming error.
|
|
panic(err)
|
|
}
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
for _, info := range c.singletonInfo {
|
|
if len(info.actionDefs.variables)+len(info.actionDefs.rules)+len(info.actionDefs.buildDefs) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Get the name of the factory function for the module.
|
|
factory := info.factory
|
|
factoryFunc := runtime.FuncForPC(reflect.ValueOf(factory).Pointer())
|
|
factoryName := factoryFunc.Name()
|
|
|
|
buf.Reset()
|
|
infoMap := map[string]interface{}{
|
|
"name": info.name,
|
|
"goFactory": factoryName,
|
|
}
|
|
err = headerTemplate.Execute(buf, infoMap)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = nw.Comment(buf.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = nw.BlankLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.writeLocalBuildActions(nw, &info.actionDefs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = nw.BlankLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Context) GetEventHandler() *metrics.EventHandler {
|
|
return c.EventHandler
|
|
}
|
|
|
|
func (c *Context) BeginEvent(name string) {
|
|
c.EventHandler.Begin(name)
|
|
}
|
|
|
|
func (c *Context) EndEvent(name string) {
|
|
c.EventHandler.End(name)
|
|
}
|
|
|
|
func (c *Context) SetBeforePrepareBuildActionsHook(hookFn func() error) {
|
|
c.BeforePrepareBuildActionsHook = hookFn
|
|
}
|
|
|
|
// phonyCandidate represents the state of a set of deps that decides its eligibility
|
|
// to be extracted as a phony output
|
|
type phonyCandidate struct {
|
|
sync.Once
|
|
phony *buildDef // the phony buildDef that wraps the set
|
|
first *buildDef // the first buildDef that uses this set
|
|
}
|
|
|
|
// keyForPhonyCandidate gives a unique identifier for a set of deps.
|
|
// If any of the deps use a variable, we return an empty string to signal
|
|
// that this set of deps is ineligible for extraction.
|
|
func keyForPhonyCandidate(deps []*ninjaString, stringDeps []string) string {
|
|
hasher := sha256.New()
|
|
for _, d := range deps {
|
|
if len(d.Variables()) != 0 {
|
|
return ""
|
|
}
|
|
io.WriteString(hasher, d.Value(nil))
|
|
}
|
|
for _, d := range stringDeps {
|
|
io.WriteString(hasher, d)
|
|
}
|
|
return base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))
|
|
}
|
|
|
|
// scanBuildDef is called for every known buildDef `b` that has a non-empty `b.OrderOnly`.
|
|
// If `b.OrderOnly` is not present in `candidates`, it gets stored.
|
|
// But if `b.OrderOnly` already exists in `candidates`, then `b.OrderOnly`
|
|
// (and phonyCandidate#first.OrderOnly) will be replaced with phonyCandidate#phony.Outputs
|
|
func scanBuildDef(wg *sync.WaitGroup, candidates *sync.Map, phonyCount *atomic.Uint32, b *buildDef) {
|
|
defer wg.Done()
|
|
key := keyForPhonyCandidate(b.OrderOnly, b.OrderOnlyStrings)
|
|
if key == "" {
|
|
return
|
|
}
|
|
if v, loaded := candidates.LoadOrStore(key, &phonyCandidate{
|
|
first: b,
|
|
}); loaded {
|
|
m := v.(*phonyCandidate)
|
|
m.Do(func() {
|
|
// this is the second occurrence and hence it makes sense to
|
|
// extract it as a phony output
|
|
phonyCount.Add(1)
|
|
m.phony = &buildDef{
|
|
Rule: Phony,
|
|
OutputStrings: []string{"dedup-" + key},
|
|
Inputs: m.first.OrderOnly, //we could also use b.OrderOnly
|
|
InputStrings: m.first.OrderOnlyStrings,
|
|
Optional: true,
|
|
}
|
|
// the previously recorded build-def, which first had these deps as its
|
|
// order-only deps, should now use this phony output instead
|
|
m.first.OrderOnlyStrings = m.phony.OutputStrings
|
|
m.first.OrderOnly = nil
|
|
m.first = nil
|
|
})
|
|
b.OrderOnlyStrings = m.phony.OutputStrings
|
|
b.OrderOnly = nil
|
|
}
|
|
}
|
|
|
|
// deduplicateOrderOnlyDeps searches for common sets of order-only dependencies across all
|
|
// buildDef instances in the provided moduleInfo instances. Each such
|
|
// common set forms a new buildDef representing a phony output that then becomes
|
|
// the sole order-only dependency of those buildDef instances
|
|
func (c *Context) deduplicateOrderOnlyDeps(infos []*moduleInfo) *localBuildActions {
|
|
c.BeginEvent("deduplicate_order_only_deps")
|
|
defer c.EndEvent("deduplicate_order_only_deps")
|
|
|
|
candidates := sync.Map{} //used as map[key]*candidate
|
|
phonyCount := atomic.Uint32{}
|
|
wg := sync.WaitGroup{}
|
|
for _, info := range infos {
|
|
for _, b := range info.actionDefs.buildDefs {
|
|
if len(b.OrderOnly) > 0 || len(b.OrderOnlyStrings) > 0 {
|
|
wg.Add(1)
|
|
go scanBuildDef(&wg, &candidates, &phonyCount, b)
|
|
}
|
|
}
|
|
}
|
|
wg.Wait()
|
|
|
|
// now collect all created phonys to return
|
|
phonys := make([]*buildDef, 0, phonyCount.Load())
|
|
candidates.Range(func(_ any, v any) bool {
|
|
candidate := v.(*phonyCandidate)
|
|
if candidate.phony != nil {
|
|
phonys = append(phonys, candidate.phony)
|
|
}
|
|
return true
|
|
})
|
|
|
|
c.EventHandler.Do("sort_phony_builddefs", func() {
|
|
// sorting for determinism, the phony output names are stable
|
|
sort.Slice(phonys, func(i int, j int) bool {
|
|
return phonys[i].OutputStrings[0] < phonys[j].OutputStrings[0]
|
|
})
|
|
})
|
|
|
|
return &localBuildActions{buildDefs: phonys}
|
|
}
|
|
|
|
func (c *Context) writeLocalBuildActions(nw *ninjaWriter,
|
|
defs *localBuildActions) error {
|
|
|
|
// Write the local variable assignments.
|
|
for _, v := range defs.variables {
|
|
// A localVariable doesn't need the package names or config to
|
|
// determine its name or value.
|
|
name := v.fullName(nil)
|
|
value, err := v.value(nil, nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = nw.Assign(name, value.Value(c.pkgNames))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(defs.variables) > 0 {
|
|
err := nw.BlankLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write the local rules.
|
|
for _, r := range defs.rules {
|
|
// A localRule doesn't need the package names or config to determine
|
|
// its name or definition.
|
|
name := r.fullName(nil)
|
|
def, err := r.def(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
err = def.WriteTo(nw, name, c.pkgNames)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = nw.BlankLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write the build definitions.
|
|
for _, buildDef := range defs.buildDefs {
|
|
err := buildDef.WriteTo(nw, c.pkgNames)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(buildDef.Args) > 0 {
|
|
err = nw.BlankLine()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func beforeInModuleList(a, b *moduleInfo, list modulesOrAliases) bool {
|
|
found := false
|
|
if a == b {
|
|
return false
|
|
}
|
|
for _, l := range list {
|
|
if l.module() == a {
|
|
found = true
|
|
} else if l.module() == b {
|
|
return found
|
|
}
|
|
}
|
|
|
|
missing := a
|
|
if found {
|
|
missing = b
|
|
}
|
|
panic(fmt.Errorf("element %v not found in list %v", missing, list))
|
|
}
|
|
|
|
type panicError struct {
|
|
panic interface{}
|
|
stack []byte
|
|
in string
|
|
}
|
|
|
|
func newPanicErrorf(panic interface{}, in string, a ...interface{}) error {
|
|
buf := make([]byte, 4096)
|
|
count := runtime.Stack(buf, false)
|
|
return panicError{
|
|
panic: panic,
|
|
in: fmt.Sprintf(in, a...),
|
|
stack: buf[:count],
|
|
}
|
|
}
|
|
|
|
func (p panicError) Error() string {
|
|
return fmt.Sprintf("panic in %s\n%s\n%s\n", p.in, p.panic, p.stack)
|
|
}
|
|
|
|
func (p *panicError) addIn(in string) {
|
|
p.in += " in " + in
|
|
}
|
|
|
|
func funcName(f interface{}) string {
|
|
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
|
}
|
|
|
|
var fileHeaderTemplate = `******************************************************************************
|
|
*** This file is generated and should not be edited ***
|
|
******************************************************************************
|
|
{{if .Pkgs}}
|
|
This file contains variables, rules, and pools with name prefixes indicating
|
|
they were generated by the following Go packages:
|
|
{{range .Pkgs}}
|
|
{{.PkgName}} [from Go package {{.PkgPath}}]{{end}}{{end}}
|
|
|
|
`
|
|
|
|
var moduleHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
Module: {{.name}}
|
|
Variant: {{.variant}}
|
|
Type: {{.typeName}}
|
|
Factory: {{.goFactory}}
|
|
Defined: {{.pos}}
|
|
`
|
|
|
|
var singletonHeaderTemplate = `# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
|
Singleton: {{.name}}
|
|
Factory: {{.goFactory}}
|
|
`
|
|
|
|
// Blueprint module type that can be used to gate blueprint files beneath this directory
|
|
type PackageIncludes struct {
|
|
properties struct {
|
|
// Package will be included if all include tags in this list are set
|
|
Match_all []string
|
|
}
|
|
name *string `blueprint:"mutated"`
|
|
}
|
|
|
|
func (pi *PackageIncludes) Name() string {
|
|
return proptools.String(pi.name)
|
|
}
|
|
|
|
// This module type does not have any build actions
|
|
func (pi *PackageIncludes) GenerateBuildActions(ctx ModuleContext) {
|
|
}
|
|
|
|
func newPackageIncludesFactory() (Module, []interface{}) {
|
|
module := &PackageIncludes{}
|
|
AddLoadHook(module, func(ctx LoadHookContext) {
|
|
module.name = proptools.StringPtr(ctx.ModuleDir() + "_includes") // Generate a synthetic name
|
|
})
|
|
return module, []interface{}{&module.properties}
|
|
}
|
|
|
|
func RegisterPackageIncludesModuleType(ctx *Context) {
|
|
ctx.RegisterModuleType("blueprint_package_includes", newPackageIncludesFactory)
|
|
}
|
|
|
|
func (pi *PackageIncludes) MatchAll() []string {
|
|
return pi.properties.Match_all
|
|
}
|
|
|
|
// Returns true if all requested include tags are set in the Context object
|
|
func (pi *PackageIncludes) MatchesIncludeTags(ctx *Context) bool {
|
|
if len(pi.MatchAll()) == 0 {
|
|
ctx.ModuleErrorf(pi, "Match_all must be a non-empty list")
|
|
}
|
|
for _, includeTag := range pi.MatchAll() {
|
|
if !ctx.ContainsIncludeTag(includeTag) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|