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
diff --git a/examples/go-echo/README.md b/examples/go-echo/README.md
new file mode 100644
index 0000000..acecee7
--- /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 0000000..5d2c130
--- /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 0000000..81abc9b
--- /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 0000000..91b9dca
--- /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 0000000..857697e
--- /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 0000000..7293bb6
--- /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 0000000..b2449c1
--- /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 0000000..57e6b35
--- /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 0000000..46ff9e1
--- /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 19260e7..a07f62a 100644
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
@@ -623,6 +623,7 @@
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 @@
// 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 deb45ec..998d77b 100644
--- a/src/flatc.cpp
+++ b/src/flatc.cpp
@@ -152,6 +152,8 @@
{ "", "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 @@
} 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 54e8864..650450f 100644
--- a/src/idl_gen_go.cpp
+++ b/src/idl_gen_go.cpp
@@ -1532,7 +1532,12 @@
// 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 8e73af2..b55cad5 100755
--- a/tests/GoTest.sh
+++ b/tests/GoTest.sh
@@ -72,3 +72,6 @@
# enable this when enums are properly formated
# exit 1
fi
+
+# Re-enable go modules when done tests
+go env -w GO111MODULE=on