xref: /aosp_15_r20/build/blueprint/transition.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
1// 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	// IsAddingDependency returns true if the transition is being called while adding a dependency
124	// after the transition mutator has already run, or false if it is being called when the transition
125	// mutator is running.  This should be used sparingly, all uses will have to be removed in order
126	// to support creating variants on demand.
127	IsAddingDependency() bool
128
129	// ModuleErrorf reports an error at the line number of the module type in the module definition.
130	ModuleErrorf(fmt string, args ...interface{})
131
132	// PropertyErrorf reports an error at the line number of a property in the module definition.
133	PropertyErrorf(property, fmt string, args ...interface{})
134}
135
136type OutgoingTransitionContext interface {
137	// Module returns the source of the dependency edge for which the transition
138	// is being computed
139	Module() Module
140
141	// DepTag() Returns the dependency tag through which this dependency is
142	// reached
143	DepTag() DependencyTag
144
145	// Config returns the config object that was passed to
146	// Context.PrepareBuildActions.
147	Config() interface{}
148
149	// Provider returns the value for a provider for the source of the dependency edge for which the
150	// transition is being computed.  If the value is not set it returns nil and false.  It panics if
151	// called before the appropriate mutator or GenerateBuildActions pass for the provider.  The value
152	// returned may be a deep copy of the value originally passed to SetProvider.
153	//
154	// This method shouldn't be used directly, prefer the type-safe android.ModuleProvider instead.
155	Provider(provider AnyProviderKey) (any, bool)
156
157	// ModuleErrorf reports an error at the line number of the module type in the module definition.
158	ModuleErrorf(fmt string, args ...interface{})
159
160	// PropertyErrorf reports an error at the line number of a property in the module definition.
161	PropertyErrorf(property, fmt string, args ...interface{})
162}
163
164type transitionMutatorImpl struct {
165	name                        string
166	mutator                     TransitionMutator
167	variantCreatingMutatorIndex int
168	inputVariants               map[*moduleGroup][]*moduleInfo
169}
170
171// Adds each argument in items to l if it's not already there.
172func addToStringListIfNotPresent(l []string, items ...string) []string {
173	for _, i := range items {
174		if !slices.Contains(l, i) {
175			l = append(l, i)
176		}
177	}
178
179	return l
180}
181
182func (t *transitionMutatorImpl) addRequiredVariation(m *moduleInfo, variation string) {
183	m.requiredVariationsLock.Lock()
184	defer m.requiredVariationsLock.Unlock()
185
186	// This is only a consistency check. Leaking the variations of a transition
187	// mutator to another one could well lead to issues that are difficult to
188	// track down.
189	if m.currentTransitionMutator != "" && m.currentTransitionMutator != t.name {
190		panic(fmt.Errorf("transition mutator is %s in mutator %s", m.currentTransitionMutator, t.name))
191	}
192
193	m.currentTransitionMutator = t.name
194	m.transitionVariations = addToStringListIfNotPresent(m.transitionVariations, variation)
195}
196
197func (t *transitionMutatorImpl) topDownMutator(mctx TopDownMutatorContext) {
198	module := mctx.(*mutatorContext).module
199	mutatorSplits := t.mutator.Split(mctx)
200	if mutatorSplits == nil || len(mutatorSplits) == 0 {
201		panic(fmt.Errorf("transition mutator %s returned no splits for module %s", t.name, mctx.ModuleName()))
202	}
203
204	// transitionVariations for given a module can be mutated by the module itself
205	// and modules that directly depend on it. Since this is a top-down mutator,
206	// all modules that directly depend on this module have already been processed
207	// so no locking is necessary.
208	// Sort the module transitions, but keep the mutatorSplits in the order returned
209	// by Split, as the order can be significant when inter-variant dependencies are
210	// used.
211	sort.Strings(module.transitionVariations)
212	module.transitionVariations = addToStringListIfNotPresent(mutatorSplits, module.transitionVariations...)
213
214	outgoingTransitionCache := make([][]string, len(module.transitionVariations))
215	for srcVariationIndex, srcVariation := range module.transitionVariations {
216		srcVariationTransitionCache := make([]string, len(module.directDeps))
217		for depIndex, dep := range module.directDeps {
218			finalVariation := t.transition(mctx)(mctx.moduleInfo(), srcVariation, dep.module, dep.tag)
219			srcVariationTransitionCache[depIndex] = finalVariation
220			t.addRequiredVariation(dep.module, finalVariation)
221		}
222		outgoingTransitionCache[srcVariationIndex] = srcVariationTransitionCache
223	}
224	module.outgoingTransitionCache = outgoingTransitionCache
225}
226
227type transitionContextImpl struct {
228	context     *Context
229	source      *moduleInfo
230	dep         *moduleInfo
231	depTag      DependencyTag
232	postMutator bool
233	config      interface{}
234	errs        []error
235}
236
237func (c *transitionContextImpl) DepTag() DependencyTag {
238	return c.depTag
239}
240
241func (c *transitionContextImpl) Config() interface{} {
242	return c.config
243}
244
245func (c *transitionContextImpl) IsAddingDependency() bool {
246	return c.postMutator
247}
248
249func (c *transitionContextImpl) error(err error) {
250	if err != nil {
251		c.errs = append(c.errs, err)
252	}
253}
254
255func (c *transitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
256	c.error(c.context.moduleErrorf(c.dep, fmt, args...))
257}
258
259func (c *transitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
260	c.error(c.context.PropertyErrorf(c.dep.logicModule, property, fmt, args...))
261}
262
263type outgoingTransitionContextImpl struct {
264	transitionContextImpl
265}
266
267func (c *outgoingTransitionContextImpl) Module() Module {
268	return c.source.logicModule
269}
270
271func (c *outgoingTransitionContextImpl) Provider(provider AnyProviderKey) (any, bool) {
272	return c.context.provider(c.source, provider.provider())
273}
274
275type incomingTransitionContextImpl struct {
276	transitionContextImpl
277}
278
279func (c *incomingTransitionContextImpl) Module() Module {
280	return c.dep.logicModule
281}
282
283func (c *incomingTransitionContextImpl) Provider(provider AnyProviderKey) (any, bool) {
284	return c.context.provider(c.dep, provider.provider())
285}
286
287func (t *transitionMutatorImpl) transition(mctx BaseModuleContext) Transition {
288	return func(source *moduleInfo, sourceVariation string, dep *moduleInfo, depTag DependencyTag) string {
289		tc := transitionContextImpl{
290			context: mctx.base().context,
291			source:  source,
292			dep:     dep,
293			depTag:  depTag,
294			config:  mctx.Config(),
295		}
296		outCtx := &outgoingTransitionContextImpl{tc}
297		outgoingVariation := t.mutator.OutgoingTransition(outCtx, sourceVariation)
298		for _, err := range outCtx.errs {
299			mctx.error(err)
300		}
301		if mctx.Failed() {
302			return outgoingVariation
303		}
304		inCtx := &incomingTransitionContextImpl{tc}
305		finalVariation := t.mutator.IncomingTransition(inCtx, outgoingVariation)
306		for _, err := range inCtx.errs {
307			mctx.error(err)
308		}
309		return finalVariation
310	}
311}
312
313func (t *transitionMutatorImpl) bottomUpMutator(mctx BottomUpMutatorContext) {
314	mc := mctx.(*mutatorContext)
315	// Fetch and clean up transition mutator state. No locking needed since the
316	// only time interaction between multiple modules is required is during the
317	// computation of the variations required by a given module.
318	variations := mc.module.transitionVariations
319	outgoingTransitionCache := mc.module.outgoingTransitionCache
320	mc.module.transitionVariations = nil
321	mc.module.outgoingTransitionCache = nil
322	mc.module.currentTransitionMutator = ""
323
324	if len(variations) < 1 {
325		panic(fmt.Errorf("no variations found for module %s by mutator %s",
326			mctx.ModuleName(), t.name))
327	}
328
329	if len(variations) == 1 && variations[0] == "" {
330		// Module is not split, just apply the transition
331		mc.context.convertDepsToVariation(mc.module, 0,
332			chooseDepByIndexes(mc.mutator.name, outgoingTransitionCache))
333	} else {
334		mc.createVariationsWithTransition(variations, outgoingTransitionCache)
335	}
336}
337
338func (t *transitionMutatorImpl) mutateMutator(mctx BottomUpMutatorContext) {
339	module := mctx.(*mutatorContext).module
340	currentVariation := module.variant.variations.get(t.name)
341	t.mutator.Mutate(mctx, currentVariation)
342}
343
344type TransitionMutatorHandle interface {
345	// NeverFar causes the variations created by this mutator to never be ignored when adding
346	// far variation dependencies. Normally, far variation dependencies ignore all the variants
347	// of the source module, and only use the variants explicitly requested by the
348	// AddFarVariationDependencies call.
349	NeverFar() TransitionMutatorHandle
350}
351
352type transitionMutatorHandle struct {
353	inner MutatorHandle
354}
355
356var _ TransitionMutatorHandle = (*transitionMutatorHandle)(nil)
357
358func (h *transitionMutatorHandle) NeverFar() TransitionMutatorHandle {
359	h.inner.setNeverFar()
360	return h
361}
362
363func (c *Context) RegisterTransitionMutator(name string, mutator TransitionMutator) TransitionMutatorHandle {
364	impl := &transitionMutatorImpl{name: name, mutator: mutator}
365
366	c.RegisterTopDownMutator(name+"_propagate", impl.topDownMutator)
367	bottomUpHandle := c.RegisterBottomUpMutator(name, impl.bottomUpMutator).setTransitionMutator(impl)
368	c.RegisterBottomUpMutator(name+"_mutate", impl.mutateMutator)
369	return &transitionMutatorHandle{inner: bottomUpHandle}
370}
371
372// This function is called for every dependency edge to determine which
373// variation of the dependency is needed. Its inputs are the depending module,
374// its variation, the dependency and the dependency tag.
375type Transition func(source *moduleInfo, sourceVariation string, dep *moduleInfo, depTag DependencyTag) string
376