blob: 619cf8615a4e6f3db7341cdc43d7a867d070d494 [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 {
Bill Peckhamc93258b2020-02-04 13:17:24 -080057// from: "device/acme/Android.bp",
Colin Cross9d34f352019-11-22 16:03:51 -080058// 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// },
Dan Willemsenb0935db2020-03-23 19:42:18 -070076// width: {
77// cflags: ["-DWIDTH=%s"],
78// },
Colin Cross9d34f352019-11-22 16:03:51 -080079// },
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",
Dan Willemsen2b8b89c2020-03-23 19:39:34 -070094// variables: ["board"],
95// bool_variables: ["feature"],
Dan Willemsenb0935db2020-03-23 19:42:18 -070096// value_variables: ["width"],
Colin Cross9d34f352019-11-22 16:03:51 -080097// properties: ["cflags", "srcs"],
98// }
99//
100// soong_config_string_variable {
101// name: "board",
102// values: ["soc_a", "soc_b"],
103// }
104//
Colin Cross9d34f352019-11-22 16:03:51 -0800105// 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
Dan Willemsenb0935db2020-03-23 19:42:18 -0700114// SOONG_CONFIG_acme_width := 200
Colin Cross9d34f352019-11-22 16:03:51 -0800115//
Dan Willemsenb0935db2020-03-23 19:42:18 -0700116// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
Colin Cross9d34f352019-11-22 16:03:51 -0800117func 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 {
Sasha Smundak116ec922020-03-10 16:10:06 -0700130 // 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)
Colin Cross9d34f352019-11-22 16:03:51 -0800134}
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
Bill Peckhamc93258b2020-02-04 13:17:24 -0800147// 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.
Colin Cross9d34f352019-11-22 16:03:51 -0800150//
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",
Dan Willemsen2b8b89c2020-03-23 19:39:34 -0700157// variables: ["board"],
158// bool_variables: ["feature"],
Dan Willemsenb0935db2020-03-23 19:42:18 -0700159// value_variables: ["width"],
Colin Cross9d34f352019-11-22 16:03:51 -0800160// properties: ["cflags", "srcs"],
161// }
162//
163// soong_config_string_variable {
164// name: "board",
165// values: ["soc_a", "soc_b"],
166// }
167//
Colin Cross9d34f352019-11-22 16:03:51 -0800168// 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// },
Dan Willemsenb0935db2020-03-23 19:42:18 -0700183// width: {
184// cflags: ["-DWIDTH=%s"],
185// },
Colin Cross9d34f352019-11-22 16:03:51 -0800186// },
187// }
188//
189// cc_library {
190// name: "libacme_foo",
191// defaults: ["acme_defaults"],
192// srcs: ["*.cpp"],
193// }
194//
Colin Cross9d34f352019-11-22 16:03:51 -0800195// 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
Dan Willemsenb0935db2020-03-23 19:42:18 -0700204// SOONG_CONFIG_acme_width := 200
Colin Cross9d34f352019-11-22 16:03:51 -0800205//
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{} {
Colin Cross39e545c2020-02-05 16:26:19 -0800313 ctx.AddNinjaFileDeps(from)
Colin Cross9d34f352019-11-22 16:03:51 -0800314 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
Colin Cross43e789d2020-01-28 09:46:50 -0800360 conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
Colin Cross9d34f352019-11-22 16:03:51 -0800361 props = append(props, conditionalProps.Interface())
362
363 AddLoadHook(module, func(ctx LoadHookContext) {
364 config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
Dan Willemsenb0935db2020-03-23 19:42:18 -0700365 newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
366 if err != nil {
367 ctx.ModuleErrorf("%s", err)
368 return
369 }
370 for _, ps := range newProps {
Colin Cross9d34f352019-11-22 16:03:51 -0800371 ctx.AppendProperties(ps)
372 }
373 })
374
375 return module, props
376 }
377 } else {
378 return factory
379 }
380}