Merge "Move TransitionMutator to transition.go and add tests" into main
This commit is contained in:
commit
6ed94b7f85
4 changed files with 504 additions and 283 deletions
|
@ -53,6 +53,7 @@ bootstrap_go_package {
|
|||
"scope.go",
|
||||
"singleton_ctx.go",
|
||||
"source_file_provider.go",
|
||||
"transition.go",
|
||||
],
|
||||
testSrcs: [
|
||||
"context_test.go",
|
||||
|
@ -63,6 +64,7 @@ bootstrap_go_package {
|
|||
"ninja_writer_test.go",
|
||||
"provider_test.go",
|
||||
"splice_modules_test.go",
|
||||
"transition_test.go",
|
||||
"visit_test.go",
|
||||
],
|
||||
}
|
||||
|
|
283
context.go
283
context.go
|
@ -681,284 +681,6 @@ func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator)
|
|||
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{}
|
||||
|
||||
// Provider returns the value for a provider for the target of the dependency edge for which the
|
||||
// transition is being computed. If the value is not set it returns nil and false. It panics if
|
||||
// called before the appropriate mutator or GenerateBuildActions pass for the provider. The value
|
||||
// returned may be a deep copy of the value originally passed to SetProvider.
|
||||
//
|
||||
// This method shouldn't be used directly, prefer the type-safe android.ModuleProvider instead.
|
||||
Provider(provider AnyProviderKey) (any, bool)
|
||||
}
|
||||
|
||||
type OutgoingTransitionContext interface {
|
||||
// Module returns the source 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
|
||||
|
||||
// Config returns the config object that was passed to
|
||||
// Context.PrepareBuildActions.
|
||||
Config() interface{}
|
||||
|
||||
// Provider returns the value for a provider for the source of the dependency edge for which the
|
||||
// transition is being computed. If the value is not set it returns nil and false. It panics if
|
||||
// called before the appropriate mutator or GenerateBuildActions pass for the provider. The value
|
||||
// returned may be a deep copy of the value originally passed to SetProvider.
|
||||
//
|
||||
// This method shouldn't be used directly, prefer the type-safe android.ModuleProvider instead.
|
||||
Provider(provider AnyProviderKey) (any, bool)
|
||||
}
|
||||
|
||||
// TransitionMutator implements a top-down mechanism where a module tells its
|
||||
// direct dependencies what variation they should be built in but the dependency
|
||||
// has the final say.
|
||||
//
|
||||
// When implementing a transition mutator, one needs to implement four methods:
|
||||
// - Split() that tells what variations a module has by itself
|
||||
// - OutgoingTransition() where a module tells what it wants from its
|
||||
// dependency
|
||||
// - IncomingTransition() where a module has the final say about its own
|
||||
// variation
|
||||
// - Mutate() that changes the state of a module depending on its variation
|
||||
//
|
||||
// That the effective variation of module B when depended on by module A is the
|
||||
// composition the outgoing transition of module A and the incoming transition
|
||||
// of module B.
|
||||
//
|
||||
// The outgoing transition should not take the properties of the dependency into
|
||||
// account, only those of the module that depends on it. For this reason, the
|
||||
// dependency is not even passed into it as an argument. Likewise, the incoming
|
||||
// transition should not take the properties of the depending module into
|
||||
// account and is thus not informed about it. This makes for a nice
|
||||
// decomposition of the decision logic.
|
||||
//
|
||||
// A given transition mutator only affects its own variation; other variations
|
||||
// stay unchanged along the dependency edges.
|
||||
//
|
||||
// Soong makes sure that all modules are created in the desired variations and
|
||||
// that dependency edges are set up correctly. This ensures that "missing
|
||||
// variation" errors do not happen and allows for more flexible changes in the
|
||||
// value of the variation among dependency edges (as opposed to bottom-up
|
||||
// mutators where if module A in variation X depends on module B and module B
|
||||
// has that variation X, A must depend on variation X of B)
|
||||
//
|
||||
// The limited power of the context objects passed to individual mutators
|
||||
// methods also makes it more difficult to shoot oneself in the foot. Complete
|
||||
// safety is not guaranteed because no one prevents individual transition
|
||||
// mutators from mutating modules in illegal ways and for e.g. Split() or
|
||||
// Mutate() to run their own visitations of the transitive dependency of the
|
||||
// module and both of these are bad ideas, but it's better than no guardrails at
|
||||
// all.
|
||||
//
|
||||
// This model is pretty close to Bazel's configuration transitions. The mapping
|
||||
// between concepts in Soong and Bazel is as follows:
|
||||
// - Module == configured target
|
||||
// - Variant == configuration
|
||||
// - Variation name == configuration flag
|
||||
// - Variation == configuration flag value
|
||||
// - Outgoing transition == attribute transition
|
||||
// - Incoming transition == rule transition
|
||||
//
|
||||
// The Split() method does not have a Bazel equivalent and Bazel split
|
||||
// transitions do not have a Soong equivalent.
|
||||
//
|
||||
// Mutate() does not make sense in Bazel due to the different models of the
|
||||
// two systems: when creating new variations, Soong clones the old module and
|
||||
// thus some way is needed to change it state whereas Bazel creates each
|
||||
// configuration of a given configured target anew.
|
||||
type TransitionMutator interface {
|
||||
// Split 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
|
||||
|
||||
// OutgoingTransition is 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
|
||||
|
||||
// IncomingTransition is 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
|
||||
|
||||
// Mutate is 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 {
|
||||
for _, i := range items {
|
||||
if !slices.Contains(l, i) {
|
||||
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)
|
||||
|
||||
outgoingTransitionCache := make([][]string, len(module.transitionVariations))
|
||||
for srcVariationIndex, srcVariation := range module.transitionVariations {
|
||||
srcVariationTransitionCache := make([]string, len(module.directDeps))
|
||||
for depIndex, dep := range module.directDeps {
|
||||
finalVariation := t.transition(mctx)(mctx.moduleInfo(), srcVariation, dep.module, dep.tag)
|
||||
srcVariationTransitionCache[depIndex] = finalVariation
|
||||
t.addRequiredVariation(dep.module, finalVariation)
|
||||
}
|
||||
outgoingTransitionCache[srcVariationIndex] = srcVariationTransitionCache
|
||||
}
|
||||
module.outgoingTransitionCache = outgoingTransitionCache
|
||||
}
|
||||
|
||||
type transitionContextImpl struct {
|
||||
context *Context
|
||||
source *moduleInfo
|
||||
dep *moduleInfo
|
||||
depTag DependencyTag
|
||||
config interface{}
|
||||
}
|
||||
|
||||
func (c *transitionContextImpl) DepTag() DependencyTag {
|
||||
return c.depTag
|
||||
}
|
||||
|
||||
func (c *transitionContextImpl) Config() interface{} {
|
||||
return c.config
|
||||
}
|
||||
|
||||
type outgoingTransitionContextImpl struct {
|
||||
transitionContextImpl
|
||||
}
|
||||
|
||||
func (c *outgoingTransitionContextImpl) Module() Module {
|
||||
return c.source.logicModule
|
||||
}
|
||||
|
||||
func (c *outgoingTransitionContextImpl) Provider(provider AnyProviderKey) (any, bool) {
|
||||
return c.context.provider(c.source, provider.provider())
|
||||
}
|
||||
|
||||
type incomingTransitionContextImpl struct {
|
||||
transitionContextImpl
|
||||
}
|
||||
|
||||
func (c *incomingTransitionContextImpl) Module() Module {
|
||||
return c.dep.logicModule
|
||||
}
|
||||
|
||||
func (c *incomingTransitionContextImpl) Provider(provider AnyProviderKey) (any, bool) {
|
||||
return c.context.provider(c.dep, provider.provider())
|
||||
}
|
||||
|
||||
func (t *transitionMutatorImpl) transition(mctx BaseModuleContext) Transition {
|
||||
return func(source *moduleInfo, sourceVariation string, dep *moduleInfo, depTag DependencyTag) string {
|
||||
tc := transitionContextImpl{
|
||||
context: mctx.base().context,
|
||||
source: source,
|
||||
dep: dep,
|
||||
depTag: depTag,
|
||||
config: mctx.Config(),
|
||||
}
|
||||
outgoingVariation := t.mutator.OutgoingTransition(&outgoingTransitionContextImpl{tc}, sourceVariation)
|
||||
if mctx.Failed() {
|
||||
return outgoingVariation
|
||||
}
|
||||
finalVariation := t.mutator.IncomingTransition(&incomingTransitionContextImpl{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
|
||||
outgoingTransitionCache := mc.module.outgoingTransitionCache
|
||||
mc.module.transitionVariations = nil
|
||||
mc.module.outgoingTransitionCache = 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.context.convertDepsToVariation(mc.module, 0,
|
||||
chooseDepByIndexes(mc.mutator.name, outgoingTransitionCache))
|
||||
} else {
|
||||
mc.createVariationsWithTransition(variations, outgoingTransitionCache)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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
|
||||
|
@ -1807,11 +1529,6 @@ func (c *Context) createVariations(origModule *moduleInfo, mutator *mutatorInfo,
|
|||
|
||||
type depChooser func(source *moduleInfo, variationIndex, depIndex int, dep depInfo) (*moduleInfo, string)
|
||||
|
||||
// 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 *moduleInfo, sourceVariation string, dep *moduleInfo, depTag DependencyTag) string
|
||||
|
||||
func chooseDep(candidates modulesOrAliases, mutatorName, variationName string, defaultVariationName *string) (*moduleInfo, string) {
|
||||
for _, m := range candidates {
|
||||
if m.moduleOrAliasVariant().variations[mutatorName] == variationName {
|
||||
|
|
304
transition.go
Normal file
304
transition.go
Normal file
|
@ -0,0 +1,304 @@
|
|||
// Copyright 2024 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package blueprint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// TransitionMutator implements a top-down mechanism where a module tells its
|
||||
// direct dependencies what variation they should be built in but the dependency
|
||||
// has the final say.
|
||||
//
|
||||
// When implementing a transition mutator, one needs to implement four methods:
|
||||
// - Split() that tells what variations a module has by itself
|
||||
// - OutgoingTransition() where a module tells what it wants from its
|
||||
// dependency
|
||||
// - IncomingTransition() where a module has the final say about its own
|
||||
// variation
|
||||
// - Mutate() that changes the state of a module depending on its variation
|
||||
//
|
||||
// That the effective variation of module B when depended on by module A is the
|
||||
// composition the outgoing transition of module A and the incoming transition
|
||||
// of module B.
|
||||
//
|
||||
// The outgoing transition should not take the properties of the dependency into
|
||||
// account, only those of the module that depends on it. For this reason, the
|
||||
// dependency is not even passed into it as an argument. Likewise, the incoming
|
||||
// transition should not take the properties of the depending module into
|
||||
// account and is thus not informed about it. This makes for a nice
|
||||
// decomposition of the decision logic.
|
||||
//
|
||||
// A given transition mutator only affects its own variation; other variations
|
||||
// stay unchanged along the dependency edges.
|
||||
//
|
||||
// Soong makes sure that all modules are created in the desired variations and
|
||||
// that dependency edges are set up correctly. This ensures that "missing
|
||||
// variation" errors do not happen and allows for more flexible changes in the
|
||||
// value of the variation among dependency edges (as opposed to bottom-up
|
||||
// mutators where if module A in variation X depends on module B and module B
|
||||
// has that variation X, A must depend on variation X of B)
|
||||
//
|
||||
// The limited power of the context objects passed to individual mutators
|
||||
// methods also makes it more difficult to shoot oneself in the foot. Complete
|
||||
// safety is not guaranteed because no one prevents individual transition
|
||||
// mutators from mutating modules in illegal ways and for e.g. Split() or
|
||||
// Mutate() to run their own visitations of the transitive dependency of the
|
||||
// module and both of these are bad ideas, but it's better than no guardrails at
|
||||
// all.
|
||||
//
|
||||
// This model is pretty close to Bazel's configuration transitions. The mapping
|
||||
// between concepts in Soong and Bazel is as follows:
|
||||
// - Module == configured target
|
||||
// - Variant == configuration
|
||||
// - Variation name == configuration flag
|
||||
// - Variation == configuration flag value
|
||||
// - Outgoing transition == attribute transition
|
||||
// - Incoming transition == rule transition
|
||||
//
|
||||
// The Split() method does not have a Bazel equivalent and Bazel split
|
||||
// transitions do not have a Soong equivalent.
|
||||
//
|
||||
// Mutate() does not make sense in Bazel due to the different models of the
|
||||
// two systems: when creating new variations, Soong clones the old module and
|
||||
// thus some way is needed to change it state whereas Bazel creates each
|
||||
// configuration of a given configured target anew.
|
||||
type TransitionMutator interface {
|
||||
// Split 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
|
||||
|
||||
// OutgoingTransition is 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
|
||||
|
||||
// IncomingTransition is 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
|
||||
|
||||
// Mutate is 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 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{}
|
||||
|
||||
// Provider returns the value for a provider for the target of the dependency edge for which the
|
||||
// transition is being computed. If the value is not set it returns nil and false. It panics if
|
||||
// called before the appropriate mutator or GenerateBuildActions pass for the provider. The value
|
||||
// returned may be a deep copy of the value originally passed to SetProvider.
|
||||
//
|
||||
// This method shouldn't be used directly, prefer the type-safe android.ModuleProvider instead.
|
||||
Provider(provider AnyProviderKey) (any, bool)
|
||||
}
|
||||
|
||||
type OutgoingTransitionContext interface {
|
||||
// Module returns the source 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
|
||||
|
||||
// Config returns the config object that was passed to
|
||||
// Context.PrepareBuildActions.
|
||||
Config() interface{}
|
||||
|
||||
// Provider returns the value for a provider for the source of the dependency edge for which the
|
||||
// transition is being computed. If the value is not set it returns nil and false. It panics if
|
||||
// called before the appropriate mutator or GenerateBuildActions pass for the provider. The value
|
||||
// returned may be a deep copy of the value originally passed to SetProvider.
|
||||
//
|
||||
// This method shouldn't be used directly, prefer the type-safe android.ModuleProvider instead.
|
||||
Provider(provider AnyProviderKey) (any, bool)
|
||||
}
|
||||
|
||||
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 {
|
||||
for _, i := range items {
|
||||
if !slices.Contains(l, i) {
|
||||
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)
|
||||
|
||||
outgoingTransitionCache := make([][]string, len(module.transitionVariations))
|
||||
for srcVariationIndex, srcVariation := range module.transitionVariations {
|
||||
srcVariationTransitionCache := make([]string, len(module.directDeps))
|
||||
for depIndex, dep := range module.directDeps {
|
||||
finalVariation := t.transition(mctx)(mctx.moduleInfo(), srcVariation, dep.module, dep.tag)
|
||||
srcVariationTransitionCache[depIndex] = finalVariation
|
||||
t.addRequiredVariation(dep.module, finalVariation)
|
||||
}
|
||||
outgoingTransitionCache[srcVariationIndex] = srcVariationTransitionCache
|
||||
}
|
||||
module.outgoingTransitionCache = outgoingTransitionCache
|
||||
}
|
||||
|
||||
type transitionContextImpl struct {
|
||||
context *Context
|
||||
source *moduleInfo
|
||||
dep *moduleInfo
|
||||
depTag DependencyTag
|
||||
config interface{}
|
||||
}
|
||||
|
||||
func (c *transitionContextImpl) DepTag() DependencyTag {
|
||||
return c.depTag
|
||||
}
|
||||
|
||||
func (c *transitionContextImpl) Config() interface{} {
|
||||
return c.config
|
||||
}
|
||||
|
||||
type outgoingTransitionContextImpl struct {
|
||||
transitionContextImpl
|
||||
}
|
||||
|
||||
func (c *outgoingTransitionContextImpl) Module() Module {
|
||||
return c.source.logicModule
|
||||
}
|
||||
|
||||
func (c *outgoingTransitionContextImpl) Provider(provider AnyProviderKey) (any, bool) {
|
||||
return c.context.provider(c.source, provider.provider())
|
||||
}
|
||||
|
||||
type incomingTransitionContextImpl struct {
|
||||
transitionContextImpl
|
||||
}
|
||||
|
||||
func (c *incomingTransitionContextImpl) Module() Module {
|
||||
return c.dep.logicModule
|
||||
}
|
||||
|
||||
func (c *incomingTransitionContextImpl) Provider(provider AnyProviderKey) (any, bool) {
|
||||
return c.context.provider(c.dep, provider.provider())
|
||||
}
|
||||
|
||||
func (t *transitionMutatorImpl) transition(mctx BaseModuleContext) Transition {
|
||||
return func(source *moduleInfo, sourceVariation string, dep *moduleInfo, depTag DependencyTag) string {
|
||||
tc := transitionContextImpl{
|
||||
context: mctx.base().context,
|
||||
source: source,
|
||||
dep: dep,
|
||||
depTag: depTag,
|
||||
config: mctx.Config(),
|
||||
}
|
||||
outgoingVariation := t.mutator.OutgoingTransition(&outgoingTransitionContextImpl{tc}, sourceVariation)
|
||||
if mctx.Failed() {
|
||||
return outgoingVariation
|
||||
}
|
||||
finalVariation := t.mutator.IncomingTransition(&incomingTransitionContextImpl{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
|
||||
outgoingTransitionCache := mc.module.outgoingTransitionCache
|
||||
mc.module.transitionVariations = nil
|
||||
mc.module.outgoingTransitionCache = 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.context.convertDepsToVariation(mc.module, 0,
|
||||
chooseDepByIndexes(mc.mutator.name, outgoingTransitionCache))
|
||||
} else {
|
||||
mc.createVariationsWithTransition(variations, outgoingTransitionCache)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// 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 *moduleInfo, sourceVariation string, dep *moduleInfo, depTag DependencyTag) string
|
198
transition_test.go
Normal file
198
transition_test.go
Normal file
|
@ -0,0 +1,198 @@
|
|||
// Copyright 2024 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package blueprint
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransition(t *testing.T) {
|
||||
ctx := newContext()
|
||||
ctx.MockFileSystem(map[string][]byte{
|
||||
"Android.bp": []byte(`
|
||||
transition_module {
|
||||
name: "A",
|
||||
deps: ["B", "C"],
|
||||
split: ["a", "b"],
|
||||
}
|
||||
|
||||
transition_module {
|
||||
name: "B",
|
||||
deps: ["C"],
|
||||
outgoing: "c",
|
||||
}
|
||||
|
||||
transition_module {
|
||||
name: "C",
|
||||
deps: ["D"],
|
||||
}
|
||||
|
||||
transition_module {
|
||||
name: "D",
|
||||
incoming: "d",
|
||||
}
|
||||
`),
|
||||
})
|
||||
|
||||
ctx.RegisterBottomUpMutator("deps", depsMutator)
|
||||
ctx.RegisterTransitionMutator("transition", transitionTestMutator{})
|
||||
|
||||
ctx.RegisterModuleType("transition_module", newTransitionModule)
|
||||
_, errs := ctx.ParseBlueprintsFiles("Android.bp", nil)
|
||||
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()
|
||||
}
|
||||
|
||||
getModule := func(name, variant string) *transitionModule {
|
||||
group := ctx.moduleGroupFromName(name, nil)
|
||||
module := group.moduleOrAliasByVariantName(variant).module()
|
||||
return module.logicModule.(*transitionModule)
|
||||
}
|
||||
|
||||
checkVariants := func(name string, expectedVariants []string) {
|
||||
t.Helper()
|
||||
group := ctx.moduleGroupFromName(name, nil)
|
||||
var gotVariants []string
|
||||
for _, variant := range group.modules {
|
||||
gotVariants = append(gotVariants, variant.moduleOrAliasVariant().variations["transition"])
|
||||
}
|
||||
if !slices.Equal(expectedVariants, gotVariants) {
|
||||
t.Errorf("expected variants of %q to be %q, got %q", name, expectedVariants, gotVariants)
|
||||
}
|
||||
}
|
||||
|
||||
// Module A uses Split to create a and b variants
|
||||
checkVariants("A", []string{"a", "b"})
|
||||
// Module B inherits a and b variants from A
|
||||
checkVariants("B", []string{"", "a", "b"})
|
||||
// Module C inherits a and b variants from A, but gets an outgoing c variant from B
|
||||
checkVariants("C", []string{"", "a", "b", "c"})
|
||||
// Module D always has incoming variant d
|
||||
checkVariants("D", []string{"", "d"})
|
||||
|
||||
A_a := getModule("A", "a")
|
||||
A_b := getModule("A", "b")
|
||||
B_a := getModule("B", "a")
|
||||
B_b := getModule("B", "b")
|
||||
C_a := getModule("C", "a")
|
||||
C_b := getModule("C", "b")
|
||||
C_c := getModule("C", "c")
|
||||
D_d := getModule("D", "d")
|
||||
|
||||
checkDeps := func(m Module, expected ...string) {
|
||||
var got []string
|
||||
ctx.VisitDirectDeps(m, func(m Module) {
|
||||
got = append(got, ctx.ModuleName(m)+"("+ctx.ModuleSubDir(m)+")")
|
||||
})
|
||||
if !slices.Equal(got, expected) {
|
||||
t.Errorf("unexpected %q dependencies, got %q expected %q",
|
||||
ctx.ModuleName(m), got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
checkDeps(A_a, "B(a)", "C(a)")
|
||||
checkDeps(A_b, "B(b)", "C(b)")
|
||||
checkDeps(B_a, "C(c)")
|
||||
checkDeps(B_b, "C(c)")
|
||||
checkDeps(C_a, "D(d)")
|
||||
checkDeps(C_b, "D(d)")
|
||||
checkDeps(C_c, "D(d)")
|
||||
checkDeps(D_d)
|
||||
|
||||
checkMutate := func(m *transitionModule, variant string) {
|
||||
t.Helper()
|
||||
if m.properties.Mutated != variant {
|
||||
t.Errorf("unexpected mutated property in %q, expected %q got %q", m.Name(), variant, m.properties.Mutated)
|
||||
}
|
||||
}
|
||||
|
||||
checkMutate(A_a, "a")
|
||||
checkMutate(A_b, "b")
|
||||
checkMutate(B_a, "a")
|
||||
checkMutate(B_b, "b")
|
||||
checkMutate(C_a, "a")
|
||||
checkMutate(C_b, "b")
|
||||
checkMutate(C_c, "c")
|
||||
checkMutate(D_d, "d")
|
||||
}
|
||||
|
||||
type transitionTestMutator struct{}
|
||||
|
||||
func (transitionTestMutator) Split(ctx BaseModuleContext) []string {
|
||||
if split := ctx.Module().(*transitionModule).properties.Split; len(split) > 0 {
|
||||
return split
|
||||
}
|
||||
return []string{""}
|
||||
}
|
||||
|
||||
func (transitionTestMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string {
|
||||
if outgoing := ctx.Module().(*transitionModule).properties.Outgoing; outgoing != nil {
|
||||
return *outgoing
|
||||
}
|
||||
return sourceVariation
|
||||
}
|
||||
|
||||
func (transitionTestMutator) IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string {
|
||||
if incoming := ctx.Module().(*transitionModule).properties.Incoming; incoming != nil {
|
||||
return *incoming
|
||||
}
|
||||
return incomingVariation
|
||||
}
|
||||
|
||||
func (transitionTestMutator) Mutate(ctx BottomUpMutatorContext, variation string) {
|
||||
ctx.Module().(*transitionModule).properties.Mutated = variation
|
||||
}
|
||||
|
||||
type transitionModule struct {
|
||||
SimpleName
|
||||
properties struct {
|
||||
Deps []string
|
||||
Split []string
|
||||
Outgoing *string
|
||||
Incoming *string
|
||||
|
||||
Mutated string `blueprint:"mutated"`
|
||||
}
|
||||
}
|
||||
|
||||
func newTransitionModule() (Module, []interface{}) {
|
||||
m := &transitionModule{}
|
||||
return m, []interface{}{&m.properties, &m.SimpleName.Properties}
|
||||
}
|
||||
|
||||
func (f *transitionModule) GenerateBuildActions(ModuleContext) {
|
||||
}
|
||||
|
||||
func (f *transitionModule) Deps() []string {
|
||||
return f.properties.Deps
|
||||
}
|
||||
|
||||
func (f *transitionModule) IgnoreDeps() []string {
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue