Implement transition mutators.
These are more limited than bottom-up or top-down mutators but in exchange have some pleasant properties: - "variant not found" errors are impossible - The logic is pleasantly split into multiple, mostly orthogonal parts - Theoretically, if every mutator is refactored like this, they make it possible to partially cache the module graph - Are quite close to a "configuration transition" in Bazel. Bug: 231370928 Test: Presubmits. Change-Id: Idcdb66b5ea75c0d2838f527aaa988df3b12553d8
This commit is contained in:
parent
0ca2d0cb67
commit
eb641de659
2 changed files with 257 additions and 26 deletions
262
context.go
262
context.go
|
@ -272,6 +272,11 @@ type moduleInfo struct {
|
||||||
// set during each runMutator
|
// set during each runMutator
|
||||||
splitModules modulesOrAliases
|
splitModules modulesOrAliases
|
||||||
|
|
||||||
|
// Used by TransitionMutator implementations
|
||||||
|
transitionVariations []string
|
||||||
|
currentTransitionMutator string
|
||||||
|
requiredVariationsLock sync.Mutex
|
||||||
|
|
||||||
// set during PrepareBuildActions
|
// set during PrepareBuildActions
|
||||||
actionDefs localBuildActions
|
actionDefs localBuildActions
|
||||||
|
|
||||||
|
@ -619,6 +624,173 @@ func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator)
|
||||||
return info
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
type MutatorHandle interface {
|
||||||
// Set the mutator to visit modules in parallel while maintaining ordering. Calling any
|
// 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
|
// method on the mutator context is thread-safe, but the mutator must handle synchronization
|
||||||
|
@ -1303,7 +1475,7 @@ func newVariant(module *moduleInfo, mutatorName string, variationName string,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
|
func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
|
||||||
defaultVariationName *string, variationNames []string, local bool) (modulesOrAliases, []error) {
|
depChooser depChooser, variationNames []string, local bool) (modulesOrAliases, []error) {
|
||||||
|
|
||||||
if len(variationNames) == 0 {
|
if len(variationNames) == 0 {
|
||||||
panic(fmt.Errorf("mutator %q passed zero-length variation list for module %q",
|
panic(fmt.Errorf("mutator %q passed zero-length variation list for module %q",
|
||||||
|
@ -1339,7 +1511,7 @@ func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
|
||||||
|
|
||||||
newModules = append(newModules, newModule)
|
newModules = append(newModules, newModule)
|
||||||
|
|
||||||
newErrs := c.convertDepsToVariation(newModule, mutatorName, variationName, defaultVariationName)
|
newErrs := c.convertDepsToVariation(newModule, depChooser)
|
||||||
if len(newErrs) > 0 {
|
if len(newErrs) > 0 {
|
||||||
errs = append(errs, newErrs...)
|
errs = append(errs, newErrs...)
|
||||||
}
|
}
|
||||||
|
@ -1355,31 +1527,79 @@ func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
|
||||||
return newModules, errs
|
return newModules, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) convertDepsToVariation(module *moduleInfo,
|
type depChooser func(source *moduleInfo, dep depInfo) (*moduleInfo, string)
|
||||||
mutatorName, variationName string, defaultVariationName *string) (errs []error) {
|
|
||||||
|
|
||||||
|
// 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 {
|
for i, dep := range module.directDeps {
|
||||||
if dep.module.logicModule == nil {
|
if dep.module.logicModule == nil {
|
||||||
var newDep *moduleInfo
|
newDep, missingVariation := depChooser(module, dep)
|
||||||
for _, m := range dep.module.splitModules {
|
|
||||||
if m.moduleOrAliasVariant().variations[mutatorName] == variationName {
|
|
||||||
newDep = m.moduleOrAliasTarget()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if newDep == nil && defaultVariationName != nil {
|
|
||||||
// give it a second chance; match with defaultVariationName
|
|
||||||
for _, m := range dep.module.splitModules {
|
|
||||||
if m.moduleOrAliasVariant().variations[mutatorName] == *defaultVariationName {
|
|
||||||
newDep = m.moduleOrAliasTarget()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if newDep == nil {
|
if newDep == nil {
|
||||||
errs = append(errs, &BlueprintError{
|
errs = append(errs, &BlueprintError{
|
||||||
Err: fmt.Errorf("failed to find variation %q for module %q needed by %q",
|
Err: fmt.Errorf("failed to find variation %q for module %q needed by %q",
|
||||||
variationName, dep.module.Name(), module.Name()),
|
missingVariation, dep.module.Name(), module.Name()),
|
||||||
Pos: module.pos,
|
Pos: module.pos,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -991,11 +991,17 @@ func (mctx *mutatorContext) MutatorName() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mctx *mutatorContext) CreateVariations(variationNames ...string) []Module {
|
func (mctx *mutatorContext) CreateVariations(variationNames ...string) []Module {
|
||||||
return mctx.createVariations(variationNames, false)
|
depChooser := chooseDepInherit(mctx.name, mctx.defaultVariation)
|
||||||
|
return mctx.createVariations(variationNames, depChooser, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mctx *mutatorContext) createVariationsWithTransition(transition Transition, variationNames ...string) []Module {
|
||||||
|
return mctx.createVariations(variationNames, chooseDepByTransition(mctx.name, transition), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mctx *mutatorContext) CreateLocalVariations(variationNames ...string) []Module {
|
func (mctx *mutatorContext) CreateLocalVariations(variationNames ...string) []Module {
|
||||||
return mctx.createVariations(variationNames, true)
|
depChooser := chooseDepInherit(mctx.name, mctx.defaultVariation)
|
||||||
|
return mctx.createVariations(variationNames, depChooser, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mctx *mutatorContext) SetVariationProvider(module Module, provider ProviderKey, value interface{}) {
|
func (mctx *mutatorContext) SetVariationProvider(module Module, provider ProviderKey, value interface{}) {
|
||||||
|
@ -1008,9 +1014,9 @@ func (mctx *mutatorContext) SetVariationProvider(module Module, provider Provide
|
||||||
panic(fmt.Errorf("module %q is not a newly created variant of %q", module, mctx.module))
|
panic(fmt.Errorf("module %q is not a newly created variant of %q", module, mctx.module))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mctx *mutatorContext) createVariations(variationNames []string, local bool) []Module {
|
func (mctx *mutatorContext) createVariations(variationNames []string, depChooser depChooser, local bool) []Module {
|
||||||
var ret []Module
|
var ret []Module
|
||||||
modules, errs := mctx.context.createVariations(mctx.module, mctx.name, mctx.defaultVariation, variationNames, local)
|
modules, errs := mctx.context.createVariations(mctx.module, mctx.name, depChooser, variationNames, local)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
mctx.errs = append(mctx.errs, errs...)
|
mctx.errs = append(mctx.errs, errs...)
|
||||||
}
|
}
|
||||||
|
@ -1091,8 +1097,13 @@ func (mctx *mutatorContext) CreateAliasVariation(aliasVariationName, targetVaria
|
||||||
panic(fmt.Errorf("no %q variation in module variations %q", targetVariationName, foundVariations))
|
panic(fmt.Errorf("no %q variation in module variations %q", targetVariationName, foundVariations))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mctx *mutatorContext) applyTransition(transition Transition) {
|
||||||
|
mctx.context.convertDepsToVariation(mctx.module, chooseDepByTransition(mctx.name, transition))
|
||||||
|
}
|
||||||
|
|
||||||
func (mctx *mutatorContext) SetDependencyVariation(variationName string) {
|
func (mctx *mutatorContext) SetDependencyVariation(variationName string) {
|
||||||
mctx.context.convertDepsToVariation(mctx.module, mctx.name, variationName, nil)
|
mctx.context.convertDepsToVariation(mctx.module, chooseDepExplicit(
|
||||||
|
mctx.name, variationName, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mctx *mutatorContext) SetDefaultDependencyVariation(variationName *string) {
|
func (mctx *mutatorContext) SetDefaultDependencyVariation(variationName *string) {
|
||||||
|
|
Loading…
Reference in a new issue