blob: a9346bbd1218728d4e46d63057a441caa0f96930 [file] [log] [blame]
Colin Cross7e129d22024-12-03 14:26:00 -08001// Copyright 2024 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import "github.com/google/blueprint"
18
19// TransitionMutator implements a top-down mechanism where a module tells its
20// direct dependencies what variation they should be built in but the dependency
21// has the final say.
22//
23// When implementing a transition mutator, one needs to implement four methods:
24// - Split() that tells what variations a module has by itself
25// - OutgoingTransition() where a module tells what it wants from its
26// dependency
27// - IncomingTransition() where a module has the final say about its own
28// variation
29// - Mutate() that changes the state of a module depending on its variation
30//
31// That the effective variation of module B when depended on by module A is the
32// composition the outgoing transition of module A and the incoming transition
33// of module B.
34//
35// the outgoing transition should not take the properties of the dependency into
36// account, only those of the module that depends on it. For this reason, the
37// dependency is not even passed into it as an argument. Likewise, the incoming
38// transition should not take the properties of the depending module into
39// account and is thus not informed about it. This makes for a nice
40// decomposition of the decision logic.
41//
42// A given transition mutator only affects its own variation; other variations
43// stay unchanged along the dependency edges.
44//
45// Soong makes sure that all modules are created in the desired variations and
46// that dependency edges are set up correctly. This ensures that "missing
47// variation" errors do not happen and allows for more flexible changes in the
48// value of the variation among dependency edges (as oppposed to bottom-up
49// mutators where if module A in variation X depends on module B and module B
50// has that variation X, A must depend on variation X of B)
51//
52// The limited power of the context objects passed to individual mutators
53// methods also makes it more difficult to shoot oneself in the foot. Complete
54// safety is not guaranteed because no one prevents individual transition
55// mutators from mutating modules in illegal ways and for e.g. Split() or
56// Mutate() to run their own visitations of the transitive dependency of the
57// module and both of these are bad ideas, but it's better than no guardrails at
58// all.
59//
60// This model is pretty close to Bazel's configuration transitions. The mapping
61// between concepts in Soong and Bazel is as follows:
62// - Module == configured target
63// - Variant == configuration
64// - Variation name == configuration flag
65// - Variation == configuration flag value
66// - Outgoing transition == attribute transition
67// - Incoming transition == rule transition
68//
69// The Split() method does not have a Bazel equivalent and Bazel split
70// transitions do not have a Soong equivalent.
71//
72// Mutate() does not make sense in Bazel due to the different models of the
73// two systems: when creating new variations, Soong clones the old module and
74// thus some way is needed to change it state whereas Bazel creates each
75// configuration of a given configured target anew.
76type TransitionMutator interface {
77 // Split returns the set of variations that should be created for a module no
78 // matter who depends on it. Used when Make depends on a particular variation
79 // or when the module knows its variations just based on information given to
80 // it in the Blueprint file. This method should not mutate the module it is
81 // called on.
82 Split(ctx BaseModuleContext) []string
83
84 // OutgoingTransition is called on a module to determine which variation it wants
85 // from its direct dependencies. The dependency itself can override this decision.
86 // This method should not mutate the module itself.
87 OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
88
89 // IncomingTransition is called on a module to determine which variation it should
90 // be in based on the variation modules that depend on it want. This gives the module
91 // a final say about its own variations. This method should not mutate the module
92 // itself.
93 IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
94
95 // Mutate is called after a module was split into multiple variations on each variation.
96 // It should not split the module any further but adding new dependencies is
97 // fine. Unlike all the other methods on TransitionMutator, this method is
98 // allowed to mutate the module.
99 Mutate(ctx BottomUpMutatorContext, variation string)
100}
101
102type IncomingTransitionContext interface {
103 ArchModuleContext
104 ModuleProviderContext
105 ModuleErrorContext
106
107 // Module returns the target of the dependency edge for which the transition
108 // is being computed
109 Module() Module
110
Colin Cross4e041522025-01-31 11:43:13 -0800111 // ModuleName returns the name of the module. This is generally the value that was returned by Module.Name() when
112 // the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
113 ModuleName() string
114
115 // DepTag() Returns the dependency tag through which this dependency is
116 // reached
117 DepTag() blueprint.DependencyTag
118
Colin Cross7e129d22024-12-03 14:26:00 -0800119 // Config returns the configuration for the build.
120 Config() Config
121
122 DeviceConfig() DeviceConfig
123
124 // IsAddingDependency returns true if the transition is being called while adding a dependency
125 // after the transition mutator has already run, or false if it is being called when the transition
126 // mutator is running. This should be used sparingly, all uses will have to be removed in order
127 // to support creating variants on demand.
128 IsAddingDependency() bool
129}
130
131type OutgoingTransitionContext interface {
132 ArchModuleContext
133 ModuleProviderContext
134
135 // Module returns the target of the dependency edge for which the transition
136 // is being computed
137 Module() Module
138
Colin Cross4e041522025-01-31 11:43:13 -0800139 // ModuleName returns the name of the module. This is generally the value that was returned by Module.Name() when
140 // the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
141 ModuleName() string
142
Colin Cross7e129d22024-12-03 14:26:00 -0800143 // DepTag() Returns the dependency tag through which this dependency is
144 // reached
145 DepTag() blueprint.DependencyTag
146
147 // Config returns the configuration for the build.
148 Config() Config
149
150 DeviceConfig() DeviceConfig
151}
152
153type androidTransitionMutator struct {
154 finalPhase bool
155 mutator TransitionMutator
156 name string
157}
158
159func (a *androidTransitionMutator) Split(ctx blueprint.BaseModuleContext) []string {
160 if a.finalPhase {
161 panic("TransitionMutator not allowed in FinalDepsMutators")
162 }
163 if m, ok := ctx.Module().(Module); ok {
164 moduleContext := m.base().baseModuleContextFactory(ctx)
165 return a.mutator.Split(&moduleContext)
166 } else {
167 return []string{""}
168 }
169}
170
171func (a *androidTransitionMutator) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext, sourceVariation string) string {
172 if m, ok := bpctx.Module().(Module); ok {
173 ctx := outgoingTransitionContextPool.Get().(*outgoingTransitionContextImpl)
174 defer outgoingTransitionContextPool.Put(ctx)
175 *ctx = outgoingTransitionContextImpl{
176 archModuleContext: m.base().archModuleContextFactory(bpctx),
177 bp: bpctx,
178 }
179 return a.mutator.OutgoingTransition(ctx, sourceVariation)
180 } else {
181 return ""
182 }
183}
184
185func (a *androidTransitionMutator) IncomingTransition(bpctx blueprint.IncomingTransitionContext, incomingVariation string) string {
186 if m, ok := bpctx.Module().(Module); ok {
187 ctx := incomingTransitionContextPool.Get().(*incomingTransitionContextImpl)
188 defer incomingTransitionContextPool.Put(ctx)
189 *ctx = incomingTransitionContextImpl{
190 archModuleContext: m.base().archModuleContextFactory(bpctx),
191 bp: bpctx,
192 }
193 return a.mutator.IncomingTransition(ctx, incomingVariation)
194 } else {
195 return ""
196 }
197}
198
199func (a *androidTransitionMutator) Mutate(ctx blueprint.BottomUpMutatorContext, variation string) {
200 if am, ok := ctx.Module().(Module); ok {
201 if variation != "" {
202 // TODO: this should really be checking whether the TransitionMutator affected this module, not
203 // the empty variant, but TransitionMutator has no concept of skipping a module.
204 base := am.base()
205 base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
206 base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
207 }
208
209 mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
210 defer bottomUpMutatorContextPool.Put(mctx)
211 a.mutator.Mutate(mctx, variation)
212 }
213}
214
215type incomingTransitionContextImpl struct {
216 archModuleContext
217 bp blueprint.IncomingTransitionContext
218}
219
220func (c *incomingTransitionContextImpl) Module() Module {
221 return c.bp.Module().(Module)
222}
223
Colin Cross4e041522025-01-31 11:43:13 -0800224func (c *incomingTransitionContextImpl) ModuleName() string {
225 return c.bp.ModuleName()
226}
227
228func (c *incomingTransitionContextImpl) DepTag() blueprint.DependencyTag {
229 return c.bp.DepTag()
230}
231
Colin Cross7e129d22024-12-03 14:26:00 -0800232func (c *incomingTransitionContextImpl) Config() Config {
233 return c.bp.Config().(Config)
234}
235
236func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
237 return DeviceConfig{c.bp.Config().(Config).deviceConfig}
238}
239
240func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
241 return c.bp.IsAddingDependency()
242}
243
244func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
245 return c.bp.Provider(provider)
246}
247
248func (c *incomingTransitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
249 c.bp.ModuleErrorf(fmt, args)
250}
251
252func (c *incomingTransitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
253 c.bp.PropertyErrorf(property, fmt, args)
254}
255
256type outgoingTransitionContextImpl struct {
257 archModuleContext
258 bp blueprint.OutgoingTransitionContext
259}
260
261func (c *outgoingTransitionContextImpl) Module() Module {
262 return c.bp.Module().(Module)
263}
264
Colin Cross4e041522025-01-31 11:43:13 -0800265func (c *outgoingTransitionContextImpl) ModuleName() string {
266 return c.bp.ModuleName()
267}
268
Colin Cross7e129d22024-12-03 14:26:00 -0800269func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
270 return c.bp.DepTag()
271}
272
273func (c *outgoingTransitionContextImpl) Config() Config {
274 return c.bp.Config().(Config)
275}
276
277func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
278 return DeviceConfig{c.bp.Config().(Config).deviceConfig}
279}
280
281func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
282 return c.bp.Provider(provider)
283}