platform_build_blueprint/blueprint/unpack.go
Romain Guy 285296519a Add support for targets in Blueprints.
The default target selector uses the name of the host OS.
Modules can implement their own target selector by implementing
the context.TargetSelector interface and its unique selectTarget()
method.

Targets are defined this way in Blueprint files:

cc_library {
    name:  "libmylib",
    deps:  ["libmath", "libutils"],
    zones: ["frontend"],
    srcs:  ["main.cpp"],
    targets: {
        darwin: {
            moduleCflags: "-framework OpenGL -framework GLUT",
        },
        linux:  {
            deps: ["libx11headers", "libglu"],
        },
    },
}

In this example, a set of C flags are defined on OS X only and on
Linux, two new dependencies are added.

When a target is selected, its properties are merged with the properties
of the modules:
- a list is appended to the original list (see deps above)
- a string is concatenated to the original string
- a bool replaces the original bool
- a map adds or replaces the key/value pairs of the original map

Change-Id: Ic627d47f795d6a4ff56ca5f6f099cad157621af1
2014-08-12 17:50:11 -07:00

298 lines
7.7 KiB
Go

package blueprint
import (
"blueprint/parser"
"fmt"
"reflect"
"unicode"
"unicode/utf8"
)
type packedProperty struct {
property *parser.Property
unpacked bool
}
type valueHandler interface {
assignBool(value reflect.Value, data bool)
assignString(value reflect.Value, data string)
assignValue(value reflect.Value, data reflect.Value)
}
type valueSetter struct {
}
func (v *valueSetter) assignBool(value reflect.Value, data bool) {
value.SetBool(data)
}
func (v *valueSetter) assignString(value reflect.Value, data string) {
value.SetString(data)
}
func (v *valueSetter) assignValue(value reflect.Value, data reflect.Value) {
value.Set(data)
}
type valueMerger struct {
}
func (v *valueMerger) assignBool(value reflect.Value, data bool) {
// when merging bools, the original value is replaced
value.SetBool(data)
}
func (v *valueMerger) assignString(value reflect.Value, data string) {
value.SetString(value.String() + data)
}
func (v *valueMerger) assignValue(value reflect.Value, data reflect.Value) {
// this should never happen
if value.Kind() != data.Kind() {
panic("attempting to merge values of different kinds")
}
if value.Kind() == reflect.Slice {
value.Set(reflect.AppendSlice(value, data))
} else if value.Kind() == reflect.Map {
for _, key := range data.MapKeys() {
value.SetMapIndex(key, data.MapIndex(key))
}
}
}
func unpackProperties(propertyDefs []*parser.Property,
propertiesStructs ...interface{}) (errs []error) {
setter := new(valueSetter);
return unpackPropertiesWithHandler(setter, propertyDefs, propertiesStructs...)
}
func mergeProperties(propertyDefs []*parser.Property,
propertiesStructs ...interface{}) (errs []error) {
merger := new(valueMerger);
return unpackPropertiesWithHandler(merger, propertyDefs, propertiesStructs...)
}
func unpackPropertiesWithHandler(handler valueHandler, propertyDefs []*parser.Property,
propertiesStructs ...interface{}) (errs []error) {
propertyMap := make(map[string]*packedProperty)
for _, propertyDef := range propertyDefs {
name := propertyDef.Name
if first, present := propertyMap[name]; present {
errs = append(errs, &Error{
Err: fmt.Errorf("property %q already defined", name),
Pos: propertyDef.Pos,
})
errs = append(errs, &Error{
Err: fmt.Errorf("<-- previous definition here"),
Pos: first.property.Pos,
})
if len(errs) >= maxErrors {
return errs
}
continue
}
propertyMap[name] = &packedProperty{
property: propertyDef,
unpacked: false,
}
}
for _, properties := range propertiesStructs {
propertiesValue := reflect.ValueOf(properties)
if propertiesValue.Kind() != reflect.Ptr {
panic("properties must be a pointer to a struct")
}
propertiesValue = propertiesValue.Elem()
if propertiesValue.Kind() != reflect.Struct {
panic("properties must be a pointer to a struct")
}
newErrs := unpackStruct(propertiesValue, propertyMap, handler)
errs = append(errs, newErrs...)
if len(errs) >= maxErrors {
return errs
}
}
// Report any properties that didn't have corresponding struct fields as
// errors.
for name, packedProperty := range propertyMap {
if !packedProperty.unpacked {
err := &Error{
Err: fmt.Errorf("unrecognized property %q", name),
Pos: packedProperty.property.Pos,
}
errs = append(errs, err)
}
}
return errs
}
func unpackStruct(structValue reflect.Value,
propertyMap map[string]*packedProperty, handler valueHandler) []error {
structType := structValue.Type()
var errs []error
for i := 0; i < structValue.NumField(); i++ {
fieldValue := structValue.Field(i)
field := structType.Field(i)
if field.PkgPath != "" {
// This is an unexported field, so just skip it.
continue
}
if !fieldValue.CanSet() {
panic(fmt.Errorf("field %s is not settable", field.Name))
}
// To make testing easier we validate the struct field's type regardless
// of whether or not the property was specified in the parsed string.
switch kind := fieldValue.Kind(); kind {
case reflect.Bool, reflect.String:
// Do nothing
case reflect.Slice:
elemType := field.Type.Elem()
if elemType.Kind() != reflect.String {
panic(fmt.Errorf("field %s is a non-string slice", field.Name))
}
case reflect.Struct:
newErrs := unpackStruct(fieldValue, propertyMap, handler)
errs = append(errs, newErrs...)
if len(errs) >= maxErrors {
return errs
}
continue // This field doesn't correspond to a specific property.
case reflect.Map:
fieldType := field.Type
if fieldType.Key().Kind() != reflect.String {
panic(fmt.Errorf("field %s uses a non-string key", field.Name))
}
if fieldType.Elem().Kind() != reflect.TypeOf(([]*parser.Property)(nil)).Kind() {
panic(fmt.Errorf("field %s uses a non-parser.Property value", field.Name))
}
default:
panic(fmt.Errorf("unsupported kind for field %s: %s",
field.Name, kind))
}
// Get the property value if it was specified.
propertyName := propertyNameForField(field)
packedProperty, ok := propertyMap[propertyName]
if !ok {
// This property wasn't specified.
continue
}
packedProperty.unpacked = true
var newErrs []error
switch kind := fieldValue.Kind(); kind {
case reflect.Bool:
newErrs = unpackBool(fieldValue, packedProperty.property, handler)
case reflect.String:
newErrs = unpackString(fieldValue, packedProperty.property, handler)
case reflect.Slice:
newErrs = unpackSlice(fieldValue, packedProperty.property, handler)
case reflect.Map:
newErrs = unpackMap(fieldValue, packedProperty.property, handler)
}
errs = append(errs, newErrs...)
if len(errs) >= maxErrors {
return errs
}
}
return errs
}
func unpackBool(boolValue reflect.Value, property *parser.Property, handler valueHandler) []error {
if property.Value.Type != parser.Bool {
return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q",
property.Value.Pos, property.Value.Type, parser.Bool,
property.Name),
}
}
handler.assignBool(boolValue, property.Value.BoolValue)
return nil
}
func unpackString(stringValue reflect.Value,
property *parser.Property, handler valueHandler) []error {
if property.Value.Type != parser.String {
return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q",
property.Value.Pos, property.Value.Type, parser.String,
property.Name),
}
}
handler.assignString(stringValue, property.Value.StringValue)
return nil
}
func unpackSlice(sliceValue reflect.Value, property *parser.Property,
handler valueHandler) []error {
if property.Value.Type != parser.List {
return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q",
property.Value.Pos, property.Value.Type, parser.List,
property.Name),
}
}
var list []string
for _, value := range property.Value.ListValue {
if value.Type != parser.String {
// The parser should not produce this.
panic("non-string value found in list")
}
list = append(list, value.StringValue)
}
handler.assignValue(sliceValue, reflect.ValueOf(list))
return nil
}
func unpackMap(mapValue reflect.Value, property *parser.Property,
handler valueHandler) []error {
if property.Value.Type != parser.Map {
return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q",
property.Value.Pos, property.Value.Type, parser.Map,
property.Name),
}
}
m := make(map[string][]*parser.Property)
for _, p := range property.Value.MapValue {
if p.Value.Type != parser.Map {
panic("non-map value found in map")
}
m[p.Name] = p.Value.MapValue
}
handler.assignValue(mapValue, reflect.ValueOf(m))
return nil;
}
func propertyNameForField(field reflect.StructField) string {
r, size := utf8.DecodeRuneInString(field.Name)
propertyName := string(unicode.ToLower(r))
if len(field.Name) > size {
propertyName += field.Name[size:]
}
return propertyName
}