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