Generate libvirt procedure wrappers.

The generated wrappers have an argument for every field in their "Args"
struct, and return everything in their "Ret" struct (these structs are
defined in the protocol file, and identified by procedure name).
Marshaling and unmarshaling is handled inside the generated procedures.
This commit is contained in:
Geoff Hickey
2017-11-13 15:18:18 -05:00
parent fb16117ff9
commit deb7a54ff8
9 changed files with 16134 additions and 2700 deletions

View File

@@ -71,10 +71,14 @@ type Generator struct {
Consts []ConstItem
// Structs holds a list of all the structs found by the parser
Structs []Structure
// Typedefs hold all the type definitions from 'typedef ...' lines.
// StructMap is a map of the structs we find for quick searching.
StructMap map[string]int
// Typedefs holds all the type definitions from 'typedef ...' lines.
Typedefs []Typedef
// Unions hold all the discriminated unions
// Unions holds all the discriminated unions.
Unions []Union
// Procs holds all the discovered libvirt procedures.
Procs []Proc
}
// Gen accumulates items as the parser runs, and is then used to produce the
@@ -85,15 +89,48 @@ var Gen Generator
// explicitly given a value.
var CurrentEnumVal int64
// oneRuneTokens lists the runes the lexer will consider to be tokens when it
// finds them. These are returned to the parser using the integer value of their
// runes.
var oneRuneTokens = `{}[]<>(),=;:*`
// goEquivTypes maps the basic types defined in the rpc spec to their golang
// equivalents.
var goEquivTypes = map[string]string{
// Some of the identifiers in the rpc specification are reserved words or
// pre-existing types in go. This renames them to something safe.
"type": "lvtype",
"error": "lverror",
"nil": "lvnil",
var reservedIdentifiers = map[string]string{
"type": "lvtype",
"string": "lvstring",
"error": "lverror",
// The libvirt spec uses this NonnullString type, which is a string with a
// specified maximum length. This makes the go code more confusing, and
// we're not enforcing the limit anyway, so collapse it here. This also
// requires us to ditch the typedef that would otherwise be generated.
"NonnullString": "string",
"String": "string",
"Int": "int",
"Uint": "uint",
"Int8": "int8",
"Uint8": "uint8",
"Int16": "int16",
"Uint16": "uint16",
"Int32": "int32",
"Uint32": "uint32",
"Int64": "int64",
"Uint64": "uint64",
"Float32": "float32",
"Float64": "float64",
"Bool": "bool",
"Byte": "byte",
}
// These defines are from libvirt-common.h. They should be fetched from there,
// but for now they're hardcoded here.
var lvTypedParams = map[string]uint32{
"VIR_TYPED_PARAM_INT": 1,
"VIR_TYPED_PARAM_UINT": 2,
"VIR_TYPED_PARAM_LLONG": 3,
"VIR_TYPED_PARAM_ULLONG": 4,
"VIR_TYPED_PARAM_DOUBLE": 5,
"VIR_TYPED_PARAM_BOOLEAN": 6,
"VIR_TYPED_PARAM_STRING": 7,
}
// Decl records a declaration, like 'int x' or 'remote_nonnull_string str'
@@ -123,14 +160,50 @@ type Union struct {
// Case holds a single case of a discriminated union.
type Case struct {
CaseName string
DiscriminantVal string
Type Decl
Decl
}
// Proc holds information about a libvirt procedure the parser has found.
type Proc struct {
Num int64
Name string
Args []Decl
Ret []Decl
ArgsStruct string
RetStruct string
}
type structStack []*Structure
// CurrentStruct will point to a struct record if we're in a struct declaration.
// When the parser adds a declaration, it will be added to the open struct if
// there is one.
var CurrentStruct *Structure
var CurrentStruct structStack
// Since it's possible to have an embedded struct definition, this implements
// a stack to keep track of the current structure.
func (s *structStack) empty() bool {
return len(*s) == 0
}
func (s *structStack) push(st *Structure) {
*s = append(*s, st)
}
func (s *structStack) pop() *Structure {
if s.empty() {
return nil
}
st := (*s)[len(*s)-1]
*s = (*s)[:len(*s)-1]
return st
}
func (s *structStack) peek() *Structure {
if s.empty() {
return nil
}
return (*s)[len(*s)-1]
}
// CurrentTypedef will point to a typedef record if we're parsing one. Typedefs
// can define a struct or union type, but the preferred for is struct xxx{...},
@@ -148,6 +221,7 @@ var CurrentCase *Case
// the path to the root of the libvirt source directory to use for the
// generation.
func Generate(proto io.Reader) error {
Gen.StructMap = make(map[string]int)
lexer, err := NewLexer(proto)
if err != nil {
return err
@@ -162,6 +236,10 @@ func Generate(proto io.Reader) error {
return fmt.Errorf("failed to parse libvirt protocol: %v", rv)
}
// When parsing is done, we can link the procedures we've found to their
// argument types.
procLink()
// Generate and write the output.
constFile, err := os.Create("../constants/constants.gen.go")
if err != nil {
@@ -216,26 +294,44 @@ func genGo(constFile, procFile io.Writer) error {
// also tries to upcase abbreviations so a name like DOMAIN_GET_XML becomes
// DomainGetXML, not DomainGetXml.
func constNameTransform(name string) string {
nn := fromSnakeToCamel(strings.TrimPrefix(name, "REMOTE_"), true)
decamelize := strings.ContainsRune(name, '_')
nn := strings.TrimPrefix(name, "REMOTE_")
if decamelize {
nn = fromSnakeToCamel(nn, true)
}
nn = fixAbbrevs(nn)
return nn
}
func identifierTransform(name string) string {
decamelize := strings.ContainsRune(name, '_')
nn := strings.TrimPrefix(name, "remote_")
nn = fromSnakeToCamel(nn, false)
if decamelize {
nn = fromSnakeToCamel(nn, true)
} else {
nn = publicize(nn)
}
nn = fixAbbrevs(nn)
nn = checkIdentifier(nn)
return nn
}
func typeTransform(name string) string {
nn := strings.TrimLeft(name, "*")
nn := strings.TrimLeft(name, "*[]")
diff := len(name) - len(nn)
nn = identifierTransform(nn)
return name[0:diff] + nn
}
func publicize(name string) string {
if len(name) <= 0 {
return name
}
r, n := utf8.DecodeRuneInString(name)
name = string(unicode.ToUpper(r)) + name[n:]
return name
}
// fromSnakeToCamel transmutes a snake-cased string to a camel-cased one. All
// runes that follow an underscore are up-cased, and the underscores themselves
// are omitted.
@@ -292,6 +388,30 @@ func fixAbbrevs(s string) string {
return s
}
// procLink associates a libvirt procedure with the types that are its arguments
// and return values, filling out those fields in the procedure struct. These
// types are extracted by iterating through the argument and return structures
// defined in the protocol file. If one or both of these structs is not defined
// then either the args or return values are empty.
func procLink() {
for ix, proc := range Gen.Procs {
argsName := proc.Name + "Args"
retName := proc.Name + "Ret"
argsIx, hasArgs := Gen.StructMap[argsName]
retIx, hasRet := Gen.StructMap[retName]
if hasArgs {
argsStruct := Gen.Structs[argsIx]
Gen.Procs[ix].ArgsStruct = argsStruct.Name
Gen.Procs[ix].Args = argsStruct.Members
}
if hasRet {
retStruct := Gen.Structs[retIx]
Gen.Procs[ix].RetStruct = retStruct.Name
Gen.Procs[ix].Ret = retStruct.Members
}
}
}
//---------------------------------------------------------------------------
// Routines called by the parser's actions.
//---------------------------------------------------------------------------
@@ -327,6 +447,7 @@ func addEnumVal(name string, val int64) error {
name = constNameTransform(name)
Gen.EnumVals = append(Gen.EnumVals, ConstItem{name, fmt.Sprintf("%d", val)})
CurrentEnumVal = val
addProc(name, val)
return nil
}
@@ -341,6 +462,17 @@ func AddConst(name, val string) error {
return nil
}
// addProc checks an enum value to see if it's a procedure number. If so, we
// add the procedure to our list for later generation.
func addProc(name string, val int64) {
if !strings.HasPrefix(name, "Proc") {
return
}
name = name[4:]
proc := &Proc{Num: val, Name: name}
Gen.Procs = append(Gen.Procs, *proc)
}
// parseNumber makes sure that a parsed numerical value can be parsed to a 64-
// bit integer.
func parseNumber(val string) (int64, error) {
@@ -357,25 +489,22 @@ func parseNumber(val string) (int64, error) {
// before the member declarations are processed.
func StartStruct(name string) {
name = identifierTransform(name)
CurrentStruct = &Structure{Name: name}
CurrentStruct.push(&Structure{Name: name})
}
// AddStruct is called when the parser has finished parsing a struct. It adds
// the now-complete struct definition to the generator's list.
func AddStruct() {
Gen.Structs = append(Gen.Structs, *CurrentStruct)
CurrentStruct = nil
st := *CurrentStruct.pop()
Gen.Structs = append(Gen.Structs, st)
Gen.StructMap[st.Name] = len(Gen.Structs) - 1
}
// StartTypedef is called when the parser finds a typedef.
func StartTypedef() {
CurrentTypedef = &Typedef{}
}
// TODO: remove before flight
func Beacon(name string) {
fmt.Println(name)
}
// StartUnion is called by the parser when it finds a union declaraion.
func StartUnion(name string) {
name = identifierTransform(name)
@@ -384,16 +513,34 @@ func StartUnion(name string) {
// AddUnion is called by the parser when it has finished processing a union
// type. It adds the union to the generator's list and clears the CurrentUnion
// pointer.
// pointer. We handle unions by declaring an interface for the union type, and
// adding methods to each of the cases so that they satisfy the interface.
func AddUnion() {
Gen.Unions = append(Gen.Unions, *CurrentUnion)
CurrentUnion = nil
}
// StartCase is called when the parser finds a case statement within a union.
func StartCase(dvalue string) {
CurrentCase = &Case{DiscriminantVal: dvalue}
// In libvirt, the discriminant values are all C pre- processor definitions.
// Since we don't run the C pre-processor on the protocol file, they're
// still just names when we get them - we don't actually have their integer
// values. We'll use the strings to build the type names, although this is
// brittle, because we're defining a type for each of the case values, and
// that type needs a name.
caseName := dvalue
if ix := strings.LastIndexByte(caseName, '_'); ix != -1 {
caseName = caseName[ix+1:]
}
caseName = fromSnakeToCamel(caseName, true)
dv, ok := lvTypedParams[dvalue]
if ok {
dvalue = strconv.FormatUint(uint64(dv), 10)
}
CurrentCase = &Case{CaseName: caseName, DiscriminantVal: dvalue}
}
// AddCase is called when the parser finishes parsing a case.
func AddCase() {
CurrentUnion.Cases = append(CurrentUnion.Cases, *CurrentCase)
CurrentCase = nil
@@ -403,27 +550,59 @@ func AddCase() {
// The declaration will be added to any open container (such as a struct, if the
// parser is working through a struct definition.)
func AddDeclaration(identifier, itype string) {
// TODO: panic if not in a struct/union/typedef?
// fmt.Println("adding", identifier, itype)
// If the name is a reserved word, transform it so it isn't.
identifier = identifierTransform(identifier)
itype = typeTransform(itype)
decl := &Decl{Name: identifier, Type: itype}
if CurrentStruct != nil {
CurrentStruct.Members = append(CurrentStruct.Members, *decl)
decl := Decl{Name: identifier, Type: itype}
if !CurrentStruct.empty() {
st := CurrentStruct.peek()
st.Members = append(st.Members, decl)
} else if CurrentTypedef != nil {
CurrentTypedef.Name = identifier
CurrentTypedef.Type = itype
Gen.Typedefs = append(Gen.Typedefs, *CurrentTypedef)
if identifier != "string" {
// Omit recursive typedefs. These happen because we're massaging
// some of the type names.
Gen.Typedefs = append(Gen.Typedefs, *CurrentTypedef)
}
CurrentTypedef = nil
} else if CurrentCase != nil {
CurrentCase.Type = *decl
CurrentCase.Name = identifier
CurrentCase.Type = itype
} else if CurrentUnion != nil {
CurrentUnion.DiscriminantType = itype
}
}
// AddFixedArray is called by the parser to add a fixed-length array to the
// current container (struct, union, etc). Fixed-length arrays are not length-
// prefixed.
func AddFixedArray(identifier, itype, len string) {
atype := fmt.Sprintf("[%v]%v", len, itype)
AddDeclaration(identifier, atype)
}
// AddVariableArray is called by the parser to add a variable-length array.
// Variable-length arrays are prefixed with a 32-bit unsigned length, and may
// also have a maximum length specified.
func AddVariableArray(identifier, itype, len string) {
// FIXME: This ignores the length restriction, so as of now we can't check
// to make sure that we're not exceeding that restriction when we fill in
// message buffers. That may not matter, if libvirt's checking is careful
// enough. This could be handled with a map, however.
atype := fmt.Sprintf("[]%v", itype)
// Handle strings specially. In the rpcgen definition a string is specified
// as a variable-length array, either with or without a max length. We want
// these to be go strings, so we'll just remove the array specifier.
if itype == "string" {
atype = itype
}
AddDeclaration(identifier, atype)
}
func checkIdentifier(i string) string {
nn, reserved := reservedIdentifiers[i]
nn, reserved := goEquivTypes[i]
if reserved {
return nn
}

View File

@@ -26,6 +26,11 @@ import (
// eof is returned by the lexer when there's no more input.
const eof = -1
// oneRuneTokens lists the runes the lexer will consider to be tokens when it
// finds them. These are returned to the parser using the integer value of their
// runes.
var oneRuneTokens = `{}[]<>(),=;:*`
type item struct {
typ int
val string

View File

@@ -6,20 +6,82 @@
package libvirt
import (
"bytes"
"github.com/davecgh/go-xdr/xdr2"
"github.com/digitalocean/go-libvirt/internal/constants"
)
const (
VirUUIDBuflen = 16
)
// Typedefs:
{{range .Typedefs}}type {{.Name}} {{.Type}}
{{end}}
// Enums:
{{range .Enums}} type {{.Name}} {{.Type}}
{{range .Enums}}type {{.Name}} {{.Type}}
{{end}}
// Structs:
{{range .Structs}}type {{.Name}} struct {
{{range .Members}} {{.Name}} {{.Type}}
{{end -}}
}
{{end}}
// Unions:
{{range .Unions}}type {{.Name}} struct {
discriminant {{.DiscriminantType}}
{{range .Unions}}type {{.Name}} interface {
Get() interface{}
{{end -}}
}
{{range .Unions}}{{$uname := .Name}}{{range .Cases}}{{$casetype := printf "%v%v" $uname .CaseName}}
type {{$casetype}} struct {
DVal uint32
{{.Name}} {{.Type}}
}
func New{{$casetype}}(v {{.Type}}) *{{$casetype}} { return &{{$casetype}}{DVal: {{.DiscriminantVal}}, {{.Name}}: v} }
func (c *{{$casetype}}) Get() interface{} { return c.{{.Name}} }
{{end}}
{{- end}}
// Procedures:
{{range .Procs}}
func (l *Libvirt) {{.Name}}({{range $ix, $arg := .Args}}{{if $ix}}, {{end}}{{.Name}} {{.Type}}{{end}}) ({{range .Ret}}r{{.Name}} {{.Type}}, {{end}}err error) {
var buf bytes.Buffer
{{if .ArgsStruct}}
args := {{.ArgsStruct}} {
{{range .Args}} {{.Name}}: {{.Name}},
{{end}} }
buf, err = encode(&args)
if err != nil {
return
}
{{end}}
var resp <-chan response
resp, err = l.request({{.Num}}, constants.Program, &buf)
if err != nil {
return
}
r := <-resp
if r.Status != StatusOK {
err = decodeError(r.Payload)
return
}
{{if .RetStruct}}
result := {{.RetStruct}}{}
rdr := bytes.NewReader(r.Payload)
dec := xdr.NewDecoder(rdr)
_, err = dec.Decode(&result)
if err != nil {
return
}
{{range .Ret}} r{{.Name}} = result.{{.Name}}
{{end}}{{end}}
return
}
{{end}}

View File

@@ -119,7 +119,7 @@ type_specifier
| DOUBLE {$$.val = "float64"}
| BOOL {$$.val = "bool"}
| STRING {$$.val = "string"}
| OPAQUE {$$.val = "[]byte"}
| OPAQUE {$$.val = "byte"}
| enum_definition
| struct_definition
| union_definition
@@ -138,12 +138,12 @@ variable_ident
;
fixed_array_declaration
: type_specifier variable_ident '[' value ']' { AddDeclaration($2.val, $1.val) } // FIXME: Handle the max size (value)?
: type_specifier variable_ident '[' value ']' { AddFixedArray($2.val, $1.val, $4.val) }
;
variable_array_declaration
: type_specifier variable_ident '<' value '>' { AddDeclaration($2.val, $1.val) } // FIXME: Handle the max size (value)?
| type_specifier variable_ident '<' '>' { AddDeclaration($2.val, $1.val) }
: type_specifier variable_ident '<' value '>' { AddVariableArray($2.val, $1.val, $4.val) }
| type_specifier variable_ident '<' '>' { AddVariableArray($2.val, $1.val, "") }
;
pointer_declaration
@@ -151,7 +151,7 @@ pointer_declaration
;
struct_definition
: STRUCT struct_ident '{' {StartStruct($2.val)} declaration_list '}' {AddStruct()}
: STRUCT struct_ident '{' {StartStruct($2.val)} declaration_list '}' { AddStruct() }
;
struct_ident