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