英文:
Import protobuf file from GitHub repository
问题
我目前有两个protobuf仓库:api
和timestamp
:
timestamp仓库:
- README.md
- timestamp.proto
- timestamp.pb.go
- go.mod
- go.sum
api仓库:
- README.md
- protos/
- dto1.proto
- dto2.proto
目前,timestamp
仓库包含一个我想在api
中使用的时间戳对象的引用,但我不确定导入应该如何工作,或者我应该如何修改编译过程来处理这个问题。复杂化这个过程的是,api
仓库被编译为一个名为api-go
的独立的下游Go仓库。
例如,考虑dto1.proto
:
syntax = "proto3";
package api.data;
import "<这里应该填什么?>";
option go_package = "github.com/my-user/api/data"; // golang
message DTO1 {
string id = 1;
Timestamp timestamp = 2;
}
我的编译命令是这样的:
find $GEN_PROTO_DIR -type f -name "*.proto" -exec protoc \
--go_out=$GEN_OUT_DIR --go_opt=module=github.com/my-user/api-go \
--go-grpc_out=$GEN_OUT_DIR --go-grpc_opt=module=github.com/my-user/api-go \
--grpc-gateway_out=$GEN_OUT_DIR --grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt paths=source_relative --grpc-gateway_opt
generate_unbound_methods=true \{} \;
假设我在timestamp
中为我想将api
编译成的每种编程语言都有一个定义,我应该如何将其导入到.proto
文件中,并且我应该做什么来确保导入在我的下游仓库中不会出错?
英文:
I currently have two protobuf repos: api
and timestamp
:
timestamp Repo:
- README.md
- timestamp.proto
- timestamp.pb.go
- go.mod
- go.sum
api Repo:
- README.md
- protos/
- dto1.proto
- dto2.proto
Currently, timestamp
contains a reference to a timestamp object that I want to use in api
but I'm not sure how the import should work or how I should modify the compilation process to handle this. Complicating this process is the fact that the api
repo is compiled to a separate, downstream repo for Go called api-go
.
For example, consider dto1.proto
:
syntax = "proto3";
package api.data;
import "<WHAT GOES HERE?>";
option go_package = "github.com/my-user/api/data"; // golang
message DTO1 {
string id = 1;
Timestamp timestamp = 2;
}
And my compilation command is this:
find $GEN_PROTO_DIR -type f -name "*.proto" -exec protoc \
--go_out=$GEN_OUT_DIR --go_opt=module=github.com/my-user/api-go \
--go-grpc_out=$GEN_OUT_DIR --go-grpc_opt=module=github.com/my-user/api-go \
--grpc-gateway_out=$GEN_OUT_DIR --grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt paths=source_relative --grpc-gateway_opt
generate_unbound_methods=true \{} \;
Assuming I have a definition in timestamp
for each of the programming languages I want to compile api
into, how would I import this into the .proto
file and what should I do to ensure that the import doesn't break in my downstream repo?
答案1
得分: 5
protobuf没有本地远程导入路径的概念。因此,导入路径必须相对于某个指定的本地文件系统基路径(通过-I
/ --proto_path
指定)。
选项1
通常,最简单的方法是在组织中只有一个包含protobuf定义的存储库,例如名为acme-contract
的存储库。
.
└── protos
└── acme
├── api
│ └── data
│ ├── dto1.proto
│ └── dto2.proto
└── timestamp
└── timestamp.proto
你的dto1.proto文件将类似于以下内容:
syntax = "proto3";
package acme.api.data;
import "acme/timestamp/timestamp.proto";
message DTO1 {
string id = 1;
acme.timestamp.Timestamp timestamp = 2;
}
只要你生成的代码相对于该存储库的protos/
目录,就不会有问题。
选项2
还有其他一些选择,你可以继续将定义拆分到不同的存储库中,但你无法摆脱导入路径与文件系统的关系。
在过去,可以通过手动克隆各个存储库并安排目录以使路径相对,或者使用-I
指定各个位置(例如$GOPATH)来处理。这些策略往往会变得非常混乱且难以维护。
现在,使用buf可以使事情变得更加容易。如果你有一个名为timestamp
的存储库:
.
├── buf.gen.yaml
├── buf.work.yaml
├── gen
│ └── acme
│ └── timestamp
│ └── timestamp.pb.go
├── go.mod
├── go.sum
└── protos
├── acme
│ └── timestamp
│ └── timestamp.proto
├── buf.lock
└── buf.yaml
其中timestamp.proto
的内容如下:
syntax = "proto3";
package acme.timestamp;
option go_package = "github.com/my-user/timestamp/gen/acme/timestamp";
message Timestamp {
int64 unix = 1;
}
buf.gen.yaml
的内容如下:
version: v1
plugins:
- name: go
out: gen
opt: paths=source_relative
- name: go-grpc
out: gen
opt:
- paths=source_relative
- require_unimplemented_servers=false
- name: grpc-gateway
out: gen
opt:
- paths=source_relative
- generate_unbound_methods=true
...并且通过buf generate
生成了gen/
目录下的所有内容。
然后在你的api
存储库中:
.
├── buf.gen.yaml
├── buf.work.yaml
├── gen
│ └── acme
│ └── api
│ └── data
│ ├── dto1.pb.go
│ └── dto2.pb.go
└── protos
├── acme
│ └── api
│ └── data
│ ├── dto1.proto
│ └── dto2.proto
├── buf.lock
└── buf.yaml
buf.yaml
的内容如下:
version: v1
name: buf.build/your-user/api
deps:
- buf.build/your-user/timestamp
breaking:
use:
- FILE
lint:
use:
- DEFAULT
dto1.proto
的内容如下:
syntax = "proto3";
package acme.api.data;
import "acme/timestamp/timestamp.proto";
option go_package = "github.com/your-user/api/gen/acme/api/data";
message DTO1 {
string id = 1;
acme.timestamp.Timestamp timestamp = 2;
}
buf.gen.yaml
与timestamp
存储库中的内容相同。
通过buf generate
生成的代码将通过Go模块依赖于timestamp
存储库:
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: acme/api/data/dto1.proto
package data
import (
timestamp "github.com/your-user/timestamp/gen/acme/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// <snip>
请注意,如果对依赖项进行更改,你需要确保buf
和Go模块保持相对同步。
选项3
如果你不想使用Go模块来导入生成的pb代码,你还可以考虑类似于"选项2"的设置,但是将所有代码生成到单独的存储库中(听起来你现在正在做的事情类似)。这最容易通过使用buf
的管理模式来实现,这将使其不需要并且忽略任何go_modules
指令。
在api-go
存储库中:
.
├── buf.gen.yaml
├── go.mod
└── go.sum
buf.gen.yaml
包含以下内容:
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/your-user/api-go/gen
plugins:
- name: go
out: gen
opt: paths=source_relative
- name: go-grpc
out: gen
opt:
- paths=source_relative
- require_unimplemented_servers=false
- name: grpc-gateway
out: gen
opt:
- paths=source_relative
- generate_unbound_methods=true
然后,你需要为每个相应的存储库生成代码(推送到BSR):
$ buf generate buf.build/your-user/api
$ buf generate buf.build/your-user/timestamp
之后,你应该会在两个存储库中都有一些生成的代码:
.
├── buf.gen.yaml
├── gen
│ └── acme
│ ├── api
│ │ └── data
│ │ ├── dto1.pb.go
│ │ └── dto2.pb.go
│ └── timestamp
│ └── timestamp.pb.go
├── go.mod
└── go.sum
并且导入将相对于当前模块:
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: acme/api/data/dto1.proto
package data
import (
timestamp "github.com/your-user/api-go/gen/acme/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// <snip>
总的来说,我建议选择选项1-将你的protobuf定义合并到一个存储库中(包括第三方定义的依赖项),除非有特别强烈的理由不这样做。
英文:
There is no native notion of remote import paths with protobuf. So the import path has to be relative to some indicated local filesystem base path (specified via -I
/ --proto_path
).
Option 1
Generally it is easiest to just have a single repository with protobuf definitions for your organisation - e.g. a repository named acme-contract
.
└── protos
└── acme
├── api
│ └── data
│ ├── dto1.proto
│ └── dto2.proto
└── timestamp
└── timestamp.proto
Your dto1.proto will look something like:
syntax = "proto3";
package acme.api.data;
import "acme/timestamp/timestamp.proto";
message DTO1 {
string id = 1;
acme.timestamp.Timestamp timestamp = 2;
}
As long as you generate code relative to the protos/
dir of this repository, there shouldn't be an issue.
Option 2
There are various alternatives whereby you continue to have definitions split over various repositories, but you can't really escape the fact that imports are filesystem relative.
Historically that could be handled by manually cloning the various repositories and arranging directories such that the path are relative, or by using -I
to point to various locations that might intentionally or incidentally contain the proto files (e.g. in $GOPATH). Those strategies tend to end up being fairly messy and difficult to maintain.
buf makes things somewhat easier now. If you were to have your timestamp
repo:
.
├── buf.gen.yaml
├── buf.work.yaml
├── gen
│ └── acme
│ └── timestamp
│ └── timestamp.pb.go
├── go.mod
├── go.sum
└── protos
├── acme
│ └── timestamp
│ └── timestamp.proto
├── buf.lock
└── buf.yaml
timestamp.proto
looking like:
syntax = "proto3";
package acme.timestamp;
option go_package = "github.com/my-user/timestamp/gen/acme/timestamp";
message Timestamp {
int64 unix = 1;
}
buf.gen.yaml
looking like:
version: v1
plugins:
- name: go
out: gen
opt: paths=source_relative
- name: go-grpc
out: gen
opt:
- paths=source_relative
- require_unimplemented_servers=false
- name: grpc-gateway
out: gen
opt:
- paths=source_relative
- generate_unbound_methods=true
... and everything under gen/
has been generated via buf generate
.
Then in your api
repository:
.
├── buf.gen.yaml
├── buf.work.yaml
├── gen
│ └── acme
│ └── api
│ └── data
│ ├── dto1.pb.go
│ └── dto2.pb.go
└── protos
├── acme
│ └── api
│ └── data
│ ├── dto1.proto
│ └── dto2.proto
├── buf.lock
└── buf.yaml
With buf.yaml
looking like:
version: v1
name: buf.build/your-user/api
deps:
- buf.build/your-user/timestamp
breaking:
use:
- FILE
lint:
use:
- DEFAULT
dto1.proto
looking like:
syntax = "proto3";
package acme.api.data;
import "acme/timestamp/timestamp.proto";
option go_package = "github.com/your-user/api/gen/acme/api/data";
message DTO1 {
string id = 1;
acme.timestamp.Timestamp timestamp = 2;
}
and buf.gen.yaml
the same as in the timestamp
repo.
The code generated via buf generate
will depend on the timestamp
repository via Go modules:
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: acme/api/data/dto1.proto
package data
import (
timestamp "github.com/your-user/timestamp/gen/acme/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// <snip>
Note that if changes are made to dependencies you'll need to ensure that both buf and Go modules are kept relatively in sync.
Option 3
If you prefer not to leverage Go modules for importing generated pb code, you could also look to have a similar setup to Option 2
, but instead generate all code into a separate repository (similar to what you're doing now, by the sounds of it). This is most easily achieved by using buf
managed mode, which will essentially make it not require + ignore any go_modules
directives.
In api-go
:
.
├── buf.gen.yaml
├── go.mod
└── go.sum
With buf.gen.yaml
containing:
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/your-user/api-go/gen
plugins:
- name: go
out: gen
opt: paths=source_relative
- name: go-grpc
out: gen
opt:
- paths=source_relative
- require_unimplemented_servers=false
- name: grpc-gateway
out: gen
opt:
- paths=source_relative
- generate_unbound_methods=true
You'd then need to generate code for each respective repo (bushed to BSR):
$ buf generate buf.build/your-user/api
$ buf generate buf.build/your-user/timestamp
After which you should have some generated code for both:
.
├── buf.gen.yaml
├── gen
│ └── acme
│ ├── api
│ │ └── data
│ │ ├── dto1.pb.go
│ │ └── dto2.pb.go
│ └── timestamp
│ └── timestamp.pb.go
├── go.mod
└── go.sum
And the imports will be relative to the current module:
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: acme/api/data/dto1.proto
package data
import (
timestamp "github.com/your-user/api-go/gen/acme/timestamp"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// <snip>
All in all, I'd recommend Option 1 - consolidating your protobuf definitions into a single repository (including vendoring 3rd party definitions) - unless there is a particularly strong reason not to.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论