Move LoadHooks from Soong to Blueprint

Move LoadHooks from Blueprint and run them during ParseBlueprintsFiles.
This will allow them to add scoped module types that are visible to the
rest of the Blueprints file.  Requires passing the config object to
ParseBlueprintsFiles.

Test: all blueprint tests
Change-Id: Ia2a2c9a0223d5458bfd48bd22ebed0fdbd0156c6
This commit is contained in:
Colin Cross 2019-12-30 18:40:09 -08:00
parent 9999eddc46
commit da70fd0b84
6 changed files with 141 additions and 23 deletions

View file

@ -140,7 +140,7 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
ctx.RegisterSingletonType("glob", globSingletonFactory(ctx)) ctx.RegisterSingletonType("glob", globSingletonFactory(ctx))
deps, errs := ctx.ParseFileList(filepath.Dir(bootstrapConfig.topLevelBlueprintsFile), filesToParse) deps, errs := ctx.ParseFileList(filepath.Dir(bootstrapConfig.topLevelBlueprintsFile), filesToParse, config)
if len(errs) > 0 { if len(errs) > 0 {
fatalErrors(errs) fatalErrors(errs)
} }

View file

@ -631,17 +631,19 @@ type fileParseContext struct {
// which the future output will depend is returned. This list will include both // 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 // Blueprints file paths as well as directory paths for cases where wildcard
// subdirs are found. // subdirs are found.
func (c *Context) ParseBlueprintsFiles(rootFile string) (deps []string, errs []error) { func (c *Context) ParseBlueprintsFiles(rootFile string,
config interface{}) (deps []string, errs []error) {
baseDir := filepath.Dir(rootFile) baseDir := filepath.Dir(rootFile)
pathsToParse, err := c.ListModulePaths(baseDir) pathsToParse, err := c.ListModulePaths(baseDir)
if err != nil { if err != nil {
return nil, []error{err} return nil, []error{err}
} }
return c.ParseFileList(baseDir, pathsToParse) return c.ParseFileList(baseDir, pathsToParse, config)
} }
func (c *Context) ParseFileList(rootDir string, filePaths []string) (deps []string, func (c *Context) ParseFileList(rootDir string, filePaths []string,
errs []error) { config interface{}) (deps []string, errs []error) {
if len(filePaths) < 1 { if len(filePaths) < 1 {
return nil, []error{fmt.Errorf("no paths provided to parse")} return nil, []error{fmt.Errorf("no paths provided to parse")}
@ -649,7 +651,12 @@ func (c *Context) ParseFileList(rootDir string, filePaths []string) (deps []stri
c.dependenciesReady = false c.dependenciesReady = false
moduleCh := make(chan *moduleInfo) type newModuleInfo struct {
*moduleInfo
added chan<- struct{}
}
moduleCh := make(chan newModuleInfo)
errsCh := make(chan []error) errsCh := make(chan []error)
doneCh := make(chan struct{}) doneCh := make(chan struct{})
var numErrs uint32 var numErrs uint32
@ -661,24 +668,45 @@ func (c *Context) ParseFileList(rootDir string, filePaths []string) (deps []stri
return return
} }
addedCh := make(chan struct{})
var addModule func(module *moduleInfo) []error
addModule = func(module *moduleInfo) (errs []error) {
moduleCh <- newModuleInfo{module, addedCh}
<-addedCh
var newModules []*moduleInfo
newModules, errs = runAndRemoveLoadHooks(c, config, module)
if len(errs) > 0 {
return errs
}
for _, n := range newModules {
errs = addModule(n)
if len(errs) > 0 {
return errs
}
}
return nil
}
for _, def := range file.Defs { for _, def := range file.Defs {
var module *moduleInfo
var errs []error
switch def := def.(type) { switch def := def.(type) {
case *parser.Module: case *parser.Module:
module, errs = c.processModuleDef(def, file.Name) module, errs := c.processModuleDef(def, file.Name)
if len(errs) == 0 && module != nil {
errs = addModule(module)
}
if len(errs) > 0 {
atomic.AddUint32(&numErrs, uint32(len(errs)))
errsCh <- errs
}
case *parser.Assignment: case *parser.Assignment:
// Already handled via Scope object // Already handled via Scope object
default: default:
panic("unknown definition type") panic("unknown definition type")
} }
if len(errs) > 0 {
atomic.AddUint32(&numErrs, uint32(len(errs)))
errsCh <- errs
} else if module != nil {
moduleCh <- module
}
} }
} }
@ -698,7 +726,10 @@ loop:
case newErrs := <-errsCh: case newErrs := <-errsCh:
errs = append(errs, newErrs...) errs = append(errs, newErrs...)
case module := <-moduleCh: case module := <-moduleCh:
newErrs := c.addModule(module) newErrs := c.addModule(module.moduleInfo)
if module.added != nil {
module.added <- struct{}{}
}
if len(newErrs) > 0 { if len(newErrs) > 0 {
errs = append(errs, newErrs...) errs = append(errs, newErrs...)
} }

View file

@ -168,7 +168,7 @@ func TestWalkDeps(t *testing.T) {
ctx.RegisterModuleType("foo_module", newFooModule) ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule) ctx.RegisterModuleType("bar_module", newBarModule)
_, errs := ctx.ParseBlueprintsFiles("Blueprints") _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
if len(errs) > 0 { if len(errs) > 0 {
t.Errorf("unexpected parse errors:") t.Errorf("unexpected parse errors:")
for _, err := range errs { for _, err := range errs {
@ -260,7 +260,7 @@ func TestWalkDepsDuplicates(t *testing.T) {
ctx.RegisterModuleType("foo_module", newFooModule) ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule) ctx.RegisterModuleType("bar_module", newBarModule)
_, errs := ctx.ParseBlueprintsFiles("Blueprints") _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
if len(errs) > 0 { if len(errs) > 0 {
t.Errorf("unexpected parse errors:") t.Errorf("unexpected parse errors:")
for _, err := range errs { for _, err := range errs {
@ -316,7 +316,7 @@ func TestCreateModule(t *testing.T) {
ctx.RegisterModuleType("foo_module", newFooModule) ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule) ctx.RegisterModuleType("bar_module", newBarModule)
_, errs := ctx.ParseBlueprintsFiles("Blueprints") _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
if len(errs) > 0 { if len(errs) > 0 {
t.Errorf("unexpected parse errors:") t.Errorf("unexpected parse errors:")
for _, err := range errs { for _, err := range errs {
@ -499,7 +499,7 @@ func TestParseFailsForModuleWithoutName(t *testing.T) {
ctx.RegisterModuleType("foo_module", newFooModule) ctx.RegisterModuleType("foo_module", newFooModule)
ctx.RegisterModuleType("bar_module", newBarModule) ctx.RegisterModuleType("bar_module", newBarModule)
_, errs := ctx.ParseBlueprintsFiles("Blueprints") _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
expectedErrs := []error{ expectedErrs := []error{
errors.New(`Blueprints:6:4: property 'name' is missing from a module`), errors.New(`Blueprints:6:4: property 'name' is missing from a module`),

View file

@ -17,6 +17,7 @@ package blueprint
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"sync"
"text/scanner" "text/scanner"
"github.com/google/blueprint/pathtools" "github.com/google/blueprint/pathtools"
@ -120,7 +121,7 @@ type DynamicDependerModule interface {
DynamicDependencies(DynamicDependerModuleContext) []string DynamicDependencies(DynamicDependerModuleContext) []string
} }
type BaseModuleContext interface { type EarlyModuleContext interface {
// Module returns the current module as a Module. It should rarely be necessary, as the module already has a // Module returns the current module as a Module. It should rarely be necessary, as the module already has a
// reference to itself. // reference to itself.
Module() Module Module() Module
@ -178,6 +179,10 @@ type BaseModuleContext interface {
// Namespace returns the Namespace object provided by the NameInterface set by Context.SetNameInterface, or the // Namespace returns the Namespace object provided by the NameInterface set by Context.SetNameInterface, or the
// default SimpleNameInterface if Context.SetNameInterface was not called. // default SimpleNameInterface if Context.SetNameInterface was not called.
Namespace() Namespace Namespace() Namespace
}
type BaseModuleContext interface {
EarlyModuleContext
// GetDirectDepWithTag returns the Module the direct dependency with the specified name, or nil if // GetDirectDepWithTag returns the Module the direct dependency with the specified name, or nil if
// none exists. It panics if the dependency does not have the specified tag. // none exists. It panics if the dependency does not have the specified tag.
@ -995,3 +1000,85 @@ type SimpleName struct {
func (s *SimpleName) Name() string { func (s *SimpleName) Name() string {
return s.Properties.Name return s.Properties.Name
} }
// Load Hooks
type LoadHookContext interface {
EarlyModuleContext
// CreateModule creates a new module by calling the factory method for the specified moduleType, and applies
// the specified property structs to it as if the properties were set in a blueprint file.
CreateModule(ModuleFactory, ...interface{}) Module
}
func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
module := l.context.newModule(factory)
module.relBlueprintsFile = l.module.relBlueprintsFile
module.pos = l.module.pos
module.propertyPos = l.module.propertyPos
module.createdBy = l.module
for _, p := range props {
err := proptools.AppendMatchingProperties(module.properties, p, nil)
if err != nil {
panic(err)
}
}
l.newModules = append(l.newModules, module)
return module.logicModule
}
type loadHookContext struct {
baseModuleContext
newModules []*moduleInfo
}
type LoadHook func(ctx LoadHookContext)
// Load hooks need to be added by module factories, which don't have any parameter to get to the
// Context, and only produce a Module interface with no base implementation, so the load hooks
// must be stored in a global map. The key is a pointer allocated by the module factory, so there
// is no chance of collisions even if tests are running in parallel with multiple contexts. The
// contents should be short-lived, they are added during a module factory and removed immediately
// after the module factory returns.
var pendingHooks sync.Map
func AddLoadHook(module Module, hook LoadHook) {
// Only one goroutine can be processing a given module, so no additional locking is required
// for the slice stored in the sync.Map.
v, exists := pendingHooks.Load(module)
if !exists {
v, _ = pendingHooks.LoadOrStore(module, new([]LoadHook))
}
hooks := v.(*[]LoadHook)
*hooks = append(*hooks, hook)
}
func runAndRemoveLoadHooks(ctx *Context, config interface{},
module *moduleInfo) (newModules []*moduleInfo, errs []error) {
if v, exists := pendingHooks.Load(module.logicModule); exists {
hooks := v.(*[]LoadHook)
mctx := &loadHookContext{
baseModuleContext: baseModuleContext{
context: ctx,
config: config,
module: module,
},
}
for _, hook := range *hooks {
hook(mctx)
newModules = append(newModules, mctx.newModules...)
errs = append(errs, mctx.errs...)
}
pendingHooks.Delete(module.logicModule)
return newModules, errs
}
return nil, nil
}

View file

@ -76,7 +76,7 @@ func TestAliases(t *testing.T) {
ctx.MockFileSystem(mockFS) ctx.MockFileSystem(mockFS)
_, errs := ctx.ParseFileList(".", []string{"Blueprints"}) _, errs := ctx.ParseFileList(".", []string{"Blueprints"}, nil)
if len(errs) > 0 { if len(errs) > 0 {
t.Errorf("unexpected parse errors:") t.Errorf("unexpected parse errors:")
for _, err := range errs { for _, err := range errs {

View file

@ -125,7 +125,7 @@ func setupVisitTest(t *testing.T) *Context {
`), `),
}) })
_, errs := ctx.ParseBlueprintsFiles("Blueprints") _, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
if len(errs) > 0 { if len(errs) > 0 {
t.Errorf("unexpected parse errors:") t.Errorf("unexpected parse errors:")
for _, err := range errs { for _, err := range errs {