soong_config_modules.go 16 KB

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