Add TopDownMutatorContext.CreateModule
Allow a module to create other modules as if they were specified
in a blueprint file. CreateModule takes the name of a module type,
calls the factory function the module type, and then inserts
properties into the property structs returned by the factory.
Bug: 64161749
Test: TestCreateModule
Change-Id: Ic1903305d6b08eae1edc7f0fb4137e85544aac0f
diff --git a/context.go b/context.go
index 93268d6..2ccc6fd 100644
--- a/context.go
+++ b/context.go
@@ -157,6 +157,7 @@
type moduleInfo struct {
// set during Parse
typeName string
+ factory ModuleFactory
relBlueprintsFile string
pos scanner.Position
propertyPos map[string]scanner.Position
@@ -260,12 +261,8 @@
parallel bool
}
-// 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 := &Context{
+func newContext() *Context {
+ return &Context{
moduleFactories: make(map[string]ModuleFactory),
moduleNames: make(map[string]*moduleGroup),
moduleInfo: make(map[Module]*moduleInfo),
@@ -273,6 +270,14 @@
globs: make(map[string]GlobPath),
fs: pathtools.OsFs,
}
+}
+
+// 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)
@@ -940,13 +945,7 @@
// 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{}) {
- typeName := origModule.typeName
- factory, ok := c.moduleFactories[typeName]
- if !ok {
- panic(fmt.Sprintf("unrecognized module type %q during cloning", typeName))
- }
-
- newLogicModule, newProperties := factory()
+ newLogicModule, newProperties := origModule.factory()
if len(newProperties) != len(origModule.properties) {
panic("mismatched properties array length in " + origModule.Name())
@@ -1062,6 +1061,19 @@
return strings.Join(names, ", ")
}
+func (c *Context) newModule(factory ModuleFactory) *moduleInfo {
+ logicModule, properties := factory()
+
+ module := &moduleInfo{
+ logicModule: logicModule,
+ factory: factory,
+ }
+
+ module.properties = properties
+
+ return module
+}
+
func (c *Context) processModuleDef(moduleDef *parser.Module,
relBlueprintsFile string) (*moduleInfo, []error) {
@@ -1079,17 +1091,12 @@
}
}
- logicModule, properties := factory()
+ module := c.newModule(factory)
+ module.typeName = moduleDef.Type
- module := &moduleInfo{
- logicModule: logicModule,
- typeName: moduleDef.Type,
- relBlueprintsFile: relBlueprintsFile,
- }
+ module.relBlueprintsFile = relBlueprintsFile
- module.properties = properties
-
- propertyMap, errs := unpackProperties(moduleDef.Properties, properties...)
+ propertyMap, errs := unpackProperties(moduleDef.Properties, module.properties...)
if len(errs) > 0 {
return nil, errs
}
@@ -1744,14 +1751,16 @@
}
type globalStateChange struct {
- reverse []reverseDep
- rename []rename
- replace []replace
+ reverse []reverseDep
+ rename []rename
+ replace []replace
+ newModules []*moduleInfo
}
reverseDeps := make(map[*moduleInfo][]depInfo)
var rename []rename
var replace []replace
+ var newModules []*moduleInfo
errsCh := make(chan []error)
globalStateCh := make(chan globalStateChange)
@@ -1798,11 +1807,12 @@
newVariationsCh <- mctx.newVariations
}
- if len(mctx.reverseDeps) > 0 || len(mctx.replace) > 0 || len(mctx.rename) > 0 {
+ if len(mctx.reverseDeps) > 0 || len(mctx.replace) > 0 || len(mctx.rename) > 0 || len(mctx.newModules) > 0 {
globalStateCh <- globalStateChange{
- reverse: mctx.reverseDeps,
- replace: mctx.replace,
- rename: mctx.rename,
+ reverse: mctx.reverseDeps,
+ replace: mctx.replace,
+ rename: mctx.rename,
+ newModules: mctx.newModules,
}
}
@@ -1821,6 +1831,7 @@
}
replace = append(replace, globalStateChange.replace...)
rename = append(rename, globalStateChange.rename...)
+ newModules = append(newModules, globalStateChange.newModules...)
case newVariations := <-newVariationsCh:
for _, m := range newVariations {
newModuleInfo[m.logicModule] = m
@@ -1870,6 +1881,14 @@
c.depsModified++
}
+ for _, module := range newModules {
+ errs = c.addModule(module)
+ if len(errs) > 0 {
+ return errs
+ }
+ atomic.AddUint32(&c.depsModified, 1)
+ }
+
errs = c.handleRenames(rename)
if len(errs) > 0 {
return errs
@@ -3007,8 +3026,7 @@
relPos.Filename = module.relBlueprintsFile
// Get the name and location of the factory function for the module.
- factory := c.moduleFactories[module.typeName]
- factoryFunc := runtime.FuncForPC(reflect.ValueOf(factory).Pointer())
+ factoryFunc := runtime.FuncForPC(reflect.ValueOf(module.factory).Pointer())
factoryName := factoryFunc.Name()
infoMap := map[string]interface{}{
diff --git a/context_test.go b/context_test.go
index b05c541..6070c99 100644
--- a/context_test.go
+++ b/context_test.go
@@ -16,6 +16,7 @@
import (
"bytes"
+ "strings"
"testing"
)
@@ -201,3 +202,81 @@
t.Fatalf("unexpected walkDeps behaviour: %s\nup should be: GFC", outputUp)
}
}
+
+func TestCreateModule(t *testing.T) {
+ ctx := newContext()
+ ctx.MockFileSystem(map[string][]byte{
+ "Blueprints": []byte(`
+ foo_module {
+ name: "A",
+ deps: ["B", "C"],
+ }
+ `),
+ })
+
+ ctx.RegisterTopDownMutator("create", createTestMutator)
+ ctx.RegisterBottomUpMutator("deps", blueprintDepsMutator)
+
+ ctx.RegisterModuleType("foo_module", newFooModule)
+ ctx.RegisterModuleType("bar_module", newBarModule)
+ _, errs := ctx.ParseBlueprintsFiles("Blueprints")
+ if len(errs) > 0 {
+ t.Errorf("unexpected parse errors:")
+ for _, err := range errs {
+ t.Errorf(" %s", err)
+ }
+ t.FailNow()
+ }
+
+ errs = ctx.ResolveDependencies(nil)
+ if len(errs) > 0 {
+ t.Errorf("unexpected dep errors:")
+ for _, err := range errs {
+ t.Errorf(" %s", err)
+ }
+ t.FailNow()
+ }
+
+ a := ctx.modulesFromName("A")[0].logicModule.(*fooModule)
+ b := ctx.modulesFromName("B")[0].logicModule.(*barModule)
+ c := ctx.modulesFromName("C")[0].logicModule.(*barModule)
+ d := ctx.modulesFromName("D")[0].logicModule.(*fooModule)
+
+ checkDeps := func(m Module, expected string) {
+ var deps []string
+ ctx.VisitDirectDeps(m, func(m Module) {
+ deps = append(deps, ctx.ModuleName(m))
+ })
+ got := strings.Join(deps, ",")
+ if got != expected {
+ t.Errorf("unexpected %q dependencies, got %q expected %q",
+ ctx.ModuleName(m), got, expected)
+ }
+ }
+
+ checkDeps(a, "B,C")
+ checkDeps(b, "D")
+ checkDeps(c, "D")
+ checkDeps(d, "")
+}
+
+func createTestMutator(ctx TopDownMutatorContext) {
+ type props struct {
+ Name string
+ Deps []string
+ }
+
+ ctx.CreateModule(newBarModule, &props{
+ Name: "B",
+ Deps: []string{"D"},
+ })
+
+ ctx.CreateModule(newBarModule, &props{
+ Name: "C",
+ Deps: []string{"D"},
+ })
+
+ ctx.CreateModule(newFooModule, &props{
+ Name: "D",
+ })
+}
diff --git a/module_ctx.go b/module_ctx.go
index 9a3a1d3..7e9a436 100644
--- a/module_ctx.go
+++ b/module_ctx.go
@@ -20,6 +20,7 @@
"text/scanner"
"github.com/google/blueprint/pathtools"
+ "github.com/google/blueprint/proptools"
)
// A Module handles generating all of the Ninja build actions needed to build a
@@ -502,7 +503,8 @@
reverseDeps []reverseDep
rename []rename
replace []replace
- newVariations []*moduleInfo
+ newVariations []*moduleInfo // new variants of existing modules
+ newModules []*moduleInfo // brand new modules
}
type baseMutatorContext interface {
@@ -527,6 +529,8 @@
OtherModuleErrorf(m Module, fmt string, args ...interface{})
OtherModuleDependencyTag(m Module) DependencyTag
+ CreateModule(ModuleFactory, ...interface{})
+
GetDirectDepWithTag(name string, tag DependencyTag) Module
GetDirectDep(name string) (Module, DependencyTag)
@@ -737,6 +741,21 @@
mctx.rename = append(mctx.rename, rename{mctx.module.group, name})
}
+// Create a new module by calling the factory method for the specified moduleType, and apply
+// the specified property structs to it as if the properties were set in a blueprint file.
+func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) {
+ module := mctx.context.newModule(factory)
+
+ for _, p := range props {
+ err := proptools.AppendMatchingProperties(module.properties, p, nil)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ mctx.newModules = append(mctx.newModules, module)
+}
+
// SimpleName is an embeddable object to implement the ModuleContext.Name method using a property
// called "name". Modules that embed it must also add SimpleName.Properties to their property
// structure list.