【问题标题】:How to use a package of generated protobuf inside a go module?如何在 go 模块中使用生成的 protobuf 包?
【发布时间】:2021-09-08 03:26:13
【问题描述】:

我遇到了 Go 模块管理和生成 protobuffers 的问题(使用 go1.16,protoc-gen-go@latest)。

我有这个项目结构:

subproj
├── go.mod         (module company.tld/proj/subproj)
├── subproj.go     (entry point : package main)
├── proto          (folder containing .proto files)
├── packageFolder
|   └── file1.go   (package packageFolder)
└── Makefile       (used to generate *.pb.go and build subproj binary)

proto 文件夹被其他项目使用(显然...)(通过 git 子模块)。
原型如下:

syntax = "proto3"
option csharp_namespace = "Proj.Proto";
option go_package = "company.tld/proj/projpb";
package entity.proj
...

由于消息版本不同,很少有 protobuffer 文件需要在另一个“命名空间”中:

option go_package = "company.tld/proj/projpb/other";
package entity.proj.other

在我的 Makefile 中,我尝试在正确的位置生成正确的 *.pb.go:

# Proto sources
PROTO= $(wildcard ${PROTODIR}/*.proto)
PBGO=  $(PROTO:.proto=.pb.go)

MODULE_NAME=company.tld/proj
GO_OPT_FLAG=   --go_opt=module=${MODULE_NAME}     
GRPC_OPT_FLAG= --go-grpc_opt=module=${MODULE_NAME}
#GO_OPT_FLAG=   --go_opt=paths=import
#GRPC_OPT_FLAG= --go-grpc_opt=paths=import

.PHONY: clean install proto

## Builds the project
build: proto
    go build ${LDFLAGS} -o ${BINARY}

$(PROTOBUF_GO_PLUGIN):
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

$(GRPC_GO_PLUGIN):
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

%.pb.go: %.proto | $(PROTOBUF_GO_PLUGIN) $(GRPC_GO_PLUGIN)
    protoc --proto_path=${PROTODIR} --go_out=. ${GO_OPT_FLAG} --go-grpc_out=. ${GRPC_OPT_FLAG} $<

proto: $(PBGO)

因此,取决于 protoc 编译器使用的选项:
→ 与--go_opt=paths=import

文件夹树company.tld/proj/projpb由项目根目录的protoc创建。每个对象都在一个名为 projpbother 的包中,在子包 other 中。

生成的 Proto 对象,包括 other 命名空间-d 对象,具有导入路径 import other "company.tld/proj/projpb/other"(由 go_package 选项带来,但这是错误的,因为它不是现有模块 - go mod tidy/vendor 抱怨找不到它)。

普通项目文件需要以下导入路径才能到达生成的 Proto 对象:
import pb "company.tld/proj/subproj/company.tld/proj/projpb"
这看起来很奇怪,而且不是正确的做法。

→ 与--go_opt=module=company.tld/proj

一个文件夹projpb由protoc在项目的根目录下创建,每个生成的.pb.go都有包projpbother,在其他的子包。

生成的 Proto 对象,包括 other 命名空间-d 对象,仍然具有导入路径 import other "company.tld/proj/projpb/other"(仍然由 go_package 选项带来并且仍然是错误的,因为这仍然是一个不存在的模块 - 这些是生成的文件......我为什么要创建这些模块?)。

很酷的是,有了这个 go_opt,使用
import pb "company.tld/proj/subproj/projpb" 访问生成的类型看起来更加正常。

最后,我尝试了

  • 在 .proto 文件中的 go_package 选项上使用本地导入路径(在构建时被拒绝,因为在生成的 protobuffer 对象中会有一个import other "./projpb/other"
  • 在 go.mod 文件中使用 replace 指令,如下所示:
replace (
    company.tld/proj/projpb => ./projpb
    company.tld/proj/projpb/other => ./projpb/other
)

(但是 go mod tidy/vendor 抱怨在生成的文件夹 ./projpb 中找不到 go.mod 文件)

有人遇到过类似的问题吗?或者我是否缺少一个命令选项来告诉 Go,«我在一个包中生成 protobuffer 对象,或者在一个包中生成包,我只是想使用它们。它们不是模块,所以请提供生成对象的正确导入路径,并让我在代码中使用它们»。


[更新 01]
我尝试了go_opt=paths=source_relative(灵感来自ticket)。
我在Makefile中创建了文件夹,protoc在里面生成文件。
备注:

  • 生成的 protos 使用通过 go_package 选项指定的完整路径来相互关联。
  • 只要go_package 选项需要完整路径,Go (go mod tidy/vendor) 就会在创建的文件夹中搜索 go.mod 文件,其中包含生成的原型。

告诉 Go 我不是在寻找模块,但仍然满足 go_package 选项在 protobuffer 文件中的完整路径约束的正确方法是什么?

【问题讨论】:

    标签: go protocol-buffers go-modules protoc


    【解决方案1】:

    在多次更改 proto 文件中的 go_package 选项后,更改 protoc 编译器命令上的 go_opt,这是我发现使用生成的 protobuffers 编译项目的唯一方法,尊重每个 Go 约束, 通过即时创建 go.mod 文件...

    final proto «header»(尊重 go_package 选项中的完整输入)

    syntax = "proto3";
    option csharp_namespace = "Proj.Proto";
    option go_package = "company.tld/proj/projpb";
    // or for subpackages...
    option csharp_namespace = "Proj.Proto.Other";
    option go_package = "company.tld/proj/projpb/other";
    

    我的 Makefile(为生成的 proto 文件创建一个 go.mod 文件)

    
    # Proto sources
    PROTO= $(shell find ${PROTODIR} -type f -name '*.proto')
    PBGO=  $(PROTO:.proto=.pb.go)
    
    DEST_DIR=.
    MODULE_NAME=company.tld/proj
    GO_OPT_FLAG=   --go_opt=module=${MODULE_NAME}
    GRPC_OPT_FLAG= --go-grpc_opt=module=${MODULE_NAME}
    
    PROTO_PKG_DIR=projpb
    PROTO_MODULE_NAME=${MODULE_NAME}/${PROTO_PKG_DIR}
    PROTO_GOMOD_FILE=${PROTO_PKG_DIR}/go.mod
    
    .PHONY: clean install proto gomod
    
    build: proto gomod
        go build ${LDFLAGS} -o ${BINARY}
    
    %.pb.go: %.proto | $(PROTOBUF_GO_PLUGIN) $(GRPC_GO_PLUGIN) $(DEST_DIR)
        ${PROTOC} --proto_path=${PROTODIR} --go_out=${DEST_DIR} ${GO_OPT_FLAG} --go-grpc_out=${DEST_DIR} ${GRPC_OPT_FLAG} $<
    
    proto: $(PBGO)
    
    gomod: ${PROTO_GOMOD_FILE}
    
    ${PROTO_GOMOD_FILE}:
        cd ${PROTO_PKG_DIR} && go mod init ${PROTO_MODULE_NAME} && cd ..
    

    我的主 go.mod 文件(将即时创建的模块重定向到项目范围内的本地文件夹)

    module company.tld/proj/subproj
    
    go 1.16
    
    require (
        // ...
        company.tld/proj/projpb v0.0.0
    )
    
    replace company.tld/proj/projpb v0.0.0 => ./projpb
    

    感谢replace 指令,go mod tidy/vendor 很高兴,不要尝试在远程存储库中搜索模块。
    生成的 *.pb.go 文件具有正确的导入路径:company.tld/proj/projpb(以及子包的company.tld/proj/projpb/other)。
    使用生成的原型的导入语句在主项目中运行良好。

    我希望有一个更简单、更漂亮的解决方案,但是唉......

    抱歉,感谢那些考虑过的人!

    【讨论】:

    • blog.golang.org/generate 中给出了更简单和更漂亮的解决方案:“[I]如果包含的包打算由go get 导入,则一旦生成(并测试!)文件,它必须是签入源代码存储库以供客户使用。”因此,此处更具可扩展性的解决方案是发布包含生成代码的实际 company.tld/proj/projpb,或者将生成的导入路径更改为在 company.tld/proj/subproj 模块内并在那里提交生成的代码。
    • @bcmills 感谢您的评论!你的观点很有趣,但它迫使我将生成的文件推送到使用 proto 文件的存储库中(如果我理解得很好......)。每次更改原型时,团队都必须在每个项目上循环并提交“更新生成的原型类”,这不是很方便。从我的角度来看,如果我没有强制在 go_package 选项中使用完整的导入路径,我可以生成并使用来自本地包的文件(无需输入 Go 模块的复杂性)。跨度>
    • 如果我没记错的话,github.com/golang/protobuf/proto API 要求每个 proto 文件只存在一个包。对我来说,这意味着存在一个单一的模块 somewhere - 可能是包含 .pb.go 文件的那个 - 它为该版本的 .proto 文件提供规范的生成代码。因此,每次更改 .proto 文件时,提交对该文件的更改的人也需要重新生成并提交 .pb.go 文件。
    • 哦,对不起! “团队必须围绕每个项目进行循环”,我的意思是使用不同语言的项目(Go、C++、C#、NodeJS,到目前为止......),但共享相同的 protobuffers。所以,是的,你说的对,API 每个 proto 文件只需要一个包,如果我们只有 Go 项目,那将是一个公平的解决方案。在我的例子中,proto 在构建每个项目(Go,C++ ...)之前自动重新生成,避免创建一个“模块”等价物,包含每种语言生成的文件。我们只需要在 proto repo/git-submodule 中指向正确的提交。
    猜你喜欢
    • 2021-04-24
    • 2019-12-13
    • 1970-01-01
    • 2023-02-14
    • 1970-01-01
    • 1970-01-01
    • 2019-04-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多