Как использовать пакет сгенерированных protobuf внутри модуля go?

У меня возникла проблема с управлением модулями Go и поколением протобуферов (с использованием 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
...

из-за другой версии сообщений несколько файлов протобуфера должны находиться в другом пространстве имен:

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)

Итак, в зависимости от параметра, используемого с компилятором протокола:
→ С --go_opt=paths=import

Дерево папок company.tld / proj / projpb создается protoc в корне проекта. Каждый объект находится в пакете с именем projpb или other, в подпакете other.

Сгенерированные объекты Proto, которые включают другие объекты пространства имен-d, имеют путь импорта import other "company.tld/proj/projpb/other" (который задается параметром go_package, но это неверно, потому что это не существующий модуль - go mod tidy / поставщик жалуется, что не может его найти).

Обычным файлам проекта необходим следующий путь импорта для доступа к объектам Generated Proto:
import pb "company.tld/proj/subproj/company.tld/proj/projpb"
что кажется странным и неправильным.

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

Папка projpb создается протоколом в корне проекта, и каждый сгенерированный .pb.go имеет пакет projpb или другой, в подпакете другой .

Сгенерированные объекты Proto, которые включают другие объекты пространства имен-d, по-прежнему имеют путь импорта import other "company.tld/proj/projpb/other" (который по-прежнему задается параметром go_package и все еще неверен, потому что это все еще несуществующий модуль - эти являются сгенерированными файлами ... зачем мне создавать модуль из них?).

Замечательно то, что с этим go_opt доступ к сгенерированным типам выглядит намного более нормальным с
import pb "company.tld/proj/subproj/projpb".

Наконец я попробовал

  • использование локального пути импорта в опции go_package в файлах .proto (это отклоняется во время сборки, потому что в сгенерированном объекте protobuffer будет import other "./projpb/other")
  • использовать инструкцию replace в файле go.mod следующим образом:
replace (
    company.tld/proj/projpb => ./projpb
    company.tld/proj/projpb/other => ./projpb/other
)

(но go mod tidy / поставщик жалуется, что не может найти файл go.mod внутри сгенерированной папки ./projpb)

Кто-нибудь сталкивался с подобной проблемой? Или мне не хватает параметра команды, чтобы сообщить Go: «Я генерирую объекты протобуфера в пакете или пакет в пакете, и я просто хочу их использовать. Они не являются модулем, поэтому, пожалуйста, укажите правильные пути импорта к сгенерированному объекту и позвольте мне использовать их в моем коде ».


[Обновление 01]
Я попробовал go_opt=paths=source_relative (вдохновленный этим билетом).
Я создал папку в Makefile, protoc генерирует файлы внутри.
Примечания:

  • сгенерированные протоколы используют полный путь, указанный с параметром go_package, для связи друг с другом.
  • Пока для параметра go_package требуется полный путь, Go (go mod tidy / vendor) будет искать файл go.mod внутри созданной папки, содержащий сгенерированные протоколы.

Как правильно сообщить Go, что я не ищу модуль, но все же удовлетворяю ограничению полного пути параметра go_package в файле protobuffer?


person Psyko    schedule 24.06.2021    source источник


Ответы (1)


После многократного изменения параметра go_package в файлах proto, изменения go_opt в команде компилятора protoc, единственный способ, который я нашел для компиляции моего проекта с моими сгенерированными протобуферами, соблюдая все ограничения Go, - это создание файл go.mod на лету ...

финальный прото «заголовок» (учитывает полный путь в опции 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 (создает файл 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/projpbcompany.tld/proj/projpb/other для подпакетов
И оператор импорта для использования сгенерированных протоколов отлично работает в основном проекте.

Я надеялся, что есть более простое и красивое решение, но, увы ...

Приносим извинения за любой и спасибо тем, кто подумал об этом!

person Psyko    schedule 25.06.2021
comment
Более простое и красивое решение приведено в blog.golang.org/generate: «[Я] f the содержащий пакет, предназначен для импорта go get, после того как файл будет создан (и протестирован!), он должен быть зарегистрирован в репозитории исходного кода, чтобы быть доступным для клиентов ». Таким образом, более масштабируемое решение здесь - либо опубликовать фактический company.tld/proj/projpb, содержащий сгенерированный код, либо изменить сгенерированные пути импорта, чтобы они находились в модуле company.tld/proj/subproj, и зафиксировать сгенерированный код там. - person bcmills; 28.06.2021
comment
@bcmills спасибо за ваш комментарий! Ваша точка зрения интересна, но она заставляет меня помещать сгенерированные файлы в репозитории, которые используют прото-файлы (если я хорошо понял ...). Каждый раз, когда изменяется прототип, команде придется обходить каждый проект и делать коммит сгенерированных обновлением прототипов классов, что на самом деле не очень удобно. С моей точки зрения, если бы меня не заставляли использовать полный путь импорта в опции go_package, я мог бы генерировать и использовать файлы как исходящие из локального пакета (без ввода сложности модулей Go). - person Psyko; 08.07.2021
comment
Если я правильно помню, github.com/golang/protobuf/proto API требует наличия только одного пакета на каждый прото-файл. Для меня это означает наличие единственного модуля где-то - вероятно, того, который содержит .pb.go файлы, - который предоставляет канонический сгенерированный код для этой версии файлов .proto. Таким образом, каждый раз, когда файл .proto изменяется, тот, кто фиксирует изменение в этом файле, также должен будет регенерировать и зафиксировать файлы .pb.go. - person bcmills; 08.07.2021
comment
ой, извини ! Под «командой придется кружить по каждому проекту» я имел в виду проекты, использующие разные языки (Go, C ++, C #, NodeJS, пока…), но использующие одни и те же протобуферы. Итак, да, вы правы, говоря, что для API требуется только один пакет для каждого прото-файла, и это было бы справедливым решением, если бы у нас были только проекты Go. В моем случае прототипы автоматически регенерируются перед сборкой каждого проекта (Go, C ++ ...), избегая создания эквивалентности модуля, содержащей сгенерированные файлы для каждого языка. Нам нужно только указать правильную фиксацию в прототипе репо / git-submodule. - person Psyko; 09.07.2021