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 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 { 147 name string 148 mutator TransitionMutator 149 inputVariants map[*moduleGroup][]*moduleInfo 150} 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. 189 // 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. 192 sort.Strings(module.transitionVariations) 193 module.transitionVariations = addToStringListIfNotPresent(mutatorSplits, module.transitionVariations...) 194 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 300 c.RegisterTopDownMutator(name+"_propagate", impl.topDownMutator).Parallel() 301 c.RegisterBottomUpMutator(name, impl.bottomUpMutator).Parallel().setTransitionMutator(impl) 302 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 309