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.