blob: 7c04effc39b45b59cca55770fd712459e8df5cf1 [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
111 // Config returns the configuration for the build.
112 Config() Config
113
114 DeviceConfig() DeviceConfig
115
116 // IsAddingDependency returns true if the transition is being called while adding a dependency
117 // after the transition mutator has already run, or false if it is being called when the transition
118 // mutator is running. This should be used sparingly, all uses will have to be removed in order
119 // to support creating variants on demand.
120 IsAddingDependency() bool
121}
122
123type OutgoingTransitionContext interface {
124 ArchModuleContext
125 ModuleProviderContext
126
127 // Module returns the target of the dependency edge for which the transition
128 // is being computed
129 Module() Module
130
131 // DepTag() Returns the dependency tag through which this dependency is
132 // reached
133 DepTag() blueprint.DependencyTag
134
135 // Config returns the configuration for the build.
136 Config() Config
137
138 DeviceConfig() DeviceConfig
139}
140
141type androidTransitionMutator struct {
142 finalPhase bool
143 mutator TransitionMutator
144 name string
145}
146
147func (a *androidTransitionMutator) Split(ctx blueprint.BaseModuleContext) []string {
148 if a.finalPhase {
149 panic("TransitionMutator not allowed in FinalDepsMutators")
150 }
151 if m, ok := ctx.Module().(Module); ok {
152 moduleContext := m.base().baseModuleContextFactory(ctx)
153 return a.mutator.Split(&moduleContext)
154 } else {
155 return []string{""}
156 }
157}
158
159func (a *androidTransitionMutator) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext, sourceVariation string) string {
160 if m, ok := bpctx.Module().(Module); ok {
161 ctx := outgoingTransitionContextPool.Get().(*outgoingTransitionContextImpl)
162 defer outgoingTransitionContextPool.Put(ctx)
163 *ctx = outgoingTransitionContextImpl{
164 archModuleContext: m.base().archModuleContextFactory(bpctx),
165 bp: bpctx,
166 }
167 return a.mutator.OutgoingTransition(ctx, sourceVariation)
168 } else {
169 return ""
170 }
171}
172
173func (a *androidTransitionMutator) IncomingTransition(bpctx blueprint.IncomingTransitionContext, incomingVariation string) string {
174 if m, ok := bpctx.Module().(Module); ok {
175 ctx := incomingTransitionContextPool.Get().(*incomingTransitionContextImpl)
176 defer incomingTransitionContextPool.Put(ctx)
177 *ctx = incomingTransitionContextImpl{
178 archModuleContext: m.base().archModuleContextFactory(bpctx),
179 bp: bpctx,
180 }
181 return a.mutator.IncomingTransition(ctx, incomingVariation)
182 } else {
183 return ""
184 }
185}
186
187func (a *androidTransitionMutator) Mutate(ctx blueprint.BottomUpMutatorContext, variation string) {
188 if am, ok := ctx.Module().(Module); ok {
189 if variation != "" {
190 // TODO: this should really be checking whether the TransitionMutator affected this module, not
191 // the empty variant, but TransitionMutator has no concept of skipping a module.
192 base := am.base()
193 base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
194 base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
195 }
196
197 mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
198 defer bottomUpMutatorContextPool.Put(mctx)
199 a.mutator.Mutate(mctx, variation)
200 }
201}
202
203type incomingTransitionContextImpl struct {
204 archModuleContext
205 bp blueprint.IncomingTransitionContext
206}
207
208func (c *incomingTransitionContextImpl) Module() Module {
209 return c.bp.Module().(Module)
210}
211
212func (c *incomingTransitionContextImpl) Config() Config {
213 return c.bp.Config().(Config)
214}
215
216func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
217 return DeviceConfig{c.bp.Config().(Config).deviceConfig}
218}
219
220func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
221 return c.bp.IsAddingDependency()
222}
223
224func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
225 return c.bp.Provider(provider)
226}
227
228func (c *incomingTransitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
229 c.bp.ModuleErrorf(fmt, args)
230}
231
232func (c *incomingTransitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
233 c.bp.PropertyErrorf(property, fmt, args)
234}
235
236type outgoingTransitionContextImpl struct {
237 archModuleContext
238 bp blueprint.OutgoingTransitionContext
239}
240
241func (c *outgoingTransitionContextImpl) Module() Module {
242 return c.bp.Module().(Module)
243}
244
245func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
246 return c.bp.DepTag()
247}
248
249func (c *outgoingTransitionContextImpl) Config() Config {
250 return c.bp.Config().(Config)
251}
252
253func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
254 return DeviceConfig{c.bp.Config().(Config).deviceConfig}
255}
256
257func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
258 return c.bp.Provider(provider)
259}