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// For example, an Android.bp file could have: 55// 56// soong_config_module_type_import { 57// from: "device/acme/Android.bp", 58// module_types: ["acme_cc_defaults"], 59// } 60// 61// acme_cc_defaults { 62// name: "acme_defaults", 63// cflags: ["-DGENERIC"], 64// soong_config_variables: { 65// board: { 66// soc_a: { 67// cflags: ["-DSOC_A"], 68// }, 69// soc_b: { 70// cflags: ["-DSOC_B"], 71// }, 72// }, 73// feature: { 74// cflags: ["-DFEATURE"], 75// }, 76// width: { 77// cflags: ["-DWIDTH=%s"], 78// }, 79// }, 80// } 81// 82// cc_library { 83// name: "libacme_foo", 84// defaults: ["acme_defaults"], 85// srcs: ["*.cpp"], 86// } 87// 88// And device/acme/Android.bp could have: 89// 90// soong_config_module_type { 91// name: "acme_cc_defaults", 92// module_type: "cc_defaults", 93// config_namespace: "acme", 94// variables: ["board"], 95// bool_variables: ["feature"], 96// value_variables: ["width"], 97// properties: ["cflags", "srcs"], 98// } 99// 100// soong_config_string_variable { 101// name: "board", 102// values: ["soc_a", "soc_b"], 103// } 104// 105// If an acme BoardConfig.mk file contained: 106// 107// SOONG_CONFIG_NAMESPACES += acme 108// SOONG_CONFIG_acme += \ 109// board \ 110// feature \ 111// 112// SOONG_CONFIG_acme_board := soc_a 113// SOONG_CONFIG_acme_feature := true 114// SOONG_CONFIG_acme_width := 200 115// 116// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200". 117func soongConfigModuleTypeImportFactory() Module { 118 module := &soongConfigModuleTypeImport{} 119 120 module.AddProperties(&module.properties) 121 AddLoadHook(module, func(ctx LoadHookContext) { 122 importModuleTypes(ctx, module.properties.From, module.properties.Module_types...) 123 }) 124 125 initAndroidModuleBase(module) 126 return module 127} 128 129func (m *soongConfigModuleTypeImport) Name() string { 130 // The generated name is non-deterministic, but it does not 131 // matter because this module does not emit any rules. 132 return soongconfig.CanonicalizeToProperty(m.properties.From) + 133 "soong_config_module_type_import_" + fmt.Sprintf("%p", m) 134} 135 136func (*soongConfigModuleTypeImport) Nameless() {} 137func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {} 138 139// Create dummy modules for soong_config_module_type and soong_config_*_variable 140 141type soongConfigModuleTypeModule struct { 142 ModuleBase 143 properties soongconfig.ModuleTypeProperties 144} 145 146// soong_config_module_type defines module types with conditionals on Soong config 147// variables. The new module type will exist for all modules after the definition 148// in an Android.bp file, and can be imported into other Android.bp files using 149// soong_config_module_type_import. 150// 151// For example, an Android.bp file could have: 152// 153// soong_config_module_type { 154// name: "acme_cc_defaults", 155// module_type: "cc_defaults", 156// config_namespace: "acme", 157// variables: ["board"], 158// bool_variables: ["feature"], 159// value_variables: ["width"], 160// properties: ["cflags", "srcs"], 161// } 162// 163// soong_config_string_variable { 164// name: "board", 165// values: ["soc_a", "soc_b"], 166// } 167// 168// acme_cc_defaults { 169// name: "acme_defaults", 170// cflags: ["-DGENERIC"], 171// soong_config_variables: { 172// board: { 173// soc_a: { 174// cflags: ["-DSOC_A"], 175// }, 176// soc_b: { 177// cflags: ["-DSOC_B"], 178// }, 179// }, 180// feature: { 181// cflags: ["-DFEATURE"], 182// }, 183// width: { 184// cflags: ["-DWIDTH=%s"], 185// }, 186// }, 187// } 188// 189// cc_library { 190// name: "libacme_foo", 191// defaults: ["acme_defaults"], 192// srcs: ["*.cpp"], 193// } 194// 195// If an acme BoardConfig.mk file contained: 196// 197// SOONG_CONFIG_NAMESPACES += acme 198// SOONG_CONFIG_acme += \ 199// board \ 200// feature \ 201// 202// SOONG_CONFIG_acme_board := soc_a 203// SOONG_CONFIG_acme_feature := true 204// SOONG_CONFIG_acme_width := 200 205// 206// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE". 207func soongConfigModuleTypeFactory() Module { 208 module := &soongConfigModuleTypeModule{} 209 210 module.AddProperties(&module.properties) 211 212 AddLoadHook(module, func(ctx LoadHookContext) { 213 // A soong_config_module_type module should implicitly import itself. 214 importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name) 215 }) 216 217 initAndroidModuleBase(module) 218 219 return module 220} 221 222func (m *soongConfigModuleTypeModule) Name() string { 223 return m.properties.Name 224} 225func (*soongConfigModuleTypeModule) Nameless() {} 226func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 227 228type soongConfigStringVariableDummyModule struct { 229 ModuleBase 230 properties soongconfig.VariableProperties 231 stringProperties soongconfig.StringVariableProperties 232} 233 234type soongConfigBoolVariableDummyModule struct { 235 ModuleBase 236 properties soongconfig.VariableProperties 237} 238 239// soong_config_string_variable defines a variable and a set of possible string values for use 240// in a soong_config_module_type definition. 241func soongConfigStringVariableDummyFactory() Module { 242 module := &soongConfigStringVariableDummyModule{} 243 module.AddProperties(&module.properties, &module.stringProperties) 244 initAndroidModuleBase(module) 245 return module 246} 247 248// soong_config_string_variable defines a variable with true or false values for use 249// in a soong_config_module_type definition. 250func soongConfigBoolVariableDummyFactory() Module { 251 module := &soongConfigBoolVariableDummyModule{} 252 module.AddProperties(&module.properties) 253 initAndroidModuleBase(module) 254 return module 255} 256 257func (m *soongConfigStringVariableDummyModule) Name() string { 258 return m.properties.Name 259} 260func (*soongConfigStringVariableDummyModule) Nameless() {} 261func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 262 263func (m *soongConfigBoolVariableDummyModule) Name() string { 264 return m.properties.Name 265} 266func (*soongConfigBoolVariableDummyModule) Nameless() {} 267func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {} 268 269func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) { 270 from = filepath.Clean(from) 271 if filepath.Ext(from) != ".bp" { 272 ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from) 273 return 274 } 275 276 if strings.HasPrefix(from, "../") { 277 ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree", 278 from) 279 return 280 } 281 282 moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from) 283 if moduleTypeDefinitions == nil { 284 return 285 } 286 for _, moduleType := range moduleTypes { 287 if factory, ok := moduleTypeDefinitions[moduleType]; ok { 288 ctx.registerScopedModuleType(moduleType, factory) 289 } else { 290 ctx.PropertyErrorf("module_types", "module type %q not defined in %q", 291 moduleType, from) 292 } 293 } 294} 295 296// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the 297// result so each file is only parsed once. 298func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory { 299 type onceKeyType string 300 key := NewCustomOnceKey(onceKeyType(filepath.Clean(from))) 301 302 reportErrors := func(ctx LoadHookContext, filename string, errs ...error) { 303 for _, err := range errs { 304 if parseErr, ok := err.(*parser.ParseError); ok { 305 ctx.Errorf(parseErr.Pos, "%s", parseErr.Err) 306 } else { 307 ctx.Errorf(scanner.Position{Filename: filename}, "%s", err) 308 } 309 } 310 } 311 312 return ctx.Config().Once(key, func() interface{} { 313 ctx.AddNinjaFileDeps(from) 314 r, err := ctx.Config().fs.Open(from) 315 if err != nil { 316 ctx.PropertyErrorf("from", "failed to open %q: %s", from, err) 317 return (map[string]blueprint.ModuleFactory)(nil) 318 } 319 320 mtDef, errs := soongconfig.Parse(r, from) 321 322 if len(errs) > 0 { 323 reportErrors(ctx, from, errs...) 324 return (map[string]blueprint.ModuleFactory)(nil) 325 } 326 327 globalModuleTypes := ctx.moduleFactories() 328 329 factories := make(map[string]blueprint.ModuleFactory) 330 331 for name, moduleType := range mtDef.ModuleTypes { 332 factory := globalModuleTypes[moduleType.BaseModuleType] 333 if factory != nil { 334 factories[name] = soongConfigModuleFactory(factory, moduleType) 335 } else { 336 reportErrors(ctx, from, 337 fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType)) 338 } 339 } 340 341 if ctx.Failed() { 342 return (map[string]blueprint.ModuleFactory)(nil) 343 } 344 345 return factories 346 }).(map[string]blueprint.ModuleFactory) 347} 348 349// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns 350// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config 351// variables. 352func soongConfigModuleFactory(factory blueprint.ModuleFactory, 353 moduleType *soongconfig.ModuleType) blueprint.ModuleFactory { 354 355 conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType) 356 if conditionalFactoryProps.IsValid() { 357 return func() (blueprint.Module, []interface{}) { 358 module, props := factory() 359 360 conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps) 361 props = append(props, conditionalProps.Interface()) 362 363 AddLoadHook(module, func(ctx LoadHookContext) { 364 config := ctx.Config().VendorConfig(moduleType.ConfigNamespace) 365 newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config) 366 if err != nil { 367 ctx.ModuleErrorf("%s", err) 368 return 369 } 370 for _, ps := range newProps { 371 ctx.AppendProperties(ps) 372 } 373 }) 374 375 return module, props 376 } 377 } else { 378 return factory 379 } 380} 381