blob: c62e76dd6cecb5ff9afcd05a647aa75d86e9084a [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
Colin Cross9d34f352019-11-22 16:03:51 -080031var soongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
32
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
233// CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
234// property layout for the Soong config variables, with each possible value an interface{} that
235// contains a nil pointer to another newly constructed type that contains the affectable properties.
236// The reflect.Value will be cloned for each call to the Soong config module type's factory method.
237//
238// For example, the acme_cc_defaults example above would
239// produce a reflect.Value whose type is:
240// *struct {
241// Soong_config_variables struct {
242// Board struct {
243// Soc_a interface{}
244// Soc_b interface{}
245// }
246// }
247// }
248// And whose value is:
249// &{
250// Soong_config_variables: {
251// Board: {
252// Soc_a: (*struct{ Cflags []string })(nil),
253// Soc_b: (*struct{ Cflags []string })(nil),
254// },
255// },
256// }
257func CreateProperties(factory blueprint.ModuleFactory, moduleType *ModuleType) reflect.Value {
258 var fields []reflect.StructField
259
260 _, factoryProps := factory()
261 affectablePropertiesType := createAffectablePropertiesType(moduleType.affectableProperties, factoryProps)
262 if affectablePropertiesType == nil {
263 return reflect.Value{}
264 }
265
266 for _, c := range moduleType.Variables {
267 fields = append(fields, reflect.StructField{
268 Name: proptools.FieldNameForProperty(c.variableProperty()),
269 Type: c.variableValuesType(),
270 })
271 }
272
273 typ := reflect.StructOf([]reflect.StructField{{
274 Name: soongConfigProperty,
275 Type: reflect.StructOf(fields),
276 }})
277
278 props := reflect.New(typ)
279 structConditions := props.Elem().FieldByName(soongConfigProperty)
280
281 for i, c := range moduleType.Variables {
282 c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
283 }
284
285 return props
286}
287
288// createAffectablePropertiesType creates a reflect.Type of a struct that has a field for each affectable property
289// that exists in factoryProps.
290func createAffectablePropertiesType(affectableProperties []string, factoryProps []interface{}) reflect.Type {
291 affectableProperties = append([]string(nil), affectableProperties...)
292 sort.Strings(affectableProperties)
293
294 var recurse func(prefix string, aps []string) ([]string, reflect.Type)
295 recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
296 var fields []reflect.StructField
297
298 for len(affectableProperties) > 0 {
299 p := affectableProperties[0]
300 if !strings.HasPrefix(affectableProperties[0], prefix) {
301 break
302 }
303 affectableProperties = affectableProperties[1:]
304
305 nestedProperty := strings.TrimPrefix(p, prefix)
306 if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
307 var nestedType reflect.Type
308 nestedPrefix := nestedProperty[:i+1]
309
310 affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
311
312 if nestedType != nil {
313 nestedFieldName := proptools.FieldNameForProperty(strings.TrimSuffix(nestedPrefix, "."))
314
315 fields = append(fields, reflect.StructField{
316 Name: nestedFieldName,
317 Type: nestedType,
318 })
319 }
320 } else {
321 typ := typeForPropertyFromPropertyStructs(factoryProps, p)
322 if typ != nil {
323 fields = append(fields, reflect.StructField{
324 Name: proptools.FieldNameForProperty(nestedProperty),
325 Type: typ,
326 })
327 }
328 }
329 }
330
331 var typ reflect.Type
332 if len(fields) > 0 {
333 typ = reflect.StructOf(fields)
334 }
335 return affectableProperties, typ
336 }
337
338 affectableProperties, typ := recurse("", affectableProperties)
339 if len(affectableProperties) > 0 {
340 panic(fmt.Errorf("didn't handle all affectable properties"))
341 }
342
343 if typ != nil {
344 return reflect.PtrTo(typ)
345 }
346
347 return nil
348}
349
350func typeForPropertyFromPropertyStructs(psList []interface{}, property string) reflect.Type {
351 for _, ps := range psList {
352 if typ := typeForPropertyFromPropertyStruct(ps, property); typ != nil {
353 return typ
354 }
355 }
356
357 return nil
358}
359
360func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.Type {
361 v := reflect.ValueOf(ps)
362 for len(property) > 0 {
363 if !v.IsValid() {
364 return nil
365 }
366
367 if v.Kind() == reflect.Interface {
368 if v.IsNil() {
369 return nil
370 } else {
371 v = v.Elem()
372 }
373 }
374
375 if v.Kind() == reflect.Ptr {
376 if v.IsNil() {
377 v = reflect.Zero(v.Type().Elem())
378 } else {
379 v = v.Elem()
380 }
381 }
382
383 if v.Kind() != reflect.Struct {
384 return nil
385 }
386
387 if index := strings.IndexRune(property, '.'); index >= 0 {
388 prefix := property[:index]
389 property = property[index+1:]
390
391 v = v.FieldByName(proptools.FieldNameForProperty(prefix))
392 } else {
393 f := v.FieldByName(proptools.FieldNameForProperty(property))
394 if !f.IsValid() {
395 return nil
396 }
397 return f.Type()
398 }
399 }
400 return nil
401}
402
403// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
404// based on SoongConfig values.
Liz Kammerfe8853d2020-12-16 09:34:33 -0800405// Expects that props contains a struct field with name soong_config_variables. The fields within
Liz Kammer432bd592020-12-16 12:42:02 -0800406// soong_config_variables are expected to be in the same order as moduleType.Variables.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700407func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
Colin Cross9d34f352019-11-22 16:03:51 -0800408 var ret []interface{}
409 props = props.Elem().FieldByName(soongConfigProperty)
410 for i, c := range moduleType.Variables {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700411 if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
412 return nil, err
413 } else if ps != nil {
Colin Cross9d34f352019-11-22 16:03:51 -0800414 ret = append(ret, ps)
415 }
416 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700417 return ret, nil
Colin Cross9d34f352019-11-22 16:03:51 -0800418}
419
420type ModuleType struct {
421 BaseModuleType string
422 ConfigNamespace string
423 Variables []soongConfigVariable
424
425 affectableProperties []string
426 variableNames []string
427}
428
Liz Kammer432bd592020-12-16 12:42:02 -0800429func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
430 mt := &ModuleType{
431 affectableProperties: props.Properties,
432 ConfigNamespace: props.Config_namespace,
433 BaseModuleType: props.Module_type,
434 variableNames: props.Variables,
435 }
436
437 for _, name := range props.Bool_variables {
438 if err := checkVariableName(name); err != nil {
439 return nil, []error{fmt.Errorf("bool_variables %s", err)}
440 }
441
442 mt.Variables = append(mt.Variables, newBoolVariable(name))
443 }
444
445 for _, name := range props.Value_variables {
446 if err := checkVariableName(name); err != nil {
447 return nil, []error{fmt.Errorf("value_variables %s", err)}
448 }
449
450 mt.Variables = append(mt.Variables, &valueVariable{
451 baseVariable: baseVariable{
452 variable: name,
453 },
454 })
455 }
456
457 return mt, nil
458}
459
460func checkVariableName(name string) error {
461 if name == "" {
462 return fmt.Errorf("name must not be blank")
463 } else if name == conditionsDefault {
464 return fmt.Errorf("%q is reserved", conditionsDefault)
465 }
466 return nil
467}
468
Colin Cross9d34f352019-11-22 16:03:51 -0800469type soongConfigVariable interface {
470 // variableProperty returns the name of the variable.
471 variableProperty() string
472
473 // conditionalValuesType returns a reflect.Type that contains an interface{} for each possible value.
474 variableValuesType() reflect.Type
475
476 // initializeProperties is passed a reflect.Value of the reflect.Type returned by conditionalValuesType and a
477 // reflect.Type of the affectable properties, and should initialize each interface{} in the reflect.Value with
478 // the zero value of the affectable properties type.
479 initializeProperties(v reflect.Value, typ reflect.Type)
480
481 // PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
482 // to the module.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700483 PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error)
Colin Cross9d34f352019-11-22 16:03:51 -0800484}
485
486type baseVariable struct {
487 variable string
488}
489
490func (c *baseVariable) variableProperty() string {
491 return CanonicalizeToProperty(c.variable)
492}
493
494type stringVariable struct {
495 baseVariable
496 values []string
497}
498
499func (s *stringVariable) variableValuesType() reflect.Type {
500 var fields []reflect.StructField
501
Liz Kammer432bd592020-12-16 12:42:02 -0800502 var values []string
503 values = append(values, s.values...)
504 values = append(values, conditionsDefault)
505 for _, v := range values {
Colin Cross9d34f352019-11-22 16:03:51 -0800506 fields = append(fields, reflect.StructField{
507 Name: proptools.FieldNameForProperty(v),
508 Type: emptyInterfaceType,
509 })
510 }
511
512 return reflect.StructOf(fields)
513}
514
Liz Kammer432bd592020-12-16 12:42:02 -0800515// initializeProperties initializes properties to zero value of typ for supported values and a final
516// conditions default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800517func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
518 for i := range s.values {
519 v.Field(i).Set(reflect.Zero(typ))
520 }
Liz Kammer432bd592020-12-16 12:42:02 -0800521 v.Field(len(s.values)).Set(reflect.Zero(typ)) // conditions default is the final value
Colin Cross9d34f352019-11-22 16:03:51 -0800522}
523
Liz Kammer432bd592020-12-16 12:42:02 -0800524// Extracts an interface from values containing the properties to apply based on config.
525// 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 -0700526func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Colin Cross9d34f352019-11-22 16:03:51 -0800527 for j, v := range s.values {
Liz Kammer432bd592020-12-16 12:42:02 -0800528 f := values.Field(j)
529 if config.String(s.variable) == v && !f.Elem().IsNil() {
530 return f.Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800531 }
532 }
Liz Kammer432bd592020-12-16 12:42:02 -0800533 // if we have reached this point, we have checked all valid values of string and either:
534 // * the value was not set
535 // * the value was set but that value was not specified in the Android.bp file
536 return values.Field(len(s.values)).Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800537}
538
Liz Kammer432bd592020-12-16 12:42:02 -0800539// Struct to allow conditions set based on a boolean variable
Colin Cross9d34f352019-11-22 16:03:51 -0800540type boolVariable struct {
541 baseVariable
542}
543
Liz Kammer432bd592020-12-16 12:42:02 -0800544// newBoolVariable constructs a boolVariable with the given name
Liz Kammerfe8853d2020-12-16 09:34:33 -0800545func newBoolVariable(name string) *boolVariable {
546 return &boolVariable{
547 baseVariable{
548 variable: name,
549 },
550 }
551}
552
Colin Cross9d34f352019-11-22 16:03:51 -0800553func (b boolVariable) variableValuesType() reflect.Type {
554 return emptyInterfaceType
555}
556
Liz Kammer432bd592020-12-16 12:42:02 -0800557// initializeProperties initializes a property to zero value of typ with an additional conditions
558// default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800559func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800560 initializePropertiesWithDefault(v, typ)
Colin Cross9d34f352019-11-22 16:03:51 -0800561}
562
Liz Kammer432bd592020-12-16 12:42:02 -0800563// initializePropertiesWithDefault, initialize with zero value, v to contain a field for each field
564// in typ, with an additional field for defaults of type typ. This should be used to initialize
565// boolVariable, valueVariable, or any future implementations of soongConfigVariable which support
566// one variable and a default.
567func initializePropertiesWithDefault(v reflect.Value, typ reflect.Type) {
568 sTyp := typ.Elem()
569 var fields []reflect.StructField
570 for i := 0; i < sTyp.NumField(); i++ {
571 fields = append(fields, sTyp.Field(i))
Colin Cross9d34f352019-11-22 16:03:51 -0800572 }
573
Liz Kammer432bd592020-12-16 12:42:02 -0800574 // create conditions_default field
575 nestedFieldName := proptools.FieldNameForProperty(conditionsDefault)
576 fields = append(fields, reflect.StructField{
577 Name: nestedFieldName,
578 Type: typ,
579 })
580
581 newTyp := reflect.PtrTo(reflect.StructOf(fields))
582 v.Set(reflect.Zero(newTyp))
583}
584
585// conditionsDefaultField extracts the conditions_default field from v. This is always the final
586// field if initialized with initializePropertiesWithDefault.
587func conditionsDefaultField(v reflect.Value) reflect.Value {
588 return v.Field(v.NumField() - 1)
589}
590
591// removeDefault removes the conditions_default field from values while retaining values from all
592// other fields. This allows
593func removeDefault(values reflect.Value) reflect.Value {
594 v := values.Elem().Elem()
595 s := conditionsDefaultField(v)
596 // if conditions_default field was not set, there will be no issues extending properties.
597 if !s.IsValid() {
598 return v
599 }
600
601 // If conditions_default field was set, it has the correct type for our property. Create a new
602 // reflect.Value of the conditions_default type and copy all fields (except for
603 // conditions_default) based on values to the result.
604 res := reflect.New(s.Type().Elem())
605 for i := 0; i < res.Type().Elem().NumField(); i++ {
606 val := v.Field(i)
607 res.Elem().Field(i).Set(val)
608 }
609
610 return res
611}
612
613// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
614// the module. If the value was not set, conditions_default interface will be returned; otherwise,
615// the interface in values, without conditions_default will be returned.
616func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
617 // If this variable was not referenced in the module, there are no properties to apply.
618 if values.Elem().IsZero() {
619 return nil, nil
620 }
621 if config.Bool(b.variable) {
622 values = removeDefault(values)
623 return values.Interface(), nil
624 }
625 v := values.Elem().Elem()
626 if f := conditionsDefaultField(v); f.IsValid() {
627 return f.Interface(), nil
628 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700629 return nil, nil
630}
631
Liz Kammer432bd592020-12-16 12:42:02 -0800632// Struct to allow conditions set based on a value variable, supporting string substitution.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700633type valueVariable struct {
634 baseVariable
635}
636
637func (s *valueVariable) variableValuesType() reflect.Type {
638 return emptyInterfaceType
639}
640
Liz Kammer432bd592020-12-16 12:42:02 -0800641// initializeProperties initializes a property to zero value of typ with an additional conditions
642// default field.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700643func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800644 initializePropertiesWithDefault(v, typ)
Dan Willemsenb0935db2020-03-23 19:42:18 -0700645}
646
Liz Kammer432bd592020-12-16 12:42:02 -0800647// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
648// the module. If the variable was not set, conditions_default interface will be returned;
649// otherwise, the interface in values, without conditions_default will be returned with all
650// appropriate string substitutions based on variable being set.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700651func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Liz Kammer432bd592020-12-16 12:42:02 -0800652 // If this variable was not referenced in the module, there are no properties to apply.
653 if !values.IsValid() || values.Elem().IsZero() {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700654 return nil, nil
655 }
Liz Kammer432bd592020-12-16 12:42:02 -0800656 if !config.IsSet(s.variable) {
657 return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
658 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700659 configValue := config.String(s.variable)
660
Liz Kammer432bd592020-12-16 12:42:02 -0800661 values = removeDefault(values)
662 propStruct := values.Elem()
Liz Kammer40ddfaa2020-12-16 10:59:00 -0800663 if !propStruct.IsValid() {
664 return nil, nil
665 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700666 for i := 0; i < propStruct.NumField(); i++ {
667 field := propStruct.Field(i)
668 kind := field.Kind()
669 if kind == reflect.Ptr {
670 if field.IsNil() {
671 continue
672 }
673 field = field.Elem()
674 }
675 switch kind {
676 case reflect.String:
677 err := printfIntoProperty(field, configValue)
678 if err != nil {
679 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
680 }
681 case reflect.Slice:
682 for j := 0; j < field.Len(); j++ {
683 err := printfIntoProperty(field.Index(j), configValue)
684 if err != nil {
685 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
686 }
687 }
688 case reflect.Bool:
689 // Nothing to do
690 default:
691 return nil, fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, propStruct.Type().Field(i).Name, kind)
692 }
693 }
694
695 return values.Interface(), nil
696}
697
698func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
699 s := propertyValue.String()
700
701 count := strings.Count(s, "%")
702 if count == 0 {
703 return nil
704 }
705
706 if count > 1 {
707 return fmt.Errorf("value variable properties only support a single '%%'")
708 }
709
710 if !strings.Contains(s, "%s") {
711 return fmt.Errorf("unsupported %% in value variable property")
712 }
713
714 propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, configValue)))
715
Colin Cross9d34f352019-11-22 16:03:51 -0800716 return nil
717}
718
719func CanonicalizeToProperty(v string) string {
720 return strings.Map(func(r rune) rune {
721 switch {
722 case r >= 'A' && r <= 'Z',
723 r >= 'a' && r <= 'z',
724 r >= '0' && r <= '9',
725 r == '_':
726 return r
727 default:
728 return '_'
729 }
730 }, v)
731}
732
733func CanonicalizeToProperties(values []string) []string {
734 ret := make([]string, len(values))
735 for i, v := range values {
736 ret[i] = CanonicalizeToProperty(v)
737 }
738 return ret
739}
740
741type emptyInterfaceStruct struct {
742 i interface{}
743}
744
745var emptyInterfaceType = reflect.TypeOf(emptyInterfaceStruct{}).Field(0).Type