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