aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Cross <ccross@android.com>2024-04-16 20:14:53 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-04-16 20:14:53 +0000
commit6ed94b7f852cfbbe96a918bcd2b3a9648dbf3f5e (patch)
tree8409d51a3ea86409e7e054198d5f7b3b66f53a31
parent28357db9d0c33ce1c749371eeeff16e0d658a252 (diff)
parent95b3627f6f1902312f4a70a3222a659cad4d311f (diff)
downloadblueprint-6ed94b7f852cfbbe96a918bcd2b3a9648dbf3f5e.tar.gz
Merge "Move TransitionMutator to transition.go and add tests" into main
-rw-r--r--Android.bp2
-rw-r--r--context.go283
-rw-r--r--transition.go304
-rw-r--r--transition_test.go198
4 files changed, 504 insertions, 283 deletions
diff --git a/Android.bp b/Android.bp
index 60a5c42..f3b9e71 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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",
],
}
diff --git a/context.go b/context.go
index a648247..0dbd223 100644
--- a/context.go
+++ b/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 {
diff --git a/transition.go b/transition.go
new file mode 100644
index 0000000..31f785e
--- /dev/null
+++ b/transition.go
@@ -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
diff --git a/transition_test.go b/transition_test.go
new file mode 100644
index 0000000..128e7d5
--- /dev/null
+++ b/transition_test.go
@@ -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
+}