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//
126//     SOONG_CONFIG_NAMESPACES += acme
127//     SOONG_CONFIG_acme += \
128//         board \
129//         feature \
130//
131//     SOONG_CONFIG_acme_board := soc_a
132//     SOONG_CONFIG_acme_feature := true
133//     SOONG_CONFIG_acme_width := 200
134//
135// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
136//
137// Alternatively, if acme BoardConfig.mk file contained:
138//
139//     SOONG_CONFIG_NAMESPACES += acme
140//     SOONG_CONFIG_acme += \
141//         board \
142//         feature \
143//
144//     SOONG_CONFIG_acme_feature := false
145//
146// Then libacme_foo would build with cflags:
147//   "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
148//
149// Similarly, if acme BoardConfig.mk file contained:
150//
151//     SOONG_CONFIG_NAMESPACES += acme
152//     SOONG_CONFIG_acme += \
153//         board \
154//         feature \
155//
156//     SOONG_CONFIG_acme_board := soc_c
157//
158// Then libacme_foo would build with cflags:
159//   "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
160
161func soongConfigModuleTypeImportFactory() Module {
162	module := &soongConfigModuleTypeImport{}
163
164	module.AddProperties(&module.properties)
165	AddLoadHook(module, func(ctx LoadHookContext) {
166		importModuleTypes(ctx, module.properties.From, module.properties.Module_types...)
167	})
168
169	initAndroidModuleBase(module)
170	return module
171}
172
173func (m *soongConfigModuleTypeImport) Name() string {
174	// The generated name is non-deterministic, but it does not
175	// matter because this module does not emit any rules.
176	return soongconfig.CanonicalizeToProperty(m.properties.From) +
177		"soong_config_module_type_import_" + fmt.Sprintf("%p", m)
178}
179
180func (*soongConfigModuleTypeImport) Nameless()                                 {}
181func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {}
182
183// Create dummy modules for soong_config_module_type and soong_config_*_variable
184
185type soongConfigModuleTypeModule struct {
186	ModuleBase
187	properties soongconfig.ModuleTypeProperties
188}
189
190// soong_config_module_type defines module types with conditionals on Soong config
191// variables.  The new module type will exist for all modules after the definition
192// in an Android.bp file, and can be imported into other Android.bp files using
193// soong_config_module_type_import.
194//
195// Each soong_config_variable supports an additional value `conditions_default`. The properties
196// specified in `conditions_default` will only be used under the following conditions:
197//   bool variable: the variable is unspecified or not set to a true value
198//   value variable: the variable is unspecified
199//   string variable: the variable is unspecified or the variable is set to a string unused in the
200//                    given module. For example, string variable `test` takes values: "a" and "b",
201//                    if the module contains a property `a` and `conditions_default`, when test=b,
202//                    the properties under `conditions_default` will be used. To specify that no
203//                    properties should be amended for `b`, you can set `b: {},`.
204//
205// For example, an Android.bp file could have:
206//
207//     soong_config_module_type {
208//         name: "acme_cc_defaults",
209//         module_type: "cc_defaults",
210//         config_namespace: "acme",
211//         variables: ["board"],
212//         bool_variables: ["feature"],
213//         value_variables: ["width"],
214//         properties: ["cflags", "srcs"],
215//     }
216//
217//     soong_config_string_variable {
218//         name: "board",
219//         values: ["soc_a", "soc_b"],
220//     }
221//
222//     acme_cc_defaults {
223//         name: "acme_defaults",
224//         cflags: ["-DGENERIC"],
225//         soong_config_variables: {
226//             board: {
227//                 soc_a: {
228//                     cflags: ["-DSOC_A"],
229//                 },
230//                 soc_b: {
231//                     cflags: ["-DSOC_B"],
232//                 },
233//                 conditions_default: {
234//                     cflags: ["-DSOC_DEFAULT"],
235//                 },
236//             },
237//             feature: {
238//                 cflags: ["-DFEATURE"],
239//                 conditions_default: {
240//                     cflags: ["-DFEATURE_DEFAULT"],
241//                 },
242//             },
243//             width: {
244//	               cflags: ["-DWIDTH=%s"],
245//                 conditions_default: {
246//                     cflags: ["-DWIDTH=DEFAULT"],
247//                 },
248//             },
249//         },
250//     }
251//
252//     cc_library {
253//         name: "libacme_foo",
254//         defaults: ["acme_defaults"],
255//         srcs: ["*.cpp"],
256//     }
257//
258// If an acme BoardConfig.mk file contained:
259//
260//     SOONG_CONFIG_NAMESPACES += acme
261//     SOONG_CONFIG_acme += \
262//         board \
263//         feature \
264//
265//     SOONG_CONFIG_acme_board := soc_a
266//     SOONG_CONFIG_acme_feature := true
267//     SOONG_CONFIG_acme_width := 200
268//
269// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
270func soongConfigModuleTypeFactory() Module {
271	module := &soongConfigModuleTypeModule{}
272
273	module.AddProperties(&module.properties)
274
275	AddLoadHook(module, func(ctx LoadHookContext) {
276		// A soong_config_module_type module should implicitly import itself.
277		importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name)
278	})
279
280	initAndroidModuleBase(module)
281
282	return module
283}
284
285func (m *soongConfigModuleTypeModule) Name() string {
286	return m.properties.Name
287}
288func (*soongConfigModuleTypeModule) Nameless()                                     {}
289func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
290
291type soongConfigStringVariableDummyModule struct {
292	ModuleBase
293	properties       soongconfig.VariableProperties
294	stringProperties soongconfig.StringVariableProperties
295}
296
297type soongConfigBoolVariableDummyModule struct {
298	ModuleBase
299	properties soongconfig.VariableProperties
300}
301
302// soong_config_string_variable defines a variable and a set of possible string values for use
303// in a soong_config_module_type definition.
304func soongConfigStringVariableDummyFactory() Module {
305	module := &soongConfigStringVariableDummyModule{}
306	module.AddProperties(&module.properties, &module.stringProperties)
307	initAndroidModuleBase(module)
308	return module
309}
310
311// soong_config_string_variable defines a variable with true or false values for use
312// in a soong_config_module_type definition.
313func soongConfigBoolVariableDummyFactory() Module {
314	module := &soongConfigBoolVariableDummyModule{}
315	module.AddProperties(&module.properties)
316	initAndroidModuleBase(module)
317	return module
318}
319
320func (m *soongConfigStringVariableDummyModule) Name() string {
321	return m.properties.Name
322}
323func (*soongConfigStringVariableDummyModule) Nameless()                                     {}
324func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
325
326func (m *soongConfigBoolVariableDummyModule) Name() string {
327	return m.properties.Name
328}
329func (*soongConfigBoolVariableDummyModule) Nameless()                                     {}
330func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
331
332func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
333	from = filepath.Clean(from)
334	if filepath.Ext(from) != ".bp" {
335		ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from)
336		return
337	}
338
339	if strings.HasPrefix(from, "../") {
340		ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree",
341			from)
342		return
343	}
344
345	moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from)
346	if moduleTypeDefinitions == nil {
347		return
348	}
349	for _, moduleType := range moduleTypes {
350		if factory, ok := moduleTypeDefinitions[moduleType]; ok {
351			ctx.registerScopedModuleType(moduleType, factory)
352		} else {
353			ctx.PropertyErrorf("module_types", "module type %q not defined in %q",
354				moduleType, from)
355		}
356	}
357}
358
359// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file.  It caches the
360// result so each file is only parsed once.
361func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory {
362	type onceKeyType string
363	key := NewCustomOnceKey(onceKeyType(filepath.Clean(from)))
364
365	reportErrors := func(ctx LoadHookContext, filename string, errs ...error) {
366		for _, err := range errs {
367			if parseErr, ok := err.(*parser.ParseError); ok {
368				ctx.Errorf(parseErr.Pos, "%s", parseErr.Err)
369			} else {
370				ctx.Errorf(scanner.Position{Filename: filename}, "%s", err)
371			}
372		}
373	}
374
375	return ctx.Config().Once(key, func() interface{} {
376		ctx.AddNinjaFileDeps(from)
377		r, err := ctx.Config().fs.Open(from)
378		if err != nil {
379			ctx.PropertyErrorf("from", "failed to open %q: %s", from, err)
380			return (map[string]blueprint.ModuleFactory)(nil)
381		}
382
383		mtDef, errs := soongconfig.Parse(r, from)
384
385		if len(errs) > 0 {
386			reportErrors(ctx, from, errs...)
387			return (map[string]blueprint.ModuleFactory)(nil)
388		}
389
390		globalModuleTypes := ctx.moduleFactories()
391
392		factories := make(map[string]blueprint.ModuleFactory)
393
394		for name, moduleType := range mtDef.ModuleTypes {
395			factory := globalModuleTypes[moduleType.BaseModuleType]
396			if factory != nil {
397				factories[name] = soongConfigModuleFactory(factory, moduleType)
398			} else {
399				reportErrors(ctx, from,
400					fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
401			}
402		}
403
404		if ctx.Failed() {
405			return (map[string]blueprint.ModuleFactory)(nil)
406		}
407
408		return factories
409	}).(map[string]blueprint.ModuleFactory)
410}
411
412// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns
413// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config
414// variables.
415func soongConfigModuleFactory(factory blueprint.ModuleFactory,
416	moduleType *soongconfig.ModuleType) blueprint.ModuleFactory {
417
418	conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
419	if conditionalFactoryProps.IsValid() {
420		return func() (blueprint.Module, []interface{}) {
421			module, props := factory()
422
423			conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
424			props = append(props, conditionalProps.Interface())
425
426			AddLoadHook(module, func(ctx LoadHookContext) {
427				config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
428				newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
429				if err != nil {
430					ctx.ModuleErrorf("%s", err)
431					return
432				}
433				for _, ps := range newProps {
434					ctx.AppendProperties(ps)
435				}
436			})
437
438			return module, props
439		}
440	} else {
441		return factory
442	}
443}
444