blob: 2867c354404d5f733989af44e0443d2efe76f1f2 [file] [log] [blame]
Colin Cross95b36272024-04-12 14:48:34 -07001// 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 blueprint
16
17import (
18 "fmt"
19 "slices"
20 "sort"
21)
22
23// TransitionMutator implements a top-down mechanism where a module tells its
24// direct dependencies what variation they should be built in but the dependency
25// has the final say.
26//
27// When implementing a transition mutator, one needs to implement four methods:
28// - Split() that tells what variations a module has by itself
29// - OutgoingTransition() where a module tells what it wants from its
30// dependency
31// - IncomingTransition() where a module has the final say about its own
32// variation
33// - Mutate() that changes the state of a module depending on its variation
34//
35// That the effective variation of module B when depended on by module A is the
36// composition the outgoing transition of module A and the incoming transition
37// of module B.
38//
39// The outgoing transition should not take the properties of the dependency into
40// account, only those of the module that depends on it. For this reason, the
41// dependency is not even passed into it as an argument. Likewise, the incoming
42// transition should not take the properties of the depending module into
43// account and is thus not informed about it. This makes for a nice
44// decomposition of the decision logic.
45//
46// A given transition mutator only affects its own variation; other variations
47// stay unchanged along the dependency edges.
48//
49// Soong makes sure that all modules are created in the desired variations and
50// that dependency edges are set up correctly. This ensures that "missing
51// variation" errors do not happen and allows for more flexible changes in the
52// value of the variation among dependency edges (as opposed to bottom-up
53// mutators where if module A in variation X depends on module B and module B
54// has that variation X, A must depend on variation X of B)
55//
56// The limited power of the context objects passed to individual mutators
57// methods also makes it more difficult to shoot oneself in the foot. Complete
58// safety is not guaranteed because no one prevents individual transition
59// mutators from mutating modules in illegal ways and for e.g. Split() or
60// Mutate() to run their own visitations of the transitive dependency of the
61// module and both of these are bad ideas, but it's better than no guardrails at
62// all.
63//
64// This model is pretty close to Bazel's configuration transitions. The mapping
65// between concepts in Soong and Bazel is as follows:
66// - Module == configured target
67// - Variant == configuration
68// - Variation name == configuration flag
69// - Variation == configuration flag value
70// - Outgoing transition == attribute transition
71// - Incoming transition == rule transition
72//
73// The Split() method does not have a Bazel equivalent and Bazel split
74// transitions do not have a Soong equivalent.
75//
76// Mutate() does not make sense in Bazel due to the different models of the
77// two systems: when creating new variations, Soong clones the old module and
78// thus some way is needed to change it state whereas Bazel creates each
79// configuration of a given configured target anew.
80type TransitionMutator interface {
81 // Split returns the set of variations that should be created for a module no matter
82 // who depends on it. Used when Make depends on a particular variation or when
83 // the module knows its variations just based on information given to it in
84 // the Blueprint file. This method should not mutate the module it is called
85 // on.
86 Split(ctx BaseModuleContext) []string
87
88 // OutgoingTransition is called on a module to determine which variation it wants
89 // from its direct dependencies. The dependency itself can override this decision.
90 // This method should not mutate the module itself.
91 OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
92
93 // IncomingTransition is called on a module to determine which variation it should
94 // be in based on the variation modules that depend on it want. This gives the module
95 // a final say about its own variations. This method should not mutate the module
96 // itself.
97 IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
98
99 // Mutate is called after a module was split into multiple variations on each
100 // variation. It should not split the module any further but adding new dependencies
101 // is fine. Unlike all the other methods on TransitionMutator, this method is
102 // allowed to mutate the module.
103 Mutate(ctx BottomUpMutatorContext, variation string)
104}
105
106type IncomingTransitionContext interface {
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 config object that was passed to
112 // Context.PrepareBuildActions.
113 Config() interface{}
114
115 // Provider returns the value for a provider for the target of the dependency edge for which the
116 // transition is being computed. If the value is not set it returns nil and false. It panics if
117 // called before the appropriate mutator or GenerateBuildActions pass for the provider. The value
118 // returned may be a deep copy of the value originally passed to SetProvider.
119 //
120 // This method shouldn't be used directly, prefer the type-safe android.ModuleProvider instead.
121 Provider(provider AnyProviderKey) (any, bool)
122}
123
124type OutgoingTransitionContext interface {
125 // Module returns the source of the dependency edge for which the transition
126 // is being computed
127 Module() Module
128
129 // DepTag() Returns the dependency tag through which this dependency is
130 // reached
131 DepTag() DependencyTag
132
133 // Config returns the config object that was passed to
134 // Context.PrepareBuildActions.
135 Config() interface{}
136
137 // Provider returns the value for a provider for the source of the dependency edge for which the
138 // transition is being computed. If the value is not set it returns nil and false. It panics if
139 // called before the appropriate mutator or GenerateBuildActions pass for the provider. The value
140 // returned may be a deep copy of the value originally passed to SetProvider.
141 //
142 // This method shouldn't be used directly, prefer the type-safe android.ModuleProvider instead.
143 Provider(provider AnyProviderKey) (any, bool)
144}
145
146type transitionMutatorImpl struct {
Colin Crossd7474dd2024-03-29 12:25:29 -0700147 name string
148 mutator TransitionMutator
149 inputVariants map[*moduleGroup][]*moduleInfo
Colin Cross95b36272024-04-12 14:48:34 -0700150}
151
152// Adds each argument in items to l if it's not already there.
153func addToStringListIfNotPresent(l []string, items ...string) []string {
154 for _, i := range items {
155 if !slices.Contains(l, i) {
156 l = append(l, i)
157 }
158 }
159
160 return l
161}
162
163func (t *transitionMutatorImpl) addRequiredVariation(m *moduleInfo, variation string) {
164 m.requiredVariationsLock.Lock()
165 defer m.requiredVariationsLock.Unlock()
166
167 // This is only a consistency check. Leaking the variations of a transition
168 // mutator to another one could well lead to issues that are difficult to
169 // track down.
170 if m.currentTransitionMutator != "" && m.currentTransitionMutator != t.name {
171 panic(fmt.Errorf("transition mutator is %s in mutator %s", m.currentTransitionMutator, t.name))
172 }
173
174 m.currentTransitionMutator = t.name
175 m.transitionVariations = addToStringListIfNotPresent(m.transitionVariations, variation)
176}
177
178func (t *transitionMutatorImpl) topDownMutator(mctx TopDownMutatorContext) {
179 module := mctx.(*mutatorContext).module
180 mutatorSplits := t.mutator.Split(mctx)
181 if mutatorSplits == nil || len(mutatorSplits) == 0 {
182 panic(fmt.Errorf("transition mutator %s returned no splits for module %s", t.name, mctx.ModuleName()))
183 }
184
185 // transitionVariations for given a module can be mutated by the module itself
186 // and modules that directly depend on it. Since this is a top-down mutator,
187 // all modules that directly depend on this module have already been processed
188 // so no locking is necessary.
Colin Crossc5b3c0c2024-05-07 13:49:51 -0700189 // Sort the module transitions, but keep the mutatorSplits in the order returned
190 // by Split, as the order can be significant when inter-variant dependencies are
191 // used.
Colin Cross95b36272024-04-12 14:48:34 -0700192 sort.Strings(module.transitionVariations)
Colin Crossc5b3c0c2024-05-07 13:49:51 -0700193 module.transitionVariations = addToStringListIfNotPresent(mutatorSplits, module.transitionVariations...)
Colin Cross95b36272024-04-12 14:48:34 -0700194
195 outgoingTransitionCache := make([][]string, len(module.transitionVariations))
196 for srcVariationIndex, srcVariation := range module.transitionVariations {
197 srcVariationTransitionCache := make([]string, len(module.directDeps))
198 for depIndex, dep := range module.directDeps {
199 finalVariation := t.transition(mctx)(mctx.moduleInfo(), srcVariation, dep.module, dep.tag)
200 srcVariationTransitionCache[depIndex] = finalVariation
201 t.addRequiredVariation(dep.module, finalVariation)
202 }
203 outgoingTransitionCache[srcVariationIndex] = srcVariationTransitionCache
204 }
205 module.outgoingTransitionCache = outgoingTransitionCache
206}
207
208type transitionContextImpl struct {
209 context *Context
210 source *moduleInfo
211 dep *moduleInfo
212 depTag DependencyTag
213 config interface{}
214}
215
216func (c *transitionContextImpl) DepTag() DependencyTag {
217 return c.depTag
218}
219
220func (c *transitionContextImpl) Config() interface{} {
221 return c.config
222}
223
224type outgoingTransitionContextImpl struct {
225 transitionContextImpl
226}
227
228func (c *outgoingTransitionContextImpl) Module() Module {
229 return c.source.logicModule
230}
231
232func (c *outgoingTransitionContextImpl) Provider(provider AnyProviderKey) (any, bool) {
233 return c.context.provider(c.source, provider.provider())
234}
235
236type incomingTransitionContextImpl struct {
237 transitionContextImpl
238}
239
240func (c *incomingTransitionContextImpl) Module() Module {
241 return c.dep.logicModule
242}
243
244func (c *incomingTransitionContextImpl) Provider(provider AnyProviderKey) (any, bool) {
245 return c.context.provider(c.dep, provider.provider())
246}
247
248func (t *transitionMutatorImpl) transition(mctx BaseModuleContext) Transition {
249 return func(source *moduleInfo, sourceVariation string, dep *moduleInfo, depTag DependencyTag) string {
250 tc := transitionContextImpl{
251 context: mctx.base().context,
252 source: source,
253 dep: dep,
254 depTag: depTag,
255 config: mctx.Config(),
256 }
257 outgoingVariation := t.mutator.OutgoingTransition(&outgoingTransitionContextImpl{tc}, sourceVariation)
258 if mctx.Failed() {
259 return outgoingVariation
260 }
261 finalVariation := t.mutator.IncomingTransition(&incomingTransitionContextImpl{tc}, outgoingVariation)
262 return finalVariation
263 }
264}
265
266func (t *transitionMutatorImpl) bottomUpMutator(mctx BottomUpMutatorContext) {
267 mc := mctx.(*mutatorContext)
268 // Fetch and clean up transition mutator state. No locking needed since the
269 // only time interaction between multiple modules is required is during the
270 // computation of the variations required by a given module.
271 variations := mc.module.transitionVariations
272 outgoingTransitionCache := mc.module.outgoingTransitionCache
273 mc.module.transitionVariations = nil
274 mc.module.outgoingTransitionCache = nil
275 mc.module.currentTransitionMutator = ""
276
277 if len(variations) < 1 {
278 panic(fmt.Errorf("no variations found for module %s by mutator %s",
279 mctx.ModuleName(), t.name))
280 }
281
282 if len(variations) == 1 && variations[0] == "" {
283 // Module is not split, just apply the transition
284 mc.context.convertDepsToVariation(mc.module, 0,
285 chooseDepByIndexes(mc.mutator.name, outgoingTransitionCache))
286 } else {
287 mc.createVariationsWithTransition(variations, outgoingTransitionCache)
288 }
289}
290
291func (t *transitionMutatorImpl) mutateMutator(mctx BottomUpMutatorContext) {
292 module := mctx.(*mutatorContext).module
293 currentVariation := module.variant.variations[t.name]
294 t.mutator.Mutate(mctx, currentVariation)
295}
296
297func (c *Context) RegisterTransitionMutator(name string, mutator TransitionMutator) {
298 impl := &transitionMutatorImpl{name: name, mutator: mutator}
299
Colin Cross9f260252024-04-30 14:08:16 -0700300 c.RegisterTopDownMutator(name+"_propagate", impl.topDownMutator).Parallel()
Colin Crossd7474dd2024-03-29 12:25:29 -0700301 c.RegisterBottomUpMutator(name, impl.bottomUpMutator).Parallel().setTransitionMutator(impl)
Colin Cross95b36272024-04-12 14:48:34 -0700302 c.RegisterBottomUpMutator(name+"_mutate", impl.mutateMutator).Parallel()
303}
304
305// This function is called for every dependency edge to determine which
306// variation of the dependency is needed. Its inputs are the depending module,
307// its variation, the dependency and the dependency tag.
308type Transition func(source *moduleInfo, sourceVariation string, dep *moduleInfo, depTag DependencyTag) string