blob: 83fcd32ef3d8dc6302eccf80edd4049a4328399c [file] [log] [blame]
Colin Cross8e0c5112015-01-23 14:15:10 -08001// Copyright 2014 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
Jamie Gennis1bc967e2014-05-27 16:34:41 -070015package blueprint
16
17import (
Jamie Gennis1bc967e2014-05-27 16:34:41 -070018 "fmt"
Colin Cross4adc8192015-06-22 13:38:45 -070019 "reflect"
20 "strconv"
21 "strings"
22
Jamie Gennis6cafc2c2015-03-20 22:39:29 -040023 "github.com/google/blueprint/parser"
24 "github.com/google/blueprint/proptools"
Jamie Gennis1bc967e2014-05-27 16:34:41 -070025)
26
27type packedProperty struct {
28 property *parser.Property
29 unpacked bool
30}
31
32func unpackProperties(propertyDefs []*parser.Property,
Jamie Gennis87622922014-09-30 11:38:25 -070033 propertiesStructs ...interface{}) (map[string]*parser.Property, []error) {
Jamie Gennis1bc967e2014-05-27 16:34:41 -070034
35 propertyMap := make(map[string]*packedProperty)
Jamie Gennis87622922014-09-30 11:38:25 -070036 errs := buildPropertyMap("", propertyDefs, propertyMap)
37 if len(errs) > 0 {
38 return nil, errs
39 }
40
41 for _, properties := range propertiesStructs {
42 propertiesValue := reflect.ValueOf(properties)
43 if propertiesValue.Kind() != reflect.Ptr {
44 panic("properties must be a pointer to a struct")
45 }
46
47 propertiesValue = propertiesValue.Elem()
48 if propertiesValue.Kind() != reflect.Struct {
49 panic("properties must be a pointer to a struct")
50 }
51
Colin Cross4adc8192015-06-22 13:38:45 -070052 newErrs := unpackStructValue("", propertiesValue, propertyMap, "", "")
Jamie Gennis87622922014-09-30 11:38:25 -070053 errs = append(errs, newErrs...)
54
55 if len(errs) >= maxErrors {
56 return nil, errs
57 }
58 }
59
60 // Report any properties that didn't have corresponding struct fields as
61 // errors.
62 result := make(map[string]*parser.Property)
63 for name, packedProperty := range propertyMap {
64 result[name] = packedProperty.property
65 if !packedProperty.unpacked {
66 err := &Error{
67 Err: fmt.Errorf("unrecognized property %q", name),
68 Pos: packedProperty.property.Pos,
69 }
70 errs = append(errs, err)
71 }
72 }
73
74 if len(errs) > 0 {
75 return nil, errs
76 }
77
78 return result, nil
79}
80
81func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property,
82 propertyMap map[string]*packedProperty) (errs []error) {
83
Jamie Gennis1bc967e2014-05-27 16:34:41 -070084 for _, propertyDef := range propertyDefs {
Colin Crossd1facc12015-01-08 14:56:03 -080085 name := namePrefix + propertyDef.Name.Name
Jamie Gennis1bc967e2014-05-27 16:34:41 -070086 if first, present := propertyMap[name]; present {
Jamie Gennis87622922014-09-30 11:38:25 -070087 if first.property == propertyDef {
88 // We've already added this property.
89 continue
90 }
91
Jamie Gennis1bc967e2014-05-27 16:34:41 -070092 errs = append(errs, &Error{
93 Err: fmt.Errorf("property %q already defined", name),
94 Pos: propertyDef.Pos,
95 })
96 errs = append(errs, &Error{
Jamie Gennisd4c53d82014-06-22 17:02:55 -070097 Err: fmt.Errorf("<-- previous definition here"),
Jamie Gennis1bc967e2014-05-27 16:34:41 -070098 Pos: first.property.Pos,
99 })
100 if len(errs) >= maxErrors {
101 return errs
102 }
103 continue
104 }
105
106 propertyMap[name] = &packedProperty{
107 property: propertyDef,
108 unpacked: false,
109 }
Jamie Gennis87622922014-09-30 11:38:25 -0700110
111 // We intentionally do not rescursively add MapValue properties to the
112 // property map here. Instead we add them when we encounter a struct
113 // into which they can be unpacked. We do this so that if we never
114 // encounter such a struct then the "unrecognized property" error will
115 // be reported only once for the map property and not for each of its
116 // sub-properties.
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700117 }
118
Jamie Gennis87622922014-09-30 11:38:25 -0700119 return
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700120}
121
Jamie Gennis87622922014-09-30 11:38:25 -0700122func unpackStructValue(namePrefix string, structValue reflect.Value,
Colin Cross4adc8192015-06-22 13:38:45 -0700123 propertyMap map[string]*packedProperty, filterKey, filterValue string) []error {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700124
125 structType := structValue.Type()
126
127 var errs []error
128 for i := 0; i < structValue.NumField(); i++ {
129 fieldValue := structValue.Field(i)
130 field := structType.Field(i)
131
Jamie Gennisd4c53d82014-06-22 17:02:55 -0700132 if field.PkgPath != "" {
133 // This is an unexported field, so just skip it.
134 continue
135 }
136
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700137 if !fieldValue.CanSet() {
138 panic(fmt.Errorf("field %s is not settable", field.Name))
139 }
140
141 // To make testing easier we validate the struct field's type regardless
142 // of whether or not the property was specified in the parsed string.
143 switch kind := fieldValue.Kind(); kind {
Jamie Gennis87622922014-09-30 11:38:25 -0700144 case reflect.Bool, reflect.String, reflect.Struct:
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700145 // Do nothing
146 case reflect.Slice:
147 elemType := field.Type.Elem()
148 if elemType.Kind() != reflect.String {
149 panic(fmt.Errorf("field %s is a non-string slice", field.Name))
150 }
Jamie Gennis87622922014-09-30 11:38:25 -0700151 case reflect.Interface:
152 if fieldValue.IsNil() {
153 panic(fmt.Errorf("field %s contains a nil interface",
154 field.Name))
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700155 }
Jamie Gennis87622922014-09-30 11:38:25 -0700156 fieldValue = fieldValue.Elem()
157 elemType := fieldValue.Type()
158 if elemType.Kind() != reflect.Ptr {
159 panic(fmt.Errorf("field %s contains a non-pointer interface",
160 field.Name))
161 }
162 fallthrough
163 case reflect.Ptr:
164 if fieldValue.IsNil() {
165 panic(fmt.Errorf("field %s contains a nil pointer",
166 field.Name))
167 }
168 fieldValue = fieldValue.Elem()
169 elemType := fieldValue.Type()
170 if elemType.Kind() != reflect.Struct {
171 panic(fmt.Errorf("field %s contains a non-struct pointer",
172 field.Name))
173 }
Colin Cross4adc8192015-06-22 13:38:45 -0700174
Colin Cross11a114f2014-12-17 16:46:09 -0800175 case reflect.Int, reflect.Uint:
176 if !hasTag(field, "blueprint", "mutated") {
177 panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, field.Name))
178 }
179
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700180 default:
181 panic(fmt.Errorf("unsupported kind for field %s: %s",
182 field.Name, kind))
183 }
184
185 // Get the property value if it was specified.
Jamie Gennis87622922014-09-30 11:38:25 -0700186 propertyName := namePrefix + proptools.PropertyNameForField(field.Name)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700187 packedProperty, ok := propertyMap[propertyName]
188 if !ok {
189 // This property wasn't specified.
190 continue
191 }
192
Colin Cross4adc8192015-06-22 13:38:45 -0700193 packedProperty.unpacked = true
Colin Cross11a114f2014-12-17 16:46:09 -0800194
195 if hasTag(field, "blueprint", "mutated") {
196 errs = append(errs,
Colin Cross4adc8192015-06-22 13:38:45 -0700197 &Error{
198 Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
199 Pos: packedProperty.property.Pos,
200 })
Colin Cross11a114f2014-12-17 16:46:09 -0800201 if len(errs) >= maxErrors {
202 return errs
203 }
204 continue
205 }
206
Colin Cross4adc8192015-06-22 13:38:45 -0700207 if filterKey != "" && !hasTag(field, filterKey, filterValue) {
208 errs = append(errs,
209 &Error{
210 Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName),
211 Pos: packedProperty.property.Pos,
212 })
213 if len(errs) >= maxErrors {
214 return errs
215 }
216 continue
217 }
218
219 var newErrs []error
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700220
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700221 switch kind := fieldValue.Kind(); kind {
222 case reflect.Bool:
Jamie Gennis1174c692014-10-05 07:41:44 -0700223 newErrs = unpackBool(fieldValue, packedProperty.property)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700224 case reflect.String:
Jamie Gennis1174c692014-10-05 07:41:44 -0700225 newErrs = unpackString(fieldValue, packedProperty.property)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700226 case reflect.Slice:
Jamie Gennis1174c692014-10-05 07:41:44 -0700227 newErrs = unpackSlice(fieldValue, packedProperty.property)
Jamie Gennis87622922014-09-30 11:38:25 -0700228 case reflect.Ptr, reflect.Interface:
Colin Cross4adc8192015-06-22 13:38:45 -0700229 fieldValue = fieldValue.Elem()
230 fallthrough
231 case reflect.Struct:
232 localFilterKey, localFilterValue := filterKey, filterValue
Colin Cross4572edd2015-05-13 14:36:24 -0700233 if k, v, err := HasFilter(field.Tag); err != nil {
Colin Cross4adc8192015-06-22 13:38:45 -0700234 errs = append(errs, err)
235 if len(errs) >= maxErrors {
236 return errs
237 }
238 } else if k != "" {
239 if filterKey != "" {
240 errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q",
241 field.Name))
242 if len(errs) >= maxErrors {
243 return errs
244 }
245 } else {
246 localFilterKey, localFilterValue = k, v
247 }
248 }
249 newErrs = unpackStruct(propertyName+".", fieldValue,
250 packedProperty.property, propertyMap, localFilterKey, localFilterValue)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700251 }
252 errs = append(errs, newErrs...)
253 if len(errs) >= maxErrors {
254 return errs
255 }
256 }
257
258 return errs
259}
260
Jamie Gennis1174c692014-10-05 07:41:44 -0700261func unpackBool(boolValue reflect.Value, property *parser.Property) []error {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700262 if property.Value.Type != parser.Bool {
263 return []error{
264 fmt.Errorf("%s: can't assign %s value to %s property %q",
265 property.Value.Pos, property.Value.Type, parser.Bool,
266 property.Name),
267 }
268 }
Jamie Gennis1174c692014-10-05 07:41:44 -0700269 boolValue.SetBool(property.Value.BoolValue)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700270 return nil
271}
272
273func unpackString(stringValue reflect.Value,
Jamie Gennis1174c692014-10-05 07:41:44 -0700274 property *parser.Property) []error {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700275
276 if property.Value.Type != parser.String {
277 return []error{
278 fmt.Errorf("%s: can't assign %s value to %s property %q",
279 property.Value.Pos, property.Value.Type, parser.String,
280 property.Name),
281 }
282 }
Jamie Gennis1174c692014-10-05 07:41:44 -0700283 stringValue.SetString(property.Value.StringValue)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700284 return nil
285}
286
Jamie Gennis1174c692014-10-05 07:41:44 -0700287func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error {
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700288 if property.Value.Type != parser.List {
289 return []error{
290 fmt.Errorf("%s: can't assign %s value to %s property %q",
291 property.Value.Pos, property.Value.Type, parser.List,
292 property.Name),
293 }
294 }
295
296 var list []string
297 for _, value := range property.Value.ListValue {
298 if value.Type != parser.String {
299 // The parser should not produce this.
300 panic("non-string value found in list")
301 }
302 list = append(list, value.StringValue)
303 }
304
Jamie Gennis1174c692014-10-05 07:41:44 -0700305 sliceValue.Set(reflect.ValueOf(list))
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700306 return nil
307}
308
Jamie Gennis87622922014-09-30 11:38:25 -0700309func unpackStruct(namePrefix string, structValue reflect.Value,
Colin Cross4adc8192015-06-22 13:38:45 -0700310 property *parser.Property, propertyMap map[string]*packedProperty,
311 filterKey, filterValue string) []error {
Jamie Gennis87622922014-09-30 11:38:25 -0700312
313 if property.Value.Type != parser.Map {
314 return []error{
315 fmt.Errorf("%s: can't assign %s value to %s property %q",
316 property.Value.Pos, property.Value.Type, parser.Map,
317 property.Name),
318 }
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700319 }
Jamie Gennis87622922014-09-30 11:38:25 -0700320
321 errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap)
322 if len(errs) > 0 {
323 return errs
324 }
325
Colin Cross4adc8192015-06-22 13:38:45 -0700326 return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue)
Jamie Gennis1bc967e2014-05-27 16:34:41 -0700327}
Colin Cross11a114f2014-12-17 16:46:09 -0800328
329func hasTag(field reflect.StructField, name, value string) bool {
330 tag := field.Tag.Get(name)
331 for _, entry := range strings.Split(tag, ",") {
332 if entry == value {
333 return true
334 }
335 }
336
337 return false
338}
Colin Cross4adc8192015-06-22 13:38:45 -0700339
Colin Cross4572edd2015-05-13 14:36:24 -0700340func HasFilter(field reflect.StructTag) (k, v string, err error) {
341 tag := field.Get("blueprint")
Colin Cross4adc8192015-06-22 13:38:45 -0700342 for _, entry := range strings.Split(tag, ",") {
343 if strings.HasPrefix(entry, "filter") {
344 if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") {
345 return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry)
346 }
347 entry = strings.TrimPrefix(entry, "filter(")
348 entry = strings.TrimSuffix(entry, ")")
349
350 s := strings.Split(entry, ":")
351 if len(s) != 2 {
352 return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry)
353 }
354 k = s[0]
355 v, err = strconv.Unquote(s[1])
356 if err != nil {
357 return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error())
358 }
359 return k, v, nil
360 }
361 }
362
363 return "", "", nil
364}