summaryrefslogtreecommitdiff
path: root/android/transition.go
blob: 0677ca1dd3a2d8be8db64c3d51c18298c6dbaa3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
// Copyright 2024 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package android

import "github.com/google/blueprint"

// TransitionMutator implements a top-down mechanism where a module tells its
// direct dependencies what variation they should be built in but the dependency
// has the final say.
//
// When implementing a transition mutator, one needs to implement four methods:
//   - Split() that tells what variations a module has by itself
//   - OutgoingTransition() where a module tells what it wants from its
//     dependency
//   - IncomingTransition() where a module has the final say about its own
//     variation
//   - Mutate() that changes the state of a module depending on its variation
//
// That the effective variation of module B when depended on by module A is the
// composition the outgoing transition of module A and the incoming transition
// of module B.
//
// the outgoing transition should not take the properties of the dependency into
// account, only those of the module that depends on it. For this reason, the
// dependency is not even passed into it as an argument. Likewise, the incoming
// transition should not take the properties of the depending module into
// account and is thus not informed about it. This makes for a nice
// decomposition of the decision logic.
//
// A given transition mutator only affects its own variation; other variations
// stay unchanged along the dependency edges.
//
// Soong makes sure that all modules are created in the desired variations and
// that dependency edges are set up correctly. This ensures that "missing
// variation" errors do not happen and allows for more flexible changes in the
// value of the variation among dependency edges (as oppposed to bottom-up
// mutators where if module A in variation X depends on module B and module B
// has that variation X, A must depend on variation X of B)
//
// The limited power of the context objects passed to individual mutators
// methods also makes it more difficult to shoot oneself in the foot. Complete
// safety is not guaranteed because no one prevents individual transition
// mutators from mutating modules in illegal ways and for e.g. Split() or
// Mutate() to run their own visitations of the transitive dependency of the
// module and both of these are bad ideas, but it's better than no guardrails at
// all.
//
// This model is pretty close to Bazel's configuration transitions. The mapping
// between concepts in Soong and Bazel is as follows:
//   - Module == configured target
//   - Variant == configuration
//   - Variation name == configuration flag
//   - Variation == configuration flag value
//   - Outgoing transition == attribute transition
//   - Incoming transition == rule transition
//
// The Split() method does not have a Bazel equivalent and Bazel split
// transitions do not have a Soong equivalent.
//
// Mutate() does not make sense in Bazel due to the different models of the
// two systems: when creating new variations, Soong clones the old module and
// thus some way is needed to change it state whereas Bazel creates each
// configuration of a given configured target anew.
type TransitionMutator[T blueprint.TransitionInfo] interface {
	// Split returns the set of variations that should be created for a module no
	// matter who depends on it. Used when Make depends on a particular variation
	// or when the module knows its variations just based on information given to
	// it in the Blueprint file. This method should not mutate the module it is
	// called on.
	Split(ctx BaseModuleContext) []T

	// OutgoingTransition is called on a module to determine which variation it wants
	// from its direct dependencies. The dependency itself can override this decision.
	// This method should not mutate the module itself.
	OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo T) T

	// IncomingTransition is called on a module to determine which variation it should
	// be in based on the variation modules that depend on it want. This gives the module
	// a final say about its own variations. This method should not mutate the module
	// itself.
	IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo T) T

	// Mutate is called after a module was split into multiple variations on each variation.
	// It should not split the module any further but adding new dependencies is
	// fine. Unlike all the other methods on TransitionMutator, this method is
	// allowed to mutate the module.
	Mutate(ctx BottomUpMutatorContext, transitionInfo T)

	// TransitionInfoFromVariation is called when adding dependencies with an explicit variation after the
	// TransitionMutator has already run.  It takes a variation name and returns a TransitionInfo for that
	// variation.  It may not be possible for some TransitionMutators to generate an appropriate TransitionInfo
	// if the variation does not contain all the information from the TransitionInfo, in which case the
	// TransitionMutator can panic in TransitionInfoFromVariation, and adding dependencies with explicit variations
	// for this TransitionMutator is not supported.
	TransitionInfoFromVariation(variation string) T
}

// androidTransitionMutator is a copy of blueprint.TransitionMutator with the context argument types changed
// from blueprint.BaseModuleContext to BaseModuleContext, etc.
type androidTransitionMutator interface {
	Split(ctx BaseModuleContext) []blueprint.TransitionInfo
	OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo
	IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo
	Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo)
	TransitionInfoFromVariation(variation string) blueprint.TransitionInfo
}

// VariationTransitionMutator is a simpler version of androidTransitionMutator that passes variation strings instead
// of a blueprint.TransitionInfo object.
type VariationTransitionMutator interface {
	Split(ctx BaseModuleContext) []string
	OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
	IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
	Mutate(ctx BottomUpMutatorContext, variation string)
}

