blob: 3a93f478dc775116a00846283bd3766e84ef5f3d [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"
23
24 "github.com/google/blueprint"
25 "github.com/google/blueprint/parser"
26 "github.com/google/blueprint/proptools"
27)
28
Liz Kammer432bd592020-12-16 12:42:02 -080029const conditionsDefault = "conditions_default"
30
Jingwen Chena47f28d2021-11-02 16:43:57 +000031var SoongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
Colin Cross9d34f352019-11-22 16:03:51 -080032
33// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
34// result so each file is only parsed once.
35func Parse(r io.Reader, from string) (*SoongConfigDefinition, []error) {
36 scope := parser.NewScope(nil)
37 file, errs := parser.ParseAndEval(from, r, scope)
38
39 if len(errs) > 0 {
40 return nil, errs
41 }
42
43 mtDef := &SoongConfigDefinition{
44 ModuleTypes: make(map[string]*ModuleType),
45 variables: make(map[string]soongConfigVariable),
46 }
47
48 for _, def := range file.Defs {
49 switch def := def.(type) {
50 case *parser.Module:
51 newErrs := processImportModuleDef(mtDef, def)
52
53 if len(newErrs) > 0 {
54 errs = append(errs, newErrs...)
55 }
56
57 case *parser.Assignment:
58 // Already handled via Scope object
59 default:
60 panic("unknown definition type")
61 }
62 }
63
64 if len(errs) > 0 {
65 return nil, errs
66 }
67
68 for name, moduleType := range mtDef.ModuleTypes {
69 for _, varName := range moduleType.variableNames {
70 if v, ok := mtDef.variables[varName]; ok {
71 moduleType.Variables = append(moduleType.Variables, v)
72 } else {
73 return nil, []error{
74 fmt.Errorf("unknown variable %q in module type %q", varName, name),
75 }
76 }
77 }
78 }
79
80 return mtDef, nil
81}
82
83func processImportModuleDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
84 switch def.Type {
85 case "soong_config_module_type":
86 return processModuleTypeDef(v, def)
87 case "soong_config_string_variable":
88 return processStringVariableDef(v, def)
89 case "soong_config_bool_variable":
90 return processBoolVariableDef(v, def)
91 default:
92 // Unknown module types will be handled when the file is parsed as a normal
93 // Android.bp file.
94 }
95
96 return nil
97}
98
99type ModuleTypeProperties struct {
100 // the name of the new module type. Unlike most modules, this name does not need to be unique,
101 // although only one module type with any name will be importable into an Android.bp file.
102 Name string
103
104 // the module type that this module type will extend.
105 Module_type string
106
107 // the SOONG_CONFIG_NAMESPACE value from a BoardConfig.mk that this module type will read
108 // configuration variables from.
109 Config_namespace string
110
111 // the list of SOONG_CONFIG variables that this module type will read
112 Variables []string
113
Dan Willemsen2b8b89c2020-03-23 19:39:34 -0700114 // the list of boolean SOONG_CONFIG variables that this module type will read
115 Bool_variables []string
116
Dan Willemsenb0935db2020-03-23 19:42:18 -0700117 // the list of SOONG_CONFIG variables that this module type will read. The value will be
118 // inserted into the properties with %s substitution.
119 Value_variables []string
120
Colin Cross9d34f352019-11-22 16:03:51 -0800121 // the list of properties that this module type will extend.
122 Properties []string
123}
124
125func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
126
127 props := &ModuleTypeProperties{}
128
129 _, errs = proptools.UnpackProperties(def.Properties, props)
130 if len(errs) > 0 {
131 return errs
132 }
133
134 if props.Name == "" {
135 errs = append(errs, fmt.Errorf("name property must be set"))
136 }
137
138 if props.Config_namespace == "" {
139 errs = append(errs, fmt.Errorf("config_namespace property must be set"))
140 }
141
142 if props.Module_type == "" {
143 errs = append(errs, fmt.Errorf("module_type property must be set"))
144 }
145
146 if len(errs) > 0 {
147 return errs
148 }
149
Liz Kammer432bd592020-12-16 12:42:02 -0800150 if mt, errs := newModuleType(props); len(errs) > 0 {
151 return errs
152 } else {
153 v.ModuleTypes[props.Name] = mt
Dan Willemsenb0935db2020-03-23 19:42:18 -0700154 }
155
Colin Cross9d34f352019-11-22 16:03:51 -0800156 return nil
157}
158
159type VariableProperties struct {
160 Name string
161}
162
163type StringVariableProperties struct {
164 Values []string
165}
166
167func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
168 stringProps := &StringVariableProperties{}
169
170 base, errs := processVariableDef(def, stringProps)
171 if len(errs) > 0 {
172 return errs
173 }
174
175 if len(stringProps.Values) == 0 {
176 return []error{fmt.Errorf("values property must be set")}
177 }
178
Liz Kammer432bd592020-12-16 12:42:02 -0800179 for _, name := range stringProps.Values {
180 if err := checkVariableName(name); err != nil {
181 return []error{fmt.Errorf("soong_config_string_variable: values property error %s", err)}
182 }
183 }
184
Colin Cross9d34f352019-11-22 16:03:51 -0800185 v.variables[base.variable] = &stringVariable{
186 baseVariable: base,
187 values: CanonicalizeToProperties(stringProps.Values),
188 }
189
190 return nil
191}
192
193func processBoolVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
194 base, errs := processVariableDef(def)
195 if len(errs) > 0 {
196 return errs
197 }
198
199 v.variables[base.variable] = &boolVariable{
200 baseVariable: base,
201 }
202
203 return nil
204}
205
206func processVariableDef(def *parser.Module,
207 extraProps ...interface{}) (cond baseVariable, errs []error) {
208
209 props := &VariableProperties{}
210
211 allProps := append([]interface{}{props}, extraProps...)
212
213 _, errs = proptools.UnpackProperties(def.Properties, allProps...)
214 if len(errs) > 0 {
215 return baseVariable{}, errs
216 }
217
218 if props.Name == "" {
219 return baseVariable{}, []error{fmt.Errorf("name property must be set")}
220 }
221
222 return baseVariable{
223 variable: props.Name,
224 }, nil
225}
226
227type SoongConfigDefinition struct {
228 ModuleTypes map[string]*ModuleType
229
230 variables map[string]soongConfigVariable
231}
232
Jingwen Chen01812022021-11-19 14:29:43 +0000233// Bp2BuildSoongConfigDefinition keeps a global record of all soong config
234// string vars, bool vars and value vars created by every
235// soong_config_module_type in this build.
236type Bp2BuildSoongConfigDefinitions struct {
237 StringVars map[string]map[string]bool
238 BoolVars map[string]bool
239 ValueVars map[string]bool
240}
241
242// SoongConfigVariablesForBp2build extracts information from a
243// SoongConfigDefinition that bp2build needs to generate constraint settings and
244// values for, in order to migrate soong_config_module_type usages to Bazel.
245func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef SoongConfigDefinition) {
246 if defs.StringVars == nil {
247 defs.StringVars = make(map[string]map[string]bool)
248 }
249 if defs.BoolVars == nil {
250 defs.BoolVars = make(map[string]bool)
251 }
252 if defs.ValueVars == nil {
253 defs.ValueVars = make(map[string]bool)
254 }
255 for _, moduleType := range mtDef.ModuleTypes {
256 for _, v := range moduleType.Variables {
257 key := strings.Join([]string{moduleType.ConfigNamespace, v.variableProperty()}, "__")
258 if strVar, ok := v.(*stringVariable); ok {
259 if _, ok := defs.StringVars[key]; !ok {
260 defs.StringVars[key] = make(map[string]bool, 0)
261 }
262 for _, value := range strVar.values {
263 defs.StringVars[key][value] = true
264 }
265 } else if _, ok := v.(*boolVariable); ok {
266 defs.BoolVars[key] = true
267 } else if _, ok := v.(*valueVariable); ok {
268 defs.ValueVars[key] = true
269 } else {
270 panic(fmt.Errorf("Unsupported variable type: %+v", v))
271 }
272 }
273 }
274}
275
276// This is a copy of the one available in soong/android/util.go, but depending
277// on the android package causes a cyclic dependency. A refactoring here is to
278// extract common utils out from android/utils.go for other packages like this.
279func sortedStringKeys(m interface{}) []string {
280 v := reflect.ValueOf(m)
281 if v.Kind() != reflect.Map {
282 panic(fmt.Sprintf("%#v is not a map", m))
283 }
284 keys := v.MapKeys()
285 s := make([]string, 0, len(keys))
286 for _, key := range keys {
287 s = append(s, key.String())
288 }
289 sort.Strings(s)
290 return s
291}
292
293// String emits the Soong config variable definitions as Starlark dictionaries.
294func (defs Bp2BuildSoongConfigDefinitions) String() string {
295 ret := ""
296 ret += "soong_config_bool_variables = {\n"
297 for _, boolVar := range sortedStringKeys(defs.BoolVars) {
298 ret += fmt.Sprintf(" \"%s\": True,\n", boolVar)
299 }
300 ret += "}\n"
301 ret += "\n"
302
303 ret += "soong_config_value_variables = {\n"
304 for _, valueVar := range sortedStringKeys(defs.ValueVars) {
305 ret += fmt.Sprintf(" \"%s\": True,\n", valueVar)
306 }
307 ret += "}\n"
308 ret += "\n"
309
310 ret += "soong_config_string_variables = {\n"
311 for _, stringVar := range sortedStringKeys(defs.StringVars) {
312 ret += fmt.Sprintf(" \"%s\": [\n", stringVar)
313 for _, choice := range sortedStringKeys(defs.StringVars[stringVar]) {
314 ret += fmt.Sprintf(" \"%s\",\n", choice)
315 }
316 ret += fmt.Sprintf(" ],\n")
317 }
318 ret += "}"
319
320 return ret
321}
322
Colin Cross9d34f352019-11-22 16:03:51 -0800323// CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
324// property layout for the Soong config variables, with each possible value an interface{} that
325// contains a nil pointer to another newly constructed type that contains the affectable properties.
326// The reflect.Value will be cloned for each call to the Soong config module type's factory method.
327//
328// For example, the acme_cc_defaults example above would
329// produce a reflect.Value whose type is:
330// *struct {
331// Soong_config_variables struct {
332// Board struct {
333// Soc_a interface{}
334// Soc_b interface{}
335// }
336// }
337// }
338// And whose value is:
339// &{
340// Soong_config_variables: {
341// Board: {
342// Soc_a: (*struct{ Cflags []string })(nil),
343// Soc_b: (*struct{ Cflags []string })(nil),
344// },
345// },
346// }
347func CreateProperties(factory blueprint.ModuleFactory, moduleType *ModuleType) reflect.Value {
348 var fields []reflect.StructField
349
350 _, factoryProps := factory()
351 affectablePropertiesType := createAffectablePropertiesType(moduleType.affectableProperties, factoryProps)
352 if affectablePropertiesType == nil {
353 return reflect.Value{}
354 }
355
356 for _, c := range moduleType.Variables {
357 fields = append(fields, reflect.StructField{
358 Name: proptools.FieldNameForProperty(c.variableProperty()),
359 Type: c.variableValuesType(),
360 })
361 }
362
363 typ := reflect.StructOf([]reflect.StructField{{
Jingwen Chena47f28d2021-11-02 16:43:57 +0000364 Name: SoongConfigProperty,
Colin Cross9d34f352019-11-22 16:03:51 -0800365 Type: reflect.StructOf(fields),
366 }})
367
368 props := reflect.New(typ)
Jingwen Chena47f28d2021-11-02 16:43:57 +0000369 structConditions := props.Elem().FieldByName(SoongConfigProperty)
Colin Cross9d34f352019-11-22 16:03:51 -0800370
371 for i, c := range moduleType.Variables {
372 c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
373 }
374
375 return props
376}
377
378// createAffectablePropertiesType creates a reflect.Type of a struct that has a field for each affectable property
379// that exists in factoryProps.
380func createAffectablePropertiesType(affectableProperties []string, factoryProps []interface{}) reflect.Type {
381 affectableProperties = append([]string(nil), affectableProperties...)
382 sort.Strings(affectableProperties)
383
384 var recurse func(prefix string, aps []string) ([]string, reflect.Type)
385 recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
386 var fields []reflect.StructField
387
Colin Cross997f27a2021-03-05 17:25:41 -0800388 // Iterate while the list is non-empty so it can be modified in the loop.
Colin Cross9d34f352019-11-22 16:03:51 -0800389 for len(affectableProperties) > 0 {
390 p := affectableProperties[0]
391 if !strings.HasPrefix(affectableProperties[0], prefix) {
Colin Cross997f27a2021-03-05 17:25:41 -0800392 // The properties are sorted and recurse is always called with a prefix that matches
393 // the first property in the list, so if we've reached one that doesn't match the
394 // prefix we are done with this prefix.
Colin Cross9d34f352019-11-22 16:03:51 -0800395 break
396 }
Colin Cross9d34f352019-11-22 16:03:51 -0800397
398 nestedProperty := strings.TrimPrefix(p, prefix)
399 if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
400 var nestedType reflect.Type
401 nestedPrefix := nestedProperty[:i+1]
402
Colin Cross997f27a2021-03-05 17:25:41 -0800403 // Recurse to handle the properties with the found prefix. This will return
404 // an updated affectableProperties with the handled entries removed from the front
405 // of the list, and the type that contains the handled entries. The type may be
406 // nil if none of the entries matched factoryProps.
Colin Cross9d34f352019-11-22 16:03:51 -0800407 affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
408
409 if nestedType != nil {
410 nestedFieldName := proptools.FieldNameForProperty(strings.TrimSuffix(nestedPrefix, "."))
411
412 fields = append(fields, reflect.StructField{
413 Name: nestedFieldName,
414 Type: nestedType,
415 })
416 }
417 } else {
418 typ := typeForPropertyFromPropertyStructs(factoryProps, p)
419 if typ != nil {
420 fields = append(fields, reflect.StructField{
421 Name: proptools.FieldNameForProperty(nestedProperty),
422 Type: typ,
423 })
424 }
Colin Cross997f27a2021-03-05 17:25:41 -0800425 // The first element in the list has been handled, remove it from the list.
426 affectableProperties = affectableProperties[1:]
Colin Cross9d34f352019-11-22 16:03:51 -0800427 }
428 }
429
430 var typ reflect.Type
431 if len(fields) > 0 {
432 typ = reflect.StructOf(fields)
433 }
434 return affectableProperties, typ
435 }
436
437 affectableProperties, typ := recurse("", affectableProperties)
438 if len(affectableProperties) > 0 {
439 panic(fmt.Errorf("didn't handle all affectable properties"))
440 }
441
442 if typ != nil {
443 return reflect.PtrTo(typ)
444 }
445
446 return nil
447}
448
449func typeForPropertyFromPropertyStructs(psList []interface{}, property string) reflect.Type {
450 for _, ps := range psList {
451 if typ := typeForPropertyFromPropertyStruct(ps, property); typ != nil {
452 return typ
453 }
454 }
455
456 return nil
457}
458
459func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.Type {
460 v := reflect.ValueOf(ps)
461 for len(property) > 0 {
462 if !v.IsValid() {
463 return nil
464 }
465
466 if v.Kind() == reflect.Interface {
467 if v.IsNil() {
468 return nil
469 } else {
470 v = v.Elem()
471 }
472 }
473
474 if v.Kind() == reflect.Ptr {
475 if v.IsNil() {
476 v = reflect.Zero(v.Type().Elem())
477 } else {
478 v = v.Elem()
479 }
480 }
481
482 if v.Kind() != reflect.Struct {
483 return nil
484 }
485
486 if index := strings.IndexRune(property, '.'); index >= 0 {
487 prefix := property[:index]
488 property = property[index+1:]
489
490 v = v.FieldByName(proptools.FieldNameForProperty(prefix))
491 } else {
492 f := v.FieldByName(proptools.FieldNameForProperty(property))
493 if !f.IsValid() {
494 return nil
495 }
496 return f.Type()
497 }
498 }
499 return nil
500}
501
502// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
503// based on SoongConfig values.
Liz Kammerfe8853d2020-12-16 09:34:33 -0800504// Expects that props contains a struct field with name soong_config_variables. The fields within
Liz Kammer432bd592020-12-16 12:42:02 -0800505// soong_config_variables are expected to be in the same order as moduleType.Variables.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700506func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
Colin Cross9d34f352019-11-22 16:03:51 -0800507 var ret []interface{}
Jingwen Chena47f28d2021-11-02 16:43:57 +0000508 props = props.Elem().FieldByName(SoongConfigProperty)
Colin Cross9d34f352019-11-22 16:03:51 -0800509 for i, c := range moduleType.Variables {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700510 if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
511 return nil, err
512 } else if ps != nil {
Colin Cross9d34f352019-11-22 16:03:51 -0800513 ret = append(ret, ps)
514 }
515 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700516 return ret, nil
Colin Cross9d34f352019-11-22 16:03:51 -0800517}
518
519type ModuleType struct {
520 BaseModuleType string
521 ConfigNamespace string
522 Variables []soongConfigVariable
523
524 affectableProperties []string
525 variableNames []string
526}
527
Liz Kammer432bd592020-12-16 12:42:02 -0800528func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
529 mt := &ModuleType{
530 affectableProperties: props.Properties,
531 ConfigNamespace: props.Config_namespace,
532 BaseModuleType: props.Module_type,
533 variableNames: props.Variables,
534 }
535
536 for _, name := range props.Bool_variables {
537 if err := checkVariableName(name); err != nil {
538 return nil, []error{fmt.Errorf("bool_variables %s", err)}
539 }
540
541 mt.Variables = append(mt.Variables, newBoolVariable(name))
542 }
543
544 for _, name := range props.Value_variables {
545 if err := checkVariableName(name); err != nil {
546 return nil, []error{fmt.Errorf("value_variables %s", err)}
547 }
548
549 mt.Variables = append(mt.Variables, &valueVariable{
550 baseVariable: baseVariable{
551 variable: name,
552 },
553 })
554 }
555
556 return mt, nil
557}
558
559func checkVariableName(name string) error {
560 if name == "" {
561 return fmt.Errorf("name must not be blank")
562 } else if name == conditionsDefault {
563 return fmt.Errorf("%q is reserved", conditionsDefault)
564 }
565 return nil
566}
567
Colin Cross9d34f352019-11-22 16:03:51 -0800568type soongConfigVariable interface {
569 // variableProperty returns the name of the variable.
570 variableProperty() string
571
572 // conditionalValuesType returns a reflect.Type that contains an interface{} for each possible value.
573 variableValuesType() reflect.Type
574
575 // initializeProperties is passed a reflect.Value of the reflect.Type returned by conditionalValuesType and a
576 // reflect.Type of the affectable properties, and should initialize each interface{} in the reflect.Value with
577 // the zero value of the affectable properties type.
578 initializeProperties(v reflect.Value, typ reflect.Type)
579
580 // PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
581 // to the module.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700582 PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error)
Colin Cross9d34f352019-11-22 16:03:51 -0800583}
584
585type baseVariable struct {
586 variable string
587}
588
589func (c *baseVariable) variableProperty() string {
590 return CanonicalizeToProperty(c.variable)
591}
592
593type stringVariable struct {
594 baseVariable
595 values []string
596}
597
598func (s *stringVariable) variableValuesType() reflect.Type {
599 var fields []reflect.StructField
600
Liz Kammer432bd592020-12-16 12:42:02 -0800601 var values []string
602 values = append(values, s.values...)
603 values = append(values, conditionsDefault)
604 for _, v := range values {
Colin Cross9d34f352019-11-22 16:03:51 -0800605 fields = append(fields, reflect.StructField{
606 Name: proptools.FieldNameForProperty(v),
607 Type: emptyInterfaceType,
608 })
609 }
610
611 return reflect.StructOf(fields)
612}
613
Liz Kammer432bd592020-12-16 12:42:02 -0800614// initializeProperties initializes properties to zero value of typ for supported values and a final
615// conditions default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800616func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
617 for i := range s.values {
618 v.Field(i).Set(reflect.Zero(typ))
619 }
Liz Kammer432bd592020-12-16 12:42:02 -0800620 v.Field(len(s.values)).Set(reflect.Zero(typ)) // conditions default is the final value
Colin Cross9d34f352019-11-22 16:03:51 -0800621}
622
Liz Kammer432bd592020-12-16 12:42:02 -0800623// Extracts an interface from values containing the properties to apply based on config.
624// 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 -0700625func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Colin Cross9d34f352019-11-22 16:03:51 -0800626 for j, v := range s.values {
Liz Kammer432bd592020-12-16 12:42:02 -0800627 f := values.Field(j)
628 if config.String(s.variable) == v && !f.Elem().IsNil() {
629 return f.Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800630 }
631 }
Liz Kammer432bd592020-12-16 12:42:02 -0800632 // if we have reached this point, we have checked all valid values of string and either:
633 // * the value was not set
634 // * the value was set but that value was not specified in the Android.bp file
635 return values.Field(len(s.values)).Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800636}
637
Liz Kammer432bd592020-12-16 12:42:02 -0800638// Struct to allow conditions set based on a boolean variable
Colin Cross9d34f352019-11-22 16:03:51 -0800639type boolVariable struct {
640 baseVariable
641}
642
Liz Kammer432bd592020-12-16 12:42:02 -0800643// newBoolVariable constructs a boolVariable with the given name
Liz Kammerfe8853d2020-12-16 09:34:33 -0800644func newBoolVariable(name string) *boolVariable {
645 return &boolVariable{
646 baseVariable{
647 variable: name,
648 },
649 }
650}
651
Colin Cross9d34f352019-11-22 16:03:51 -0800652func (b boolVariable) variableValuesType() reflect.Type {
653 return emptyInterfaceType
654}
655
Liz Kammer432bd592020-12-16 12:42:02 -0800656// initializeProperties initializes a property to zero value of typ with an additional conditions
657// default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800658func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800659 initializePropertiesWithDefault(v, typ)
Colin Cross9d34f352019-11-22 16:03:51 -0800660}
661
Liz Kammer432bd592020-12-16 12:42:02 -0800662// initializePropertiesWithDefault, initialize with zero value, v to contain a field for each field
663// in typ, with an additional field for defaults of type typ. This should be used to initialize
664// boolVariable, valueVariable, or any future implementations of soongConfigVariable which support
665// one variable and a default.
666func initializePropertiesWithDefault(v reflect.Value, typ reflect.Type) {
667 sTyp := typ.Elem()
668 var fields []reflect.StructField
669 for i := 0; i < sTyp.NumField(); i++ {
670 fields = append(fields, sTyp.Field(i))
Colin Cross9d34f352019-11-22 16:03:51 -0800671 }
672
Liz Kammer432bd592020-12-16 12:42:02 -0800673 // create conditions_default field
674 nestedFieldName := proptools.FieldNameForProperty(conditionsDefault)
675 fields = append(fields, reflect.StructField{
676 Name: nestedFieldName,
677 Type: typ,
678 })
679
680 newTyp := reflect.PtrTo(reflect.StructOf(fields))
681 v.Set(reflect.Zero(newTyp))
682}
683
684// conditionsDefaultField extracts the conditions_default field from v. This is always the final
685// field if initialized with initializePropertiesWithDefault.
686func conditionsDefaultField(v reflect.Value) reflect.Value {
687 return v.Field(v.NumField() - 1)
688}
689
690// removeDefault removes the conditions_default field from values while retaining values from all
691// other fields. This allows
692func removeDefault(values reflect.Value) reflect.Value {
693 v := values.Elem().Elem()
694 s := conditionsDefaultField(v)
695 // if conditions_default field was not set, there will be no issues extending properties.
696 if !s.IsValid() {
697 return v
698 }
699
700 // If conditions_default field was set, it has the correct type for our property. Create a new
701 // reflect.Value of the conditions_default type and copy all fields (except for
702 // conditions_default) based on values to the result.
703 res := reflect.New(s.Type().Elem())
704 for i := 0; i < res.Type().Elem().NumField(); i++ {
705 val := v.Field(i)
706 res.Elem().Field(i).Set(val)
707 }
708
709 return res
710}
711
712// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
713// the module. If the value was not set, conditions_default interface will be returned; otherwise,
714// the interface in values, without conditions_default will be returned.
715func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
716 // If this variable was not referenced in the module, there are no properties to apply.
717 if values.Elem().IsZero() {
718 return nil, nil
719 }
720 if config.Bool(b.variable) {
721 values = removeDefault(values)
722 return values.Interface(), nil
723 }
724 v := values.Elem().Elem()
725 if f := conditionsDefaultField(v); f.IsValid() {
726 return f.Interface(), nil
727 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700728 return nil, nil
729}
730
Liz Kammer432bd592020-12-16 12:42:02 -0800731// Struct to allow conditions set based on a value variable, supporting string substitution.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700732type valueVariable struct {
733 baseVariable
734}
735
736func (s *valueVariable) variableValuesType() reflect.Type {
737 return emptyInterfaceType
738}
739
Liz Kammer432bd592020-12-16 12:42:02 -0800740// initializeProperties initializes a property to zero value of typ with an additional conditions
741// default field.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700742func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800743 initializePropertiesWithDefault(v, typ)
Dan Willemsenb0935db2020-03-23 19:42:18 -0700744}
745
Liz Kammer432bd592020-12-16 12:42:02 -0800746// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
747// the module. If the variable was not set, conditions_default interface will be returned;
748// otherwise, the interface in values, without conditions_default will be returned with all
749// appropriate string substitutions based on variable being set.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700750func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Liz Kammer432bd592020-12-16 12:42:02 -0800751 // If this variable was not referenced in the module, there are no properties to apply.
752 if !values.IsValid() || values.Elem().IsZero() {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700753 return nil, nil
754 }
Liz Kammer432bd592020-12-16 12:42:02 -0800755 if !config.IsSet(s.variable) {
756 return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
757 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700758 configValue := config.String(s.variable)
759
Liz Kammer432bd592020-12-16 12:42:02 -0800760 values = removeDefault(values)
761 propStruct := values.Elem()
Liz Kammer40ddfaa2020-12-16 10:59:00 -0800762 if !propStruct.IsValid() {
763 return nil, nil
764 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700765 for i := 0; i < propStruct.NumField(); i++ {
766 field := propStruct.Field(i)
767 kind := field.Kind()
768 if kind == reflect.Ptr {
769 if field.IsNil() {
770 continue
771 }
772 field = field.Elem()
773 }
774 switch kind {
775 case reflect.String:
776 err := printfIntoProperty(field, configValue)
777 if err != nil {
778 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
779 }
780 case reflect.Slice:
781 for j := 0; j < field.Len(); j++ {
782 err := printfIntoProperty(field.Index(j), configValue)
783 if err != nil {
784 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
785 }
786 }
787 case reflect.Bool:
788 // Nothing to do
789 default:
790 return nil, fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, propStruct.Type().Field(i).Name, kind)
791 }
792 }
793
794 return values.Interface(), nil
795}
796
797func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
798 s := propertyValue.String()
799
800 count := strings.Count(s, "%")
801 if count == 0 {
802 return nil
803 }
804
805 if count > 1 {
806 return fmt.Errorf("value variable properties only support a single '%%'")
807 }
808
809 if !strings.Contains(s, "%s") {
810 return fmt.Errorf("unsupported %% in value variable property")
811 }
812
813 propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, configValue)))
814
Colin Cross9d34f352019-11-22 16:03:51 -0800815 return nil
816}
817
818func CanonicalizeToProperty(v string) string {
819 return strings.Map(func(r rune) rune {
820 switch {
821 case r >= 'A' && r <= 'Z',
822 r >= 'a' && r <= 'z',
823 r >= '0' && r <= '9',
824 r == '_':
825 return r
826 default:
827 return '_'
828 }
829 }, v)
830}
831
832func CanonicalizeToProperties(values []string) []string {
833 ret := make([]string, len(values))
834 for i, v := range values {
835 ret[i] = CanonicalizeToProperty(v)
836 }
837 return ret
838}
839
840type emptyInterfaceStruct struct {
841 i interface{}
842}
843
844var emptyInterfaceType = reflect.TypeOf(emptyInterfaceStruct{}).Field(0).Type