blob: 8fc08ad5729fd1d7f2047031f76a6983664468da [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// },
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 {
Sasha Smundak116ec922020-03-10 16:10:06 -0700128 // The generated name is non-deterministic, but it does not
129 // matter because this module does not emit any rules.
130 return soongconfig.CanonicalizeToProperty(m.properties.From) +
131 "soong_config_module_type_import_" + fmt.Sprintf("%p", m)
Colin Cross9d34f352019-11-22 16:03:51 -0800132}
133
134func (*soongConfigModuleTypeImport) Nameless() {}
135func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {}
136
137// Create dummy modules for soong_config_module_type and soong_config_*_variable
138
139type soongConfigModuleTypeModule struct {
140 ModuleBase
141 properties soongconfig.ModuleTypeProperties
142}
143
144// soong_config_module_type defines module types with conditionals on Soong config
Bill Peckhamc93258b2020-02-04 13:17:24 -0800145// variables. The new module type will exist for all modules after the definition
146// in an Android.bp file, and can be imported into other Android.bp files using
147// soong_config_module_type_import.
Colin Cross9d34f352019-11-22 16:03:51 -0800148//
149// For example, an Android.bp file could have:
150//
151// soong_config_module_type {
152// name: "acme_cc_defaults",
153// module_type: "cc_defaults",
154// config_namespace: "acme",
155// variables: ["board", "feature"],
156// properties: ["cflags", "srcs"],
157// }
158//
159// soong_config_string_variable {
160// name: "board",
161// values: ["soc_a", "soc_b"],
162// }
163//
164// soong_config_bool_variable {
165// name: "feature",
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// },
184// }
185//
186// cc_library {
187// name: "libacme_foo",
188// defaults: ["acme_defaults"],
189// srcs: ["*.cpp"],
190// }
191//
Colin Cross9d34f352019-11-22 16:03:51 -0800192// If an acme BoardConfig.mk file contained:
193//
194// SOONG_CONFIG_NAMESPACES += acme
195// SOONG_CONFIG_acme += \
196// board \
197// feature \
198//
199// SOONG_CONFIG_acme_board := soc_a
200// SOONG_CONFIG_acme_feature := true
201//
202// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
203func soongConfigModuleTypeFactory() Module {
204 module := &soongConfigModuleTypeModule{}
205
206 module.AddProperties(&module.properties)
207
208 AddLoadHook(module, func(ctx LoadHookContext) {
209 // A soong_config_module_type module should implicitly import itself.
210 importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name)
211 })
212
213 initAndroidModuleBase(module)
214
215 return module
216}
217
218func (m *soongConfigModuleTypeModule) Name() string {
219 return m.properties.Name
220}
221func (*soongConfigModuleTypeModule) Nameless() {}
222func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
223
224type soongConfigStringVariableDummyModule struct {
225 ModuleBase
226 properties soongconfig.VariableProperties
227 stringProperties soongconfig.StringVariableProperties
228}
229
230type soongConfigBoolVariableDummyModule struct {
231 ModuleBase
232 properties soongconfig.VariableProperties
233}
234
235// soong_config_string_variable defines a variable and a set of possible string values for use
236// in a soong_config_module_type definition.
237func soongConfigStringVariableDummyFactory() Module {
238 module := &soongConfigStringVariableDummyModule{}
239 module.AddProperties(&module.properties, &module.stringProperties)
240 initAndroidModuleBase(module)
241 return module
242}
243
244// soong_config_string_variable defines a variable with true or false values for use
245// in a soong_config_module_type definition.
246func soongConfigBoolVariableDummyFactory() Module {
247 module := &soongConfigBoolVariableDummyModule{}
248 module.AddProperties(&module.properties)
249 initAndroidModuleBase(module)
250 return module
251}
252
253func (m *soongConfigStringVariableDummyModule) Name() string {
254 return m.properties.Name
255}
256func (*soongConfigStringVariableDummyModule) Nameless() {}
257func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
258
259func (m *soongConfigBoolVariableDummyModule) Name() string {
260 return m.properties.Name
261}
262func (*soongConfigBoolVariableDummyModule) Nameless() {}
263func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
264
265func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
266 from = filepath.Clean(from)
267 if filepath.Ext(from) != ".bp" {
268 ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from)
269 return
270 }
271
272 if strings.HasPrefix(from, "../") {
273 ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree",
274 from)
275 return
276 }
277
278 moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from)
279 if moduleTypeDefinitions == nil {
280 return
281 }
282 for _, moduleType := range moduleTypes {
283 if factory, ok := moduleTypeDefinitions[moduleType]; ok {
284 ctx.registerScopedModuleType(moduleType, factory)
285 } else {
286 ctx.PropertyErrorf("module_types", "module type %q not defined in %q",
287 moduleType, from)
288 }
289 }
290}
291
292// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
293// result so each file is only parsed once.
294func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory {
295 type onceKeyType string
296 key := NewCustomOnceKey(onceKeyType(filepath.Clean(from)))
297
298 reportErrors := func(ctx LoadHookContext, filename string, errs ...error) {
299 for _, err := range errs {
300 if parseErr, ok := err.(*parser.ParseError); ok {
301 ctx.Errorf(parseErr.Pos, "%s", parseErr.Err)
302 } else {
303 ctx.Errorf(scanner.Position{Filename: filename}, "%s", err)
304 }
305 }
306 }
307
308 return ctx.Config().Once(key, func() interface{} {
Colin Cross39e545c2020-02-05 16:26:19 -0800309 ctx.AddNinjaFileDeps(from)
Colin Cross9d34f352019-11-22 16:03:51 -0800310 r, err := ctx.Config().fs.Open(from)
311 if err != nil {
312 ctx.PropertyErrorf("from", "failed to open %q: %s", from, err)
313 return (map[string]blueprint.ModuleFactory)(nil)
314 }
315
316 mtDef, errs := soongconfig.Parse(r, from)
317
318 if len(errs) > 0 {
319 reportErrors(ctx, from, errs...)
320 return (map[string]blueprint.ModuleFactory)(nil)
321 }
322
323 globalModuleTypes := ctx.moduleFactories()
324
325 factories := make(map[string]blueprint.ModuleFactory)
326
327 for name, moduleType := range mtDef.ModuleTypes {
328 factory := globalModuleTypes[moduleType.BaseModuleType]
329 if factory != nil {
330 factories[name] = soongConfigModuleFactory(factory, moduleType)
331 } else {
332 reportErrors(ctx, from,
333 fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
334 }
335 }
336
337 if ctx.Failed() {
338 return (map[string]blueprint.ModuleFactory)(nil)
339 }
340
341 return factories
342 }).(map[string]blueprint.ModuleFactory)
343}
344
345// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns
346// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config
347// variables.
348func soongConfigModuleFactory(factory blueprint.ModuleFactory,
349 moduleType *soongconfig.ModuleType) blueprint.ModuleFactory {
350
351 conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
352 if conditionalFactoryProps.IsValid() {
353 return func() (blueprint.Module, []interface{}) {
354 module, props := factory()
355
Colin Cross43e789d2020-01-28 09:46:50 -0800356 conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
Colin Cross9d34f352019-11-22 16:03:51 -0800357 props = append(props, conditionalProps.Interface())
358
359 AddLoadHook(module, func(ctx LoadHookContext) {
360 config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
361 for _, ps := range soongconfig.PropertiesToApply(moduleType, conditionalProps, config) {
362 ctx.AppendProperties(ps)
363 }
364 })
365
366 return module, props
367 }
368 } else {
369 return factory
370 }
371}