type IncomingTransitionContext interface {
	ArchModuleContext
	ModuleProviderContext
	ModuleErrorContext

	// Module returns the target of the dependency edge for which the transition
	// is being computed
	Module() Module

	// ModuleName returns the name of the module.  This is generally the value that was returned by Module.Name() when
	// the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
	ModuleName() string

	// DepTag() Returns the dependency tag through which this dependency is
	// reached
	DepTag() blueprint.DependencyTag

	// Config returns the configuration for the build.
	Config() Config

	DeviceConfig() DeviceConfig

	// IsAddingDependency returns true if the transition is being called while adding a dependency
	// after the transition mutator has already run, or false if it is being called when the transition
	// mutator is running.  This should be used sparingly, all uses will have to be removed in order
	// to support creating variants on demand.
	IsAddingDependency() bool
}

type OutgoingTransitionContext interface {
	ArchModuleContext
	ModuleProviderContext

	// Module returns the target of the dependency edge for which the transition
	// is being computed
	Module() Module

	// ModuleName returns the name of the module.  This is generally the value that was returned by Module.Name() when
	// the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
	ModuleName() string

	// DepTag() Returns the dependency tag through which this dependency is
	// reached
	DepTag() blueprint.DependencyTag

	// Config returns the configuration for the build.
	Config() Config

	DeviceConfig() DeviceConfig
}

// androidTransitionMutatorAdapter wraps an androidTransitionMutator to convert it to a blueprint.TransitionInfo
// by converting the blueprint.*Context objects into android.*Context objects.
type androidTransitionMutatorAdapter struct {
	finalPhase bool
	mutator    androidTransitionMutator
	name       string
}

func (a *androidTransitionMutatorAdapter) Split(ctx blueprint.BaseModuleContext) []blueprint.TransitionInfo {
	if a.finalPhase {
		panic("TransitionMutator not allowed in FinalDepsMutators")
	}
	m := ctx.Module().(Module)
	moduleContext := m.base().baseModuleContextFactory(ctx)
	return a.mutator.Split(&moduleContext)
}

func (a *androidTransitionMutatorAdapter) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext,
	sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
	m := bpctx.Module().(Module)
	ctx := outgoingTransitionContextPool.Get()
	defer outgoingTransitionContextPool.Put(ctx)
	*ctx = outgoingTransitionContextImpl{
		archModuleContext: m.base().archModuleContextFactory(bpctx),
		bp:                bpctx,
	}
	return a.mutator.OutgoingTransition(ctx, sourceTransitionInfo)
}

func (a *androidTransitionMutatorAdapter) IncomingTransition(bpctx blueprint.IncomingTransitionContext,
	incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
	m := bpctx.Module().(Module)
	ctx := incomingTransitionContextPool.Get()
	defer incomingTransitionContextPool.Put(ctx)
	*ctx = incomingTransitionContextImpl{
		archModuleContext: m.base().archModuleContextFactory(bpctx),
		bp:                bpctx,
	}
	return a.mutator.IncomingTransition(ctx, incomingTransitionInfo)
}

func (a *androidTransitionMutatorAdapter) Mutate(ctx blueprint.BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
	am := ctx.Module().(Module)
	variation := transitionInfo.Variation()
	if variation != "" {
		// TODO: this should really be checking whether the TransitionMutator affected this module, not
		//  the empty variant, but TransitionMutator has no concept of skipping a module.
		base := am.base()
		base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
		base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
	}

	mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
	defer bottomUpMutatorContextPool.Put(mctx)
	a.mutator.Mutate(mctx, transitionInfo)
}

func (a *androidTransitionMutatorAdapter) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
	return a.mutator.TransitionInfoFromVariation(variation)
}

// variationTransitionMutatorAdapter wraps a VariationTransitionMutator to convert it to an androidTransitionMutator
// by wrapping the string info object used by VariationTransitionMutator with variationTransitionInfo to convert it into
// blueprint.TransitionInfo.
type variationTransitionMutatorAdapter struct {
	m VariationTransitionMutator
}

func (v variationTransitionMutatorAdapter) Split(ctx BaseModuleContext) []blueprint.TransitionInfo {
	variations := v.m.Split(ctx)
	transitionInfos := make([]blueprint.TransitionInfo, 0, len(variations))
	for _, variation := range variations {
		transitionInfos = append(transitionInfos, variationTransitionInfo{variation})
	}
	return transitionInfos
}

