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