英文:
Trying to write shared protobuf definitions
问题
我正在尝试在共享的Go模块中创建共享的protobuf定义,但是运气不太好。具体来说:
- 我想在模块protos中有一个包含共享定义的proto文件。
- 模块protos中的其他proto文件将引用这些共享定义。
- 模块protos-use将通过go.mod引用模块protos。
- 模块protos-use的Go代码将导入并使用模块protos中的定义。
- 这两个模块都存储在github.com上。
我无法使各种包名称对齐。无论我使用什么,都会出现某种错误。目前,模块protos没有任何错误,但是在模块protos-use上运行go mod tidy会失败。
模块protos位于https://github.com/rob-woerner/protos。
模块protos-use位于https://github.com/rob-woerner/protos-use。
我还没有找到解决这种情况的示例。如果我简单地在各处复制共享的proto定义,就可以使其工作。最近的错误是:
$ go mod tidy
go: finding module for package github.com/rob-woerner/protos/messages
go: finding module for package github.com/rob-woerner/protos/common
github.com/rob-woerner/protos-use/code imports
github.com/rob-woerner/protos/common: module github.com/rob-woerner/protos@latest found (XXX), but does not contain package github.com/rob-woerner/protos/common
github.com/rob-woerner/protos-use/code imports
github.com/rob-woerner/protos/messages: module github.com/rob-woerner/protos@latest found (XXX), but does not contain package github.com/rob-woerner/protos/messages
但是我强调,我会得到很多不同的错误,这取决于需要包名称的十几个位置使用了什么包名称。
如果有人知道一个可行的示例,或者如果不支持这种情况,请告诉我。
为了简洁起见,省略了代码片段。
模块protos:
go.mod
Makefile
protobuf:
common.proto
messages.proto
pb:
common.pb.go
messages.pb.go
模块protos-use:
go.mod
code:
project.go
模块protos:
go.mod:
module protos
go 1.16
require google.golang.org/protobuf v1.27.1
protobuf/common.proto:
package common;
option go_package = "github.com/rob-woerner/protos";
enum MyState { LOGGED_OUT = 0; }
protobuf/messages.proto:
import "common.proto";
package messages;
option go_package = "github.com/rob-woerner/protos";
message MyRequest { common.MyState state = 1; }
Makefile:
protos:
protoc --proto_path=protobuf --go_out=plugins=grpc:pb --go_opt=module=github.com/rob-woerner/protos protobuf/*.proto
模块protos-use:
go.mod:
module github.com/rob-woerner/protos-use
go 1.16
require (
github.com/rob-woerner/protos latest
)
code/project.go:
package code
import (
"github.com/rob-woerner/protos/common"
"github.com/rob-woerner/protos/messages"
)
func MyFunc() messages.MyMessage {
return messages.MyMessage { state: common.MyState_LOGGED_OUT}
}
英文:
I'm trying to create shared protobuf definitions in a shared go module, but I'm not having much luck. Specifically:<br/>
- I want a proto file in module protos that contains shared definitions.<br/>
- Other proto files in module protos would reference the shared definitions.<br/>
- Module protos-use would reference module protos (via go.mod)<br/>
- Module protos-use go code would import and use definitions from module protos<br/>
- Both modules are stored in github.com
I'm unable to get the various package names to align. No matter what I use, something fails. Currently, module protos doesn't have any errors, but go mod tidy on module protos-use fails.
Module protos is at https://github.com/rob-woerner/protos<br/>
Module protos-use is at https://github.com/rob-woerner/protos-use
I haven't found an example that addresses this scenario. If I simply duplicate the shared proto definitions everywhere, I can get it to work. The most recent error is:<br/>
$ go mod tidy<br/>
go: finding module for package github.com/rob-woerner/protos/messages<br/>
go: finding module for package github.com/rob-woerner/protos/common<br/>
github.com/rob-woerner/protos-use/code imports<br/>
github.com/rob-woerner/protos/common: module github.com/rob-woerner/protos@latest found (XXX), but does not contain package github.com/rob-woerner/protos/common<br/>
github.com/rob-woerner/protos-use/code imports<br/>
github.com/rob-woerner/protos/messages: module github.com/rob-woerner/protos@latest found (XXX), but does not contain package github.com/rob-woerner/protos/messages<br/>
But I emphasize that I get lots of different errors, depending on what package names I use in the dozen or so places that need package names.
If anyone knows of a working example, or if this is not supported, I'd appreciate it.
Code snippets are elided for compactness
Module protos:
go.mod<br/>
Makefile<br/>
protobuf:<br/>
common.proto<br/>
messages.proto<br/>
pb:<br/>
common.pb.go<br/>
messages.pb.go<br/>
Module protos-use:<br/>
go.mod<br/>
code:<br/>
project.go<br/>
Module protos:
go.mod:<br/><br/>
module protos<br/>
go 1.16<br/>
require google.golang.org/protobuf v1.27.1<br/>
protobuf/common.proto:<br/>
package common;<br/>
option go_package = "github.com/rob-woerner/protos";<br/>
enum MyState { LOGGED_OUT = 0; }<br/>
protobuf/messages.proto:<br/>
import "common.proto";<br/>
package messages;<br/>
option go_package = "github.com/rob-woerner/protos";<br/>
message MyRequest { common.MyState state = 1; }<br/>
Makefile:<br/>
protos:<br/>
protoc --proto_path=protobuf --go_out=plugins=grpc:pb --go_opt=module=github.com/rob-woerner/protos protobuf/*.proto<br/>
Module protos-use:
go.mod:<br/>
module github.com/rob-woerner/protos-use<br/>
go 1.16<br/>
require (<br/>
github.com/rob-woerner/protos latest<br/>
)<br/>
code/project.go:<br/>
package code<br/>
import (<br/>
"github.com/rob-woerner/protos/common"<br/>
"github.com/rob-woerner/protos/messages"<br/>
)<br/>
func MyFunc() messages.MyMessage {<br/>
return messages.MyMessage { state: common.MyState_LOGGED_OUT}<br/>
}<br/>
答案1
得分: 2
这在使用protobuf时确实具有挑战性,并且文档记录不完善。
我感觉其中一部分复杂性在于protobuf的维护者试图解决可能使用的各种运行时和包的问题。
如果我正确理解了你的问题,根据你的情况,protos-use
遇到困难是因为code/product.go
应该是这样的:
import (
"github.com/rob-woerner/protos/pb/common" // /pb/
...
)
我有一个可行的机制,我将尝试将其应用到你的机制上。我认为对common
的引用不是你的问题,而是整体结构和模块与包的导入:
github.com/foo/protos
github.com/bar/app
在foo/protos
中,我在根目录下有*.proto
文件(但这只是一种约定,并不一定是必须的):
example.proto
syntax = "proto3";
package baz;
option go_package = "github.com/foo/protos;baz";
service Some {...}
注意
;baz
描述了生成代码的最终Go包名称(baz
)。它不必与仓库名称(protos
)匹配,我不希望这样,因为(见下文),我将生成的代码放在一个非根目录(/some/baz
)中。
然后:
MODULE="github.com/foo/protos"
protoc \
--include_imports \
--include_source_info \
--proto_path=. \
--descriptor_set_out=foo.pb \
--go_out=./some/baz \
--go_opt=module=${MODULE} \
--go-grpc_out=./some/baz \
--go-grpc_opt=module=${MODULE} \
./*.proto
注意 这将给我们一个包名为
github.com/foo/protos/some/baz
的包。
这将产生以下结果:
.
├── example.proto
├── some
│ └── baz
│ ├── example_grpc.pb.go
│ └── example.pb.go
├── go.mod
├── go.sum
└── protoc-3.17.3-linux-x86_64
然后,在bar/app
中,我有:
go.mod
:
module github.com/bar/app
go 1.16
require (
github.com/foo/protos v0.0.1
...
)
并且,在该模块中,我为导入进行了别名:
package main
import (
...
pb "github.com/foo/protos/some/baz"
)
func main() {
...
pb.RegisterSomeServer(grpcServer, ...)
更新
注意 回应评论线程
common
和messages
可以在一个包中或者两个包中。
我们来做两个包:
common.proto
:
syntax = "proto3";
package common;
option go_package = "github.com/rob-woerner/protos/common;common";
enum MyState { LOGGED_OUT = 0; }
messages.proto
:
syntax = "proto3";
package messages;
import "common.proto";
option go_package = "github.com/rob-woerner/protos/messages;messages";
message MyRequest { common.MyState state = 1; }
注意 因为我们想要两个包,我们需要给protos(!)唯一的
package
、go_package
名称./protos/common;common
。虽然我们可以使用./protos/common;freddie
,但问题是路径必须是唯一的,以提供唯一的包。
然后:
MODULE="github.com/rob-woerner/protos"
protoc \
--proto_path=. \
--go_out=./some \
--go_opt=module=${MODULE} \
--go-grpc_out=./some \
--go-grpc_opt=module=${MODULE}
注意 这里使用
some
仅仅是为了确保生成的代码最终聚合在某个包(some
)下面,而不是仓库(==模块)的根目录。如果你希望包直接位于模块根目录下,可以删除/some
。
结果为:
some
├── common
│ └── common.pb.go
└── messages
└── messages.pb.go
注意
some
成为根包github.com/rob-woerner/protos/some
,common.pb.go
位于子包github.com/rob-woerner/protos/some/common
中。
现在,我们可以:
main.go
:
package main
import (
"github.com/rob-woerner/protos/some/common"
"github.com/rob-woerner/protos/some/messages"
)
func main() {
rqst := messages.MyRequest{
state: common.MyState_LOGGED_OUT,
}
// 对`rqst`做一些操作
}
如果你希望common
和messages
在同一个包中,那么你可以简单地:
package protos;
option go_package = "github.com/rob-woerner/protos;protos";
然后:
message MyRequest { MyState state = 1; } // 现在在同一个包中
然后:
MODULE="github.com/rob-woerner/protos"
protoc \
--proto_path=. \
--go_out=./protos \
--go_opt=module=${MODULE} \
--go-grpc_out=./protos \
--go-grpc_opt=module=${MODULE}
注意 在这种情况下,我们需要
_out=./protos
,以便将生成的文件放在正确的包(protos
)下面。
main.go
:
package main
import (
pb "github.com/rob-woerner/protos"
)
func main() {
rqst := pb.MyRequest{
state: pb.MyState_LOGGED_OUT,
}
// 对`rqst`做一些操作
}
哇!我说过这很具有挑战性;-)
英文:
This is challenging with protobufs and is poorly documented.
Part of the complexity -- I sense -- is that the protobuf maintainers are trying to solve for the myriad runtimes and packages that may be used.
If I understand your problem correctly, with what you have, protos-use
is struggling because code/product.go
should:
import (
"github.com/rob-woerner/protos/pb/common" // /pb/
...
)
I've got a working mechanism that I'll try to apply to your mechanism. I think the references to common
aren't your issue but the overall structure and modules vs. packages imports:
github.com/foo/protos
github.com/bar/app
In foo/protos
, I have *.proto
files in the root (but this is just a convention and need not be the case):
example.proto
syntax = "proto3";
package baz;
option go_package = "github.com/foo/protos;baz";
service Some {...}
> NOTE The ;baz
this describe the eventual Go package name (baz
) of the generated code. It need not match the the repo name (protos
) and I don't want this because (see below), I'm putting the generated code in a non-root directory (/some/baz
)
Then:
MODULE="github.com/foo/protos"
protoc \
--include_imports \
--include_source_info \
--proto_path=. \
--descriptor_set_out=foo.pb \
--go_out=./some/baz \
--go_opt=module=${MODULE} \
--go-grpc_out=./some/baz \
--go-grpc_opt=module=${MODULE} \
./*.proto
> NOTE This will give us a package name of github.com/foo/protos/some/baz
This will yield:
.
├── example.proto
├── some
│   └── baz
│   ├── example_grpc.pb.go
│   └── example.pb.go
├── go.mod
├── go.sum
└── protoc-3.17.3-linux-x86_64
Then, from bar/app
, I have:
go.mod
:
module github.com/bar/app
go 1.16
require (
github.com/foo/protos v0.0.1
...
)
And, within that module, I alias the imports:
package main
import (
...
pb "github.com/foo/protos/some/baz"
)
func main() {
...
pb.RegisterSomeServer(grpcServer, ...)
Update
> NOTE Responding to the comment thread
common
and messages
are either in one package or in two.
Let's do two:
common.proto
:
syntax = "proto3";
package common;
option go_package = "github.com/rob-woerner/protos/common;common";
enum MyState { LOGGED_OUT = 0; }
messages.proto
:
syntax = "proto3";
package messages;
import "common.proto";
option go_package = "github.com/rob-woerner/protos/messages;messages";
message MyRequest { common.MyState state = 1; }
> NOTE Because we want 2 packages, we need to give the protos (!) unique package
, go_package
names ./protos/common;common
. Although we could have ./protos/common;freddie
, the issue is that the path must be unique to give a unique package.
Then:
MODULE="github.com/rob-woerner/protos"
protoc \
--proto_path=. \
--go_out=./some \
--go_opt=module=${MODULE} \
--go-grpc_out=./some \
--go-grpc_opt=module=${MODULE}
> NOTE The use of some
here is solely to ensure that the generated code ends up aggregated by some package (some
) below the repo(==module) root. You can remove /some
if you want the packages directly beneath the module root.
yields:
some
├── common
│   └── common.pb.go
└── messages
└── messages.pb.go
> NOTE some
becomes the root package github.com/rob-woerner/protos/some
and common.pb.go
is in the sub-package github.com/rob-woerner/protos/some/common
So, we can now:
main.go
:
package main
import (
"github.com/rob-woerner/protos/some/common"
"github.com/rob-woerner/protos/some/messages"
)
func main() {
rqst := messages.MyRequest{
state: common.MyState_LOGGED_OUT,
}
// do something with `rqst`
}
If you want common
and messages
to be in the same package, then you can just:
package protos;
option go_package = "github.com/rob-woerner/protos;protos`;
And:
message MyRequest { MyState state = 1; } // Now in same package
And then:
MODULE="github.com/rob-woerner/protos"
protoc \
--proto_path=. \
--go_out=./protos \
--go_opt=module=${MODULE} \
--go-grpc_out=./protos \
--go-grpc_opt=module=${MODULE}
> NOTE In this case, we need _out=./protos
so that the generated files get put under the correct package (protos
main.go
:
package main
import (
pb "github.com/rob-woerner/protos"
)
func main() {
rqst := pb.MyRequest{
state: pb.MyState_LOGGED_OUT,
}
// do something with `rqst`
}
Phew! I said it is challenging
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论