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