From e000458bb1bdb25993ec19b04c70c1949c18c8fa Mon Sep 17 00:00:00 2001 From: Michael Le Date: Tue, 22 Nov 2022 14:28:01 -0800 Subject: [PATCH] Add --go-module-name flag to support generating Go module compatible code (#7651) * Add --go-module-name flag to support generating code for go modules * Rename echo example folder * Grammar * Update readme for go-echo example * Update readme for go-echo example * Re-enable go modules after test is done --- examples/go-echo/README.md | 27 +++++++++ examples/go-echo/client/client.go | 62 +++++++++++++++++++++ examples/go-echo/go.mod | 5 ++ examples/go-echo/hero.fbs | 6 ++ examples/go-echo/hero/Warrior.go | 93 +++++++++++++++++++++++++++++++ examples/go-echo/net.fbs | 11 ++++ examples/go-echo/net/Request.go | 82 +++++++++++++++++++++++++++ examples/go-echo/net/Response.go | 82 +++++++++++++++++++++++++++ examples/go-echo/server/server.go | 35 ++++++++++++ include/flatbuffers/idl.h | 3 +- src/flatc.cpp | 5 ++ src/idl_gen_go.cpp | 7 ++- tests/GoTest.sh | 3 + 13 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 examples/go-echo/README.md create mode 100644 examples/go-echo/client/client.go create mode 100644 examples/go-echo/go.mod create mode 100644 examples/go-echo/hero.fbs create mode 100644 examples/go-echo/hero/Warrior.go create mode 100644 examples/go-echo/net.fbs create mode 100644 examples/go-echo/net/Request.go create mode 100644 examples/go-echo/net/Response.go create mode 100644 examples/go-echo/server/server.go diff --git a/examples/go-echo/README.md b/examples/go-echo/README.md new file mode 100644 index 000000000..acecee7e4 --- /dev/null +++ b/examples/go-echo/README.md @@ -0,0 +1,27 @@ +# Go Echo Example + +A simple example demonstrating how to send flatbuffers over the network in Go. + +## Generate flatbuffer code + +``` +flatc -g --gen-object-api --go-module-name echo hero.fbs net.fbs +``` + +## Running example + +1. Run go mod tidy to get dependencies +``` +go mod tidy +``` + +2. Start a server +``` +go run server/server.go +``` + +3. Run the client in another terminal +``` +go run client/client.go +``` + diff --git a/examples/go-echo/client/client.go b/examples/go-echo/client/client.go new file mode 100644 index 000000000..5d2c13082 --- /dev/null +++ b/examples/go-echo/client/client.go @@ -0,0 +1,62 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + + "echo/hero" + "echo/net" + + flatbuffers "github.com/google/flatbuffers/go" +) + +func RequestBody() *bytes.Reader { + b := flatbuffers.NewBuilder(0) + r := net.RequestT{Player: &hero.WarriorT{Name: "Krull", Hp: 100}} + b.Finish(r.Pack(b)) + + // Encode builder head in last 4 bytes of request body + buf := make([]byte, 4) + flatbuffers.WriteUOffsetT(buf, b.Head()) + buf = append(b.Bytes, buf...) + + return bytes.NewReader(buf) +} + +func ReadResponse(r *http.Response) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + fmt.Printf("Unable to read request body: %v\n", err) + return + } + + // Last 4 bytes is offset. + off := flatbuffers.GetUOffsetT(body[len(body)-4:]) + buf := body[:len(body) - 4] + + res := net.GetRootAsResponse(buf, off) + player := res.Player(nil) + + fmt.Printf("Got response (name: %v, hp: %v)\n", string(player.Name()), player.Hp()) +} + +func main() { + + body := RequestBody() + req, err := http.NewRequest("POST", "http://localhost:8080/echo", body) + if err != nil { + fmt.Println(err) + return + } + + client := http.DefaultClient + resp, err := client.Do(req) + if err != nil { + fmt.Println(err) + return + } + + ReadResponse(resp) +} diff --git a/examples/go-echo/go.mod b/examples/go-echo/go.mod new file mode 100644 index 000000000..81abc9b5b --- /dev/null +++ b/examples/go-echo/go.mod @@ -0,0 +1,5 @@ +module echo + +go 1.19 + +require github.com/google/flatbuffers v22.10.26+incompatible diff --git a/examples/go-echo/hero.fbs b/examples/go-echo/hero.fbs new file mode 100644 index 000000000..91b9dcaaf --- /dev/null +++ b/examples/go-echo/hero.fbs @@ -0,0 +1,6 @@ +namespace hero; + +table Warrior { + name: string; + hp: uint32; +} diff --git a/examples/go-echo/hero/Warrior.go b/examples/go-echo/hero/Warrior.go new file mode 100644 index 000000000..857697e16 --- /dev/null +++ b/examples/go-echo/hero/Warrior.go @@ -0,0 +1,93 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package hero + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type WarriorT struct { + Name string `json:"name"` + Hp uint32 `json:"hp"` +} + +func (t *WarriorT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + if t == nil { return 0 } + nameOffset := builder.CreateString(t.Name) + WarriorStart(builder) + WarriorAddName(builder, nameOffset) + WarriorAddHp(builder, t.Hp) + return WarriorEnd(builder) +} + +func (rcv *Warrior) UnPackTo(t *WarriorT) { + t.Name = string(rcv.Name()) + t.Hp = rcv.Hp() +} + +func (rcv *Warrior) UnPack() *WarriorT { + if rcv == nil { return nil } + t := &WarriorT{} + rcv.UnPackTo(t) + return t +} + +type Warrior struct { + _tab flatbuffers.Table +} + +func GetRootAsWarrior(buf []byte, offset flatbuffers.UOffsetT) *Warrior { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &Warrior{} + x.Init(buf, n+offset) + return x +} + +func GetSizePrefixedRootAsWarrior(buf []byte, offset flatbuffers.UOffsetT) *Warrior { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &Warrior{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func (rcv *Warrior) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *Warrior) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *Warrior) Name() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *Warrior) Hp() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *Warrior) MutateHp(n uint32) bool { + return rcv._tab.MutateUint32Slot(6, n) +} + +func WarriorStart(builder *flatbuffers.Builder) { + builder.StartObject(2) +} +func WarriorAddName(builder *flatbuffers.Builder, name flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(name), 0) +} +func WarriorAddHp(builder *flatbuffers.Builder, hp uint32) { + builder.PrependUint32Slot(1, hp, 0) +} +func WarriorEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/examples/go-echo/net.fbs b/examples/go-echo/net.fbs new file mode 100644 index 000000000..7293bb699 --- /dev/null +++ b/examples/go-echo/net.fbs @@ -0,0 +1,11 @@ +include "hero.fbs"; + +namespace net; + +table Request { + player: hero.Warrior; +} + +table Response { + player: hero.Warrior; +} diff --git a/examples/go-echo/net/Request.go b/examples/go-echo/net/Request.go new file mode 100644 index 000000000..b2449c1ca --- /dev/null +++ b/examples/go-echo/net/Request.go @@ -0,0 +1,82 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package net + +import ( + flatbuffers "github.com/google/flatbuffers/go" + + hero "echo/hero" +) + +type RequestT struct { + Player *hero.WarriorT `json:"player"` +} + +func (t *RequestT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + if t == nil { return 0 } + playerOffset := t.Player.Pack(builder) + RequestStart(builder) + RequestAddPlayer(builder, playerOffset) + return RequestEnd(builder) +} + +func (rcv *Request) UnPackTo(t *RequestT) { + t.Player = rcv.Player(nil).UnPack() +} + +func (rcv *Request) UnPack() *RequestT { + if rcv == nil { return nil } + t := &RequestT{} + rcv.UnPackTo(t) + return t +} + +type Request struct { + _tab flatbuffers.Table +} + +func GetRootAsRequest(buf []byte, offset flatbuffers.UOffsetT) *Request { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &Request{} + x.Init(buf, n+offset) + return x +} + +func GetSizePrefixedRootAsRequest(buf []byte, offset flatbuffers.UOffsetT) *Request { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &Request{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func (rcv *Request) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *Request) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *Request) Player(obj *hero.Warrior) *hero.Warrior { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Indirect(o + rcv._tab.Pos) + if obj == nil { + obj = new(hero.Warrior) + } + obj.Init(rcv._tab.Bytes, x) + return obj + } + return nil +} + +func RequestStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func RequestAddPlayer(builder *flatbuffers.Builder, player flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(player), 0) +} +func RequestEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/examples/go-echo/net/Response.go b/examples/go-echo/net/Response.go new file mode 100644 index 000000000..57e6b3535 --- /dev/null +++ b/examples/go-echo/net/Response.go @@ -0,0 +1,82 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package net + +import ( + flatbuffers "github.com/google/flatbuffers/go" + + hero "echo/hero" +) + +type ResponseT struct { + Player *hero.WarriorT `json:"player"` +} + +func (t *ResponseT) Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + if t == nil { return 0 } + playerOffset := t.Player.Pack(builder) + ResponseStart(builder) + ResponseAddPlayer(builder, playerOffset) + return ResponseEnd(builder) +} + +func (rcv *Response) UnPackTo(t *ResponseT) { + t.Player = rcv.Player(nil).UnPack() +} + +func (rcv *Response) UnPack() *ResponseT { + if rcv == nil { return nil } + t := &ResponseT{} + rcv.UnPackTo(t) + return t +} + +type Response struct { + _tab flatbuffers.Table +} + +func GetRootAsResponse(buf []byte, offset flatbuffers.UOffsetT) *Response { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &Response{} + x.Init(buf, n+offset) + return x +} + +func GetSizePrefixedRootAsResponse(buf []byte, offset flatbuffers.UOffsetT) *Response { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &Response{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func (rcv *Response) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *Response) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *Response) Player(obj *hero.Warrior) *hero.Warrior { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Indirect(o + rcv._tab.Pos) + if obj == nil { + obj = new(hero.Warrior) + } + obj.Init(rcv._tab.Bytes, x) + return obj + } + return nil +} + +func ResponseStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func ResponseAddPlayer(builder *flatbuffers.Builder, player flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(player), 0) +} +func ResponseEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/examples/go-echo/server/server.go b/examples/go-echo/server/server.go new file mode 100644 index 000000000..46ff9e108 --- /dev/null +++ b/examples/go-echo/server/server.go @@ -0,0 +1,35 @@ +package main + +import ( + "echo/net" + "fmt" + "io/ioutil" + "net/http" + + flatbuffers "github.com/google/flatbuffers/go" +) + +func echo(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + fmt.Printf("Unable to read request body: %v\n", err) + return + } + + // Last 4 bytes is offset. See client.go. + off := flatbuffers.GetUOffsetT(body[len(body)-4:]) + buf := body[:len(body) - 4] + + req := net.GetRootAsRequest(buf, off) + player := req.Player(nil) + + fmt.Printf("Got request (name: %v, hp: %v)\n", string(player.Name()), player.Hp()) + w.Write(body) +} + +func main() { + http.HandleFunc("/echo", echo) + + fmt.Println("Listening on port :8080") + http.ListenAndServe(":8080", nil) +} diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 19260e77b..a07f62a30 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -623,6 +623,7 @@ struct IDLOptions { bool binary_schema_gen_embed; std::string go_import; std::string go_namespace; + std::string go_module_name; bool protobuf_ascii_alike; bool size_prefixed; std::string root_type; @@ -915,7 +916,7 @@ class Parser : public ParserState { // Returns the number of characters were consumed when parsing a JSON string. std::ptrdiff_t BytesConsumed() const; - + // Set the root type. May override the one set in the schema. bool SetRootType(const char *name); diff --git a/src/flatc.cpp b/src/flatc.cpp index deb45ec88..998d77b8f 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -152,6 +152,8 @@ const static FlatCOption options[] = { { "", "go-import", "IMPORT", "Generate the overriding import for flatbuffers in Golang (default is " "\"github.com/google/flatbuffers/go\")." }, + { "", "go-module-name", "", + "Prefix local import paths of generated go code with the module name" }, { "", "raw-binary", "", "Allow binaries without file_identifier to be read. This may crash flatc " "given a mismatched schema." }, @@ -448,6 +450,9 @@ int FlatCompiler::Compile(int argc, const char **argv) { } else if (arg == "--go-import") { if (++argi >= argc) Error("missing golang import" + arg, true); opts.go_import = argv[argi]; + } else if (arg == "--go-module-name") { + if (++argi >= argc) Error("missing golang module name" + arg, true); + opts.go_module_name = argv[argi]; } else if (arg == "--defaults-json") { opts.output_default_scalars_in_json = true; } else if (arg == "--unknown-json") { diff --git a/src/idl_gen_go.cpp b/src/idl_gen_go.cpp index 54e886458..650450fb6 100644 --- a/src/idl_gen_go.cpp +++ b/src/idl_gen_go.cpp @@ -1532,7 +1532,12 @@ class GoGenerator : public BaseGenerator { // Create the full path for the imported namespace (format: A/B/C). std::string NamespaceImportPath(const Namespace *ns) const { - return namer_.Directories(*ns, SkipDir::OutputPathAndTrailingPathSeparator); + std::string path = + namer_.Directories(*ns, SkipDir::OutputPathAndTrailingPathSeparator); + if (!parser_.opts.go_module_name.empty()) { + path = parser_.opts.go_module_name + "/" + path; + } + return path; } // Ensure that a type is prefixed with its go package import name if it is diff --git a/tests/GoTest.sh b/tests/GoTest.sh index 8e73af241..b55cad577 100755 --- a/tests/GoTest.sh +++ b/tests/GoTest.sh @@ -72,3 +72,6 @@ if [[ ${NOT_FMT_FILES} != "" ]]; then # enable this when enums are properly formated # exit 1 fi + +# Re-enable go modules when done tests +go env -w GO111MODULE=on