blob: 09a5057228dc06bec2e0cb4fc1ad6296f2f9f29a [file] [log] [blame]
Colin Cross9d34f352019-11-22 16:03:51 -08001// Copyright 2020 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 soongconfig
16
17import (
18 "fmt"
19 "io"
20 "reflect"
21 "sort"
22 "strings"
Jingwen Chen4ad40d92021-11-24 03:40:23 +000023 "sync"
Colin Cross9d34f352019-11-22 16:03:51 -080024
25 "github.com/google/blueprint"
26 "github.com/google/blueprint/parser"
27 "github.com/google/blueprint/proptools"
28)
29
Liz Kammer432bd592020-12-16 12:42:02 -080030const conditionsDefault = "conditions_default"
31
Jingwen Chena47f28d2021-11-02 16:43:57 +000032var SoongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
Colin Cross9d34f352019-11-22 16:03:51 -080033
34// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
35// result so each file is only parsed once.
36func Parse(r io.Reader, from string) (*SoongConfigDefinition, []error) {
37 scope := parser.NewScope(nil)
38 file, errs := parser.ParseAndEval(from, r, scope)
39
40 if len(errs) > 0 {
41 return nil, errs
42 }
43
44 mtDef := &SoongConfigDefinition{
45 ModuleTypes: make(map[string]*ModuleType),
46 variables: make(map[string]soongConfigVariable),
47 }
48
49 for _, def := range file.Defs {
50 switch def := def.(type) {
51 case *parser.Module:
52 newErrs := processImportModuleDef(mtDef, def)
53
54 if len(newErrs) > 0 {
55 errs = append(errs, newErrs...)
56 }
57
58 case *parser.Assignment:
59 // Already handled via Scope object
60 default:
61 panic("unknown definition type")
62 }
63 }
64
65 if len(errs) > 0 {
66 return nil, errs
67 }
68
69 for name, moduleType := range mtDef.ModuleTypes {
70 for _, varName := range moduleType.variableNames {
71 if v, ok := mtDef.variables[varName]; ok {
72 moduleType.Variables = append(moduleType.Variables, v)
73 } else {
74 return nil, []error{
75 fmt.Errorf("unknown variable %q in module type %q", varName, name),
76 }
77 }
78 }
79 }
80
81 return mtDef, nil
82}
83
84func processImportModuleDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
85 switch def.Type {
86 case "soong_config_module_type":
87 return processModuleTypeDef(v, def)
88 case "soong_config_string_variable":
89 return processStringVariableDef(v, def)
90 case "soong_config_bool_variable":
91 return processBoolVariableDef(v, def)
92 default:
93 // Unknown module types will be handled when the file is parsed as a normal
94 // Android.bp file.
95 }
96
97 return nil
98}
99
100type ModuleTypeProperties struct {
101 // the name of the new module type. Unlike most modules, this name does not need to be unique,
102 // although only one module type with any name will be importable into an Android.bp file.
103 Name string
104
105 // the module type that this module type will extend.
106 Module_type string
107
108 // the SOONG_CONFIG_NAMESPACE value from a BoardConfig.mk that this module type will read
109 // configuration variables from.
110 Config_namespace string
111
112 // the list of SOONG_CONFIG variables that this module type will read
113 Variables []string
114
Dan Willemsen2b8b89c2020-03-23 19:39:34 -0700115 // the list of boolean SOONG_CONFIG variables that this module type will read
116 Bool_variables []string
117
Dan Willemsenb0935db2020-03-23 19:42:18 -0700118 // the list of SOONG_CONFIG variables that this module type will read. The value will be
119 // inserted into the properties with %s substitution.
120 Value_variables []string
121
Colin Cross9d34f352019-11-22 16:03:51 -0800122 // the list of properties that this module type will extend.
123 Properties []string
124}
125
126func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
127
128 props := &ModuleTypeProperties{}
129
130 _, errs = proptools.UnpackProperties(def.Properties, props)
131 if len(errs) > 0 {
132 return errs
133 }
134
135 if props.Name == "" {
136 errs = append(errs, fmt.Errorf("name property must be set"))
137 }
138
139 if props.Config_namespace == "" {
140 errs = append(errs, fmt.Errorf("config_namespace property must be set"))
141 }
142
143 if props.Module_type == "" {
144 errs = append(errs, fmt.Errorf("module_type property must be set"))
145 }
146
147 if len(errs) > 0 {
148 return errs
149 }
150
Liz Kammer432bd592020-12-16 12:42:02 -0800151 if mt, errs := newModuleType(props); len(errs) > 0 {
152 return errs
153 } else {
154 v.ModuleTypes[props.Name] = mt
Dan Willemsenb0935db2020-03-23 19:42:18 -0700155 }
156
Colin Cross9d34f352019-11-22 16:03:51 -0800157 return nil
158}
159
160type VariableProperties struct {
161 Name string
162}
163
164type StringVariableProperties struct {
165 Values []string
166}
167
168func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
169 stringProps := &StringVariableProperties{}
170
171 base, errs := processVariableDef(def, stringProps)
172 if len(errs) > 0 {
173 return errs
174 }
175
176 if len(stringProps.Values) == 0 {
177 return []error{fmt.Errorf("values property must be set")}
178 }
179
Liz Kammer432bd592020-12-16 12:42:02 -0800180 for _, name := range stringProps.Values {
181 if err := checkVariableName(name); err != nil {
182 return []error{fmt.Errorf("soong_config_string_variable: values property error %s", err)}
183 }
184 }
185
Colin Cross9d34f352019-11-22 16:03:51 -0800186 v.variables[base.variable] = &stringVariable{
187 baseVariable: base,
188 values: CanonicalizeToProperties(stringProps.Values),
189 }
190
191 return nil
192}
193
194func processBoolVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
195 base, errs := processVariableDef(def)
196 if len(errs) > 0 {
197 return errs
198 }
199
200 v.variables[base.variable] = &boolVariable{
201 baseVariable: base,
202 }
203
204 return nil
205}
206
207func processVariableDef(def *parser.Module,
208 extraProps ...interface{}) (cond baseVariable, errs []error) {
209
210 props := &VariableProperties{}
211
212 allProps := append([]interface{}{props}, extraProps...)
213
214 _, errs = proptools.UnpackProperties(def.Properties, allProps...)
215 if len(errs) > 0 {
216 return baseVariable{}, errs
217 }
218
219 if props.Name == "" {
220 return baseVariable{}, []error{fmt.Errorf("name property must be set")}
221 }
222
223 return baseVariable{
224 variable: props.Name,
225 }, nil
226}
227
228type SoongConfigDefinition struct {
229 ModuleTypes map[string]*ModuleType
230
231 variables map[string]soongConfigVariable
232}
233
Jingwen Chen01812022021-11-19 14:29:43 +0000234// Bp2BuildSoongConfigDefinition keeps a global record of all soong config
235// string vars, bool vars and value vars created by every
236// soong_config_module_type in this build.
237type Bp2BuildSoongConfigDefinitions struct {
238 StringVars map[string]map[string]bool
239 BoolVars map[string]bool
240 ValueVars map[string]bool
241}
242
Jingwen Chen4ad40d92021-11-24 03:40:23 +0000243var bp2buildSoongConfigVarsLock sync.Mutex
244
Jingwen Chen01812022021-11-19 14:29:43 +0000245// SoongConfigVariablesForBp2build extracts information from a
246// SoongConfigDefinition that bp2build needs to generate constraint settings and
247// values for, in order to migrate soong_config_module_type usages to Bazel.
248func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition) {
Jingwen Chen4ad40d92021-11-24 03:40:23 +0000249 // In bp2build mode, this method is called concurrently in goroutines from
250 // loadhooks while parsing soong_config_module_type, so add a mutex to
251 // prevent concurrent map writes. See b/207572723
252 bp2buildSoongConfigVarsLock.Lock()
253 defer bp2buildSoongConfigVarsLock.Unlock()
254
Jingwen Chen01812022021-11-19 14:29:43 +0000255 if defs.StringVars == nil {
256 defs.StringVars = make(map[string]map[string]bool)
257 }
258 if defs.BoolVars == nil {
259 defs.BoolVars = make(map[string]bool)
260 }
261 if defs.ValueVars == nil {
262 defs.ValueVars = make(map[string]bool)
263 }
264 for _, moduleType := range mtDef.ModuleTypes {
265 for _, v := range moduleType.Variables {
266 key := strings.Join([]string{moduleType.ConfigNamespace, v.variableProperty()}, "__")
267 if strVar, ok := v.(*stringVariable); ok {
268 if _, ok := defs.StringVars[key]; !ok {
269 defs.StringVars[key] = make(map[string]bool, 0)
270 }
271 for _, value := range strVar.values {
272 defs.StringVars[key][value] = true
273 }
274 } else if _, ok := v.(*boolVariable); ok {
275 defs.BoolVars[key] = true
276 } else if _, ok := v.(*valueVariable); ok {
277 defs.ValueVars[key] = true
278 } else {
279 panic(fmt.Errorf("Unsupported variable type: %+v", v))
280 }
281 }
282 }
283}
284
285// This is a copy of the one available in soong/android/util.go, but depending
286// on the android package causes a cyclic dependency. A refactoring here is to
287// extract common utils out from android/utils.go for other packages like this.
288func sortedStringKeys(m interface{}) []string {
289 v := reflect.ValueOf(m)
290 if v.Kind() != reflect.Map {
291 panic(fmt.Sprintf("%#v is not a map", m))
292 }
293 keys := v.MapKeys()
294 s := make([]string, 0, len(keys))
295 for _, key := range keys {
296 s = append(s, key.String())
297 }
298 sort.Strings(s)
299 return s
300}
301
302// String emits the Soong config variable definitions as Starlark dictionaries.
303func (defs Bp2BuildSoongConfigDefinitions) String() string {
304 ret := ""
305 ret += "soong_config_bool_variables = {\n"
306 for _, boolVar := range sortedStringKeys(defs.BoolVars) {
307 ret += fmt.Sprintf(" \"%s\": True,\n", boolVar)
308 }
309 ret += "}\n"
310 ret += "\n"
311
312 ret += "soong_config_value_variables = {\n"
313 for _, valueVar := range sortedStringKeys(defs.ValueVars) {
314 ret += fmt.Sprintf(" \"%s\": True,\n", valueVar)
315 }
316 ret += "}\n"
317 ret += "\n"
318
319 ret += "soong_config_string_variables = {\n"
320 for _, stringVar := range sortedStringKeys(defs.StringVars) {
321 ret += fmt.Sprintf(" \"%s\": [\n", stringVar)
322 for _, choice := range sortedStringKeys(defs.StringVars[stringVar]) {
323 ret += fmt.Sprintf(" \"%s\",\n", choice)
324 }
325 ret += fmt.Sprintf(" ],\n")
326 }
327 ret += "}"
328
329 return ret
330}
331
Colin Cross9d34f352019-11-22 16:03:51 -0800332// CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
333// property layout for the Soong config variables, with each possible value an interface{} that
334// contains a nil pointer to another newly constructed type that contains the affectable properties.
335// The reflect.Value will be cloned for each call to the Soong config module type's factory method.
336//
337// For example, the acme_cc_defaults example above would
338// produce a reflect.Value whose type is:
339// *struct {
340// Soong_config_variables struct {
341// Board struct {
342// Soc_a interface{}
343// Soc_b interface{}
344// }
345// }
346// }
347// And whose value is:
348// &{
349// Soong_config_variables: {
350// Board: {
351// Soc_a: (*struct{ Cflags []string })(nil),
352// Soc_b: (*struct{ Cflags []string })(nil),
353// },
354// },
355// }
356func CreateProperties(factory blueprint.ModuleFactory, moduleType *ModuleType) reflect.Value {
357 var fields []reflect.StructField
358
359 _, factoryProps := factory()
360 affectablePropertiesType := createAffectablePropertiesType(moduleType.affectableProperties, factoryProps)
361 if affectablePropertiesType == nil {
362 return reflect.Value{}
363 }
364
365 for _, c := range moduleType.Variables {
366 fields = append(fields, reflect.StructField{
367 Name: proptools.FieldNameForProperty(c.variableProperty()),
368 Type: c.variableValuesType(),
369 })
370 }
371
372 typ := reflect.StructOf([]reflect.StructField{{
Jingwen Chena47f28d2021-11-02 16:43:57 +0000373 Name: SoongConfigProperty,
Colin Cross9d34f352019-11-22 16:03:51 -0800374 Type: reflect.StructOf(fields),
375 }})
376
377 props := reflect.New(typ)
Jingwen Chena47f28d2021-11-02 16:43:57 +0000378 structConditions := props.Elem().FieldByName(SoongConfigProperty)
Colin Cross9d34f352019-11-22 16:03:51 -0800379
380 for i, c := range moduleType.Variables {
381 c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
382 }
383
384 return props
385}
386
387// createAffectablePropertiesType creates a reflect.Type of a struct that has a field for each affectable property
388// that exists in factoryProps.
389func createAffectablePropertiesType(affectableProperties []string, factoryProps []interface{}) reflect.Type {
390 affectableProperties = append([]string(nil), affectableProperties...)
391 sort.Strings(affectableProperties)
392
393 var recurse func(prefix string, aps []string) ([]string, reflect.Type)
394 recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
395 var fields []reflect.StructField
396
Colin Cross997f27a2021-03-05 17:25:41 -0800397 // Iterate while the list is non-empty so it can be modified in the loop.
Colin Cross9d34f352019-11-22 16:03:51 -0800398 for len(affectableProperties) > 0 {
399 p := affectableProperties[0]
400 if !strings.HasPrefix(affectableProperties[0], prefix) {
Colin Cross997f27a2021-03-05 17:25:41 -0800401 // The properties are sorted and recurse is always called with a prefix that matches
402 // the first property in the list, so if we've reached one that doesn't match the
403 // prefix we are done with this prefix.
Colin Cross9d34f352019-11-22 16:03:51 -0800404 break
405 }
Colin Cross9d34f352019-11-22 16:03:51 -0800406
407 nestedProperty := strings.TrimPrefix(p, prefix)
408 if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
409 var nestedType reflect.Type
410 nestedPrefix := nestedProperty[:i+1]
411
Colin Cross997f27a2021-03-05 17:25:41 -0800412 // Recurse to handle the properties with the found prefix. This will return
413 // an updated affectableProperties with the handled entries removed from the front
414 // of the list, and the type that contains the handled entries. The type may be
415 // nil if none of the entries matched factoryProps.
Colin Cross9d34f352019-11-22 16:03:51 -0800416 affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
417
418 if nestedType != nil {
419 nestedFieldName := proptools.FieldNameForProperty(strings.TrimSuffix(nestedPrefix, "."))
420
421 fields = append(fields, reflect.StructField{
422 Name: nestedFieldName,
423 Type: nestedType,
424 })
425 }
426 } else {
427 typ := typeForPropertyFromPropertyStructs(factoryProps, p)
428 if typ != nil {
429 fields = append(fields, reflect.StructField{
430 Name: proptools.FieldNameForProperty(nestedProperty),
431 Type: typ,
432 })
433 }
Colin Cross997f27a2021-03-05 17:25:41 -0800434 // The first element in the list has been handled, remove it from the list.
435 affectableProperties = affectableProperties[1:]
Colin Cross9d34f352019-11-22 16:03:51 -0800436 }
437 }
438
439 var typ reflect.Type
440 if len(fields) > 0 {
441 typ = reflect.StructOf(fields)
442 }
443 return affectableProperties, typ
444 }
445
446 affectableProperties, typ := recurse("", affectableProperties)
447 if len(affectableProperties) > 0 {
448 panic(fmt.Errorf("didn't handle all affectable properties"))
449 }
450
451 if typ != nil {
452 return reflect.PtrTo(typ)
453 }
454
455 return nil
456}
457
458func typeForPropertyFromPropertyStructs(psList []interface{}, property string) reflect.Type {
459 for _, ps := range psList {
460 if typ := typeForPropertyFromPropertyStruct(ps, property); typ != nil {
461 return typ
462 }
463 }
464
465 return nil
466}
467
468func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.Type {
469 v := reflect.ValueOf(ps)
470 for len(property) > 0 {
471 if !v.IsValid() {
472 return nil
473 }
474
475 if v.Kind() == reflect.Interface {
476 if v.IsNil() {
477 return nil
478 } else {
479 v = v.Elem()
480 }
481 }
482
483 if v.Kind() == reflect.Ptr {
484 if v.IsNil() {
485 v = reflect.Zero(v.Type().Elem())
486 } else {
487 v = v.Elem()
488 }
489 }
490
491 if v.Kind() != reflect.Struct {
492 return nil
493 }
494
495 if index := strings.IndexRune(property, '.'); index >= 0 {
496 prefix := property[:index]
497 property = property[index+1:]
498
499 v = v.FieldByName(proptools.FieldNameForProperty(prefix))
500 } else {
501 f := v.FieldByName(proptools.FieldNameForProperty(property))
502 if !f.IsValid() {
503 return nil
504 }
505 return f.Type()
506 }
507 }
508 return nil
509}
510
511// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
512// based on SoongConfig values.
Liz Kammerfe8853d2020-12-16 09:34:33 -0800513// Expects that props contains a struct field with name soong_config_variables. The fields within
Liz Kammer432bd592020-12-16 12:42:02 -0800514// soong_config_variables are expected to be in the same order as moduleType.Variables.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700515func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
Colin Cross9d34f352019-11-22 16:03:51 -0800516 var ret []interface{}
Jingwen Chena47f28d2021-11-02 16:43:57 +0000517 props = props.Elem().FieldByName(SoongConfigProperty)
Colin Cross9d34f352019-11-22 16:03:51 -0800518 for i, c := range moduleType.Variables {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700519 if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
520 return nil, err
521 } else if ps != nil {
Colin Cross9d34f352019-11-22 16:03:51 -0800522 ret = append(ret, ps)
523 }
524 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700525 return ret, nil
Colin Cross9d34f352019-11-22 16:03:51 -0800526}
527
528type ModuleType struct {
529 BaseModuleType string
530 ConfigNamespace string
531 Variables []soongConfigVariable
532
533 affectableProperties []string
534 variableNames []string
535}
536
Liz Kammer432bd592020-12-16 12:42:02 -0800537func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
538 mt := &ModuleType{
539 affectableProperties: props.Properties,
540 ConfigNamespace: props.Config_namespace,
541 BaseModuleType: props.Module_type,
542 variableNames: props.Variables,
543 }
544
545 for _, name := range props.Bool_variables {
546 if err := checkVariableName(name); err != nil {
547 return nil, []error{fmt.Errorf("bool_variables %s", err)}
548 }
549
550 mt.Variables = append(mt.Variables, newBoolVariable(name))
551 }
552
553 for _, name := range props.Value_variables {
554 if err := checkVariableName(name); err != nil {
555 return nil, []error{fmt.Errorf("value_variables %s", err)}
556 }
557
558 mt.Variables = append(mt.Variables, &valueVariable{
559 baseVariable: baseVariable{
560 variable: name,
561 },
562 })
563 }
564
565 return mt, nil
566}
567
568func checkVariableName(name string) error {
569 if name == "" {
570 return fmt.Errorf("name must not be blank")
571 } else if name == conditionsDefault {
572 return fmt.Errorf("%q is reserved", conditionsDefault)
573 }
574 return nil
575}
576
Colin Cross9d34f352019-11-22 16:03:51 -0800577type soongConfigVariable interface {
578 // variableProperty returns the name of the variable.
579 variableProperty() string
580
581 // conditionalValuesType returns a reflect.Type that contains an interface{} for each possible value.
582 variableValuesType() reflect.Type
583
584 // initializeProperties is passed a reflect.Value of the reflect.Type returned by conditionalValuesType and a
585 // reflect.Type of the affectable properties, and should initialize each interface{} in the reflect.Value with
586 // the zero value of the affectable properties type.
587 initializeProperties(v reflect.Value, typ reflect.Type)
588
589 // PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
590 // to the module.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700591 PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error)
Colin Cross9d34f352019-11-22 16:03:51 -0800592}
593
594type baseVariable struct {
595 variable string
596}
597
598func (c *baseVariable) variableProperty() string {
599 return CanonicalizeToProperty(c.variable)
600}
601
602type stringVariable struct {
603 baseVariable
604 values []string
605}
606
607func (s *stringVariable) variableValuesType() reflect.Type {
608 var fields []reflect.StructField
609
Liz Kammer432bd592020-12-16 12:42:02 -0800610 var values []string
611 values = append(values, s.values...)
612 values = append(values, conditionsDefault)
613 for _, v := range values {
Colin Cross9d34f352019-11-22 16:03:51 -0800614 fields = append(fields, reflect.StructField{
615 Name: proptools.FieldNameForProperty(v),
616 Type: emptyInterfaceType,
617 })
618 }
619
620 return reflect.StructOf(fields)
621}
622
Liz Kammer432bd592020-12-16 12:42:02 -0800623// initializeProperties initializes properties to zero value of typ for supported values and a final
624// conditions default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800625func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
626 for i := range s.values {
627 v.Field(i).Set(reflect.Zero(typ))
628 }
Liz Kammer432bd592020-12-16 12:42:02 -0800629 v.Field(len(s.values)).Set(reflect.Zero(typ)) // conditions default is the final value
Colin Cross9d34f352019-11-22 16:03:51 -0800630}
631
Liz Kammer432bd592020-12-16 12:42:02 -0800632// Extracts an interface from values containing the properties to apply based on config.
633// If config does not match a value with a non-nil property set, the default value will be returned.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700634func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Colin Cross9d34f352019-11-22 16:03:51 -0800635 for j, v := range s.values {
Liz Kammer432bd592020-12-16 12:42:02 -0800636 f := values.Field(j)
637 if config.String(s.variable) == v && !f.Elem().IsNil() {
638 return f.Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800639 }
640 }
Liz Kammer432bd592020-12-16 12:42:02 -0800641 // if we have reached this point, we have checked all valid values of string and either:
642 // * the value was not set
643 // * the value was set but that value was not specified in the Android.bp file
644 return values.Field(len(s.values)).Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800645}
646
Liz Kammer432bd592020-12-16 12:42:02 -0800647// Struct to allow conditions set based on a boolean variable
Colin Cross9d34f352019-11-22 16:03:51 -0800648type boolVariable struct {
649 baseVariable
650}
651
Liz Kammer432bd592020-12-16 12:42:02 -0800652// newBoolVariable constructs a boolVariable with the given name
Liz Kammerfe8853d2020-12-16 09:34:33 -0800653func newBoolVariable(name string) *boolVariable {
654 return &boolVariable{
655 baseVariable{
656 variable: name,
657 },
658 }
659}
660
Colin Cross9d34f352019-11-22 16:03:51 -0800661func (b boolVariable) variableValuesType() reflect.Type {
662 return emptyInterfaceType
663}
664
Liz Kammer432bd592020-12-16 12:42:02 -0800665// initializeProperties initializes a property to zero value of typ with an additional conditions
666// default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800667func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800668 initializePropertiesWithDefault(v, typ)
Colin Cross9d34f352019-11-22 16:03:51 -0800669}
670
Liz Kammer432bd592020-12-16 12:42:02 -0800671// initializePropertiesWithDefault, initialize with zero value, v to contain a field for each field
672// in typ, with an additional field for defaults of type typ. This should be used to initialize
673// boolVariable, valueVariable, or any future implementations of soongConfigVariable which support
674// one variable and a default.
675func initializePropertiesWithDefault(v reflect.Value, typ reflect.Type) {
676 sTyp := typ.Elem()
677 var fields []reflect.StructField
678 for i := 0; i < sTyp.NumField(); i++ {
679 fields = append(fields, sTyp.Field(i))
Colin Cross9d34f352019-11-22 16:03:51 -0800680 }
681
Liz Kammer432bd592020-12-16 12:42:02 -0800682 // create conditions_default field
683 nestedFieldName := proptools.FieldNameForProperty(conditionsDefault)
684 fields = append(fields, reflect.StructField{
685 Name: nestedFieldName,
686 Type: typ,
687 })
688
689 newTyp := reflect.PtrTo(reflect.StructOf(fields))
690 v.Set(reflect.Zero(newTyp))
691}
692
693// conditionsDefaultField extracts the conditions_default field from v. This is always the final
694// field if initialized with initializePropertiesWithDefault.
695func conditionsDefaultField(v reflect.Value) reflect.Value {
696 return v.Field(v.NumField() - 1)
697}
698
699// removeDefault removes the conditions_default field from values while retaining values from all
700// other fields. This allows
701func removeDefault(values reflect.Value) reflect.Value {
702 v := values.Elem().Elem()
703 s := conditionsDefaultField(v)
704 // if conditions_default field was not set, there will be no issues extending properties.
705 if !s.IsValid() {
706 return v
707 }
708
709 // If conditions_default field was set, it has the correct type for our property. Create a new
710 // reflect.Value of the conditions_default type and copy all fields (except for
711 // conditions_default) based on values to the result.
712 res := reflect.New(s.Type().Elem())
713 for i := 0; i < res.Type().Elem().NumField(); i++ {
714 val := v.Field(i)
715 res.Elem().Field(i).Set(val)
716 }
717
718 return res
719}
720
721// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
722// the module. If the value was not set, conditions_default interface will be returned; otherwise,
723// the interface in values, without conditions_default will be returned.
724func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
725 // If this variable was not referenced in the module, there are no properties to apply.
726 if values.Elem().IsZero() {
727 return nil, nil
728 }
729 if config.Bool(b.variable) {
730 values = removeDefault(values)
731 return values.Interface(), nil
732 }
733 v := values.Elem().Elem()
734 if f := conditionsDefaultField(v); f.IsValid() {
735 return f.Interface(), nil
736 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700737 return nil, nil
738}
739
Liz Kammer432bd592020-12-16 12:42:02 -0800740// Struct to allow conditions set based on a value variable, supporting string substitution.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700741type valueVariable struct {
742 baseVariable
743}
744
745func (s *valueVariable) variableValuesType() reflect.Type {
746 return emptyInterfaceType
747}
748
Liz Kammer432bd592020-12-16 12:42:02 -0800749// initializeProperties initializes a property to zero value of typ with an additional conditions
750// default field.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700751func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800752 initializePropertiesWithDefault(v, typ)
Dan Willemsenb0935db2020-03-23 19:42:18 -0700753}
754
Liz Kammer432bd592020-12-16 12:42:02 -0800755// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
756// the module. If the variable was not set, conditions_default interface will be returned;
757// otherwise, the interface in values, without conditions_default will be returned with all
758// appropriate string substitutions based on variable being set.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700759func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Liz Kammer432bd592020-12-16 12:42:02 -0800760 // If this variable was not referenced in the module, there are no properties to apply.
761 if !values.IsValid() || values.Elem().IsZero() {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700762 return nil, nil
763 }
Liz Kammer432bd592020-12-16 12:42:02 -0800764 if !config.IsSet(s.variable) {
765 return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
766 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700767 configValue := config.String(s.variable)
768
Liz Kammer432bd592020-12-16 12:42:02 -0800769 values = removeDefault(values)
770 propStruct := values.Elem()
Liz Kammer40ddfaa2020-12-16 10:59:00 -0800771 if !propStruct.IsValid() {
772 return nil, nil
773 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700774 for i := 0; i < propStruct.NumField(); i++ {
775 field := propStruct.Field(i)
776 kind := field.Kind()
777 if kind == reflect.Ptr {
778 if field.IsNil() {
779 continue
780 }
781 field = field.Elem()
782 }
783 switch kind {
784 case reflect.String:
785 err := printfIntoProperty(field, configValue)
786 if err != nil {
787 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
788 }
789 case reflect.Slice:
790 for j := 0; j < field.Len(); j++ {
791 err := printfIntoProperty(field.Index(j), configValue)
792 if err != nil {
793 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
794 }
795 }
796 case reflect.Bool:
797 // Nothing to do
798 default:
799 return nil, fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, propStruct.Type().Field(i).Name, kind)
800 }
801 }
802
803 return values.Interface(), nil
804}
805
806func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
807 s := propertyValue.String()
808
809 count := strings.Count(s, "%")
810 if count == 0 {
811 return nil
812 }
813
814 if count > 1 {
815 return fmt.Errorf("value variable properties only support a single '%%'")
816 }
817
818 if !strings.Contains(s, "%s") {
819 return fmt.Errorf("unsupported %% in value variable property")
820 }
821
822 propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, configValue)))
823
Colin Cross9d34f352019-11-22 16:03:51 -0800824 return nil
825}
826
827func CanonicalizeToProperty(v string) string {
828 return strings.Map(func(r rune) rune {
829 switch {
830 case r >= 'A' && r <= 'Z',
831 r >= 'a' && r <= 'z',
832 r >= '0' && r <= '9',
833 r == '_':
834 return r
835 default:
836 return '_'
837 }
838 }, v)
839}
840
841func CanonicalizeToProperties(values []string) []string {
842 ret := make([]string, len(values))
843 for i, v := range values {
844 ret[i] = CanonicalizeToProperty(v)
845 }
846 return ret
847}
848
849type emptyInterfaceStruct struct {
850 i interface{}
851}
852
853var emptyInterfaceType = reflect.TypeOf(emptyInterfaceStruct{}).Field(0).Type