blob: 23c8afa0b75462d1bd81cf7ee56be6245ce89eda [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
Colin Cross9d34f352019-11-22 16:03:51 -080025 "github.com/google/blueprint/parser"
26 "github.com/google/blueprint/proptools"
Liz Kammer72beb342022-02-03 08:42:10 -050027
28 "android/soong/starlark_fmt"
Colin Cross9d34f352019-11-22 16:03:51 -080029)
30
Liz Kammer432bd592020-12-16 12:42:02 -080031const conditionsDefault = "conditions_default"
32
Jingwen Chena47f28d2021-11-02 16:43:57 +000033var SoongConfigProperty = proptools.FieldNameForProperty("soong_config_variables")
Colin Cross9d34f352019-11-22 16:03:51 -080034
35// loadSoongConfigModuleTypeDefinition loads module types from an Android.bp file. It caches the
36// result so each file is only parsed once.
37func Parse(r io.Reader, from string) (*SoongConfigDefinition, []error) {
38 scope := parser.NewScope(nil)
39 file, errs := parser.ParseAndEval(from, r, scope)
40
41 if len(errs) > 0 {
42 return nil, errs
43 }
44
45 mtDef := &SoongConfigDefinition{
46 ModuleTypes: make(map[string]*ModuleType),
47 variables: make(map[string]soongConfigVariable),
48 }
49
50 for _, def := range file.Defs {
51 switch def := def.(type) {
52 case *parser.Module:
53 newErrs := processImportModuleDef(mtDef, def)
54
55 if len(newErrs) > 0 {
56 errs = append(errs, newErrs...)
57 }
58
59 case *parser.Assignment:
60 // Already handled via Scope object
61 default:
62 panic("unknown definition type")
63 }
64 }
65
66 if len(errs) > 0 {
67 return nil, errs
68 }
69
70 for name, moduleType := range mtDef.ModuleTypes {
71 for _, varName := range moduleType.variableNames {
72 if v, ok := mtDef.variables[varName]; ok {
73 moduleType.Variables = append(moduleType.Variables, v)
74 } else {
75 return nil, []error{
76 fmt.Errorf("unknown variable %q in module type %q", varName, name),
77 }
78 }
79 }
80 }
81
82 return mtDef, nil
83}
84
85func processImportModuleDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
86 switch def.Type {
87 case "soong_config_module_type":
88 return processModuleTypeDef(v, def)
89 case "soong_config_string_variable":
90 return processStringVariableDef(v, def)
91 case "soong_config_bool_variable":
92 return processBoolVariableDef(v, def)
93 default:
94 // Unknown module types will be handled when the file is parsed as a normal
95 // Android.bp file.
96 }
97
98 return nil
99}
100
101type ModuleTypeProperties struct {
102 // the name of the new module type. Unlike most modules, this name does not need to be unique,
103 // although only one module type with any name will be importable into an Android.bp file.
104 Name string
105
106 // the module type that this module type will extend.
107 Module_type string
108
109 // the SOONG_CONFIG_NAMESPACE value from a BoardConfig.mk that this module type will read
110 // configuration variables from.
111 Config_namespace string
112
113 // the list of SOONG_CONFIG variables that this module type will read
114 Variables []string
115
Dan Willemsen2b8b89c2020-03-23 19:39:34 -0700116 // the list of boolean SOONG_CONFIG variables that this module type will read
117 Bool_variables []string
118
Dan Willemsenb0935db2020-03-23 19:42:18 -0700119 // the list of SOONG_CONFIG variables that this module type will read. The value will be
120 // inserted into the properties with %s substitution.
121 Value_variables []string
122
Colin Cross9d34f352019-11-22 16:03:51 -0800123 // the list of properties that this module type will extend.
124 Properties []string
125}
126
127func processModuleTypeDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
128
129 props := &ModuleTypeProperties{}
130
131 _, errs = proptools.UnpackProperties(def.Properties, props)
132 if len(errs) > 0 {
133 return errs
134 }
135
136 if props.Name == "" {
137 errs = append(errs, fmt.Errorf("name property must be set"))
138 }
139
140 if props.Config_namespace == "" {
141 errs = append(errs, fmt.Errorf("config_namespace property must be set"))
142 }
143
144 if props.Module_type == "" {
145 errs = append(errs, fmt.Errorf("module_type property must be set"))
146 }
147
148 if len(errs) > 0 {
149 return errs
150 }
151
Liz Kammer432bd592020-12-16 12:42:02 -0800152 if mt, errs := newModuleType(props); len(errs) > 0 {
153 return errs
154 } else {
155 v.ModuleTypes[props.Name] = mt
Dan Willemsenb0935db2020-03-23 19:42:18 -0700156 }
157
Colin Cross9d34f352019-11-22 16:03:51 -0800158 return nil
159}
160
161type VariableProperties struct {
162 Name string
163}
164
165type StringVariableProperties struct {
166 Values []string
167}
168
169func processStringVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
170 stringProps := &StringVariableProperties{}
171
172 base, errs := processVariableDef(def, stringProps)
173 if len(errs) > 0 {
174 return errs
175 }
176
177 if len(stringProps.Values) == 0 {
178 return []error{fmt.Errorf("values property must be set")}
179 }
180
Liz Kammer72beb342022-02-03 08:42:10 -0500181 vals := make(map[string]bool, len(stringProps.Values))
Liz Kammer432bd592020-12-16 12:42:02 -0800182 for _, name := range stringProps.Values {
183 if err := checkVariableName(name); err != nil {
184 return []error{fmt.Errorf("soong_config_string_variable: values property error %s", err)}
Liz Kammer72beb342022-02-03 08:42:10 -0500185 } else if _, ok := vals[name]; ok {
186 return []error{fmt.Errorf("soong_config_string_variable: values property error: duplicate value: %q", name)}
Liz Kammer432bd592020-12-16 12:42:02 -0800187 }
Liz Kammer72beb342022-02-03 08:42:10 -0500188 vals[name] = true
Liz Kammer432bd592020-12-16 12:42:02 -0800189 }
190
Colin Cross9d34f352019-11-22 16:03:51 -0800191 v.variables[base.variable] = &stringVariable{
192 baseVariable: base,
193 values: CanonicalizeToProperties(stringProps.Values),
194 }
195
196 return nil
197}
198
199func processBoolVariableDef(v *SoongConfigDefinition, def *parser.Module) (errs []error) {
200 base, errs := processVariableDef(def)
201 if len(errs) > 0 {
202 return errs
203 }
204
205 v.variables[base.variable] = &boolVariable{
206 baseVariable: base,
207 }
208
209 return nil
210}
211
212func processVariableDef(def *parser.Module,
213 extraProps ...interface{}) (cond baseVariable, errs []error) {
214
215 props := &VariableProperties{}
216
217 allProps := append([]interface{}{props}, extraProps...)
218
219 _, errs = proptools.UnpackProperties(def.Properties, allProps...)
220 if len(errs) > 0 {
221 return baseVariable{}, errs
222 }
223
224 if props.Name == "" {
225 return baseVariable{}, []error{fmt.Errorf("name property must be set")}
226 }
227
228 return baseVariable{
229 variable: props.Name,
230 }, nil
231}
232
233type SoongConfigDefinition struct {
234 ModuleTypes map[string]*ModuleType
235
236 variables map[string]soongConfigVariable
237}
238
Jingwen Chen01812022021-11-19 14:29:43 +0000239// Bp2BuildSoongConfigDefinition keeps a global record of all soong config
240// string vars, bool vars and value vars created by every
241// soong_config_module_type in this build.
242type Bp2BuildSoongConfigDefinitions struct {
Liz Kammer8103dc42023-03-06 09:02:38 -0500243 StringVars map[string]map[string]bool
Jingwen Chen01812022021-11-19 14:29:43 +0000244 BoolVars map[string]bool
245 ValueVars map[string]bool
246}
247
Jingwen Chen4ad40d92021-11-24 03:40:23 +0000248var bp2buildSoongConfigVarsLock sync.Mutex
249
Jingwen Chen01812022021-11-19 14:29:43 +0000250// SoongConfigVariablesForBp2build extracts information from a
251// SoongConfigDefinition that bp2build needs to generate constraint settings and
252// values for, in order to migrate soong_config_module_type usages to Bazel.
Liz Kammer8103dc42023-03-06 09:02:38 -0500253func (defs *Bp2BuildSoongConfigDefinitions) AddVars(mtDef *SoongConfigDefinition) {
Jingwen Chen4ad40d92021-11-24 03:40:23 +0000254 // In bp2build mode, this method is called concurrently in goroutines from
255 // loadhooks while parsing soong_config_module_type, so add a mutex to
256 // prevent concurrent map writes. See b/207572723
257 bp2buildSoongConfigVarsLock.Lock()
258 defer bp2buildSoongConfigVarsLock.Unlock()
259
Jingwen Chen01812022021-11-19 14:29:43 +0000260 if defs.StringVars == nil {
Liz Kammer8103dc42023-03-06 09:02:38 -0500261 defs.StringVars = make(map[string]map[string]bool)
Jingwen Chen01812022021-11-19 14:29:43 +0000262 }
263 if defs.BoolVars == nil {
264 defs.BoolVars = make(map[string]bool)
265 }
266 if defs.ValueVars == nil {
267 defs.ValueVars = make(map[string]bool)
268 }
Liz Kammer8103dc42023-03-06 09:02:38 -0500269 // varCache contains a cache of string variables namespace + property
270 // The same variable may be used in multiple module types (for example, if need support
271 // for cc_default and java_default), only need to process once
272 varCache := map[string]bool{}
273
Jingwen Chen01812022021-11-19 14:29:43 +0000274 for _, moduleType := range mtDef.ModuleTypes {
275 for _, v := range moduleType.Variables {
276 key := strings.Join([]string{moduleType.ConfigNamespace, v.variableProperty()}, "__")
Liz Kammer72beb342022-02-03 08:42:10 -0500277
278 // The same variable may be used in multiple module types (for example, if need support
279 // for cc_default and java_default), only need to process once
Liz Kammer8103dc42023-03-06 09:02:38 -0500280 if _, keyInCache := varCache[key]; keyInCache {
Liz Kammer72beb342022-02-03 08:42:10 -0500281 continue
282 } else {
Liz Kammer8103dc42023-03-06 09:02:38 -0500283 varCache[key] = true
Liz Kammer72beb342022-02-03 08:42:10 -0500284 }
285
Jingwen Chen01812022021-11-19 14:29:43 +0000286 if strVar, ok := v.(*stringVariable); ok {
Liz Kammer8103dc42023-03-06 09:02:38 -0500287 if _, ok := defs.StringVars[key]; !ok {
288 defs.StringVars[key] = make(map[string]bool, len(strVar.values))
289 }
Jingwen Chen01812022021-11-19 14:29:43 +0000290 for _, value := range strVar.values {
Liz Kammer8103dc42023-03-06 09:02:38 -0500291 defs.StringVars[key][value] = true
Jingwen Chen01812022021-11-19 14:29:43 +0000292 }
293 } else if _, ok := v.(*boolVariable); ok {
294 defs.BoolVars[key] = true
295 } else if _, ok := v.(*valueVariable); ok {
296 defs.ValueVars[key] = true
297 } else {
298 panic(fmt.Errorf("Unsupported variable type: %+v", v))
299 }
300 }
301 }
302}
303
Liz Kammer8103dc42023-03-06 09:02:38 -0500304// This is a copy of the one available in soong/android/util.go, but depending
305// on the android package causes a cyclic dependency. A refactoring here is to
306// extract common utils out from android/utils.go for other packages like this.
307func sortedStringKeys(m interface{}) []string {
308 v := reflect.ValueOf(m)
309 if v.Kind() != reflect.Map {
310 panic(fmt.Sprintf("%#v is not a map", m))
311 }
312 keys := v.MapKeys()
313 s := make([]string, 0, len(keys))
314 for _, key := range keys {
315 s = append(s, key.String())
316 }
317 sort.Strings(s)
318 return s
319}
320
Jingwen Chen01812022021-11-19 14:29:43 +0000321// String emits the Soong config variable definitions as Starlark dictionaries.
322func (defs Bp2BuildSoongConfigDefinitions) String() string {
323 ret := ""
Liz Kammer72beb342022-02-03 08:42:10 -0500324 ret += "soong_config_bool_variables = "
325 ret += starlark_fmt.PrintBoolDict(defs.BoolVars, 0)
326 ret += "\n\n"
Jingwen Chen01812022021-11-19 14:29:43 +0000327
Liz Kammer72beb342022-02-03 08:42:10 -0500328 ret += "soong_config_value_variables = "
329 ret += starlark_fmt.PrintBoolDict(defs.ValueVars, 0)
330 ret += "\n\n"
Jingwen Chen01812022021-11-19 14:29:43 +0000331
Liz Kammer8103dc42023-03-06 09:02:38 -0500332 stringVars := make(map[string][]string, len(defs.StringVars))
333 for k, v := range defs.StringVars {
334 stringVars[k] = sortedStringKeys(v)
335 }
336
Liz Kammer72beb342022-02-03 08:42:10 -0500337 ret += "soong_config_string_variables = "
Liz Kammer8103dc42023-03-06 09:02:38 -0500338 ret += starlark_fmt.PrintStringListDict(stringVars, 0)
Jingwen Chen01812022021-11-19 14:29:43 +0000339
340 return ret
341}
342
Colin Cross9d34f352019-11-22 16:03:51 -0800343// CreateProperties returns a reflect.Value of a newly constructed type that contains the desired
344// property layout for the Soong config variables, with each possible value an interface{} that
345// contains a nil pointer to another newly constructed type that contains the affectable properties.
346// The reflect.Value will be cloned for each call to the Soong config module type's factory method.
347//
348// For example, the acme_cc_defaults example above would
349// produce a reflect.Value whose type is:
Colin Crossd079e0b2022-08-16 10:27:33 -0700350//
351// *struct {
352// Soong_config_variables struct {
353// Board struct {
354// Soc_a interface{}
355// Soc_b interface{}
356// }
357// }
358// }
359//
Colin Cross9d34f352019-11-22 16:03:51 -0800360// And whose value is:
Colin Crossd079e0b2022-08-16 10:27:33 -0700361//
362// &{
363// Soong_config_variables: {
364// Board: {
365// Soc_a: (*struct{ Cflags []string })(nil),
366// Soc_b: (*struct{ Cflags []string })(nil),
367// },
368// },
369// }
Paul Duffine8b47682023-01-09 15:42:57 +0000370func CreateProperties(factoryProps []interface{}, moduleType *ModuleType) reflect.Value {
Colin Cross9d34f352019-11-22 16:03:51 -0800371 var fields []reflect.StructField
372
Colin Cross9d34f352019-11-22 16:03:51 -0800373 affectablePropertiesType := createAffectablePropertiesType(moduleType.affectableProperties, factoryProps)
374 if affectablePropertiesType == nil {
375 return reflect.Value{}
376 }
377
378 for _, c := range moduleType.Variables {
379 fields = append(fields, reflect.StructField{
380 Name: proptools.FieldNameForProperty(c.variableProperty()),
381 Type: c.variableValuesType(),
382 })
383 }
384
385 typ := reflect.StructOf([]reflect.StructField{{
Jingwen Chena47f28d2021-11-02 16:43:57 +0000386 Name: SoongConfigProperty,
Colin Cross9d34f352019-11-22 16:03:51 -0800387 Type: reflect.StructOf(fields),
388 }})
389
390 props := reflect.New(typ)
Jingwen Chena47f28d2021-11-02 16:43:57 +0000391 structConditions := props.Elem().FieldByName(SoongConfigProperty)
Colin Cross9d34f352019-11-22 16:03:51 -0800392
393 for i, c := range moduleType.Variables {
394 c.initializeProperties(structConditions.Field(i), affectablePropertiesType)
395 }
396
397 return props
398}
399
400// createAffectablePropertiesType creates a reflect.Type of a struct that has a field for each affectable property
401// that exists in factoryProps.
402func createAffectablePropertiesType(affectableProperties []string, factoryProps []interface{}) reflect.Type {
403 affectableProperties = append([]string(nil), affectableProperties...)
404 sort.Strings(affectableProperties)
405
406 var recurse func(prefix string, aps []string) ([]string, reflect.Type)
407 recurse = func(prefix string, aps []string) ([]string, reflect.Type) {
408 var fields []reflect.StructField
409
Colin Cross997f27a2021-03-05 17:25:41 -0800410 // Iterate while the list is non-empty so it can be modified in the loop.
Colin Cross9d34f352019-11-22 16:03:51 -0800411 for len(affectableProperties) > 0 {
412 p := affectableProperties[0]
413 if !strings.HasPrefix(affectableProperties[0], prefix) {
Colin Cross997f27a2021-03-05 17:25:41 -0800414 // The properties are sorted and recurse is always called with a prefix that matches
415 // the first property in the list, so if we've reached one that doesn't match the
416 // prefix we are done with this prefix.
Colin Cross9d34f352019-11-22 16:03:51 -0800417 break
418 }
Colin Cross9d34f352019-11-22 16:03:51 -0800419
420 nestedProperty := strings.TrimPrefix(p, prefix)
421 if i := strings.IndexRune(nestedProperty, '.'); i >= 0 {
422 var nestedType reflect.Type
423 nestedPrefix := nestedProperty[:i+1]
424
Colin Cross997f27a2021-03-05 17:25:41 -0800425 // Recurse to handle the properties with the found prefix. This will return
426 // an updated affectableProperties with the handled entries removed from the front
427 // of the list, and the type that contains the handled entries. The type may be
428 // nil if none of the entries matched factoryProps.
Colin Cross9d34f352019-11-22 16:03:51 -0800429 affectableProperties, nestedType = recurse(prefix+nestedPrefix, affectableProperties)
430
431 if nestedType != nil {
432 nestedFieldName := proptools.FieldNameForProperty(strings.TrimSuffix(nestedPrefix, "."))
433
434 fields = append(fields, reflect.StructField{
435 Name: nestedFieldName,
436 Type: nestedType,
437 })
438 }
439 } else {
440 typ := typeForPropertyFromPropertyStructs(factoryProps, p)
441 if typ != nil {
442 fields = append(fields, reflect.StructField{
443 Name: proptools.FieldNameForProperty(nestedProperty),
444 Type: typ,
445 })
446 }
Colin Cross997f27a2021-03-05 17:25:41 -0800447 // The first element in the list has been handled, remove it from the list.
448 affectableProperties = affectableProperties[1:]
Colin Cross9d34f352019-11-22 16:03:51 -0800449 }
450 }
451
452 var typ reflect.Type
453 if len(fields) > 0 {
454 typ = reflect.StructOf(fields)
455 }
456 return affectableProperties, typ
457 }
458
459 affectableProperties, typ := recurse("", affectableProperties)
460 if len(affectableProperties) > 0 {
461 panic(fmt.Errorf("didn't handle all affectable properties"))
462 }
463
464 if typ != nil {
465 return reflect.PtrTo(typ)
466 }
467
468 return nil
469}
470
471func typeForPropertyFromPropertyStructs(psList []interface{}, property string) reflect.Type {
472 for _, ps := range psList {
473 if typ := typeForPropertyFromPropertyStruct(ps, property); typ != nil {
474 return typ
475 }
476 }
477
478 return nil
479}
480
481func typeForPropertyFromPropertyStruct(ps interface{}, property string) reflect.Type {
482 v := reflect.ValueOf(ps)
483 for len(property) > 0 {
484 if !v.IsValid() {
485 return nil
486 }
487
488 if v.Kind() == reflect.Interface {
489 if v.IsNil() {
490 return nil
491 } else {
492 v = v.Elem()
493 }
494 }
495
496 if v.Kind() == reflect.Ptr {
497 if v.IsNil() {
498 v = reflect.Zero(v.Type().Elem())
499 } else {
500 v = v.Elem()
501 }
502 }
503
504 if v.Kind() != reflect.Struct {
505 return nil
506 }
507
508 if index := strings.IndexRune(property, '.'); index >= 0 {
509 prefix := property[:index]
510 property = property[index+1:]
511
512 v = v.FieldByName(proptools.FieldNameForProperty(prefix))
513 } else {
514 f := v.FieldByName(proptools.FieldNameForProperty(property))
515 if !f.IsValid() {
516 return nil
517 }
518 return f.Type()
519 }
520 }
521 return nil
522}
523
524// PropertiesToApply returns the applicable properties from a ModuleType that should be applied
525// based on SoongConfig values.
Liz Kammerfe8853d2020-12-16 09:34:33 -0800526// Expects that props contains a struct field with name soong_config_variables. The fields within
Liz Kammer432bd592020-12-16 12:42:02 -0800527// soong_config_variables are expected to be in the same order as moduleType.Variables.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700528func PropertiesToApply(moduleType *ModuleType, props reflect.Value, config SoongConfig) ([]interface{}, error) {
Colin Cross9d34f352019-11-22 16:03:51 -0800529 var ret []interface{}
Jingwen Chena47f28d2021-11-02 16:43:57 +0000530 props = props.Elem().FieldByName(SoongConfigProperty)
Colin Cross9d34f352019-11-22 16:03:51 -0800531 for i, c := range moduleType.Variables {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700532 if ps, err := c.PropertiesToApply(config, props.Field(i)); err != nil {
533 return nil, err
534 } else if ps != nil {
Colin Cross9d34f352019-11-22 16:03:51 -0800535 ret = append(ret, ps)
536 }
537 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700538 return ret, nil
Colin Cross9d34f352019-11-22 16:03:51 -0800539}
540
541type ModuleType struct {
542 BaseModuleType string
543 ConfigNamespace string
544 Variables []soongConfigVariable
545
546 affectableProperties []string
547 variableNames []string
548}
549
Liz Kammer432bd592020-12-16 12:42:02 -0800550func newModuleType(props *ModuleTypeProperties) (*ModuleType, []error) {
551 mt := &ModuleType{
552 affectableProperties: props.Properties,
553 ConfigNamespace: props.Config_namespace,
554 BaseModuleType: props.Module_type,
555 variableNames: props.Variables,
556 }
557
558 for _, name := range props.Bool_variables {
559 if err := checkVariableName(name); err != nil {
560 return nil, []error{fmt.Errorf("bool_variables %s", err)}
561 }
562
563 mt.Variables = append(mt.Variables, newBoolVariable(name))
564 }
565
566 for _, name := range props.Value_variables {
567 if err := checkVariableName(name); err != nil {
568 return nil, []error{fmt.Errorf("value_variables %s", err)}
569 }
570
571 mt.Variables = append(mt.Variables, &valueVariable{
572 baseVariable: baseVariable{
573 variable: name,
574 },
575 })
576 }
577
578 return mt, nil
579}
580
581func checkVariableName(name string) error {
582 if name == "" {
583 return fmt.Errorf("name must not be blank")
584 } else if name == conditionsDefault {
585 return fmt.Errorf("%q is reserved", conditionsDefault)
586 }
587 return nil
588}
589
Colin Cross9d34f352019-11-22 16:03:51 -0800590type soongConfigVariable interface {
591 // variableProperty returns the name of the variable.
592 variableProperty() string
593
594 // conditionalValuesType returns a reflect.Type that contains an interface{} for each possible value.
595 variableValuesType() reflect.Type
596
597 // initializeProperties is passed a reflect.Value of the reflect.Type returned by conditionalValuesType and a
598 // reflect.Type of the affectable properties, and should initialize each interface{} in the reflect.Value with
599 // the zero value of the affectable properties type.
600 initializeProperties(v reflect.Value, typ reflect.Type)
601
602 // PropertiesToApply should return one of the interface{} values set by initializeProperties to be applied
603 // to the module.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700604 PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error)
Colin Cross9d34f352019-11-22 16:03:51 -0800605}
606
607type baseVariable struct {
608 variable string
609}
610
611func (c *baseVariable) variableProperty() string {
612 return CanonicalizeToProperty(c.variable)
613}
614
615type stringVariable struct {
616 baseVariable
617 values []string
618}
619
620func (s *stringVariable) variableValuesType() reflect.Type {
621 var fields []reflect.StructField
622
Liz Kammer432bd592020-12-16 12:42:02 -0800623 var values []string
624 values = append(values, s.values...)
625 values = append(values, conditionsDefault)
626 for _, v := range values {
Colin Cross9d34f352019-11-22 16:03:51 -0800627 fields = append(fields, reflect.StructField{
628 Name: proptools.FieldNameForProperty(v),
629 Type: emptyInterfaceType,
630 })
631 }
632
633 return reflect.StructOf(fields)
634}
635
Liz Kammer432bd592020-12-16 12:42:02 -0800636// initializeProperties initializes properties to zero value of typ for supported values and a final
637// conditions default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800638func (s *stringVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
639 for i := range s.values {
640 v.Field(i).Set(reflect.Zero(typ))
641 }
Liz Kammer432bd592020-12-16 12:42:02 -0800642 v.Field(len(s.values)).Set(reflect.Zero(typ)) // conditions default is the final value
Colin Cross9d34f352019-11-22 16:03:51 -0800643}
644
Liz Kammer432bd592020-12-16 12:42:02 -0800645// Extracts an interface from values containing the properties to apply based on config.
646// 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 -0700647func (s *stringVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Cole Faustb0a91332022-11-01 17:10:23 +0000648 configValue := config.String(s.variable)
649 if configValue != "" && !InList(configValue, s.values) {
650 return nil, fmt.Errorf("Soong config property %q must be one of %v, found %q", s.variable, s.values, configValue)
651 }
Colin Cross9d34f352019-11-22 16:03:51 -0800652 for j, v := range s.values {
Liz Kammer432bd592020-12-16 12:42:02 -0800653 f := values.Field(j)
Cole Faustb0a91332022-11-01 17:10:23 +0000654 if configValue == v && !f.Elem().IsNil() {
Liz Kammer432bd592020-12-16 12:42:02 -0800655 return f.Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800656 }
657 }
Liz Kammer432bd592020-12-16 12:42:02 -0800658 // if we have reached this point, we have checked all valid values of string and either:
659 // * the value was not set
660 // * the value was set but that value was not specified in the Android.bp file
661 return values.Field(len(s.values)).Interface(), nil
Colin Cross9d34f352019-11-22 16:03:51 -0800662}
663
Liz Kammer432bd592020-12-16 12:42:02 -0800664// Struct to allow conditions set based on a boolean variable
Colin Cross9d34f352019-11-22 16:03:51 -0800665type boolVariable struct {
666 baseVariable
667}
668
Liz Kammer432bd592020-12-16 12:42:02 -0800669// newBoolVariable constructs a boolVariable with the given name
Liz Kammerfe8853d2020-12-16 09:34:33 -0800670func newBoolVariable(name string) *boolVariable {
671 return &boolVariable{
672 baseVariable{
673 variable: name,
674 },
675 }
676}
677
Colin Cross9d34f352019-11-22 16:03:51 -0800678func (b boolVariable) variableValuesType() reflect.Type {
679 return emptyInterfaceType
680}
681
Liz Kammer432bd592020-12-16 12:42:02 -0800682// initializeProperties initializes a property to zero value of typ with an additional conditions
683// default field.
Colin Cross9d34f352019-11-22 16:03:51 -0800684func (b boolVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800685 initializePropertiesWithDefault(v, typ)
Colin Cross9d34f352019-11-22 16:03:51 -0800686}
687
Liz Kammer432bd592020-12-16 12:42:02 -0800688// initializePropertiesWithDefault, initialize with zero value, v to contain a field for each field
689// in typ, with an additional field for defaults of type typ. This should be used to initialize
690// boolVariable, valueVariable, or any future implementations of soongConfigVariable which support
691// one variable and a default.
692func initializePropertiesWithDefault(v reflect.Value, typ reflect.Type) {
693 sTyp := typ.Elem()
694 var fields []reflect.StructField
695 for i := 0; i < sTyp.NumField(); i++ {
696 fields = append(fields, sTyp.Field(i))
Colin Cross9d34f352019-11-22 16:03:51 -0800697 }
698
Liz Kammer432bd592020-12-16 12:42:02 -0800699 // create conditions_default field
700 nestedFieldName := proptools.FieldNameForProperty(conditionsDefault)
701 fields = append(fields, reflect.StructField{
702 Name: nestedFieldName,
703 Type: typ,
704 })
705
706 newTyp := reflect.PtrTo(reflect.StructOf(fields))
707 v.Set(reflect.Zero(newTyp))
708}
709
710// conditionsDefaultField extracts the conditions_default field from v. This is always the final
711// field if initialized with initializePropertiesWithDefault.
712func conditionsDefaultField(v reflect.Value) reflect.Value {
713 return v.Field(v.NumField() - 1)
714}
715
716// removeDefault removes the conditions_default field from values while retaining values from all
717// other fields. This allows
718func removeDefault(values reflect.Value) reflect.Value {
719 v := values.Elem().Elem()
720 s := conditionsDefaultField(v)
721 // if conditions_default field was not set, there will be no issues extending properties.
722 if !s.IsValid() {
723 return v
724 }
725
726 // If conditions_default field was set, it has the correct type for our property. Create a new
727 // reflect.Value of the conditions_default type and copy all fields (except for
728 // conditions_default) based on values to the result.
729 res := reflect.New(s.Type().Elem())
730 for i := 0; i < res.Type().Elem().NumField(); i++ {
731 val := v.Field(i)
732 res.Elem().Field(i).Set(val)
733 }
734
735 return res
736}
737
738// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
739// the module. If the value was not set, conditions_default interface will be returned; otherwise,
740// the interface in values, without conditions_default will be returned.
741func (b boolVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
742 // If this variable was not referenced in the module, there are no properties to apply.
743 if values.Elem().IsZero() {
744 return nil, nil
745 }
746 if config.Bool(b.variable) {
747 values = removeDefault(values)
748 return values.Interface(), nil
749 }
750 v := values.Elem().Elem()
751 if f := conditionsDefaultField(v); f.IsValid() {
752 return f.Interface(), nil
753 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700754 return nil, nil
755}
756
Liz Kammer432bd592020-12-16 12:42:02 -0800757// Struct to allow conditions set based on a value variable, supporting string substitution.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700758type valueVariable struct {
759 baseVariable
760}
761
762func (s *valueVariable) variableValuesType() reflect.Type {
763 return emptyInterfaceType
764}
765
Liz Kammer432bd592020-12-16 12:42:02 -0800766// initializeProperties initializes a property to zero value of typ with an additional conditions
767// default field.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700768func (s *valueVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
Liz Kammer432bd592020-12-16 12:42:02 -0800769 initializePropertiesWithDefault(v, typ)
Dan Willemsenb0935db2020-03-23 19:42:18 -0700770}
771
Liz Kammer432bd592020-12-16 12:42:02 -0800772// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
773// the module. If the variable was not set, conditions_default interface will be returned;
774// otherwise, the interface in values, without conditions_default will be returned with all
775// appropriate string substitutions based on variable being set.
Dan Willemsenb0935db2020-03-23 19:42:18 -0700776func (s *valueVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
Liz Kammer432bd592020-12-16 12:42:02 -0800777 // If this variable was not referenced in the module, there are no properties to apply.
778 if !values.IsValid() || values.Elem().IsZero() {
Dan Willemsenb0935db2020-03-23 19:42:18 -0700779 return nil, nil
780 }
Liz Kammer432bd592020-12-16 12:42:02 -0800781 if !config.IsSet(s.variable) {
782 return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
783 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700784 configValue := config.String(s.variable)
785
Liz Kammer432bd592020-12-16 12:42:02 -0800786 values = removeDefault(values)
787 propStruct := values.Elem()
Liz Kammer40ddfaa2020-12-16 10:59:00 -0800788 if !propStruct.IsValid() {
789 return nil, nil
790 }
Dan Willemsenb0935db2020-03-23 19:42:18 -0700791 for i := 0; i < propStruct.NumField(); i++ {
792 field := propStruct.Field(i)
793 kind := field.Kind()
794 if kind == reflect.Ptr {
795 if field.IsNil() {
796 continue
797 }
798 field = field.Elem()
799 }
800 switch kind {
801 case reflect.String:
802 err := printfIntoProperty(field, 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 case reflect.Slice:
807 for j := 0; j < field.Len(); j++ {
808 err := printfIntoProperty(field.Index(j), configValue)
809 if err != nil {
810 return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
811 }
812 }
813 case reflect.Bool:
814 // Nothing to do
815 default:
816 return nil, fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, propStruct.Type().Field(i).Name, kind)
817 }
818 }
819
820 return values.Interface(), nil
821}
822
823func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
824 s := propertyValue.String()
825
826 count := strings.Count(s, "%")
827 if count == 0 {
828 return nil
829 }
830
831 if count > 1 {
832 return fmt.Errorf("value variable properties only support a single '%%'")
833 }
834
835 if !strings.Contains(s, "%s") {
836 return fmt.Errorf("unsupported %% in value variable property")
837 }
838
839 propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, configValue)))
840
Colin Cross9d34f352019-11-22 16:03:51 -0800841 return nil
842}
843
844func CanonicalizeToProperty(v string) string {
845 return strings.Map(func(r rune) rune {
846 switch {
847 case r >= 'A' && r <= 'Z',
848 r >= 'a' && r <= 'z',
849 r >= '0' && r <= '9',
850 r == '_':
851 return r
852 default:
853 return '_'
854 }
855 }, v)
856}
857
858func CanonicalizeToProperties(values []string) []string {
859 ret := make([]string, len(values))
860 for i, v := range values {
861 ret[i] = CanonicalizeToProperty(v)
862 }
863 return ret
864}
865
866type emptyInterfaceStruct struct {
867 i interface{}
868}
869
870var emptyInterfaceType = reflect.TypeOf(emptyInterfaceStruct{}).Field(0).Type
Cole Faustb0a91332022-11-01 17:10:23 +0000871
872// InList checks if the string belongs to the list
873func InList(s string, list []string) bool {
874 for _, s2 := range list {
875 if s2 == s {
876 return true
877 }
878 }
879 return false
880}