soong_config_modules.go 19 KB

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