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