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// For example, an Android.bp file could have:
55//
56//     soong_config_module_type_import {
57//         from: "device/acme/Android.bp",
58//         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//             width: {
77//                 cflags: ["-DWIDTH=%s"],
78//             },
79//         },
80//     }
81//
82//     cc_library {
83//         name: "libacme_foo",
84//         defaults: ["acme_defaults"],
85//         srcs: ["*.cpp"],
86//     }
87//
88// And device/acme/Android.bp could have:
89//
90//     soong_config_module_type {
91//         name: "acme_cc_defaults",
92//         module_type: "cc_defaults",
93//         config_namespace: "acme",
94//         variables: ["board"],
95//         bool_variables: ["feature"],
96//         value_variables: ["width"],
97//         properties: ["cflags", "srcs"],
98//     }
99//
100//     soong_config_string_variable {
101//         name: "board",
102//         values: ["soc_a", "soc_b"],
103//     }
104//
105// If an acme BoardConfig.mk file contained:
106//
107//     SOONG_CONFIG_NAMESPACES += acme
108//     SOONG_CONFIG_acme += \
109//         board \
110//         feature \
111//
112//     SOONG_CONFIG_acme_board := soc_a
113//     SOONG_CONFIG_acme_feature := true
114//     SOONG_CONFIG_acme_width := 200
115//
116// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
117func soongConfigModuleTypeImportFactory() Module {
118	module := &soongConfigModuleTypeImport{}
119
120	module.AddProperties(&module.properties)
121	AddLoadHook(module, func(ctx LoadHookContext) {
122		importModuleTypes(ctx, module.properties.From, module.properties.Module_types...)
123	})
124
125	initAndroidModuleBase(module)
126	return module
127}
128
129func (m *soongConfigModuleTypeImport) Name() string {
130	// The generated name is non-deterministic, but it does not
131	// matter because this module does not emit any rules.
132	return soongconfig.CanonicalizeToProperty(m.properties.From) +
133		"soong_config_module_type_import_" + fmt.Sprintf("%p", m)
134}
135
136func (*soongConfigModuleTypeImport) Nameless()                                 {}
137func (*soongConfigModuleTypeImport) GenerateAndroidBuildActions(ModuleContext) {}
138
139// Create dummy modules for soong_config_module_type and soong_config_*_variable
140
141type soongConfigModuleTypeModule struct {
142	ModuleBase
143	properties soongconfig.ModuleTypeProperties
144}
145
146// soong_config_module_type defines module types with conditionals on Soong config
147// variables.  The new module type will exist for all modules after the definition
148// in an Android.bp file, and can be imported into other Android.bp files using
149// soong_config_module_type_import.
150//
151// For example, an Android.bp file could have:
152//
153//     soong_config_module_type {
154//         name: "acme_cc_defaults",
155//         module_type: "cc_defaults",
156//         config_namespace: "acme",
157//         variables: ["board"],
158//         bool_variables: ["feature"],
159//         value_variables: ["width"],
160//         properties: ["cflags", "srcs"],
161//     }
162//
163//     soong_config_string_variable {
164//         name: "board",
165//         values: ["soc_a", "soc_b"],
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//             width: {
184//	               cflags: ["-DWIDTH=%s"],
185//             },
186//         },
187//     }
188//
189//     cc_library {
190//         name: "libacme_foo",
191//         defaults: ["acme_defaults"],
192//         srcs: ["*.cpp"],
193//     }
194//
195// If an acme BoardConfig.mk file contained:
196//
197//     SOONG_CONFIG_NAMESPACES += acme
198//     SOONG_CONFIG_acme += \
199//         board \
200//         feature \
201//
202//     SOONG_CONFIG_acme_board := soc_a
203//     SOONG_CONFIG_acme_feature := true
204//     SOONG_CONFIG_acme_width := 200
205//
206// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
207func soongConfigModuleTypeFactory() Module {
208	module := &soongConfigModuleTypeModule{}
209
210	module.AddProperties(&module.properties)
211
212	AddLoadHook(module, func(ctx LoadHookContext) {
213		// A soong_config_module_type module should implicitly import itself.
214		importModuleTypes(ctx, ctx.BlueprintsFile(), module.properties.Name)
215	})
216
217	initAndroidModuleBase(module)
218
219	return module
220}
221
222func (m *soongConfigModuleTypeModule) Name() string {
223	return m.properties.Name
224}
225func (*soongConfigModuleTypeModule) Nameless()                                     {}
226func (*soongConfigModuleTypeModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
227
228type soongConfigStringVariableDummyModule struct {
229	ModuleBase
230	properties       soongconfig.VariableProperties
231	stringProperties soongconfig.StringVariableProperties
232}
233
234type soongConfigBoolVariableDummyModule struct {
235	ModuleBase
236	properties soongconfig.VariableProperties
237}
238
239// soong_config_string_variable defines a variable and a set of possible string values for use
240// in a soong_config_module_type definition.
241func soongConfigStringVariableDummyFactory() Module {
242	module := &soongConfigStringVariableDummyModule{}
243	module.AddProperties(&module.properties, &module.stringProperties)
244	initAndroidModuleBase(module)
245	return module
246}
247
248// soong_config_string_variable defines a variable with true or false values for use
249// in a soong_config_module_type definition.
250func soongConfigBoolVariableDummyFactory() Module {
251	module := &soongConfigBoolVariableDummyModule{}
252	module.AddProperties(&module.properties)
253	initAndroidModuleBase(module)
254	return module
255}
256
257func (m *soongConfigStringVariableDummyModule) Name() string {
258	return m.properties.Name
259}
260func (*soongConfigStringVariableDummyModule) Nameless()                                     {}
261func (*soongConfigStringVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
262
263func (m *soongConfigBoolVariableDummyModule) Name() string {
264	return m.properties.Name
265}
266func (*soongConfigBoolVariableDummyModule) Nameless()                                     {}
267func (*soongConfigBoolVariableDummyModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
268
269func importModuleTypes(ctx LoadHookContext, from string, moduleTypes ...string) {
270	from = filepath.Clean(from)
271	if filepath.Ext(from) != ".bp" {
272		ctx.PropertyErrorf("from", "%q must be a file with extension .bp", from)
273		return
274	}
275
276	if strings.HasPrefix(from, "../") {
277		ctx.PropertyErrorf("from", "%q must not use ../ to escape the source tree",
278			from)
279		return
280	}
281
282	moduleTypeDefinitions := loadSoongConfigModuleTypeDefinition(ctx, from)
283	if moduleTypeDefinitions == nil {
284		return
285	}
286	for _, moduleType := range moduleTypes {
287		if factory, ok := moduleTypeDefinitions[moduleType]; ok {
288			ctx.registerScopedModuleType(moduleType, factory)
289		} else {
290			ctx.PropertyErrorf("module_types", "module type %q not defined in %q",
291				moduleType, from)
292		}
293	}
294}
295
296// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file.  It caches the
297// result so each file is only parsed once.
298func loadSoongConfigModuleTypeDefinition(ctx LoadHookContext, from string) map[string]blueprint.ModuleFactory {
299	type onceKeyType string
300	key := NewCustomOnceKey(onceKeyType(filepath.Clean(from)))
301
302	reportErrors := func(ctx LoadHookContext, filename string, errs ...error) {
303		for _, err := range errs {
304			if parseErr, ok := err.(*parser.ParseError); ok {
305				ctx.Errorf(parseErr.Pos, "%s", parseErr.Err)
306			} else {
307				ctx.Errorf(scanner.Position{Filename: filename}, "%s", err)
308			}
309		}
310	}
311
312	return ctx.Config().Once(key, func() interface{} {
313		ctx.AddNinjaFileDeps(from)
314		r, err := ctx.Config().fs.Open(from)
315		if err != nil {
316			ctx.PropertyErrorf("from", "failed to open %q: %s", from, err)
317			return (map[string]blueprint.ModuleFactory)(nil)
318		}
319
320		mtDef, errs := soongconfig.Parse(r, from)
321
322		if len(errs) > 0 {
323			reportErrors(ctx, from, errs...)
324			return (map[string]blueprint.ModuleFactory)(nil)
325		}
326
327		globalModuleTypes := ctx.moduleFactories()
328
329		factories := make(map[string]blueprint.ModuleFactory)
330
331		for name, moduleType := range mtDef.ModuleTypes {
332			factory := globalModuleTypes[moduleType.BaseModuleType]
333			if factory != nil {
334				factories[name] = soongConfigModuleFactory(factory, moduleType)
335			} else {
336				reportErrors(ctx, from,
337					fmt.Errorf("missing global module type factory for %q", moduleType.BaseModuleType))
338			}
339		}
340
341		if ctx.Failed() {
342			return (map[string]blueprint.ModuleFactory)(nil)
343		}
344
345		return factories
346	}).(map[string]blueprint.ModuleFactory)
347}
348
349// soongConfigModuleFactory takes an existing soongConfigModuleFactory and a ModuleType and returns
350// a new soongConfigModuleFactory that wraps the existing soongConfigModuleFactory and adds conditional on Soong config
351// variables.
352func soongConfigModuleFactory(factory blueprint.ModuleFactory,
353	moduleType *soongconfig.ModuleType) blueprint.ModuleFactory {
354
355	conditionalFactoryProps := soongconfig.CreateProperties(factory, moduleType)
356	if conditionalFactoryProps.IsValid() {
357		return func() (blueprint.Module, []interface{}) {
358			module, props := factory()
359
360			conditionalProps := proptools.CloneEmptyProperties(conditionalFactoryProps)
361			props = append(props, conditionalProps.Interface())
362
363			AddLoadHook(module, func(ctx LoadHookContext) {
364				config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
365				newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
366				if err != nil {
367					ctx.ModuleErrorf("%s", err)
368					return
369				}
370				for _, ps := range newProps {
371					ctx.AppendProperties(ps)
372				}
373			})
374
375			return module, props
376		}
377	} else {
378		return factory
379	}
380}
381