从GitHub仓库导入protobuf文件。

huangapple go评论105阅读模式
英文:

Import protobuf file from GitHub repository

问题

我目前有两个protobuf仓库:apitimestamp

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 = &quot;proto3&quot;;
package api.data;

import &quot;&lt;WHAT GOES HERE?&gt;&quot;;

option go_package = &quot;github.com/my-user/api/data&quot;; // golang

message DTO1 {
    string id = 1;
    Timestamp timestamp = 2;
}

And my compilation command is this:

find $GEN_PROTO_DIR -type f -name &quot;*.proto&quot; -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.yamltimestamp存储库中的内容相同。

通过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 = &quot;proto3&quot;;

package acme.api.data;

import &quot;acme/timestamp/timestamp.proto&quot;;

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 = &quot;proto3&quot;;

package acme.timestamp;

option go_package = &quot;github.com/my-user/timestamp/gen/acme/timestamp&quot;;

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 = &quot;proto3&quot;;

package acme.api.data;

import &quot;acme/timestamp/timestamp.proto&quot;;

option go_package = &quot;github.com/your-user/api/gen/acme/api/data&quot;;

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 &quot;github.com/your-user/timestamp/gen/acme/timestamp&quot;
	protoreflect &quot;google.golang.org/protobuf/reflect/protoreflect&quot;
	protoimpl &quot;google.golang.org/protobuf/runtime/protoimpl&quot;
	reflect &quot;reflect&quot;
	sync &quot;sync&quot;
)

// &lt;snip&gt;

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 &quot;github.com/your-user/api-go/gen/acme/timestamp&quot;
	protoreflect &quot;google.golang.org/protobuf/reflect/protoreflect&quot;
	protoimpl &quot;google.golang.org/protobuf/runtime/protoimpl&quot;
	reflect &quot;reflect&quot;
	sync &quot;sync&quot;
)

// &lt;snip&gt;

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.

huangapple
  • 本文由 发表于 2022年10月31日 15:15:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/74259855.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定