1// Copyright 2019 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 17// This file provides module types that implement wrapper module types that add conditionals on 18// Soong config variables. 19 20import ( 21 "fmt" 22 "path/filepath" 23 "reflect" 24 "strings" 25 "sync" 26 "text/scanner" 27 28 "github.com/google/blueprint" 29 "github.com/google/blueprint/parser" 30 "github.com/google/blueprint/proptools" 31 32 "android/soong/android/soongconfig" 33) 34 35func init() { 36 RegisterSoongConfigModuleBuildComponents(InitRegistrationContext) 37} 38 39func RegisterSoongConfigModuleBuildComponents(ctx RegistrationContext) { 40 ctx.RegisterModuleType("soong_config_module_type_import", SoongConfigModuleTypeImportFactory) 41 ctx.RegisterModuleType("soong_config_module_type", SoongConfigModuleTypeFactory) 42 ctx.RegisterModuleType("soong_config_string_variable", SoongConfigStringVariableDummyFactory) 43 ctx.RegisterModuleType("soong_config_bool_variable", SoongConfigBoolVariableDummyFactory) 44} 45 46var PrepareForTestWithSoongConfigModuleBuildComponents = FixtureRegisterWithContext(RegisterSoongConfigModuleBuildComponents) 47 48type soongConfigModuleTypeImport struct { 49 ModuleBase 50 properties soongConfigModuleTypeImportProperties 51} 52 53type soongConfigModuleTypeImportProperties struct { 54 From string 55 Module_types []string 56} 57 58// soong_config_module_type_import imports module types with conditionals on Soong config 59// variables from another Android.bp file. The imported module type will exist for all 60// modules after the import in the Android.bp file. 61// 62// Each soong_config_variable supports an additional value `conditions_default`. The properties 63// specified in `conditions_default` will only be used under the following conditions: 64// bool variable: the variable is unspecified or not set to a true value 65// value variable: the variable is unspecified 66// string variable: the variable is unspecified or the variable is set to a string unused in the 67// given module. For example, string variable `test` takes values: "a" and "b", 68// if the module contains a property `a` and `conditions_default`, when test=b, 69// the properties under `conditions_default` will be used. To specify that no 70// properties should be amended for `b`, you can set `b: {},`. 71// 72// For example, an Android.bp file could have: 73// 74// soong_config_module_type_import { 75// from: "device/acme/Android.bp", 76// module_types: ["acme_cc_defaults"], 77// } 78// 79// acme_cc_defaults { 80// name: "acme_defaults", 81// cflags: ["-DGENERIC"], 82// soong_config_variables: { 83// board: { 84// soc_a: { 85// cflags: ["-DSOC_A"], 86// }, 87// soc_b: { 88// cflags: ["-DSOC_B"], 89// }, 90// conditions_default: { 91// cflags: ["-DSOC_DEFAULT"], 92// }, 93// }, 94// feature: { 95// cflags: ["-DFEATURE"], 96// conditions_default: { 97// cflags: ["-DFEATURE_DEFAULT"], 98// }, 99// }, 100// width: { 101// cflags: ["-DWIDTH=%s"], 102// conditions_default: { 103// cflags: ["-DWIDTH=DEFAULT"], 104// }, 105// }, 106// }, 107// } 108// 109// cc_library { 110// name: "libacme_foo", 111// defaults: ["acme_defaults"], 112// srcs: ["*.cpp"], 113// } 114// 115// And device/acme/Android.bp could have: 116// 117// soong_config_module_type { 118// name: "acme_cc_defaults", 119// module_type: "cc_defaults", 120// config_namespace: "acme", 121// variables: ["board"], 122// bool_variables: ["feature"], 123// value_variables: ["width"], 124// properties: ["cflags", "srcs"], 125// } 126// 127// soong_config_string_variable { 128// name: "board", 129// values: ["soc_a", "soc_b", "soc_c"], 130// } 131// 132// If an acme BoardConfig.mk file contained: 133// $(call add_sonng_config_namespace, acme) 134// $(call add_soong_config_var_value, acme, board, soc_a) 135// $(call add_soong_config_var_value, acme, feature, true) 136// $(call add_soong_config_var_value, acme, width, 200) 137// 138// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200". 139// 140// Alternatively, if acme BoardConfig.mk file contained: 141// 142// SOONG_CONFIG_NAMESPACES += acme 143// SOONG_CONFIG_acme += \ 144// board \ 145// feature \ 146// 147// SOONG_CONFIG_acme_feature := false 148// 149// Then libacme_foo would build with cflags: 150// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT". 151// 152// Similarly, if acme BoardConfig.mk file contained: 153// 154// SOONG_CONFIG_NAMESPACES += acme 155// SOONG_CONFIG_acme += \ 156// board \ 157// feature \ 158// 159// SOONG_CONFIG_acme_board := soc_c 160// 161// Then libacme_foo would build with cflags: 162// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT". 163 164func SoongConfigModuleTypeImportFactory() Module { 165 module := &soongConfigModuleTypeImport{} 166 167 module.AddProperties(&module.properties) 168 AddLoadHook(module, func(ctx LoadHookContext) { 169 importModuleTypes(ctx, module.properties.From, module.properties.Module_types...) 170 }) 171 172 initAndroidModuleBase(module) 173 return module 174} 175 176func (m *soongConfigModuleTypeImport) Name() string { 177 // The generated name is non-deterministic, but it does not 178 // matter because this module does not emit any rules. 179 return soongconfig.CanonicalizeToProperty(m.properties.From) + 180 "soong_config_module_type_import_" + fmt.Sprintf("%p", m) 181} 182 183func (*soongConfigModuleTypeImport) Namespaceless() {} 184func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {} 185 186// Create dummy modules for soong_config_module_type and soong_config_*_variable 187 188type soongConfigModuleTypeModule struct { 189 ModuleBase 190 BazelModuleBase 191 properties soongconfig.ModuleTypeProperties 192} 193 194// soong_config_module_type defines module types with conditionals on Soong config 195// variables. The new module type will exist for all modules after the definition 196// in an Android.bp file, and can be imported into other Android.bp files using 197// soong_config_module_type_import. 198// 199// Each soong_config_variable supports an additional value `conditions_default`. The properties 200// specified in `conditions_default` will only be used under the following conditions: 201// 202// bool variable: the variable is unspecified or not set to a true value 203// value variable: the variable is unspecified 204// string variable: the variable is unspecified or the variable is set to a string unused in the 205// given module. For example, string variable `test` takes values: "a" and "b", 206// if the module contains a property `a` and `conditions_default`, when test=b, 207// the properties under `conditions_default` will be used. To specify that no 208// properties should be amended for `b`, you can set `b: {},`. 209// 210// For example, an Android.bp file could have: 211// 212// soong_config_module_type { 213// name: "acme_cc_defaults", 214// module_type: "cc_defaults", 215// config_namespace: "acme", 216// variables: ["board"], 217// bool_variables: ["feature"], 218// value_variables: ["width"], 219// properties: ["cflags", "srcs"], 220// } 221// 222// soong_config_string_variable { 223// name: "board", 224// values: ["soc_a", "soc_b"], 225// } 226// 227// acme_cc_defaults { 228// name: "acme_defaults", 229// cflags: ["-DGENERIC"], 230// soong_config_variables: { 231// board: { 232// soc_a: { 233// cflags: ["-DSOC_A"], 234// }, 235// soc_b: { 236// cflags: ["-DSOC_B"], 237// }, 238// conditions_default: { 239// cflags: ["-DSOC_DEFAULT"], 240// }, 241// }, 242// feature: { 243// cflags: ["-DFEATURE"], 244// conditions_default: { 245// cflags: ["-DFEATURE_DEFAULT"], 246// }, 247// }, 248// width: { 249// cflags: ["-DWIDTH=%s"], 250// conditions_default: { 251// cflags: ["-DWIDTH=DEFAULT"], 252// }, 253// }, 254// }, 255// } 256// 257// cc_library { 258// name: "libacme_foo", 259// defaults: ["acme_defaults"], 260// srcs: ["*.cpp"], 261// } 262// 263// If an acme BoardConfig.mk file contained: 264// 265// SOONG_CONFIG_NAMESPACES += acme 266// SOONG_CONFIG_acme += \ 267// board \ 268// feature \ 269// 270// SOONG_CONFIG_acme_board := soc_a 271// SOONG_CONFIG_acme_feature := true 272// SOONG_CONFIG_acme_width := 200 273// 274// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE". 275func SoongConfigModuleTypeFactory() Module { 276 module := &soongConfigModuleTypeModule{} 277 278 module.AddProperties(&module.properties) 279 280 AddLoadHook(module, func(ctx LoadHookContext) { 281 // A soong_config_module_type module should implicitly import itself. 282 importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name) 283 }) 284 285 initAndroidModuleBase(module) 286 287 return module 288} 289 290func (m *soongConfigModuleTypeModule) Name() string { 291 return m.properties.Name + fmt.Sprintf("%p", m) 292} 293func (*soongConfigModuleTypeModule) Namespaceless() {} 294func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 295 296type soongConfigStringVariableDummyModule struct { 297 ModuleBase 298 properties soongconfig.VariableProperties 299 stringProperties soongconfig.StringVariableProperties 300} 301 302type soongConfigBoolVariableDummyModule struct { 303 ModuleBase 304 properties soongconfig.VariableProperties 305} 306 307// soong_config_string_variable defines a variable and a set of possible string values for use 308// in a soong_config_module_type definition. 309func SoongConfigStringVariableDummyFactory() Module { 310 module := &soongConfigStringVariableDummyModule{} 311 module.AddProperties(&module.properties, &module.stringProperties) 312 initAndroidModuleBase(module) 313 return module 314} 315 316// soong_config_string_variable defines a variable with true or false values for use 317// in a soong_config_module_type definition. 318func SoongConfigBoolVariableDummyFactory() Module { 319 module := &soongConfigBoolVariableDummyModule{} 320 module.AddProperties(&module.properties) 321 initAndroidModuleBase(module) 322 return module 323} 324 325func (m *soongConfigStringVariableDummyModule) Name() string { 326 return m.properties.Name + fmt.Sprintf("%p", m) 327} 328func (*soongConfigStringVariableDummyModule) Namespaceless() {} 329func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 330 331func (m *soongConfigBoolVariableDummyModule) Name() string { 332 return m.properties.Name + fmt.Sprintf("%p", m) 333} 334func (*soongConfigBoolVariableDummyModule) Namespaceless() {} 335func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 336 337// importModuleTypes registers the module factories for a list of module types defined 338// in an Android.bp file. These module factories are scoped for the current Android.bp 339// file only. 340func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) { 341 from = filepath.Clean(from) 342 if filepath.Ext(from) != ".bp" { 343 ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from) 344 return 345 } 346 347 if strings.HasPrefix(from, "../") { 348 ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree", 349 from) 350 return 351 } 352 353 moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from) 354 if moduleTypeDefinitions == nil { 355 return 356 } 357 for _, moduleType := range moduleTypes { 358 if factory, ok := moduleTypeDefinitions[moduleType]; ok { 359 ctx.registerScopedModuleType(moduleType, factory) 360 } else { 361 ctx.PropertyErrorf("module_types", "module type %q not defined in %q", 362 moduleType, from) 363 } 364 } 365} 366 367// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the 368// result so each file is only parsed once. 369func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory { 370 type onceKeyType string 371 key := NewCustomOnceKey(onceKeyType(filepath.Clean(from))) 372 373 reportErrors := func(ctx LoadHookContext, filename string, errs ...error) { 374 for _, err := range errs { 375 if parseErr, ok := err.(*parser.ParseError); ok { 376 ctx.Errorf(parseErr.Pos, "%s", parseErr.Err) 377 } else { 378 ctx.Errorf(scanner.Position{Filename: filename}, "%s", err) 379 } 380 } 381 } 382 383 return ctx.Config().Once(key, func() interface{} { 384 ctx.AddNinjaFileDeps(from) 385 r, err := ctx.Config().fs.Open(from) 386 if err != nil { 387 ctx.PropertyErrorf("from", "failed to open %q: %s", from, err) 388 return (map[string]blueprint.ModuleFactory)(nil) 389 } 390 defer r.Close() 391 392 mtDef, errs := soongconfig.Parse(r, from) 393 if len(errs) > 0 { 394 reportErrors(ctx, from, errs...) 395 return (map[string]blueprint.ModuleFactory)(nil) 396 } 397 398 if ctx.Config().BuildMode == Bp2build { 399 ctx.Config().Bp2buildSoongConfigDefinitions.AddVars(mtDef) 400 } 401 402 globalModuleTypes := ctx.moduleFactories() 403 404 factories := make(map[string]blueprint.ModuleFactory) 405 406 for name, moduleType := range mtDef.ModuleTypes { 407 factory := globalModuleTypes[moduleType.BaseModuleType] 408 if factory != nil { 409 factories[name] = configModuleFactory(factory, moduleType, ctx.Config().BuildMode == Bp2build) 410 } else { 411 reportErrors(ctx, from, 412 fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType)) 413 } 414 } 415 416 if ctx.Failed() { 417 return (map[string]blueprint.ModuleFactory)(nil) 418 } 419 420 return factories 421 }).(map[string]blueprint.ModuleFactory) 422} 423 424// configModuleFactory takes an existing soongConfigModuleFactory and a 425// ModuleType to create a new ModuleFactory that uses a custom loadhook. 426func configModuleFactory(factory blueprint.ModuleFactory, moduleType *soongconfig.ModuleType, bp2build bool) blueprint.ModuleFactory { 427 // Defer creation of conditional properties struct until the first call from the factory 428 // method. That avoids having to make a special call to the factory to create the properties 429 // structs from which the conditional properties struct is created. This is needed in order to 430 // allow singleton modules to be customized by soong_config_module_type as the 431 // SingletonModuleFactoryAdaptor factory registers a load hook for the singleton module 432 // everytime that it is called. Calling the factory twice causes a build failure as the load 433 // hook is called twice, the first time it updates the singleton module to indicate that it has 434 // been registered as a module, and the second time it fails because it thinks it has been 435 // registered again and a singleton module can only be registered once. 436 // 437 // This is an issue for singleton modules because: 438 // * Load hooks are registered on the module object and are only called when the module object 439 // is created by Blueprint while processing the Android.bp file. 440 // * The module factory for a singleton module returns the same module object each time it is 441 // called, and registers its load hook on that same module object. 442 // * When the module factory is called by Blueprint it then calls all the load hooks that have 443 // been registered for every call to that module factory. 444 // 445 // It is not an issue for normal modules because they return a new module object each time the 446 // factory is called and so any load hooks registered on module objects which are discarded will 447 // not be run. 448 once := &sync.Once{} 449 conditionalFactoryProps := reflect.Value{} 450 getConditionalFactoryProps := func(props []interface{}) reflect.Value { 451 once.Do(func() { 452 conditionalFactoryProps = soongconfig.CreateProperties(props, moduleType) 453 }) 454 return conditionalFactoryProps 455 } 456 457 return func() (blueprint.Module, []interface{}) { 458 module, props := factory() 459 conditionalFactoryProps := getConditionalFactoryProps(props) 460 if !conditionalFactoryProps.IsValid() { 461 return module, props 462 } 463 464 conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps) 465 props = append(props, conditionalProps.Interface()) 466 467 if bp2build { 468 // The loadhook is different for bp2build, since we don't want to set a specific 469 // set of property values based on a vendor var -- we want __all of them__ to 470 // generate select statements, so we put the entire soong_config_variables 471 // struct, together with the namespace representing those variables, while 472 // creating the custom module with the factory. 473 AddLoadHook(module, func(ctx LoadHookContext) { 474 if m, ok := module.(Bazelable); ok { 475 m.SetBaseModuleType(moduleType.BaseModuleType) 476 // Instead of applying all properties, keep the entire conditionalProps struct as 477 // part of the custom module so dependent modules can create the selects accordingly 478 m.setNamespacedVariableProps(namespacedVariableProperties{ 479 moduleType.ConfigNamespace: []interface{}{conditionalProps.Interface()}, 480 }) 481 } 482 }) 483 } else { 484 // Regular Soong operation wraps the existing module factory with a 485 // conditional on Soong config variables by reading the product 486 // config variables from Make. 487 AddLoadHook(module, func(ctx LoadHookContext) { 488 config := ctx.Config().VendorConfig(moduleType.ConfigNamespace) 489 newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config) 490 if err != nil { 491 ctx.ModuleErrorf("%s", err) 492 return 493 } 494 for _, ps := range newProps { 495 ctx.AppendProperties(ps) 496 } 497 }) 498 } 499 return module, props 500 } 501} 502