Add support for Mutators

Add Mutators, which can be used to split a module into multiple
variants.

Change-Id: Ib992da2e93a557559a3a34571d5033192e129e49
This commit is contained in:
Colin Cross 2014-12-18 16:28:54 -08:00
parent 11a114f454
commit c902848e5a
2 changed files with 459 additions and 39 deletions

View file

@ -2,6 +2,7 @@ package blueprint
import (
"blueprint/parser"
"blueprint/proptools"
"bytes"
"errors"
"fmt"
@ -51,6 +52,7 @@ type Context struct {
moduleInfo map[Module]*moduleInfo
moduleGroupsSorted []*moduleGroup
singletonInfo map[string]*singletonInfo
mutatorInfo []*mutatorInfo
dependenciesReady bool // set to true on a successful ResolveDependencies
buildActionsReady bool // set to true on a successful PrepareBuildActions
@ -105,9 +107,31 @@ type moduleGroup struct {
}
type moduleInfo struct {
directDeps []*moduleInfo // set during ResolveDependencies
logicModule Module
group *moduleGroup
name []subName
logicModule Module
group *moduleGroup
moduleProperties []interface{}
// set during ResolveDependencies
directDeps []*moduleInfo
// set during each runMutator
splitModules []*moduleInfo
}
type subName struct {
mutatorName string
variantName string
}
func (module *moduleInfo) subName() string {
names := []string{}
for _, subName := range module.name {
if subName.variantName != "" {
names = append(names, subName.variantName)
}
}
return strings.Join(names, "_")
}
type singletonInfo struct {
@ -119,6 +143,13 @@ type singletonInfo struct {
actionDefs localBuildActions
}
type mutatorInfo struct {
// set during RegisterMutator
topDownMutator TopDownMutator
bottomUpMutator BottomUpMutator
name string
}
func (e *Error) Error() string {
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
@ -146,7 +177,8 @@ type ModuleFactory func() (m Module, propertyStructs []interface{})
// 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.
// 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
@ -246,6 +278,43 @@ func singletonTypeName(singleton Singleton) string {
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 once per Module, and is invoked on a module before being invoked
// on any of its dependencies
//
// The mutator type names given here must be unique for the context.
func (c *Context) RegisterTopDownMutator(name string, mutator TopDownMutator) {
for _, m := range c.mutatorInfo {
if m.name == name && m.topDownMutator != nil {
panic(fmt.Errorf("mutator name %s is already registered", name))
}
}
c.mutatorInfo = append(c.mutatorInfo, &mutatorInfo{
topDownMutator: mutator,
name: name,
})
}
// RegisterBottomUpMutator registers a mutator that will be invoked to split
// Modules into variants. Each registered mutator is invoked once per Module,
// and is invoked on dependencies before being invoked on dependers.
//
// The mutator type names given here must be unique for the context.
func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator) {
for _, m := range c.mutatorInfo {
if m.name == name && m.bottomUpMutator != nil {
panic(fmt.Errorf("mutator name %s is already registered", name))
}
}
c.mutatorInfo = append(c.mutatorInfo, &mutatorInfo{
bottomUpMutator: mutator,
name: name,
})
}
// 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
@ -513,6 +582,95 @@ func (c *Context) processAssignment(
}
}
func (c *Context) createVariants(origModule *moduleInfo, mutatorName string,
variantNames []string) []*moduleInfo {
newModules := []*moduleInfo{}
origVariantName := origModule.name
group := origModule.group
for i, variantName := range variantNames {
typeName := group.typeName
factory, ok := c.moduleFactories[typeName]
if !ok {
panic(fmt.Sprintf("unrecognized module type %q during cloning", typeName))
}
var newLogicModule Module
var newProperties []interface{}
if i == 0 {
// Reuse the existing module for the first new variant
newLogicModule = origModule.logicModule
newProperties = origModule.moduleProperties
} else {
props := []interface{}{
&group.properties,
}
newLogicModule, newProperties = factory()
newProperties = append(props, newProperties...)
if len(newProperties) != len(origModule.moduleProperties) {
panic("mismatched properties array length in " + group.properties.Name)
}
for i := range newProperties {
dst := reflect.ValueOf(newProperties[i]).Elem()
src := reflect.ValueOf(origModule.moduleProperties[i]).Elem()
proptools.CopyProperties(dst, src)
}
}
newVariantName := append([]subName(nil), origVariantName...)
newSubName := subName{
mutatorName: mutatorName,
variantName: variantName,
}
newVariantName = append(newVariantName, newSubName)
newModule := &moduleInfo{
group: group,
directDeps: append([]*moduleInfo(nil), origModule.directDeps...),
logicModule: newLogicModule,
name: newVariantName,
moduleProperties: newProperties,
}
newModules = append(newModules, newModule)
c.moduleInfo[newModule.logicModule] = newModule
c.convertDepsToVariant(newModule, newSubName)
}
// 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
return newModules
}
func (c *Context) convertDepsToVariant(module *moduleInfo, newSubName subName) {
for i, dep := range module.directDeps {
if dep.logicModule == nil {
var newDep *moduleInfo
for _, m := range dep.splitModules {
if len(m.name) > 0 && m.name[len(m.name)-1] == newSubName {
newDep = m
break
}
}
if newDep == nil {
panic(fmt.Sprintf("failed to find variant %s for module %s needed by %s",
newSubName.variantName, dep.group.properties.Name, module.group.properties.Name))
}
module.directDeps[i] = newDep
}
}
}
func (c *Context) processModuleDef(moduleDef *parser.Module,
relBlueprintsFile string) []error {
@ -579,8 +737,9 @@ func (c *Context) processModuleDef(moduleDef *parser.Module,
}
module := &moduleInfo{
group: group,
logicModule: logicModule,
group: group,
logicModule: logicModule,
moduleProperties: properties,
}
c.moduleGroups[name] = group
@ -677,38 +836,48 @@ func (c *Context) resolveDependencies(config interface{}) (errs []error) {
panic("expected a single module in resolveDependencies")
}
group.modules[0].directDeps = make([]*moduleInfo, 0, len(depNames))
depsPos := group.propertyPos["deps"]
for _, depName := range depNames {
if depName == group.properties.Name {
errs = append(errs, &Error{
Err: fmt.Errorf("%q depends on itself", depName),
Pos: depsPos,
})
newErrs := c.addDependency(group.modules[0], depName)
if len(newErrs) > 0 {
errs = append(errs, newErrs...)
continue
}
depInfo, ok := c.moduleGroups[depName]
if !ok {
errs = append(errs, &Error{
Err: fmt.Errorf("%q depends on undefined module %q",
group.properties.Name, depName),
Pos: depsPos,
})
continue
}
if len(depInfo.modules) != 1 {
panic("expected a single module in resolveDependencies")
}
group.modules[0].directDeps = append(group.modules[0].directDeps, depInfo.modules[0])
}
}
return
}
func (c *Context) addDependency(module *moduleInfo, depName string) []error {
depsPos := module.group.propertyPos["deps"]
if depName == module.group.properties.Name {
return []error{&Error{
Err: fmt.Errorf("%q depends on itself", depName),
Pos: depsPos,
}}
}
depInfo, ok := c.moduleGroups[depName]
if !ok {
return []error{&Error{
Err: fmt.Errorf("%q depends on undefined module %q",
module.group.properties.Name, depName),
Pos: depsPos,
}}
}
if len(depInfo.modules) != 1 {
panic(fmt.Sprintf("cannot add dependency from %s to %s, it already has multiple variants",
module.group.properties.Name, depInfo.properties.Name))
}
module.directDeps = append(module.directDeps, depInfo.modules[0])
return nil
}
// rebuildSortedModuleList recursively walks the module dependency graph and
// builds a sorted list of modules such that dependencies of a module always
// appear first. It also reports errors when it encounters dependency cycles.
@ -825,6 +994,11 @@ func (c *Context) PrepareBuildActions(config interface{}) (deps []string, errs [
}
}
errs = c.runMutators(config)
if len(errs) > 0 {
return nil, errs
}
liveGlobals := newLiveTracker(config)
c.initSpecialVariables()
@ -865,6 +1039,107 @@ func (c *Context) PrepareBuildActions(config interface{}) (deps []string, errs [
return deps, nil
}
func (c *Context) runMutators(config interface{}) (errs []error) {
for _, mutator := range c.mutatorInfo {
if mutator.topDownMutator != nil {
errs = c.runTopDownMutator(config, mutator.name, mutator.topDownMutator)
} else if mutator.bottomUpMutator != nil {
errs = c.runBottomUpMutator(config, mutator.name, mutator.bottomUpMutator)
} else {
panic("no mutator set on " + mutator.name)
}
if len(errs) > 0 {
return errs
}
}
return nil
}
func (c *Context) runTopDownMutator(config interface{},
name string, mutator TopDownMutator) (errs []error) {
for i := 0; i < len(c.moduleGroupsSorted); i++ {
group := c.moduleGroupsSorted[len(c.moduleGroupsSorted)-1-i]
for _, module := range group.modules {
mctx := &mutatorContext{
baseModuleContext: baseModuleContext{
context: c,
config: config,
group: group,
},
module: module,
name: name,
}
mutator(mctx)
if len(mctx.errs) > 0 {
errs = append(errs, mctx.errs...)
return errs
}
}
}
return errs
}
func (c *Context) runBottomUpMutator(config interface{},
name string, mutator BottomUpMutator) (errs []error) {
dependenciesModified := false
for _, group := range c.moduleGroupsSorted {
newModules := make([]*moduleInfo, 0, len(group.modules))
for _, module := range group.modules {
mctx := &mutatorContext{
baseModuleContext: baseModuleContext{
context: c,
config: config,
group: group,
},
module: module,
name: name,
}
mutator(mctx)
if len(mctx.errs) > 0 {
errs = append(errs, mctx.errs...)
return errs
}
// Fix up any remaining dependencies on modules that were split into variants
// by replacing them with the first variant
for i, dep := range module.directDeps {
if dep.logicModule == nil {
module.directDeps[i] = dep.splitModules[0]
}
}
if mctx.dependenciesModified {
dependenciesModified = true
}
if module.splitModules != nil {
newModules = append(newModules, module.splitModules...)
} else {
newModules = append(newModules, module)
}
}
group.modules = newModules
}
if dependenciesModified {
errs = c.rebuildSortedModuleList()
if len(errs) > 0 {
return errs
}
}
return errs
}
func (c *Context) initSpecialVariables() {
c.buildDir = nil
c.requiredNinjaMajor = 1
@ -918,7 +1193,8 @@ func (c *Context) generateModuleBuildActions(config interface{},
config: config,
group: group,
},
module: module,
module: module,
primaryModule: group.modules[0],
},
scope: scope,
}
@ -1004,12 +1280,11 @@ func (c *Context) processLocalBuildActions(out, in *localBuildActions,
return errs
}
out.buildDefs = in.buildDefs
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
// moduleInfo we remove them from the live globals set.
out.variables = nil
// moduleGroup we remove them from the live globals set.
for _, v := range in.variables {
_, isLive := liveGlobals.variables[v]
if isLive {
@ -1018,7 +1293,6 @@ func (c *Context) processLocalBuildActions(out, in *localBuildActions,
}
}
out.rules = nil
for _, r := range in.rules {
_, isLive := liveGlobals.rules[r]
if isLive {
@ -1074,6 +1348,22 @@ func (c *Context) visitDepsDepthFirstIf(topModule *moduleInfo, pred func(Module)
walk(topModule)
}
func (c *Context) visitDirectDeps(module *moduleInfo, visit func(Module)) {
for _, dep := range module.directDeps {
visit(dep.logicModule)
}
}
func (c *Context) visitDirectDepsIf(module *moduleInfo, pred func(Module) bool,
visit func(Module)) {
for _, dep := range module.directDeps {
if pred(dep.logicModule) {
visit(dep.logicModule)
}
}
}
func (c *Context) sortedModuleNames() []string {
if c.cachedSortedModuleNames == nil {
c.cachedSortedModuleNames = make([]string, 0, len(c.moduleGroups))

View file

@ -7,13 +7,17 @@ import (
)
// A Module handles generating all of the Ninja build actions needed to build a
// single module that is defined in a Blueprints file. Module objects are
// created during the parse phase of a Context using one of the registered
// module types (and the associated ModuleFactory function). The Module's
// properties struct is automatically filled in with the property values
// specified in the Blueprints file (see Context.RegisterModuleType for more
// single module based on properties defined in a Blueprints file. Module
// objects are initially created during the parse phase of a Context using one
// of the registered module types (and the associated ModuleFactory function).
// The Module's properties struct is automatically filled in with the property
// values specified in the Blueprints file (see Context.RegisterModuleType for more
// information on this).
//
// A Module can be split into multiple Modules by a Mutator. All existing
// properties set on the module will be duplicated to the new Module, and then
// modified as necessary by the Mutator.
//
// The Module implementation can access the build configuration as well as any
// modules on which on which it depends (as defined by the "deps" property
// specified in the Blueprints file or dynamically added by implementing the
@ -115,11 +119,15 @@ type PreModuleContext interface {
type ModuleContext interface {
PreModuleContext
ModuleSubDir() string
Variable(pctx *PackageContext, name, value string)
Rule(pctx *PackageContext, name string, params RuleParams, argNames ...string) Rule
Build(pctx *PackageContext, params BuildParams)
AddNinjaFileDeps(deps ...string)
PrimaryModule() Module
}
var _ BaseModuleContext = (*baseModuleContext)(nil)
@ -188,7 +196,8 @@ var _ PreModuleContext = (*preModuleContext)(nil)
type preModuleContext struct {
baseModuleContext
module *moduleInfo
module *moduleInfo
primaryModule *moduleInfo
}
func (m *preModuleContext) OtherModuleName(module Module) string {
@ -225,6 +234,10 @@ type moduleContext struct {
actionDefs localBuildActions
}
func (m *moduleContext) ModuleSubDir() string {
return m.module.subName()
}
func (m *moduleContext) Variable(pctx *PackageContext, name, value string) {
m.scope.ReparentTo(pctx)
@ -265,3 +278,120 @@ func (m *moduleContext) Build(pctx *PackageContext, params BuildParams) {
func (m *moduleContext) AddNinjaFileDeps(deps ...string) {
m.ninjaFileDeps = append(m.ninjaFileDeps, deps...)
}
func (m *moduleContext) PrimaryModule() Module {
return m.primaryModule.logicModule
}
//
// MutatorContext
//
type mutatorContext struct {
baseModuleContext
module *moduleInfo
name string
dependenciesModified bool
}
type baseMutatorContext interface {
BaseModuleContext
Module() Module
}
type TopDownMutatorContext interface {
baseMutatorContext
VisitDirectDeps(visit func(Module))
VisitDirectDepsIf(pred func(Module) bool, visit func(Module))
VisitDepsDepthFirst(visit func(Module))
VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module))
}
type BottomUpMutatorContext interface {
baseMutatorContext
AddDependency(module Module, name string)
CreateVariants(...string) []Module
SetDependencyVariant(string)
}
// A Mutator function is called for each Module, and can use
// MutatorContext.CreateSubVariants to split a Module into multiple Modules,
// modifying properties on the new modules to differentiate them. It is called
// after parsing all Blueprint files, but before generating any build rules,
// and is always called on dependencies before being called on the depending module.
//
// The Mutator function should only modify members of properties structs, and not
// members of the module struct itself, to ensure the modified values are copied
// if a second Mutator chooses to split the module a second time.
type TopDownMutator func(mctx TopDownMutatorContext)
type BottomUpMutator func(mctx BottomUpMutatorContext)
// Split a module into mulitple variants, one for each name in the variantNames
// parameter. It returns a list of new modules in the same order as the variantNames
// list.
//
// If any of the dependencies of the module being operated on were already split
// by calling CreateVariants with the same name, the dependency will automatically
// be updated to point the matching variant.
//
// If a module is split, and then a module depending on the first module is not split
// when the Mutator is later called on it, the dependency of the depending module will
// automatically be updated to point to the first variant.
func (mctx *mutatorContext) CreateVariants(variantNames ...string) []Module {
ret := []Module{}
modules := mctx.context.createVariants(mctx.module, mctx.name, variantNames)
for _, module := range modules {
ret = append(ret, module.logicModule)
}
if len(ret) != len(variantNames) {
panic("oops!")
}
return ret
}
// Set all dangling dependencies on the current module to point to the variant
// with given name.
func (mctx *mutatorContext) SetDependencyVariant(variantName string) {
subName := subName{
mutatorName: mctx.name,
variantName: variantName,
}
mctx.context.convertDepsToVariant(mctx.module, subName)
}
func (mctx *mutatorContext) Module() Module {
return mctx.module.logicModule
}
// Add a dependency to the given module. The depender can be a specific variant
// of a module, but the dependee must be a module that only has a single variant.
// Does not affect the ordering of the current mutator pass, but will be ordered
// correctly for all future mutator passes.
func (mctx *mutatorContext) AddDependency(module Module, depName string) {
mctx.context.addDependency(mctx.context.moduleInfo[module], depName)
mctx.dependenciesModified = true
}
func (mctx *mutatorContext) VisitDirectDeps(visit func(Module)) {
mctx.context.visitDirectDeps(mctx.module, visit)
}
func (mctx *mutatorContext) VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) {
mctx.context.visitDirectDepsIf(mctx.module, pred, visit)
}
func (mctx *mutatorContext) VisitDepsDepthFirst(visit func(Module)) {
mctx.context.visitDepsDepthFirst(mctx.module, visit)
}
func (mctx *mutatorContext) VisitDepsDepthFirstIf(pred func(Module) bool,
visit func(Module)) {
mctx.context.visitDepsDepthFirstIf(mctx.module, pred, visit)
}