github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/unpack.go (about)

     1  // 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  
    15  package blueprint
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/google/blueprint/parser"
    24  	"github.com/google/blueprint/proptools"
    25  )
    26  
    27  type packedProperty struct {
    28  	property *parser.Property
    29  	unpacked bool
    30  }
    31  
    32  func unpackProperties(propertyDefs []*parser.Property,
    33  	propertiesStructs ...interface{}) (map[string]*parser.Property, []error) {
    34  
    35  	propertyMap := make(map[string]*packedProperty)
    36  	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  
    52  		newErrs := unpackStructValue("", propertiesValue, propertyMap, "", "")
    53  		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 := &BlueprintError{
    67  				Err: fmt.Errorf("unrecognized property %q", name),
    68  				Pos: packedProperty.property.ColonPos,
    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  
    81  func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property,
    82  	propertyMap map[string]*packedProperty) (errs []error) {
    83  
    84  	for _, propertyDef := range propertyDefs {
    85  		name := namePrefix + propertyDef.Name
    86  		if first, present := propertyMap[name]; present {
    87  			if first.property == propertyDef {
    88  				// We've already added this property.
    89  				continue
    90  			}
    91  			errs = append(errs, &BlueprintError{
    92  				Err: fmt.Errorf("property %q already defined", name),
    93  				Pos: propertyDef.ColonPos,
    94  			})
    95  			errs = append(errs, &BlueprintError{
    96  				Err: fmt.Errorf("<-- previous definition here"),
    97  				Pos: first.property.ColonPos,
    98  			})
    99  			if len(errs) >= maxErrors {
   100  				return errs
   101  			}
   102  			continue
   103  		}
   104  
   105  		propertyMap[name] = &packedProperty{
   106  			property: propertyDef,
   107  			unpacked: false,
   108  		}
   109  
   110  		// We intentionally do not rescursively add MapValue properties to the
   111  		// property map here.  Instead we add them when we encounter a struct
   112  		// into which they can be unpacked.  We do this so that if we never
   113  		// encounter such a struct then the "unrecognized property" error will
   114  		// be reported only once for the map property and not for each of its
   115  		// sub-properties.
   116  	}
   117  
   118  	return
   119  }
   120  
   121  func unpackStructValue(namePrefix string, structValue reflect.Value,
   122  	propertyMap map[string]*packedProperty, filterKey, filterValue string) []error {
   123  
   124  	structType := structValue.Type()
   125  
   126  	var errs []error
   127  	for i := 0; i < structValue.NumField(); i++ {
   128  		fieldValue := structValue.Field(i)
   129  		field := structType.Field(i)
   130  
   131  		// In Go 1.7, runtime-created structs are unexported, so it's not
   132  		// possible to create an exported anonymous field with a generated
   133  		// type. So workaround this by special-casing "BlueprintEmbed" to
   134  		// behave like an anonymous field for structure unpacking.
   135  		if field.Name == "BlueprintEmbed" {
   136  			field.Name = ""
   137  			field.Anonymous = true
   138  		}
   139  
   140  		if field.PkgPath != "" {
   141  			// This is an unexported field, so just skip it.
   142  			continue
   143  		}
   144  
   145  		propertyName := namePrefix + proptools.PropertyNameForField(field.Name)
   146  
   147  		if !fieldValue.CanSet() {
   148  			panic(fmt.Errorf("field %s is not settable", propertyName))
   149  		}
   150  
   151  		// Get the property value if it was specified.
   152  		packedProperty, propertyIsSet := propertyMap[propertyName]
   153  
   154  		origFieldValue := fieldValue
   155  
   156  		// To make testing easier we validate the struct field's type regardless
   157  		// of whether or not the property was specified in the parsed string.
   158  		// TODO(ccross): we don't validate types inside nil struct pointers
   159  		// Move type validation to a function that runs on each factory once
   160  		switch kind := fieldValue.Kind(); kind {
   161  		case reflect.Bool, reflect.String, reflect.Struct:
   162  			// Do nothing
   163  		case reflect.Slice:
   164  			elemType := field.Type.Elem()
   165  			if elemType.Kind() != reflect.String {
   166  				panic(fmt.Errorf("field %s is a non-string slice", propertyName))
   167  			}
   168  		case reflect.Interface:
   169  			if fieldValue.IsNil() {
   170  				panic(fmt.Errorf("field %s contains a nil interface", propertyName))
   171  			}
   172  			fieldValue = fieldValue.Elem()
   173  			elemType := fieldValue.Type()
   174  			if elemType.Kind() != reflect.Ptr {
   175  				panic(fmt.Errorf("field %s contains a non-pointer interface", propertyName))
   176  			}
   177  			fallthrough
   178  		case reflect.Ptr:
   179  			switch ptrKind := fieldValue.Type().Elem().Kind(); ptrKind {
   180  			case reflect.Struct:
   181  				if fieldValue.IsNil() && (propertyIsSet || field.Anonymous) {
   182  					// Instantiate nil struct pointers
   183  					// Set into origFieldValue in case it was an interface, in which case
   184  					// fieldValue points to the unsettable pointer inside the interface
   185  					fieldValue = reflect.New(fieldValue.Type().Elem())
   186  					origFieldValue.Set(fieldValue)
   187  				}
   188  				fieldValue = fieldValue.Elem()
   189  			case reflect.Bool, reflect.Int64, reflect.String:
   190  				// Nothing
   191  			default:
   192  				panic(fmt.Errorf("field %s contains a pointer to %s", propertyName, ptrKind))
   193  			}
   194  
   195  		case reflect.Int, reflect.Uint:
   196  			if !proptools.HasTag(field, "blueprint", "mutated") {
   197  				panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, propertyName))
   198  			}
   199  
   200  		default:
   201  			panic(fmt.Errorf("unsupported kind for field %s: %s", propertyName, kind))
   202  		}
   203  
   204  		if field.Anonymous && fieldValue.Kind() == reflect.Struct {
   205  			newErrs := unpackStructValue(namePrefix, fieldValue, propertyMap, filterKey, filterValue)
   206  			errs = append(errs, newErrs...)
   207  			continue
   208  		}
   209  
   210  		if !propertyIsSet {
   211  			// This property wasn't specified.
   212  			continue
   213  		}
   214  
   215  		packedProperty.unpacked = true
   216  
   217  		if proptools.HasTag(field, "blueprint", "mutated") {
   218  			errs = append(errs,
   219  				&BlueprintError{
   220  					Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName),
   221  					Pos: packedProperty.property.ColonPos,
   222  				})
   223  			if len(errs) >= maxErrors {
   224  				return errs
   225  			}
   226  			continue
   227  		}
   228  
   229  		if filterKey != "" && !proptools.HasTag(field, filterKey, filterValue) {
   230  			errs = append(errs,
   231  				&BlueprintError{
   232  					Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName),
   233  					Pos: packedProperty.property.ColonPos,
   234  				})
   235  			if len(errs) >= maxErrors {
   236  				return errs
   237  			}
   238  			continue
   239  		}
   240  
   241  		var newErrs []error
   242  
   243  		if fieldValue.Kind() == reflect.Struct {
   244  			localFilterKey, localFilterValue := filterKey, filterValue
   245  			if k, v, err := HasFilter(field.Tag); err != nil {
   246  				errs = append(errs, err)
   247  				if len(errs) >= maxErrors {
   248  					return errs
   249  				}
   250  			} else if k != "" {
   251  				if filterKey != "" {
   252  					errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q",
   253  						field.Name))
   254  					if len(errs) >= maxErrors {
   255  						return errs
   256  					}
   257  				} else {
   258  					localFilterKey, localFilterValue = k, v
   259  				}
   260  			}
   261  			newErrs = unpackStruct(propertyName+".", fieldValue,
   262  				packedProperty.property, propertyMap, localFilterKey, localFilterValue)
   263  
   264  			errs = append(errs, newErrs...)
   265  			if len(errs) >= maxErrors {
   266  				return errs
   267  			}
   268  
   269  			continue
   270  		}
   271  
   272  		// Handle basic types and pointers to basic types
   273  
   274  		propertyValue, err := propertyToValue(fieldValue.Type(), packedProperty.property)
   275  		if err != nil {
   276  			errs = append(errs, err)
   277  			if len(errs) >= maxErrors {
   278  				return errs
   279  			}
   280  		}
   281  
   282  		proptools.ExtendBasicType(fieldValue, propertyValue, proptools.Append)
   283  	}
   284  
   285  	return errs
   286  }
   287  
   288  func propertyToValue(typ reflect.Type, property *parser.Property) (reflect.Value, error) {
   289  	var value reflect.Value
   290  
   291  	var ptr bool
   292  	if typ.Kind() == reflect.Ptr {
   293  		typ = typ.Elem()
   294  		ptr = true
   295  	}
   296  
   297  	switch kind := typ.Kind(); kind {
   298  	case reflect.Bool:
   299  		b, ok := property.Value.Eval().(*parser.Bool)
   300  		if !ok {
   301  			return value, fmt.Errorf("%s: can't assign %s value to bool property %q",
   302  				property.Value.Pos(), property.Value.Type(), property.Name)
   303  		}
   304  		value = reflect.ValueOf(b.Value)
   305  
   306  	case reflect.Int64:
   307  		b, ok := property.Value.Eval().(*parser.Int64)
   308  		if !ok {
   309  			return value, fmt.Errorf("%s: can't assign %s value to int64 property %q",
   310  				property.Value.Pos(), property.Value.Type(), property.Name)
   311  		}
   312  		value = reflect.ValueOf(b.Value)
   313  
   314  	case reflect.String:
   315  		s, ok := property.Value.Eval().(*parser.String)
   316  		if !ok {
   317  			return value, fmt.Errorf("%s: can't assign %s value to string property %q",
   318  				property.Value.Pos(), property.Value.Type(), property.Name)
   319  		}
   320  		value = reflect.ValueOf(s.Value)
   321  
   322  	case reflect.Slice:
   323  		l, ok := property.Value.Eval().(*parser.List)
   324  		if !ok {
   325  			return value, fmt.Errorf("%s: can't assign %s value to list property %q",
   326  				property.Value.Pos(), property.Value.Type(), property.Name)
   327  		}
   328  
   329  		list := make([]string, len(l.Values))
   330  		for i, value := range l.Values {
   331  			s, ok := value.Eval().(*parser.String)
   332  			if !ok {
   333  				// The parser should not produce this.
   334  				panic(fmt.Errorf("non-string value %q found in list", value))
   335  			}
   336  			list[i] = s.Value
   337  		}
   338  
   339  		value = reflect.ValueOf(list)
   340  
   341  	default:
   342  		panic(fmt.Errorf("unexpected kind %s", kind))
   343  	}
   344  
   345  	if ptr {
   346  		ptrValue := reflect.New(value.Type())
   347  		ptrValue.Elem().Set(value)
   348  		value = ptrValue
   349  	}
   350  
   351  	return value, nil
   352  }
   353  
   354  func unpackStruct(namePrefix string, structValue reflect.Value,
   355  	property *parser.Property, propertyMap map[string]*packedProperty,
   356  	filterKey, filterValue string) []error {
   357  
   358  	m, ok := property.Value.Eval().(*parser.Map)
   359  	if !ok {
   360  		return []error{
   361  			fmt.Errorf("%s: can't assign %s value to map property %q",
   362  				property.Value.Pos(), property.Value.Type(), property.Name),
   363  		}
   364  	}
   365  
   366  	errs := buildPropertyMap(namePrefix, m.Properties, propertyMap)
   367  	if len(errs) > 0 {
   368  		return errs
   369  	}
   370  
   371  	return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue)
   372  }
   373  
   374  func HasFilter(field reflect.StructTag) (k, v string, err error) {
   375  	tag := field.Get("blueprint")
   376  	for _, entry := range strings.Split(tag, ",") {
   377  		if strings.HasPrefix(entry, "filter") {
   378  			if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") {
   379  				return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry)
   380  			}
   381  			entry = strings.TrimPrefix(entry, "filter(")
   382  			entry = strings.TrimSuffix(entry, ")")
   383  
   384  			s := strings.Split(entry, ":")
   385  			if len(s) != 2 {
   386  				return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry)
   387  			}
   388  			k = s[0]
   389  			v, err = strconv.Unquote(s[1])
   390  			if err != nil {
   391  				return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error())
   392  			}
   393  			return k, v, nil
   394  		}
   395  	}
   396  
   397  	return "", "", nil
   398  }