func (v variationTransitionMutatorAdapter) OutgoingTransition(ctx OutgoingTransitionContext,
	sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {

	sourceVariationTransitionInfo, _ := sourceTransitionInfo.(variationTransitionInfo)
	outgoingVariation := v.m.OutgoingTransition(ctx, sourceVariationTransitionInfo.variation)
	return variationTransitionInfo{outgoingVariation}
}

func (v variationTransitionMutatorAdapter) IncomingTransition(ctx IncomingTransitionContext,
	incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {

	incomingVariationTransitionInfo, _ := incomingTransitionInfo.(variationTransitionInfo)
	variation := v.m.IncomingTransition(ctx, incomingVariationTransitionInfo.variation)
	return variationTransitionInfo{variation}
}

func (v variationTransitionMutatorAdapter) Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
	variationTransitionInfo, _ := transitionInfo.(variationTransitionInfo)
	v.m.Mutate(ctx, variationTransitionInfo.variation)
}

func (v variationTransitionMutatorAdapter) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
	return variationTransitionInfo{variation}
}

// variationTransitionInfo is a blueprint.TransitionInfo that contains a single variation string.
type variationTransitionInfo struct {
	variation string
}

func (v variationTransitionInfo) Variation() string {
	return v.variation
}

// genericTransitionMutatorAdapter wraps a TransitionMutator to convert it to an androidTransitionMutator
type genericTransitionMutatorAdapter[T blueprint.TransitionInfo] struct {
	m TransitionMutator[T]
}

// NewGenericTransitionMutatorAdapter is used to convert a generic TransitionMutator[T] into an androidTransitionMutator
// that can be passed to RegisterMutatorsContext.InfoBasedTransition.
func NewGenericTransitionMutatorAdapter[T blueprint.TransitionInfo](m TransitionMutator[T]) androidTransitionMutator {
	return &genericTransitionMutatorAdapter[T]{m}
}

func (g *genericTransitionMutatorAdapter[T]) convertTransitionInfoToT(transitionInfo blueprint.TransitionInfo) T {
	if transitionInfo == nil {
		var zero T
		return zero
	}
	return transitionInfo.(T)
}

func (g *genericTransitionMutatorAdapter[T]) Split(ctx BaseModuleContext) []blueprint.TransitionInfo {
	transitionInfos := g.m.Split(ctx)
	bpTransitionInfos := make([]blueprint.TransitionInfo, 0, len(transitionInfos))
	for _, transitionInfo := range transitionInfos {
		bpTransitionInfos = append(bpTransitionInfos, transitionInfo)
	}
	return bpTransitionInfos
}

func (g *genericTransitionMutatorAdapter[T]) OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
	sourceTransitionInfoT := g.convertTransitionInfoToT(sourceTransitionInfo)
	return g.m.OutgoingTransition(ctx, sourceTransitionInfoT)
}

func (g *genericTransitionMutatorAdapter[T]) IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
	incomingTransitionInfoT := g.convertTransitionInfoToT(incomingTransitionInfo)
	return g.m.IncomingTransition(ctx, incomingTransitionInfoT)
}

func (g *genericTransitionMutatorAdapter[T]) Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
	transitionInfoT := g.convertTransitionInfoToT(transitionInfo)
	g.m.Mutate(ctx, transitionInfoT)
}

func (g *genericTransitionMutatorAdapter[T]) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
	return g.m.TransitionInfoFromVariation(variation)
}

// incomingTransitionContextImpl wraps a blueprint.IncomingTransitionContext to convert it to an
// IncomingTransitionContext.
type incomingTransitionContextImpl struct {
	archModuleContext
	bp blueprint.IncomingTransitionContext
}

func (c *incomingTransitionContextImpl) Module() Module {
	return c.bp.Module().(Module)
}

func (c *incomingTransitionContextImpl) ModuleName() string {
	return c.bp.ModuleName()
}

func (c *incomingTransitionContextImpl) DepTag() blueprint.DependencyTag {
	return c.bp.DepTag()
}

func (c *incomingTransitionContextImpl) Config() Config {
	return c.bp.Config().(Config)
}

func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
}

func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
	return c.bp.IsAddingDependency()
}

func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
	return c.bp.Provider(provider)
}

func (c *incomingTransitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
	c.bp.ModuleErrorf(fmt, args)
}

func (c *incomingTransitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
	c.bp.PropertyErrorf(property, fmt, args)
}

// outgoingTransitionContextImpl wraps a blueprint.OutgoingTransitionContext to convert it to an
// OutgoingTransitionContext.
type outgoingTransitionContextImpl struct {
	archModuleContext
	bp blueprint.OutgoingTransitionContext
}

func (c *outgoingTransitionContextImpl) Module() Module {
	return c.bp.Module().(Module)
}

func (c *outgoingTransitionContextImpl) ModuleName() string {
	return c.bp.ModuleName()
}

func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
	return c.bp.DepTag()
}

func (c *outgoingTransitionContextImpl) Config() Config {
	return c.bp.Config().(Config)
}

func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
}

func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
	return c.bp.Provider(provider)
}