blob: 8dd9b8949faca5b34ba625e22eb9134907e6268c [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"
Liz Kammer72beb342022-02-03 08:42:10 -050028
29 "android/soong/starlark_fmt"
Colin Cross9d34f352019-11-22 16:03:51 -080030)
31
Liz Kammer432bd592020-12-16 12:42:02 -080032const conditionsDefault = "conditions_default"
33
Jingwen Chena47f28d2021-11-02 16:43:57 +000034var SoongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
Colin Cross9d34f352019-11-22 16:03:51 -080035
36// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
37// result so each file is only parsed once.
38func Parse(r io.Reader, from string) (*SoongConfigDefinition, []error) {
39 scope := parser.NewScope(nil)
40 file, errs := parser.ParseAndEval(from, r, scope)
41
42 if len(errs) > 0 {
43 return nil, errs
44 }
45
46 mtDef := &SoongConfigDefinition{
47 ModuleTypes: make(map[string]*ModuleType),
48 variables: make(map[string]soongConfigVariable),
49 }
50
51 for _, def := range file.Defs {
52 switch def := def.(type) {
53 case *parser.Module:
54 newErrs := processImportModuleDef(mtDef, def)
55
56 if len(newErrs) > 0 {
57 errs = append(errs, newErrs...)
58 }
59
60 case *parser.Assignment:
61 // Already handled via Scope object
62 default:
63 panic("unknown definition type")
64 }
65 }
66
67 if len(errs) > 0 {
68 return nil, errs
69 }
70
71 for name, moduleType := range mtDef.ModuleTypes {
72 for _, varName := range moduleType.variableNames {
73 if v, ok := mtDef.variables[varName]; ok {
74 moduleType.Variables = append(moduleType.Variables, v)
75 } else {
76 return nil, []error{
77 fmt.Errorf("unknown variable %q in module type %q", varName, name),
78 }
79 }
80 }
81 }
82
83 return mtDef, nil
84}
85
86func processImportModuleDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
87 switch def.Type {
88 case "soong_config_module_type":
89 return processModuleTypeDef(v, def)
90 case "soong_config_string_variable":
91 return processStringVariableDef(v, def)
92 case "soong_config_bool_variable":
93 return processBoolVariableDef(v, def)
94 default:
95 // Unknown module types will be handled when the file is parsed as a normal
96 // Android.bp file.
97 }
98
99 return nil
100}
101
102type ModuleTypeProperties struct {
103 // the name of the new module type. Unlike most modules, this name does not need to be unique,
104 // although only one module type with any name will be importable into an Android.bp file.
105 Name string
106
107 // the module type that this module type will extend.
108 Module_type string
109
110 // the SOONG_CONFIG_NAMESPACE value from a BoardConfig.mk that this module type will read
111 // configuration variables from.
112 Config_namespace string
113
114 // the list of SOONG_CONFIG variables that this module type will read
115 Variables []string
116
Dan Willemsen2b8b89c2020-03-23 19:39:34 -0700117 // the list of boolean SOONG_CONFIG variables that this module type will read
118 Bool_variables []string
119
Dan Willemsenb0935db2020-03-23 19:42:18 -0700120 // the list of SOONG_CONFIG variables that this module type will read. The value will be
121 // inserted into the properties with %s substitution.
122 Value_variables []string
123
Colin Cross9d34f352019-11-22 16:03:51 -0800124 // the list of properties that this module type will extend.
125 Properties []string
126}
127
128func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
129
130 props := &ModuleTypeProperties{}
131
132 _, errs = proptools.UnpackProperties(def.Properties, props)
133 if len(errs) > 0 {
134 return errs
135 }
136
137 if props.Name == "" {
138 errs = append(errs, fmt.Errorf("name property must be set"))
139 }
140
141 if props.Config_namespace == "" {
142 errs = append(errs, fmt.Errorf("config_namespace property must be set"))
143 }
144
145 if props.Module_type == "" {
146 errs = append(errs, fmt.Errorf("module_type property must be set"))
147 }
148
149 if len(errs) > 0 {
150 return errs
151 }
152
Liz Kammer432bd592020-12-16 12:42:02 -0800153 if mt, errs := newModuleType(props); len(errs) > 0 {
154 return errs
155 } else {
156 v.ModuleTypes[props.Name] = mt
Dan Willemsenb0935db2020-03-23 19:42:18 -0700157 }
158
Colin Cross9d34f352019-11-22 16:03:51 -0800159 return nil
160}
161
162type VariableProperties struct {
163 Name string
164}
165
166type StringVariableProperties struct {
167 Values []string
168}
169
170func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
171 stringProps := &StringVariableProperties{}
172
173 base, errs := processVariableDef(def, stringProps)
174 if len(errs) > 0 {
175 return errs
176 }
177
178 if len(stringProps.Values) == 0 {
179 return []error{fmt.Errorf("values property must be set")}
180 }
181
Liz Kammer72beb342022-02-03 08:42:10 -0500182 vals := make(map[string]bool, len(stringProps.Values))
Liz Kammer432bd592020-12-16 12:42:02 -0800183 for _, name := range stringProps.Values {
184 if err := checkVariableName(name); err != nil {
185 return []error{fmt.Errorf("soong_config_string_variable: values property error %s", err)}
Liz Kammer72beb342022-02-03 08:42:10 -0500186 } else if _, ok := vals[name]; ok {
187 return []error{fmt.Errorf("soong_config_string_variable: values property error: duplicate value: %q", name)}
Liz Kammer432bd592020-12-16 12:42:02 -0800188 }
Liz Kammer72beb342022-02-03 08:42:10 -0500189 vals[name] = true
Liz Kammer432bd592020-12-16 12:42:02 -0800190 }
191
Colin Cross9d34f352019-11-22 16:03:51 -0800192 v.variables[base.variable] = &stringVariable{
193 baseVariable: base,
194 values: CanonicalizeToProperties(stringProps.Values),
195 }
196
197 return nil
198}
199
200func processBoolVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
201 base, errs := processVariableDef(def)
202 if len(errs) > 0 {
203 return errs
204 }
205
206 v.variables[base.variable] = &boolVariable{
207 baseVariable: base,
208 }
209
210 return nil
211}
212
213func processVariableDef(def *parser.Module,
214 extraProps ...interface{}) (cond baseVariable, errs []error) {
215
216 props := &VariableProperties{}
217
218 allProps := append([]interface{}{props}, extraProps...)
219
220 _, errs = proptools.UnpackProperties(def.Properties, allProps...)
221 if len(errs) > 0 {
222 return baseVariable{}, errs
223 }
224
225 if props.Name == "" {
226 return baseVariable{}, []error{fmt.Errorf("name property must be set")}
227 }
228
229 return baseVariable{
230 variable: props.Name,
231 }, nil
232}
233
234type SoongConfigDefinition struct {
235 ModuleTypes map[string]*ModuleType
236
237 variables map[string]soongConfigVariable
238}
239
Jingwen Chen01812022021-11-19 14:29:43 +0000240// Bp2BuildSoongConfigDefinition keeps a global record of all soong config
241// string vars, bool vars and value vars created by every
242// soong_config_module_type in this build.
243type Bp2BuildSoongConfigDefinitions struct {
Liz Kammer72beb342022-02-03 08:42:10 -0500244 // varCache contains a cache of string variables namespace + property
245 // The same variable may be used in multiple module types (for example, if need support
246 // for cc_default and java_default), only need to process once
247 varCache map[string]bool
248
249 StringVars map[string][]string
Jingwen Chen01812022021-11-19 14:29:43 +0000250 BoolVars map[string]bool
251 ValueVars map[string]bool
252}
253
Jingwen Chen4ad40d92021-11-24 03:40:23 +0000254var bp2buildSoongConfigVarsLock sync.Mutex
255
Jingwen Chen01812022021-11-19 14:29:43 +0000256// SoongConfigVariablesForBp2build extracts information from a
257// SoongConfigDefinition that bp2build needs to generate constraint settings and
258// values for, in order to migrate soong_config_module_type usages to Bazel.
259func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition) {
Jingwen Chen4ad40d92021-11-24 03:40:23 +0000260 // In bp2build mode, this method is called concurrently in goroutines from
261 // loadhooks while parsing soong_config_module_type, so add a mutex to
262 // prevent concurrent map writes. See b/207572723
263 bp2buildSoongConfigVarsLock.Lock()
264 defer bp2buildSoongConfigVarsLock.Unlock()
265
Jingwen Chen01812022021-11-19 14:29:43 +0000266 if defs.StringVars == nil {
Liz Kammer72beb342022-02-03 08:42:10 -0500267 defs.StringVars = make(map[string][]string)
Jingwen Chen01812022021-11-19 14:29:43 +0000268 }
269 if defs.BoolVars == nil {
270 defs.BoolVars = make(map[string]bool)
271 }
272 if defs.ValueVars == nil {
273 defs.ValueVars = make(map[string]bool)
274 }
Liz Kammer72beb342022-02-03 08:42:10 -0500275 if defs.varCache == nil {
276 defs.varCache = make(map[string]bool)
277 }
Jingwen Chen01812022021-11-19 14:29:43 +0000278 for _, moduleType := range mtDef.ModuleTypes {
279 for _, v := range moduleType.Variables {
280 key := strings.Join([]string{moduleType.ConfigNamespace, v.variableProperty()}, "__")
Liz Kammer72beb342022-02-03 08:42:10 -0500281
282 // The same variable may be used in multiple module types (for example, if need support
283 // for cc_default and java_default), only need to process once
284 if _, keyInCache := defs.varCache[key]; keyInCache {
285 continue
286 } else {
287 defs.varCache[key] = true
288 }
289
Jingwen Chen01812022021-11-19 14:29:43 +0000290 if strVar, ok := v.(*stringVariable); ok {
Jingwen Chen01812022021-11-19 14:29:43 +0000291 for _, value := range strVar.values {
Liz Kammer72beb342022-02-03 08:42:10 -0500292 defs.StringVars[key] = append(defs.StringVars[key], value)
Jingwen Chen01812022021-11-19 14:29:43 +0000293 }
294 } else if _, ok := v.(*boolVariable); ok {
295 defs.BoolVars[key] = true
296 } else if _, ok := v.(*valueVariable); ok {
297 defs.ValueVars[key] = true
298 } else {
299 panic(fmt.Errorf("Unsupported variable type: %+v", v))
300 }
301 }
302 }
303}
304
305// This is a copy of the one available in soong/android/util.go, but depending
306// on the android package causes a cyclic dependency. A refactoring here is to
307// extract common utils out from android/utils.go for other packages like this.
308func sortedStringKeys(m interface{}) []string {
309 v := reflect.ValueOf(m)
310 if v.Kind() != reflect.Map {
311 panic(fmt.Sprintf("%#v is not a map", m))
312 }
313 keys := v.MapKeys()
314 s := make([]string, 0, len(keys))
315 for _, key := range keys {
316 s = append(s, key.String())
317 }
318 sort.Strings(s)
319 return s
320}
321
322// String emits the Soong config variable definitions as Starlark dictionaries.
323func (defs Bp2BuildSoongConfigDefinitions) String() string {
324 ret := ""
Liz Kammer72beb342022-02-03 08:42:10 -0500325 ret += "soong_config_bool_variables = "
326 ret += starlark_fmt.PrintBoolDict(defs.BoolVars, 0)
327 ret += "\n\n"
Jingwen Chen01812022021-11-19 14:29:43 +0000328
Liz Kammer72beb342022-02-03 08:42:10 -0500329 ret += "soong_config_value_variables = "
330 ret += starlark_fmt.PrintBoolDict(defs.ValueVars, 0)
331 ret += "\n\n"
Jingwen Chen01812022021-11-19 14:29:43 +0000332
Liz Kammer72beb342022-02-03 08:42:10 -0500333 ret += "soong_config_string_variables = "
334 ret += starlark_fmt.PrintStringListDict(defs.StringVars, 0)
Jingwen Chen01812022021-11-19 14:29:43 +0000335
336 return ret
337}
338
Colin Cross9d34f352019-11-22 16:03:51 -0800339// CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
340// property layout for the Soong config variables, with each possible value an interface{} that
341// contains a nil pointer to another newly constructed type that contains the affectable properties.
342// The reflect.Value will be cloned for each call to the Soong config module type's factory method.
343//
344// For example, the acme_cc_defaults example above would
345// produce a reflect.Value whose type is:
346// *struct {
347// Soong_config_variables struct {
348// Board struct {
349// Soc_a interface{}
350// Soc_b interface{}
351// }
352// }
353// }
354// And whose value is:
355// &{
356// Soong_config_variables: {
357// Board: {
358// Soc_a: (*struct{ Cflags []string })(nil),
359// Soc_b: (*struct{ Cflags []string })(nil),
360// },
361// },
362// }
363func CreateProperties(factory blueprint.ModuleFactory, moduleType *ModuleType) reflect.Value {
364 var fields []reflect.StructField
365
366 _, factoryProps := factory()
367 affectablePropertiesType := createAffectablePropertiesType(moduleType.affectableProperties, factoryProps)
368 if affectablePropertiesType == nil {
369 return reflect.Value{}
370 }
371
372 for _, c := range moduleType.Variables {
373 fields = append(fields, reflect.StructField{
374 Name: proptools.FieldNameForProperty(c.variableProperty()),
375 Type: c.variableValuesType(),
376 })
377 }
378
379 typ := reflect.StructOf([]reflect.StructField{{
Jingwen Chena47f28d2021-11-02 16:43:57 +0000380 Name: SoongConfigProperty,
Colin Cross9d34f352019-11-22 16:03:51 -0800381 Type: reflect.StructOf(fields),
382 }})
383
384 props := reflect.New(typ)
Jingwen Chena47f28d2021-11-02 16:43:57 +0000385 structConditions := props.Elem().FieldByName(SoongConfigProperty)
Colin Cross9d34f352019-11-22 16:03:51 -0800386
387 for i, c := range moduleType.Variables {
388 c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
389 }
390
391 return props
392}
393
394// createAffectablePropertiesType creates a reflect.Type of a struct that has a field for each affectable property
395// that exists in factoryProps.
396func createAffectablePropertiesType(affectableProperties []string, factoryProps []interface{}) reflect.Type {
397 affectableProperties = append([]string(nil), affectableProperties...)
398 sort.Strings(affectableProperties)
399
400 var recurse func(prefix string, aps []string) ([]string, reflect.Type)
401 recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
402 var fields []reflect.StructField
403
Colin Cross997f27a2021-03-05 17:25:41 -0800404 // Iterate while the list is non-empty so it can be modified in the loop.
Colin Cross9d34f352019-11-22 16:03:51 -0800405 for len(affectableProperties) > 0 {
406 p := affectableProperties[0]
407 if !strings.HasPrefix(affectableProperties[0], prefix) {
Colin Cross997f27a2021-03-05 17:25:41 -0800408 // The properties are sorted and recurse is always called with a prefix that matches
409 // the first property in the list, so if we've reached one that doesn't match the
410 // prefix we are done with this prefix.
Colin Cross9d34f352019-11-22 16:03:51 -0800411 break
412 }
Colin Cross9d34f352019-11-22 16:03:51 -0800413
414 nestedProperty := strings.TrimPrefix(p, prefix)
415 if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
416 var nestedType reflect.Type
417 nestedPrefix := nestedProperty[:i+1]
418
Colin Cross997f27a2021-03-05 17:25:41 -0800419 // Recurse to handle the properties with the found prefix. This will return
420 // an updated affectableProperties with the handled entries removed from the front
421 // of the list, and the type that contains the handled entries. The type may be
422 // nil if none of the entries matched factoryProps.
Colin Cross9d34f352019-11-22 16:03:51 -0800423 affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
424
425 if nestedType != nil {
426 nestedFieldName := proptools.FieldNameForProperty(strings.TrimSuffix(nestedPrefix, "."))
427
428 fields = append(fields, reflect.StructField{
429 Name: nestedFieldName,
430 Type: nestedType,
431 })
432 }
433 } else {
434 typ := typeForPropertyFromPropertyStructs(factoryProps, p)
435 if typ != nil {
436 fields = append(fields, reflect.StructField{
437 Name: proptools.FieldNameForProperty(nestedProperty),
438 Type: typ,
439 })
440 }
Colin Cross997f27a2021-03-05 17:25:41 -0800441 // The first element in the list has been handled, remove it from the list.
442 affectableProperties = affectableProperties[1:]
Colin Cross9d34f352019-11-22 16:03:51 -0800443 }
444 }
445
446 var typ reflect.Type
447 if len(fields) > 0 {
448 typ = reflect.StructOf(fields)
449 }
450 return affectableProperties, typ
451 }
452
453 affectableProperties, typ := recurse("", affectableProperties)
454 if len(affectableProperties) > 0 {
455 panic(fmt.Errorf("didn't handle all affectable properties"))
456 }
457
458 if typ != nil {
459 return reflect.PtrTo(typ)
460 }
461
462 return nil
463}
464
465func typeForPropertyFromPropertyStructs(psList []interface{}, property string) reflect.Type {
466 for _, ps := range psList {
467 if typ := typeForPropertyFromPropertyStruct(ps, property); typ != nil {
468 return typ
469 }
470 }
471
472 return nil
473}
474
475func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.Type {
476 v := reflect.ValueOf(ps)
477 for len(property) > 0 {
478 if !v.IsValid() {
479 return nil
480 }
481
482 if v.Kind() == reflect.Interface {
483 if v.IsNil() {
484 return nil
485 } else {
486 v = v.Elem()
487 }
488 }
489
490 if v.Kind() == reflect.Ptr {
491 if v.IsNil() {
492 v = reflect.Zero(v.Type().Elem())
493 } else {
494 v = v.Elem()
495 }
496 }
497
498 if v.Kind() != reflect.Struct {
499 return nil
500 }
501
502 if index := strings.IndexRune(property, '.'); index >= 0 {
503 prefix := property[:index]
504 property = property[index+1:]
505
506 v = v.FieldByName(proptools.FieldNameForProperty(prefix))
507 } else {
508 f := v.FieldByName(proptools.FieldNameForProperty(property))
509 if !f.IsValid() {
510 return nil
511 }
512 return f.Type()
513 }
514 }
515 return nil
516}
517
518// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
519// based on SoongConfig values.
Liz Kammerfe8853d2020-12-16 09:34:33 -0800520// Expects that props contains a struct field with name soong_config_variables. The fields within
Liz Kammer432bd592020-12-16 12:42:02 -0800521// soong_config_variables are expected to be in the same order as moduleType.Variables.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700522func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
Colin Cross9d34f352019-11-22 16:03:51 -0800523 var ret []interface{}
Jingwen Chena47f28d2021-11-02 16:43:57 +0000524 props = props.Elem().FieldByName(SoongConfigProperty)
Colin Cross9d34f352019-11-22 16:03:51 -0800525 for i, c := range moduleType.Variables {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700526 if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
527 return nil, err
528 } else if ps != nil {
Colin Cross9d34f352019-11-22 16:03:51 -0800529 ret = append(ret, ps)
530 }
531 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700532 return ret, nil
Colin Cross9d34f352019-11-22 16:03:51 -0800533}
534
535type ModuleType struct {
536 BaseModuleType string
537 ConfigNamespace string
538 Variables []soongConfigVariable
539
540 affectableProperties []string
541 variableNames []string
542}
543
Liz Kammer432bd592020-12-16 12:42:02 -0800544func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
545 mt := &ModuleType{
546 affectableProperties: props.Properties,
547 ConfigNamespace: props.Config_namespace,
548 BaseModuleType: props.Module_type,
549 variableNames: props.Variables,
550 }
551
552 for _, name := range props.Bool_variables {
553 if err := checkVariableName(name); err != nil {
554 return nil, []error{fmt.Errorf("bool_variables %s", err)}
555 }
556
557 mt.Variables = append(mt.Variables, newBoolVariable(name))
558 }
559
560 for _, name := range props.Value_variables {
561 if err := checkVariableName(name); err != nil {
562 return nil, []error{fmt.Errorf("value_variables %s", err)}
563 }
564
565 mt.Variables = append(mt.Variables, &valueVariable{
566 baseVariable: baseVariable{
567 variable: name,
568 },
569 })
570 }
571
572 return mt, nil
573}
574
575func checkVariableName(name string) error {
576 if name == "" {
577 return fmt.Errorf("name must not be blank")
578 } else if name == conditionsDefault {
579 return fmt.Errorf("%q is reserved", conditionsDefault)
580 }
581 return nil
582}
583
Colin Cross9d34f352019-11-22 16:03:51 -0800584type soongConfigVariable interface {
585 // variableProperty returns the name of the variable.
586 variableProperty() string
587
588 // conditionalValuesType returns a reflect.Type that contains an interface{} for each possible value.
589 variableValuesType() reflect.Type
590
591 // initializeProperties is passed a reflect.Value of the reflect.Type returned by conditionalValuesType and a
592 // reflect.Type of the affectable properties, and should initialize each interface{} in the reflect.Value with
593 // the zero value of the affectable properties type.
594 initializeProperties(v reflect.Value, typ reflect.Type)
595
596 // PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
597 // to the module.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700598 PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error)
Colin Cross9d34f352019-11-22 16:03:51 -0800599}
600
601type baseVariable struct {
602 variable string
603}
604
605func (c *baseVariable) variableProperty() string {
606 return CanonicalizeToProperty(c.variable)
607}
608
609type stringVariable struct {
610 baseVariable
611 values []string
612}
613
614func (s *stringVariable) variableValuesType() reflect.Type {
615 var fields []reflect.StructField
616
Liz Kammer432bd592020-12-16 12:42:02 -0800617 var values []string
618 values = append(values, s.values...)
619 values = append(values, conditionsDefault)
620 for _, v := range values {
Colin Cross9d34f352019-11-22 16:03:51 -0800621 fields = append(fields, reflect.StructField{
622 Name: proptools.FieldNameForProperty(v),
623 Type: emptyInterfaceType,
624 })
625 }
626
627 return reflect.StructOf(fields)
628}
629
Liz Kammer432bd592020-12-16 12:42:02 -0800630// initializeProperties initializes properties to zero value of typ for supported values and a final
631// conditions default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800632func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
633 for i := range s.values {
634 v.Field(i).Set(reflect.Zero(typ))
635 }
Liz Kammer432bd592020-12-16 12:42:02 -0800636 v.Field(len(s.values)).Set(reflect.Zero(typ)) // conditions default is the final value
Colin Cross9d34f352019-11-22 16:03:51 -0800637}
638
Liz Kammer432bd592020-12-16 12:42:02 -0800639// Extracts an interface from values containing the properties to apply based on config.
640// 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 -0700641func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Cole Faustb0a91332022-11-01 17:10:23 +0000642 configValue := config.String(s.variable)
643 if configValue != "" && !InList(configValue, s.values) {
644 return nil, fmt.Errorf("Soong config property %q must be one of %v, found %q", s.variable, s.values, configValue)
645 }
Colin Cross9d34f352019-11-22 16:03:51 -0800646 for j, v := range s.values {
Liz Kammer432bd592020-12-16 12:42:02 -0800647 f := values.Field(j)
Cole Faustb0a91332022-11-01 17:10:23 +0000648 if configValue == v && !f.Elem().IsNil() {
Liz Kammer432bd592020-12-16 12:42:02 -0800649 return f.Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800650 }
651 }
Liz Kammer432bd592020-12-16 12:42:02 -0800652 // if we have reached this point, we have checked all valid values of string and either:
653 // * the value was not set
654 // * the value was set but that value was not specified in the Android.bp file
655 return values.Field(len(s.values)).Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800656}
657
Liz Kammer432bd592020-12-16 12:42:02 -0800658// Struct to allow conditions set based on a boolean variable
Colin Cross9d34f352019-11-22 16:03:51 -0800659type boolVariable struct {
660 baseVariable
661}
662
Liz Kammer432bd592020-12-16 12:42:02 -0800663// newBoolVariable constructs a boolVariable with the given name
Liz Kammerfe8853d2020-12-16 09:34:33 -0800664func newBoolVariable(name string) *boolVariable {
665 return &boolVariable{
666 baseVariable{
667 variable: name,
668 },
669 }
670}
671
Colin Cross9d34f352019-11-22 16:03:51 -0800672func (b boolVariable) variableValuesType() reflect.Type {
673 return emptyInterfaceType
674}
675
Liz Kammer432bd592020-12-16 12:42:02 -0800676// initializeProperties initializes a property to zero value of typ with an additional conditions
677// default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800678func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800679 initializePropertiesWithDefault(v, typ)
Colin Cross9d34f352019-11-22 16:03:51 -0800680}
681
Liz Kammer432bd592020-12-16 12:42:02 -0800682// initializePropertiesWithDefault, initialize with zero value, v to contain a field for each field
683// in typ, with an additional field for defaults of type typ. This should be used to initialize
684// boolVariable, valueVariable, or any future implementations of soongConfigVariable which support
685// one variable and a default.
686func initializePropertiesWithDefault(v reflect.Value, typ reflect.Type) {
687 sTyp := typ.Elem()
688 var fields []reflect.StructField
689 for i := 0; i < sTyp.NumField(); i++ {
690 fields = append(fields, sTyp.Field(i))
Colin Cross9d34f352019-11-22 16:03:51 -0800691 }
692
Liz Kammer432bd592020-12-16 12:42:02 -0800693 // create conditions_default field
694 nestedFieldName := proptools.FieldNameForProperty(conditionsDefault)
695 fields = append(fields, reflect.StructField{
696 Name: nestedFieldName,
697 Type: typ,
698 })
699
700 newTyp := reflect.PtrTo(reflect.StructOf(fields))
701 v.Set(reflect.Zero(newTyp))
702}
703
704// conditionsDefaultField extracts the conditions_default field from v. This is always the final
705// field if initialized with initializePropertiesWithDefault.
706func conditionsDefaultField(v reflect.Value) reflect.Value {
707 return v.Field(v.NumField() - 1)
708}
709
710// removeDefault removes the conditions_default field from values while retaining values from all
711// other fields. This allows
712func removeDefault(values reflect.Value) reflect.Value {
713 v := values.Elem().Elem()
714 s := conditionsDefaultField(v)
715 // if conditions_default field was not set, there will be no issues extending properties.
716 if !s.IsValid() {
717 return v
718 }
719
720 // If conditions_default field was set, it has the correct type for our property. Create a new
721 // reflect.Value of the conditions_default type and copy all fields (except for
722 // conditions_default) based on values to the result.
723 res := reflect.New(s.Type().Elem())
724 for i := 0; i < res.Type().Elem().NumField(); i++ {
725 val := v.Field(i)
726 res.Elem().Field(i).Set(val)
727 }
728
729 return res
730}
731
732// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
733// the module. If the value was not set, conditions_default interface will be returned; otherwise,
734// the interface in values, without conditions_default will be returned.
735func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
736 // If this variable was not referenced in the module, there are no properties to apply.
737 if values.Elem().IsZero() {
738 return nil, nil
739 }
740 if config.Bool(b.variable) {
741 values = removeDefault(values)
742 return values.Interface(), nil
743 }
744 v := values.Elem().Elem()
745 if f := conditionsDefaultField(v); f.IsValid() {
746 return f.Interface(), nil
747 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700748 return nil, nil
749}
750
Liz Kammer432bd592020-12-16 12:42:02 -0800751// Struct to allow conditions set based on a value variable, supporting string substitution.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700752type valueVariable struct {
753 baseVariable
754}
755
756func (s *valueVariable) variableValuesType() reflect.Type {
757 return emptyInterfaceType
758}
759
Liz Kammer432bd592020-12-16 12:42:02 -0800760// initializeProperties initializes a property to zero value of typ with an additional conditions
761// default field.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700762func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800763 initializePropertiesWithDefault(v, typ)
Dan Willemsenb0935db2020-03-23 19:42:18 -0700764}
765
Liz Kammer432bd592020-12-16 12:42:02 -0800766// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
767// the module. If the variable was not set, conditions_default interface will be returned;
768// otherwise, the interface in values, without conditions_default will be returned with all
769// appropriate string substitutions based on variable being set.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700770func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Liz Kammer432bd592020-12-16 12:42:02 -0800771 // If this variable was not referenced in the module, there are no properties to apply.
772 if !values.IsValid() || values.Elem().IsZero() {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700773 return nil, nil
774 }
Liz Kammer432bd592020-12-16 12:42:02 -0800775 if !config.IsSet(s.variable) {
776 return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
777 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700778 configValue := config.String(s.variable)
779
Liz Kammer432bd592020-12-16 12:42:02 -0800780 values = removeDefault(values)
781 propStruct := values.Elem()
Liz Kammer40ddfaa2020-12-16 10:59:00 -0800782 if !propStruct.IsValid() {
783 return nil, nil
784 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700785 for i := 0; i < propStruct.NumField(); i++ {
786 field := propStruct.Field(i)
787 kind := field.Kind()
788 if kind == reflect.Ptr {
789 if field.IsNil() {
790 continue
791 }
792 field = field.Elem()
793 }
794 switch kind {
795 case reflect.String:
796 err := printfIntoProperty(field, configValue)
797 if err != nil {
798 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
799 }
800 case reflect.Slice:
801 for j := 0; j < field.Len(); j++ {
802 err := printfIntoProperty(field.Index(j), configValue)
803 if err != nil {
804 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
805 }
806 }
807 case reflect.Bool:
808 // Nothing to do
809 default:
810 return nil, fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, propStruct.Type().Field(i).Name, kind)
811 }
812 }
813
814 return values.Interface(), nil
815}
816
817func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
818 s := propertyValue.String()
819
820 count := strings.Count(s, "%")
821 if count == 0 {
822 return nil
823 }
824
825 if count > 1 {
826 return fmt.Errorf("value variable properties only support a single '%%'")
827 }
828
829 if !strings.Contains(s, "%s") {
830 return fmt.Errorf("unsupported %% in value variable property")
831 }
832
833 propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, configValue)))
834
Colin Cross9d34f352019-11-22 16:03:51 -0800835 return nil
836}
837
838func CanonicalizeToProperty(v string) string {
839 return strings.Map(func(r rune) rune {
840 switch {
841 case r >= 'A' && r <= 'Z',
842 r >= 'a' && r <= 'z',
843 r >= '0' && r <= '9',
844 r == '_':
845 return r
846 default:
847 return '_'
848 }
849 }, v)
850}
851
852func CanonicalizeToProperties(values []string) []string {
853 ret := make([]string, len(values))
854 for i, v := range values {
855 ret[i] = CanonicalizeToProperty(v)
856 }
857 return ret
858}
859
860type emptyInterfaceStruct struct {
861 i interface{}
862}
863
864var emptyInterfaceType = reflect.TypeOf(emptyInterfaceStruct{}).Field(0).Type
Cole Faustb0a91332022-11-01 17:10:23 +0000865
866// InList checks if the string belongs to the list
867func InList(s string, list []string) bool {
868 for _, s2 := range list {
869 if s2 == s {
870 return true
871 }
872 }
873 return false
874}