• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 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[T blueprint.TransitionInfo] 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) []T
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, sourceTransitionInfo T) T
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, incomingTransitionInfo T) T
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, transitionInfo T)
100
101	// TransitionInfoFromVariation is called when adding dependencies with an explicit variation after the
102	// TransitionMutator has already run.  It takes a variation name and returns a TransitionInfo for that
103	// variation.  It may not be possible for some TransitionMutators to generate an appropriate TransitionInfo
104	// if the variation does not contain all the information from the TransitionInfo, in which case the
105	// TransitionMutator can panic in TransitionInfoFromVariation, and adding dependencies with explicit variations
106	// for this TransitionMutator is not supported.
107	TransitionInfoFromVariation(variation string) T
108}
109
110// androidTransitionMutator is a copy of blueprint.TransitionMutator with the context argument types changed
111// from blueprint.BaseModuleContext to BaseModuleContext, etc.
112type androidTransitionMutator interface {
113	Split(ctx BaseModuleContext) []blueprint.TransitionInfo
114	OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo
115	IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo
116	Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo)
117	TransitionInfoFromVariation(variation string) blueprint.TransitionInfo
118}
119
120// VariationTransitionMutator is a simpler version of androidTransitionMutator that passes variation strings instead
121// of a blueprint.TransitionInfo object.
122type VariationTransitionMutator interface {
123	Split(ctx BaseModuleContext) []string
124	OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
125	IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
126	Mutate(ctx BottomUpMutatorContext, variation string)
127}
128
129type IncomingTransitionContext interface {
130	ArchModuleContext
131	ModuleProviderContext
132	ModuleErrorContext
133
134	// Module returns the target of the dependency edge for which the transition
135	// is being computed
136	Module() Module
137
138	// ModuleName returns the name of the module.  This is generally the value that was returned by Module.Name() when
139	// the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
140	ModuleName() string
141
142	// DepTag() Returns the dependency tag through which this dependency is
143	// reached
144	DepTag() blueprint.DependencyTag
145
146	// Config returns the configuration for the build.
147	Config() Config
148
149	DeviceConfig() DeviceConfig
150
151	// IsAddingDependency returns true if the transition is being called while adding a dependency
152	// after the transition mutator has already run, or false if it is being called when the transition
153	// mutator is running.  This should be used sparingly, all uses will have to be removed in order
154	// to support creating variants on demand.
155	IsAddingDependency() bool
156}
157
158type OutgoingTransitionContext interface {
159	ArchModuleContext
160	ModuleProviderContext
161
162	// Module returns the target of the dependency edge for which the transition
163	// is being computed
164	Module() Module
165
166	// ModuleName returns the name of the module.  This is generally the value that was returned by Module.Name() when
167	// the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
168	ModuleName() string
169
170	// DepTag() Returns the dependency tag through which this dependency is
171	// reached
172	DepTag() blueprint.DependencyTag
173
174	// Config returns the configuration for the build.
175	Config() Config
176
177	DeviceConfig() DeviceConfig
178}
179
180// androidTransitionMutatorAdapter wraps an androidTransitionMutator to convert it to a blueprint.TransitionInfo
181// by converting the blueprint.*Context objects into android.*Context objects.
182type androidTransitionMutatorAdapter struct {
183	finalPhase bool
184	mutator    androidTransitionMutator
185	name       string
186}
187
188func (a *androidTransitionMutatorAdapter) Split(ctx blueprint.BaseModuleContext) []blueprint.TransitionInfo {
189	if a.finalPhase {
190		panic("TransitionMutator not allowed in FinalDepsMutators")
191	}
192	m := ctx.Module().(Module)
193	moduleContext := m.base().baseModuleContextFactory(ctx)
194	return a.mutator.Split(&moduleContext)
195}
196
197func (a *androidTransitionMutatorAdapter) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext,
198	sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
199	m := bpctx.Module().(Module)
200	ctx := outgoingTransitionContextPool.Get()
201	defer outgoingTransitionContextPool.Put(ctx)
202	*ctx = outgoingTransitionContextImpl{
203		archModuleContext: m.base().archModuleContextFactory(bpctx),
204		bp:                bpctx,
205	}
206	return a.mutator.OutgoingTransition(ctx, sourceTransitionInfo)
207}
208
209func (a *androidTransitionMutatorAdapter) IncomingTransition(bpctx blueprint.IncomingTransitionContext,
210	incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
211	m := bpctx.Module().(Module)
212	ctx := incomingTransitionContextPool.Get()
213	defer incomingTransitionContextPool.Put(ctx)
214	*ctx = incomingTransitionContextImpl{
215		archModuleContext: m.base().archModuleContextFactory(bpctx),
216		bp:                bpctx,
217	}
218	return a.mutator.IncomingTransition(ctx, incomingTransitionInfo)
219}
220
221func (a *androidTransitionMutatorAdapter) Mutate(ctx blueprint.BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
222	am := ctx.Module().(Module)
223	variation := transitionInfo.Variation()
224	if variation != "" {
225		// TODO: this should really be checking whether the TransitionMutator affected this module, not
226		//  the empty variant, but TransitionMutator has no concept of skipping a module.
227		base := am.base()
228		base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
229		base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
230	}
231
232	mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
233	defer bottomUpMutatorContextPool.Put(mctx)
234	a.mutator.Mutate(mctx, transitionInfo)
235}
236
237func (a *androidTransitionMutatorAdapter) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
238	return a.mutator.TransitionInfoFromVariation(variation)
239}
240
241// variationTransitionMutatorAdapter wraps a VariationTransitionMutator to convert it to an androidTransitionMutator
242// by wrapping the string info object used by VariationTransitionMutator with variationTransitionInfo to convert it into
243// blueprint.TransitionInfo.
244type variationTransitionMutatorAdapter struct {
245	m VariationTransitionMutator
246}
247
248func (v variationTransitionMutatorAdapter) Split(ctx BaseModuleContext) []blueprint.TransitionInfo {
249	variations := v.m.Split(ctx)
250	transitionInfos := make([]blueprint.TransitionInfo, 0, len(variations))
251	for _, variation := range variations {
252		transitionInfos = append(transitionInfos, variationTransitionInfo{variation})
253	}
254	return transitionInfos
255}
256
257func (v variationTransitionMutatorAdapter) OutgoingTransition(ctx OutgoingTransitionContext,
258	sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
259
260	sourceVariationTransitionInfo, _ := sourceTransitionInfo.(variationTransitionInfo)
261	outgoingVariation := v.m.OutgoingTransition(ctx, sourceVariationTransitionInfo.variation)
262	return variationTransitionInfo{outgoingVariation}
263}
264
265func (v variationTransitionMutatorAdapter) IncomingTransition(ctx IncomingTransitionContext,
266	incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
267
268	incomingVariationTransitionInfo, _ := incomingTransitionInfo.(variationTransitionInfo)
269	variation := v.m.IncomingTransition(ctx, incomingVariationTransitionInfo.variation)
270	return variationTransitionInfo{variation}
271}
272
273func (v variationTransitionMutatorAdapter) Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
274	variationTransitionInfo, _ := transitionInfo.(variationTransitionInfo)
275	v.m.Mutate(ctx, variationTransitionInfo.variation)
276}
277
278func (v variationTransitionMutatorAdapter) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
279	return variationTransitionInfo{variation}
280}
281
282// variationTransitionInfo is a blueprint.TransitionInfo that contains a single variation string.
283type variationTransitionInfo struct {
284	variation string
285}
286
287func (v variationTransitionInfo) Variation() string {
288	return v.variation
289}
290
291// genericTransitionMutatorAdapter wraps a TransitionMutator to convert it to an androidTransitionMutator
292type genericTransitionMutatorAdapter[T blueprint.TransitionInfo] struct {
293	m TransitionMutator[T]
294}
295
296// NewGenericTransitionMutatorAdapter is used to convert a generic TransitionMutator[T] into an androidTransitionMutator
297// that can be passed to RegisterMutatorsContext.InfoBasedTransition.
298func NewGenericTransitionMutatorAdapter[T blueprint.TransitionInfo](m TransitionMutator[T]) androidTransitionMutator {
299	return &genericTransitionMutatorAdapter[T]{m}
300}
301
302func (g *genericTransitionMutatorAdapter[T]) convertTransitionInfoToT(transitionInfo blueprint.TransitionInfo) T {
303	if transitionInfo == nil {
304		var zero T
305		return zero
306	}
307	return transitionInfo.(T)
308}
309
310func (g *genericTransitionMutatorAdapter[T]) Split(ctx BaseModuleContext) []blueprint.TransitionInfo {
311	transitionInfos := g.m.Split(ctx)
312	bpTransitionInfos := make([]blueprint.TransitionInfo, 0, len(transitionInfos))
313	for _, transitionInfo := range transitionInfos {
314		bpTransitionInfos = append(bpTransitionInfos, transitionInfo)
315	}
316	return bpTransitionInfos
317}
318
319func (g *genericTransitionMutatorAdapter[T]) OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
320	sourceTransitionInfoT := g.convertTransitionInfoToT(sourceTransitionInfo)
321	return g.m.OutgoingTransition(ctx, sourceTransitionInfoT)
322}
323
324func (g *genericTransitionMutatorAdapter[T]) IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
325	incomingTransitionInfoT := g.convertTransitionInfoToT(incomingTransitionInfo)
326	return g.m.IncomingTransition(ctx, incomingTransitionInfoT)
327}
328
329func (g *genericTransitionMutatorAdapter[T]) Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
330	transitionInfoT := g.convertTransitionInfoToT(transitionInfo)
331	g.m.Mutate(ctx, transitionInfoT)
332}
333
334func (g *genericTransitionMutatorAdapter[T]) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
335	return g.m.TransitionInfoFromVariation(variation)
336}
337
338// incomingTransitionContextImpl wraps a blueprint.IncomingTransitionContext to convert it to an
339// IncomingTransitionContext.
340type incomingTransitionContextImpl struct {
341	archModuleContext
342	bp blueprint.IncomingTransitionContext
343}
344
345func (c *incomingTransitionContextImpl) Module() Module {
346	return c.bp.Module().(Module)
347}
348
349func (c *incomingTransitionContextImpl) ModuleName() string {
350	return c.bp.ModuleName()
351}
352
353func (c *incomingTransitionContextImpl) DepTag() blueprint.DependencyTag {
354	return c.bp.DepTag()
355}
356
357func (c *incomingTransitionContextImpl) Config() Config {
358	return c.bp.Config().(Config)
359}
360
361func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
362	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
363}
364
365func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
366	return c.bp.IsAddingDependency()
367}
368
369func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
370	return c.bp.Provider(provider)
371}
372
373func (c *incomingTransitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
374	c.bp.ModuleErrorf(fmt, args)
375}
376
377func (c *incomingTransitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
378	c.bp.PropertyErrorf(property, fmt, args)
379}
380
381// outgoingTransitionContextImpl wraps a blueprint.OutgoingTransitionContext to convert it to an
382// OutgoingTransitionContext.
383type outgoingTransitionContextImpl struct {
384	archModuleContext
385	bp blueprint.OutgoingTransitionContext
386}
387
388func (c *outgoingTransitionContextImpl) Module() Module {
389	return c.bp.Module().(Module)
390}
391
392func (c *outgoingTransitionContextImpl) ModuleName() string {
393	return c.bp.ModuleName()
394}
395
396func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
397	return c.bp.DepTag()
398}
399
400func (c *outgoingTransitionContextImpl) Config() Config {
401	return c.bp.Config().(Config)
402}
403
404func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
405	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
406}
407
408func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
409	return c.bp.Provider(provider)
410}
411