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