Move transition code to transition.go
Split the transition code out of mutator.go in preparation for adding
more complexity in TransitionMutator.
Bug: 372543712
Test: builds
Change-Id: I30f82aea1f296e0db365e0ca3606ef6a4c6cb778
diff --git a/android/Android.bp b/android/Android.bp
index dfea8f9..20cd28b 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -109,6 +109,7 @@
"test_asserts.go",
"test_suites.go",
"testing.go",
+ "transition.go",
"util.go",
"variable.go",
"vendor_api_levels.go",
@@ -154,6 +155,7 @@
"singleton_module_test.go",
"soong_config_modules_test.go",
"test_suites_test.go",
+ "transition_test.go",
"util_test.go",
"variable_test.go",
"vintf_fragment_test.go",
diff --git a/android/mutator.go b/android/mutator.go
index fdd16a8..1523794 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -337,248 +337,6 @@
return mutator
}
-type IncomingTransitionContext interface {
- ArchModuleContext
- ModuleProviderContext
- ModuleErrorContext
-
- // Module returns the target of the dependency edge for which the transition
- // is being computed
- Module() Module
-
- // Config returns the configuration for the build.
- Config() Config
-
- DeviceConfig() DeviceConfig
-
- // IsAddingDependency returns true if the transition is being called while adding a dependency
- // after the transition mutator has already run, or false if it is being called when the transition
- // mutator is running. This should be used sparingly, all uses will have to be removed in order
- // to support creating variants on demand.
- IsAddingDependency() bool
-}
-
-type OutgoingTransitionContext interface {
- ArchModuleContext
- ModuleProviderContext
-
- // 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() blueprint.DependencyTag
-
- // Config returns the configuration for the build.
- Config() Config
-
- DeviceConfig() DeviceConfig
-}
-
-// 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 oppposed 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 androidTransitionMutator struct {
- finalPhase bool
- mutator TransitionMutator
- name string
-}
-
-func (a *androidTransitionMutator) Split(ctx blueprint.BaseModuleContext) []string {
- if a.finalPhase {
- panic("TransitionMutator not allowed in FinalDepsMutators")
- }
- if m, ok := ctx.Module().(Module); ok {
- moduleContext := m.base().baseModuleContextFactory(ctx)
- return a.mutator.Split(&moduleContext)
- } else {
- return []string{""}
- }
-}
-
-type outgoingTransitionContextImpl struct {
- archModuleContext
- bp blueprint.OutgoingTransitionContext
-}
-
-func (c *outgoingTransitionContextImpl) Module() Module {
- return c.bp.Module().(Module)
-}
-
-func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
- return c.bp.DepTag()
-}
-
-func (c *outgoingTransitionContextImpl) Config() Config {
- return c.bp.Config().(Config)
-}
-
-func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
- return DeviceConfig{c.bp.Config().(Config).deviceConfig}
-}
-
-func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
- return c.bp.Provider(provider)
-}
-
-func (a *androidTransitionMutator) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext, sourceVariation string) string {
- if m, ok := bpctx.Module().(Module); ok {
- ctx := outgoingTransitionContextPool.Get().(*outgoingTransitionContextImpl)
- defer outgoingTransitionContextPool.Put(ctx)
- *ctx = outgoingTransitionContextImpl{
- archModuleContext: m.base().archModuleContextFactory(bpctx),
- bp: bpctx,
- }
- return a.mutator.OutgoingTransition(ctx, sourceVariation)
- } else {
- return ""
- }
-}
-
-type incomingTransitionContextImpl struct {
- archModuleContext
- bp blueprint.IncomingTransitionContext
-}
-
-func (c *incomingTransitionContextImpl) Module() Module {
- return c.bp.Module().(Module)
-}
-
-func (c *incomingTransitionContextImpl) Config() Config {
- return c.bp.Config().(Config)
-}
-
-func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
- return DeviceConfig{c.bp.Config().(Config).deviceConfig}
-}
-
-func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
- return c.bp.IsAddingDependency()
-}
-
-func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
- return c.bp.Provider(provider)
-}
-
-func (c *incomingTransitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
- c.bp.ModuleErrorf(fmt, args)
-}
-
-func (c *incomingTransitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
- c.bp.PropertyErrorf(property, fmt, args)
-}
-
-func (a *androidTransitionMutator) IncomingTransition(bpctx blueprint.IncomingTransitionContext, incomingVariation string) string {
- if m, ok := bpctx.Module().(Module); ok {
- ctx := incomingTransitionContextPool.Get().(*incomingTransitionContextImpl)
- defer incomingTransitionContextPool.Put(ctx)
- *ctx = incomingTransitionContextImpl{
- archModuleContext: m.base().archModuleContextFactory(bpctx),
- bp: bpctx,
- }
- return a.mutator.IncomingTransition(ctx, incomingVariation)
- } else {
- return ""
- }
-}
-
-func (a *androidTransitionMutator) Mutate(ctx blueprint.BottomUpMutatorContext, variation string) {
- if am, ok := ctx.Module().(Module); ok {
- if variation != "" {
- // TODO: this should really be checking whether the TransitionMutator affected this module, not
- // the empty variant, but TransitionMutator has no concept of skipping a module.
- base := am.base()
- base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
- base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
- }
-
- mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
- defer bottomUpMutatorContextPool.Put(mctx)
- a.mutator.Mutate(mctx, variation)
- }
-}
-
func (x *registerMutatorsContext) Transition(name string, m TransitionMutator) TransitionMutatorHandle {
atm := &androidTransitionMutator{
finalPhase: x.finalPhase,
diff --git a/android/mutator_test.go b/android/mutator_test.go
index 1d5f890..60a6119 100644
--- a/android/mutator_test.go
+++ b/android/mutator_test.go
@@ -83,132 +83,6 @@
AssertDeepEquals(t, "foo missing deps", []string{"added_missing_dep", "regular_missing_dep"}, foo.missingDeps)
}
-type testTransitionMutator struct {
- split func(ctx BaseModuleContext) []string
- outgoingTransition func(ctx OutgoingTransitionContext, sourceVariation string) string
- incomingTransition func(ctx IncomingTransitionContext, incomingVariation string) string
- mutate func(ctx BottomUpMutatorContext, variation string)
-}
-
-func (t *testTransitionMutator) Split(ctx BaseModuleContext) []string {
- if t.split != nil {
- return t.split(ctx)
- }
- return []string{""}
-}
-
-func (t *testTransitionMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string {
- if t.outgoingTransition != nil {
- return t.outgoingTransition(ctx, sourceVariation)
- }
- return sourceVariation
-}
-
-func (t *testTransitionMutator) IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string {
- if t.incomingTransition != nil {
- return t.incomingTransition(ctx, incomingVariation)
- }
- return incomingVariation
-}
-
-func (t *testTransitionMutator) Mutate(ctx BottomUpMutatorContext, variation string) {
- if t.mutate != nil {
- t.mutate(ctx, variation)
- }
-}
-
-func TestModuleString(t *testing.T) {
- bp := `
- test {
- name: "foo",
- }
- `
-
- var moduleStrings []string
-
- GroupFixturePreparers(
- FixtureRegisterWithContext(func(ctx RegistrationContext) {
-
- ctx.PreArchMutators(func(ctx RegisterMutatorsContext) {
- ctx.Transition("pre_arch", &testTransitionMutator{
- split: func(ctx BaseModuleContext) []string {
- moduleStrings = append(moduleStrings, ctx.Module().String())
- return []string{"a", "b"}
- },
- })
- })
-
- ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
- ctx.Transition("pre_deps", &testTransitionMutator{
- split: func(ctx BaseModuleContext) []string {
- moduleStrings = append(moduleStrings, ctx.Module().String())
- return []string{"c", "d"}
- },
- })
- })
-
- ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
- ctx.Transition("post_deps", &testTransitionMutator{
- split: func(ctx BaseModuleContext) []string {
- moduleStrings = append(moduleStrings, ctx.Module().String())
- return []string{"e", "f"}
- },
- outgoingTransition: func(ctx OutgoingTransitionContext, sourceVariation string) string {
- return ""
- },
- })
- ctx.BottomUp("rename_bottom_up", func(ctx BottomUpMutatorContext) {
- moduleStrings = append(moduleStrings, ctx.Module().String())
- ctx.Rename(ctx.Module().base().Name() + "_renamed1")
- }).UsesRename()
- ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
- moduleStrings = append(moduleStrings, ctx.Module().String())
- })
- })
-
- ctx.RegisterModuleType("test", mutatorTestModuleFactory)
- }),
- FixtureWithRootAndroidBp(bp),
- ).RunTest(t)
-
- want := []string{
- // Initial name.
- "foo{}",
-
- // After pre_arch (reversed because rename_top_down is TopDown so it visits in reverse order).
- "foo{pre_arch:b}",
- "foo{pre_arch:a}",
-
- // After pre_deps (reversed because post_deps TransitionMutator.Split is TopDown).
- "foo{pre_arch:b,pre_deps:d}",
- "foo{pre_arch:b,pre_deps:c}",
- "foo{pre_arch:a,pre_deps:d}",
- "foo{pre_arch:a,pre_deps:c}",
-
- // After post_deps.
- "foo{pre_arch:a,pre_deps:c,post_deps:e}",
- "foo{pre_arch:a,pre_deps:c,post_deps:f}",
- "foo{pre_arch:a,pre_deps:d,post_deps:e}",
- "foo{pre_arch:a,pre_deps:d,post_deps:f}",
- "foo{pre_arch:b,pre_deps:c,post_deps:e}",
- "foo{pre_arch:b,pre_deps:c,post_deps:f}",
- "foo{pre_arch:b,pre_deps:d,post_deps:e}",
- "foo{pre_arch:b,pre_deps:d,post_deps:f}",
-
- // After rename_bottom_up.
- "foo_renamed1{pre_arch:a,pre_deps:c,post_deps:e}",
- "foo_renamed1{pre_arch:a,pre_deps:c,post_deps:f}",
- "foo_renamed1{pre_arch:a,pre_deps:d,post_deps:e}",
- "foo_renamed1{pre_arch:a,pre_deps:d,post_deps:f}",
- "foo_renamed1{pre_arch:b,pre_deps:c,post_deps:e}",
- "foo_renamed1{pre_arch:b,pre_deps:c,post_deps:f}",
- "foo_renamed1{pre_arch:b,pre_deps:d,post_deps:e}",
- "foo_renamed1{pre_arch:b,pre_deps:d,post_deps:f}",
- }
-
- AssertDeepEquals(t, "module String() values", want, moduleStrings)
-}
-
func TestFinalDepsPhase(t *testing.T) {
bp := `
test {
@@ -288,22 +162,3 @@
AssertDeepEquals(t, "final", finalWant, finalGotMap)
}
-
-func TestTransitionMutatorInFinalDeps(t *testing.T) {
- GroupFixturePreparers(
- FixtureRegisterWithContext(func(ctx RegistrationContext) {
- ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
- ctx.Transition("vars", &testTransitionMutator{
- split: func(ctx BaseModuleContext) []string {
- return []string{"a", "b"}
- },
- })
- })
-
- ctx.RegisterModuleType("test", mutatorTestModuleFactory)
- }),
- FixtureWithRootAndroidBp(`test {name: "foo"}`),
- ).
- ExtendWithErrorHandler(FixtureExpectsOneErrorPattern("not allowed in FinalDepsMutators")).
- RunTest(t)
-}
diff --git a/android/transition.go b/android/transition.go
new file mode 100644
index 0000000..7c04eff
--- /dev/null
+++ b/android/transition.go
@@ -0,0 +1,259 @@
+// 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 android
+
+import "github.com/google/blueprint"
+
+// 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 oppposed 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 {
+ ArchModuleContext
+ ModuleProviderContext
+ ModuleErrorContext
+
+ // Module returns the target of the dependency edge for which the transition
+ // is being computed
+ Module() Module
+
+ // Config returns the configuration for the build.
+ Config() Config
+
+ DeviceConfig() DeviceConfig
+
+ // IsAddingDependency returns true if the transition is being called while adding a dependency
+ // after the transition mutator has already run, or false if it is being called when the transition
+ // mutator is running. This should be used sparingly, all uses will have to be removed in order
+ // to support creating variants on demand.
+ IsAddingDependency() bool
+}
+
+type OutgoingTransitionContext interface {
+ ArchModuleContext
+ ModuleProviderContext
+
+ // 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() blueprint.DependencyTag
+
+ // Config returns the configuration for the build.
+ Config() Config
+
+ DeviceConfig() DeviceConfig
+}
+
+type androidTransitionMutator struct {
+ finalPhase bool
+ mutator TransitionMutator
+ name string
+}
+
+func (a *androidTransitionMutator) Split(ctx blueprint.BaseModuleContext) []string {
+ if a.finalPhase {
+ panic("TransitionMutator not allowed in FinalDepsMutators")
+ }
+ if m, ok := ctx.Module().(Module); ok {
+ moduleContext := m.base().baseModuleContextFactory(ctx)
+ return a.mutator.Split(&moduleContext)
+ } else {
+ return []string{""}
+ }
+}
+
+func (a *androidTransitionMutator) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext, sourceVariation string) string {
+ if m, ok := bpctx.Module().(Module); ok {
+ ctx := outgoingTransitionContextPool.Get().(*outgoingTransitionContextImpl)
+ defer outgoingTransitionContextPool.Put(ctx)
+ *ctx = outgoingTransitionContextImpl{
+ archModuleContext: m.base().archModuleContextFactory(bpctx),
+ bp: bpctx,
+ }
+ return a.mutator.OutgoingTransition(ctx, sourceVariation)
+ } else {
+ return ""
+ }
+}
+
+func (a *androidTransitionMutator) IncomingTransition(bpctx blueprint.IncomingTransitionContext, incomingVariation string) string {
+ if m, ok := bpctx.Module().(Module); ok {
+ ctx := incomingTransitionContextPool.Get().(*incomingTransitionContextImpl)
+ defer incomingTransitionContextPool.Put(ctx)
+ *ctx = incomingTransitionContextImpl{
+ archModuleContext: m.base().archModuleContextFactory(bpctx),
+ bp: bpctx,
+ }
+ return a.mutator.IncomingTransition(ctx, incomingVariation)
+ } else {
+ return ""
+ }
+}
+
+func (a *androidTransitionMutator) Mutate(ctx blueprint.BottomUpMutatorContext, variation string) {
+ if am, ok := ctx.Module().(Module); ok {
+ if variation != "" {
+ // TODO: this should really be checking whether the TransitionMutator affected this module, not
+ // the empty variant, but TransitionMutator has no concept of skipping a module.
+ base := am.base()
+ base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
+ base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
+ }
+
+ mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
+ defer bottomUpMutatorContextPool.Put(mctx)
+ a.mutator.Mutate(mctx, variation)
+ }
+}
+
+type incomingTransitionContextImpl struct {
+ archModuleContext
+ bp blueprint.IncomingTransitionContext
+}
+
+func (c *incomingTransitionContextImpl) Module() Module {
+ return c.bp.Module().(Module)
+}
+
+func (c *incomingTransitionContextImpl) Config() Config {
+ return c.bp.Config().(Config)
+}
+
+func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
+ return DeviceConfig{c.bp.Config().(Config).deviceConfig}
+}
+
+func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
+ return c.bp.IsAddingDependency()
+}
+
+func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
+ return c.bp.Provider(provider)
+}
+
+func (c *incomingTransitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
+ c.bp.ModuleErrorf(fmt, args)
+}
+
+func (c *incomingTransitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
+ c.bp.PropertyErrorf(property, fmt, args)
+}
+
+type outgoingTransitionContextImpl struct {
+ archModuleContext
+ bp blueprint.OutgoingTransitionContext
+}
+
+func (c *outgoingTransitionContextImpl) Module() Module {
+ return c.bp.Module().(Module)
+}
+
+func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
+ return c.bp.DepTag()
+}
+
+func (c *outgoingTransitionContextImpl) Config() Config {
+ return c.bp.Config().(Config)
+}
+
+func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
+ return DeviceConfig{c.bp.Config().(Config).deviceConfig}
+}
+
+func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
+ return c.bp.Provider(provider)
+}
diff --git a/android/transition_test.go b/android/transition_test.go
new file mode 100644
index 0000000..f7618f3
--- /dev/null
+++ b/android/transition_test.go
@@ -0,0 +1,162 @@
+// 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 android
+
+import "testing"
+
+type testTransitionMutator struct {
+ split func(ctx BaseModuleContext) []string
+ outgoingTransition func(ctx OutgoingTransitionContext, sourceVariation string) string
+ incomingTransition func(ctx IncomingTransitionContext, incomingVariation string) string
+ mutate func(ctx BottomUpMutatorContext, variation string)
+}
+
+func (t *testTransitionMutator) Split(ctx BaseModuleContext) []string {
+ if t.split != nil {
+ return t.split(ctx)
+ }
+ return []string{""}
+}
+
+func (t *testTransitionMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string {
+ if t.outgoingTransition != nil {
+ return t.outgoingTransition(ctx, sourceVariation)
+ }
+ return sourceVariation
+}
+
+func (t *testTransitionMutator) IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string {
+ if t.incomingTransition != nil {
+ return t.incomingTransition(ctx, incomingVariation)
+ }
+ return incomingVariation
+}
+
+func (t *testTransitionMutator) Mutate(ctx BottomUpMutatorContext, variation string) {
+ if t.mutate != nil {
+ t.mutate(ctx, variation)
+ }
+}
+
+func TestModuleString(t *testing.T) {
+ bp := `
+ test {
+ name: "foo",
+ }
+ `
+
+ var moduleStrings []string
+
+ GroupFixturePreparers(
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+
+ ctx.PreArchMutators(func(ctx RegisterMutatorsContext) {
+ ctx.Transition("pre_arch", &testTransitionMutator{
+ split: func(ctx BaseModuleContext) []string {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ return []string{"a", "b"}
+ },
+ })
+ })
+
+ ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+ ctx.Transition("pre_deps", &testTransitionMutator{
+ split: func(ctx BaseModuleContext) []string {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ return []string{"c", "d"}
+ },
+ })
+ })
+
+ ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
+ ctx.Transition("post_deps", &testTransitionMutator{
+ split: func(ctx BaseModuleContext) []string {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ return []string{"e", "f"}
+ },
+ outgoingTransition: func(ctx OutgoingTransitionContext, sourceVariation string) string {
+ return ""
+ },
+ })
+ ctx.BottomUp("rename_bottom_up", func(ctx BottomUpMutatorContext) {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ ctx.Rename(ctx.Module().base().Name() + "_renamed1")
+ }).UsesRename()
+ ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
+ moduleStrings = append(moduleStrings, ctx.Module().String())
+ })
+ })
+
+ ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+ }),
+ FixtureWithRootAndroidBp(bp),
+ ).RunTest(t)
+
+ want := []string{
+ // Initial name.
+ "foo{}",
+
+ // After pre_arch (reversed because rename_top_down is TopDown so it visits in reverse order).
+ "foo{pre_arch:b}",
+ "foo{pre_arch:a}",
+
+ // After pre_deps (reversed because post_deps TransitionMutator.Split is TopDown).
+ "foo{pre_arch:b,pre_deps:d}",
+ "foo{pre_arch:b,pre_deps:c}",
+ "foo{pre_arch:a,pre_deps:d}",
+ "foo{pre_arch:a,pre_deps:c}",
+
+ // After post_deps.
+ "foo{pre_arch:a,pre_deps:c,post_deps:e}",
+ "foo{pre_arch:a,pre_deps:c,post_deps:f}",
+ "foo{pre_arch:a,pre_deps:d,post_deps:e}",
+ "foo{pre_arch:a,pre_deps:d,post_deps:f}",
+ "foo{pre_arch:b,pre_deps:c,post_deps:e}",
+ "foo{pre_arch:b,pre_deps:c,post_deps:f}",
+ "foo{pre_arch:b,pre_deps:d,post_deps:e}",
+ "foo{pre_arch:b,pre_deps:d,post_deps:f}",
+
+ // After rename_bottom_up.
+ "foo_renamed1{pre_arch:a,pre_deps:c,post_deps:e}",
+ "foo_renamed1{pre_arch:a,pre_deps:c,post_deps:f}",
+ "foo_renamed1{pre_arch:a,pre_deps:d,post_deps:e}",
+ "foo_renamed1{pre_arch:a,pre_deps:d,post_deps:f}",
+ "foo_renamed1{pre_arch:b,pre_deps:c,post_deps:e}",
+ "foo_renamed1{pre_arch:b,pre_deps:c,post_deps:f}",
+ "foo_renamed1{pre_arch:b,pre_deps:d,post_deps:e}",
+ "foo_renamed1{pre_arch:b,pre_deps:d,post_deps:f}",
+ }
+
+ AssertDeepEquals(t, "module String() values", want, moduleStrings)
+}
+
+func TestTransitionMutatorInFinalDeps(t *testing.T) {
+ GroupFixturePreparers(
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
+ ctx.Transition("vars", &testTransitionMutator{
+ split: func(ctx BaseModuleContext) []string {
+ return []string{"a", "b"}
+ },
+ })
+ })
+
+ ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+ }),
+ FixtureWithRootAndroidBp(`test {name: "foo"}`),
+ ).
+ ExtendWithErrorHandler(FixtureExpectsOneErrorPattern("not allowed in FinalDepsMutators")).
+ RunTest(t)
+}