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// 126// SOONG_CONFIG_NAMESPACES += acme 127// SOONG_CONFIG_acme += \ 128// board \ 129// feature \ 130// 131// SOONG_CONFIG_acme_board := soc_a 132// SOONG_CONFIG_acme_feature := true 133// SOONG_CONFIG_acme_width := 200 134// 135// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200". 136// 137// Alternatively, if acme BoardConfig.mk file contained: 138// 139// SOONG_CONFIG_NAMESPACES += acme 140// SOONG_CONFIG_acme += \ 141// board \ 142// feature \ 143// 144// SOONG_CONFIG_acme_feature := false 145// 146// Then libacme_foo would build with cflags: 147// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT". 148// 149// Similarly, if acme BoardConfig.mk file contained: 150// 151// SOONG_CONFIG_NAMESPACES += acme 152// SOONG_CONFIG_acme += \ 153// board \ 154// feature \ 155// 156// SOONG_CONFIG_acme_board := soc_c 157// 158// Then libacme_foo would build with cflags: 159// "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT". 160 161func soongConfigModuleTypeImportFactory() Module { 162 module := &soongConfigModuleTypeImport{} 163 164 module.AddProperties(&module.properties) 165 AddLoadHook(module, func(ctx LoadHookContext) { 166 importModuleTypes(ctx, module.properties.From, module.properties.Module_types...) 167 }) 168 169 initAndroidModuleBase(module) 170 return module 171} 172 173func (m *soongConfigModuleTypeImport) Name() string { 174 // The generated name is non-deterministic, but it does not 175 // matter because this module does not emit any rules. 176 return soongconfig.CanonicalizeToProperty(m.properties.From) + 177 "soong_config_module_type_import_" + fmt.Sprintf("%p", m) 178} 179 180func (*soongConfigModuleTypeImport) Nameless() {} 181func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {} 182 183// Create dummy modules for soong_config_module_type and soong_config_*_variable 184 185type soongConfigModuleTypeModule struct { 186 ModuleBase 187 properties soongconfig.ModuleTypeProperties 188} 189 190// soong_config_module_type defines module types with conditionals on Soong config 191// variables. The new module type will exist for all modules after the definition 192// in an Android.bp file, and can be imported into other Android.bp files using 193// soong_config_module_type_import. 194// 195// Each soong_config_variable supports an additional value `conditions_default`. The properties 196// specified in `conditions_default` will only be used under the following conditions: 197// bool variable: the variable is unspecified or not set to a true value 198// value variable: the variable is unspecified 199// string variable: the variable is unspecified or the variable is set to a string unused in the 200// given module. For example, string variable `test` takes values: "a" and "b", 201// if the module contains a property `a` and `conditions_default`, when test=b, 202// the properties under `conditions_default` will be used. To specify that no 203// properties should be amended for `b`, you can set `b: {},`. 204// 205// For example, an Android.bp file could have: 206// 207// soong_config_module_type { 208// name: "acme_cc_defaults", 209// module_type: "cc_defaults", 210// config_namespace: "acme", 211// variables: ["board"], 212// bool_variables: ["feature"], 213// value_variables: ["width"], 214// properties: ["cflags", "srcs"], 215// } 216// 217// soong_config_string_variable { 218// name: "board", 219// values: ["soc_a", "soc_b"], 220// } 221// 222// acme_cc_defaults { 223// name: "acme_defaults", 224// cflags: ["-DGENERIC"], 225// soong_config_variables: { 226// board: { 227// soc_a: { 228// cflags: ["-DSOC_A"], 229// }, 230// soc_b: { 231// cflags: ["-DSOC_B"], 232// }, 233// conditions_default: { 234// cflags: ["-DSOC_DEFAULT"], 235// }, 236// }, 237// feature: { 238// cflags: ["-DFEATURE"], 239// conditions_default: { 240// cflags: ["-DFEATURE_DEFAULT"], 241// }, 242// }, 243// width: { 244// cflags: ["-DWIDTH=%s"], 245// conditions_default: { 246// cflags: ["-DWIDTH=DEFAULT"], 247// }, 248// }, 249// }, 250// } 251// 252// cc_library { 253// name: "libacme_foo", 254// defaults: ["acme_defaults"], 255// srcs: ["*.cpp"], 256// } 257// 258// If an acme BoardConfig.mk file contained: 259// 260// SOONG_CONFIG_NAMESPACES += acme 261// SOONG_CONFIG_acme += \ 262// board \ 263// feature \ 264// 265// SOONG_CONFIG_acme_board := soc_a 266// SOONG_CONFIG_acme_feature := true 267// SOONG_CONFIG_acme_width := 200 268// 269// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE". 270func soongConfigModuleTypeFactory() Module { 271 module := &soongConfigModuleTypeModule{} 272 273 module.AddProperties(&module.properties) 274 275 AddLoadHook(module, func(ctx LoadHookContext) { 276 // A soong_config_module_type module should implicitly import itself. 277 importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name) 278 }) 279 280 initAndroidModuleBase(module) 281 282 return module 283} 284 285func (m *soongConfigModuleTypeModule) Name() string { 286 return m.properties.Name 287} 288func (*soongConfigModuleTypeModule) Nameless() {} 289func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 290 291type soongConfigStringVariableDummyModule struct { 292 ModuleBase 293 properties soongconfig.VariableProperties 294 stringProperties soongconfig.StringVariableProperties 295} 296 297type soongConfigBoolVariableDummyModule struct { 298 ModuleBase 299 properties soongconfig.VariableProperties 300} 301 302// soong_config_string_variable defines a variable and a set of possible string values for use 303// in a soong_config_module_type definition. 304func soongConfigStringVariableDummyFactory() Module { 305 module := &soongConfigStringVariableDummyModule{} 306 module.AddProperties(&module.properties, &module.stringProperties) 307 initAndroidModuleBase(module) 308 return module 309} 310 311// soong_config_string_variable defines a variable with true or false values for use 312// in a soong_config_module_type definition. 313func soongConfigBoolVariableDummyFactory() Module { 314 module := &soongConfigBoolVariableDummyModule{} 315 module.AddProperties(&module.properties) 316 initAndroidModuleBase(module) 317 return module 318} 319 320func (m *soongConfigStringVariableDummyModule) Name() string { 321 return m.properties.Name 322} 323func (*soongConfigStringVariableDummyModule) Nameless() {} 324func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 325 326func (m *soongConfigBoolVariableDummyModule) Name() string { 327 return m.properties.Name 328} 329func (*soongConfigBoolVariableDummyModule) Nameless() {} 330func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 331 332func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) { 333 from = filepath.Clean(from) 334 if filepath.Ext(from) != ".bp" { 335 ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from) 336 return 337 } 338 339 if strings.HasPrefix(from, "../") { 340 ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree", 341 from) 342 return 343 } 344 345 moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from) 346 if moduleTypeDefinitions == nil { 347 return 348 } 349 for _, moduleType := range moduleTypes { 350 if factory, ok := moduleTypeDefinitions[moduleType]; ok { 351 ctx.registerScopedModuleType(moduleType, factory) 352 } else { 353 ctx.PropertyErrorf("module_types", "module type %q not defined in %q", 354 moduleType, from) 355 } 356 } 357} 358 359// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the 360// result so each file is only parsed once. 361func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory { 362 type onceKeyType string 363 key := NewCustomOnceKey(onceKeyType(filepath.Clean(from))) 364 365 reportErrors := func(ctx LoadHookContext, filename string, errs ...error) { 366 for _, err := range errs { 367 if parseErr, ok := err.(*parser.ParseError); ok { 368 ctx.Errorf(parseErr.Pos, "%s", parseErr.Err) 369 } else { 370 ctx.Errorf(scanner.Position{Filename: filename}, "%s", err) 371 } 372 } 373 } 374 375 return ctx.Config().Once(key, func() interface{} { 376 ctx.AddNinjaFileDeps(from) 377 r, err := ctx.Config().fs.Open(from) 378 if err != nil { 379 ctx.PropertyErrorf("from", "failed to open %q: %s", from, err) 380 return (map[string]blueprint.ModuleFactory)(nil) 381 } 382 383 mtDef, errs := soongconfig.Parse(r, from) 384 385 if len(errs) > 0 { 386 reportErrors(ctx, from, errs...) 387 return (map[string]blueprint.ModuleFactory)(nil) 388 } 389 390 globalModuleTypes := ctx.moduleFactories() 391 392 factories := make(map[string]blueprint.ModuleFactory) 393 394 for name, moduleType := range mtDef.ModuleTypes { 395 factory := globalModuleTypes[moduleType.BaseModuleType] 396 if factory != nil { 397 factories[name] = soongConfigModuleFactory(factory, moduleType) 398 } else { 399 reportErrors(ctx, from, 400 fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType)) 401 } 402 } 403 404 if ctx.Failed() { 405 return (map[string]blueprint.ModuleFactory)(nil) 406 } 407 408 return factories 409 }).(map[string]blueprint.ModuleFactory) 410} 411 412// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns 413// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config 414// variables. 415func soongConfigModuleFactory(factory blueprint.ModuleFactory, 416 moduleType *soongconfig.ModuleType) blueprint.ModuleFactory { 417 418 conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType) 419 if conditionalFactoryProps.IsValid() { 420 return func() (blueprint.Module, []interface{}) { 421 module, props := factory() 422 423 conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps) 424 props = append(props, conditionalProps.Interface()) 425 426 AddLoadHook(module, func(ctx LoadHookContext) { 427 config := ctx.Config().VendorConfig(moduleType.ConfigNamespace) 428 newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config) 429 if err != nil { 430 ctx.ModuleErrorf("%s", err) 431 return 432 } 433 for _, ps := range newProps { 434 ctx.AppendProperties(ps) 435 } 436 }) 437 438 return module, props 439 } 440 } else { 441 return factory 442 } 443} 444