Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 11 из 25 ~20 мин +50 XP

buf generate: кодогенерация

buf generate — команда, которую вы набирали с самого второго урока, и пора открыть её капот: анатомия buf.gen.yaml, локальные и удалённые плагины, managed mode, точечная генерация и воспроизводимость в CI. Запрос protoc buf в поисковике приводит в старые туториалы с хороводами флагов — buf существует ровно затем, чтобы этот ритуал забыть: вся кодогенерация buf-проекта описывается одним YAML-файлом.

Содержание урока · 11 разделов

Команду buf generate вы прогоняли в каждом уроке, начиная со второго, — она молча превращала схемы notes.v1 и eliza.v1 в Go-код сервера и TypeScript-модули фронта. Пришло время разобрать её построчно. Что делает buf generate на самом деле: читает buf.yaml и находит все схемы модуля; собирает из них образ дескрипторов — компактное бинарное описание всех пакетов, сообщений и сервисов; передаёт этот образ на стандартный ввод каждому плагину из buf.gen.yaml; собирает файлы со стандартного вывода плагинов и раскладывает их по каталогам out. Ключевое следствие этой архитектуры: плагину не важно, откуда пришёл образ — из локального каталога proto, из git или из реестра BSR, о котором пойдёт речь в двенадцатом уроке. Если вы гуглили пару protoc buf — вы уже видели старый мир: protoc с флагами --plugin и --go_out на каждый чих. Buf существует ровно затем, чтобы этот ритуал забыть: вся кодогенерация buf-проекта описывается одним YAML-файлом рядом с buf.yaml. План урока: анатомия buf.gen.yaml, где buf ищет плагины и как их ставить, чем локальные плагины отличаются от удалённых, зачем нужен managed mode, как генерировать точечно и как держать каталог gen/ честным в git и CI.

buf.gen.yaml: анатомия рецепта#

buf.gen.yaml лежит в корне проекта рядом с buf.yaml — это отдельный файл с отдельной ролью: buf.yaml описывает модуль схем, buf.gen.yaml — рецепт генерации. Актуальная версия — v2, и секция plugins — это список шагов: каждый элемент описывает один плагин. У плагина три главных поля. local или remote — откуда взять программу-плагин: локальный бинарник на вашей машине или сервис на buf.build (об этом ниже). out — каталог, куда пишутся результаты. opt — опции, которые передаются плагину как аргументы командной строки. Порядок плагинов в списке не важен — buf запускает их независимо, каждый со своим образом дескрипторов на входе. Заметьте, чего в конфиге нет: списка файлов. Buf сам находит все .proto внутри корней модуля из buf.yaml — вы уже видели это свойство в третьем уроке, когда eliza.proto подхватился без единой правки конфига. И это принципиально отличается от старого protoc-пути, где файлы перечисляли руками и забывали добавить новый — классическая грабелька, из-за которой половина кода молча не генерировалась.

buf.gen.yaml курса: три плагина, три языка

GO
version: v2

plugins:
  # Go: message-типы и RPC-слой для сервера
  - local: protoc-gen-go
    out: gen
    opt: paths=source_relative

  - local: protoc-gen-connect-go
    out: gen
    opt: paths=source_relative

  # Из девятого урока: TypeScript для фронтенда
  - local: protoc-gen-es
    out: src/gen
    opt: target=ts

Полный рецепт из курса: два Go-плагина пишут в gen (пути повторяют структуру схем благодаря paths=source_relative, поэтому notes/v1/notes.proto ложится в gen/notes/v1/), плагин protoc-gen-es пишет TypeScript в src/gen с опцией target=ts. Один прогон buf generate обновляет и сервер, и фронт из одной схемы — та самая proto-first дисциплина в действии.

local: плагин — просто программа в PATH#

Локальный плагин — это обычный исполняемый файл, который buf запускает как дочерний процесс: ищет его в PATH и передаёт образ дескрипторов на stdin. Отсюда все вопросы установки. Go-плагины ставятся go install-ом и попадают в каталог бинов Go (GOBIN или GOPATH/bin — он должен быть в PATH): protoc-gen-go из модуля google.golang.org/protobuf и protoc-gen-connect-go из connectrpc.com/connect. TypeScript-плагин protoc-gen-es ставится npm-ом в devDependencies проекта, и buf умеет находить его в node_modules/.bin — специально заходить в этот каталог в PATH не нужно. Если buf пишет, что не нашёл исполняемый файл плагина — это всегда история про PATH, а не про конфиг: либо плагин не установлен, либо каталог с ним не виден shell-у. Второй типовой грабль — рассинхрон версий: плагин генератора и рантайм-библиотека развиваются парой, и старый protoc-gen-go при свежем runtime может выдать код, который не соберётся. Держите версии плагинов зафиксированными в go.mod и package.json — вы уже видели в девятом уроке, как рассинхрон рантайма и генератора рождает самые невнятные ошибки.

Установка плагинов и запуск генерации

GO
# Go-плагины: бинарники в $GOBIN / $GOPATH/bin
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest

# TypeScript-плагин: живёт в node_modules/.bin
npm install -D @bufbuild/protoc-gen-es

# Один прогон — и Go, и TypeScript из одной схемы
buf generate

Три команды установки и один запуск. buf generate сам читает оба конфига: buf.yaml говорит, откуда схемы, buf.gen.yaml — что с ними делать. Никаких аргументов команде не нужно — весь рецепт в файлах, и новый разработчик получает рабочую генерацию одним buf generate после клона репозитория.

buf generate падает: plugin "protoc-gen-connect-go": exec: "protoc-gen-connect-go": executable file not found in $PATH. Что произошло и что делать?

remote: плагины, которые не нужно ставить#

Второй способ подключить плагин — remote: buf отправит образ дескрипторов на серверы buf.build, там прогонит нужный плагин и вернёт готовые файлы. Имена удалённых плагинов — это ссылки на их страницы в реестре: remote: buf.build/protocolbuffers/go для Go-типов и remote: buf.build/bufbuild/connect-go для Connect-слоя — те же самые плагины, что вы ставили локально, только в облаке. Выгоды три. Ноль установки: новый разработчик и CI-агент не ставят тулчейн вообще — конфиг самодостаточен. Воспроизводимость: версию плагина можно закрепить прямо в конфиге суффиксом после двоеточия, и вся команда генерирует код одной и той же версией — ничего не «плавает» от машины к машине. Скорость CI: не нужен шаг go install перед генерацией. Но есть и честная цена: удалённый плагин получает вашу схему целиком — образ дескрипторов уезжает на сервер buf.build. Для публичных схем это неважно, для приватных коммерческих контрактов — уже решение о безопасности, и его надо принимать осознанно: такие проекты держат локальные плагины. Второй нюанс — сеть: каждый прогон buf generate ходит по сети, на большом модуле это медленнее локального прогона и требует доступа к buf.build из CI.

buf.gen.yaml на удалённых плагинах с закреплённой версией

GO
version: v2

plugins:
  - remote: buf.build/protocolbuffers/go:v1.36.0
    out: gen
    opt: paths=source_relative

  - remote: buf.build/bufbuild/connect-go:v1.18.1
    out: gen
    opt: paths=source_relative

Тот же результат генерации, что с локальными плагинами, но без единой установки: buf сам скачает образ схемы на buf.build и заберёт файлы. Версия после двоеточия закрепляет плагин — без неё buf использует актуальную, и генерация может неожиданно поменять вывод после релиза новой версии плагина. Для воспроизводимости закрепляйте версии всегда; локальные и удалённые плагины можно свободно смешивать в одном конфиге.

Команда генерирует код удалённым плагином remote: buf.build/protocolbuffers/go. Какое утверждение верно?

managed mode: go_package без ручной возни#

Опция go_package в схеме из первого урока была вынужденной: protoc-gen-go без неё не знает, в какой Go-пакет положить код и под каким путём его импортировать. Проблема в том, что go_package — это языковая опция внутри языко-независимой схемы: для каждого нового целевого языка придётся добавлять свои опции, и proto-файлы обрастают мусором. Managed mode решает это централизованно: секция managed в buf.gen.yaml со значением enabled: true включает режим, в котором buf сам проставляет опции плагинам. Самая ходовая — override go_package_prefix: единый префикс импорта для всех схем модуля. В многоязычном проекте managed-секция растёт по одному override на язык, а схемы остаются чистыми: только контракты, ничего про Go, TypeScript или Kotlin. Это особенно ценно, когда одну схему потребляют несколько команд с разными соглашениями об импортах — каждому свой buf.gen.yaml с managed-настройками поверх общих протофайлов. Для нашего курса managed не обязателен: go_package уже зашит в схемы и работает с первого урока, но знать о режиме нужно — на собеседованиях и в чужих репозиториях вы его встретите постоянно.

buf.gen.yaml с managed mode

GO
version: v2
managed:
  enabled: true
  override:
    - file_option: go_package_prefix
      value: example.com/notes/gen
plugins:
  - local: protoc-gen-go
    out: gen
    opt: paths=source_relative
  - local: protoc-gen-connect-go
    out: gen
    opt: paths=source_relative

С managed-секцией protoc-gen-go получит go_package с префиксом example.com/notes/gen для каждой схемы автоматически — опцию go_package в самих протофайлах можно удалять. file_option: go_package_prefix — имя настройки, value — её значение. Управлять можно и точечнее: override-ы поддерживают ограничение по модулям, путям и файлам, но префикс закрывает девять случаев из десяти.

Что перегенерируется?

Прочитайте код и выберите, что он выведет:

# В проекте две схемы: notes и eliza (из третьего урока)
$ buf generate --path proto/eliza/v1

Точечно и по шаблонам: --template, --path, clean#

Три приёма, которые делают генерацию гибкой. Первый — отдельные шаблоны: файл buf.gen.yaml не обязан быть единственным, и для многоязычных проектов удобно держать buf.gen.yaml для Go и buf.gen.ts.yaml для фронтенда — плагин protoc-gen-es тогда живёт в своём рецепте, и Go-разработчики не запускают TypeScript-генерацию лишний раз. Выбор шаблона — флаг --template с путём к файлу. Второй приём вы уже видели в predict-задании: --path фильтрует вход по путям модуля. Третий — опция clean: true в buf.gen.yaml: buf удалит из каталогов out файлы, которые не были созданы текущим прогоном. Это лечит проблему осиротевших файлов: переименовали схему или удалили rpc — старый сгенерированный код без clean остаётся в gen/ и продолжает собираться, маскируя правку. И последний мост к следующему уроку: в buf.gen.yaml v2 можно описать и вход — секция inputs умеет брать модуль прямо из реестра, например module: buf.build/acme/notes, и тогда вы генерируете код из опубликованных чужих схем, не скачивая их в репозиторий. Это вторая половина истории про BSR — там и продолжим.

Отдельный шаблон для фронтенда

GO
# buf.gen.ts.yaml — только TypeScript
version: v2
clean: true
plugins:
  - local: protoc-gen-es
    out: src/gen
    opt: target=ts

# Запуск:
$ buf generate --template buf.gen.ts.yaml
$ buf generate --path proto/notes/v1

Два независимых рецепта: buf.gen.yaml для Go и buf.gen.ts.yaml для фронта. clean: true в шаблоне гарантирует, что в src/gen не останется файлов от удалённых или переименованных схем. --path на втором прогоне ограничивает вход одной директорией схемы — оба флага комбинируются свободно.

gen/ и git: коммитить или проверять свежесть#

Сгенерированный код — артефакт, и вокруг него два лагеря, оба с аргументами. Первый: коммитить gen/ в репозиторий — так делает наш курс, вы видели gen/ в дереве проекта со второго урока. Плюсы: потребитель кода не должен уметь собирать тулчейн — клиент вашего API подключают через go get, и сгенерированные пакеты приходят с обычными исходниками; локальная сборка не требует buf; diff-ы видны в ревью. Второй лагерь: не коммитить, генерировать в CI — каталог чист, историю не пылит, но каждый потребитель обязан поставить buf и плагины. Третий путь, примиряющий оба, — проверка свежести в CI: buf generate прогоняется на чистом чекауте, затем git diff --exit-code — если сгенерированный код отличается от закоммиченного, PR падает. Это ловит две классические аварии: разработчик забыл перегенерировать после правки схемы (сервер едет с устаревшим кодом) и подредактировал gen/ руками (правка затрётся следующим прогоном). Работает при одном условии: генерация должна быть идемпотентной — одинаковые входы и версии плагинов дают одинаковый вывод. Поэтому версии плагинов фиксируются в go.mod, package.json или прямо в конфиге для remote — и buf generate превращается в детерминированную функцию от схем.

Локальные и удалённые плагины: сравнение#

Аспектlocal: плагинremote: плагин
Установкаgo install / npm install, нужен PATHничего — плагин живёт на buf.build
Версияфиксируется в go.mod или package.jsonзакрепляется суффиксом :версия в buf.gen.yaml
Приватностьсхема не покидает машинуобраз дескрипторов уходит на серверы buf.build
Скоростьмгновенно, без сетисетевой roundtrip на каждый прогон
CIнужен шаг установки плагиновноль установки, конфиг самодостаточен

Зачем в CI ставят связку buf generate + git diff --exit-code?

Соберите buf.gen.yaml и команду

Восстановите рецепт генерации для Connect-плагина и саму команду.

version: plugins: - local: protoc-gen-go out: opt: - local: protoc-gen-connect-go out: gen opt: paths=source_relative $

Путь одного прогона buf generate

Расставьте события от правки схемы до пересобранного сервера.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: buf generate

Термин

buf.gen.yaml

нажмите, чтобы перевернуть
Определение

Рецепт генерации: версия v2, секция plugins с local/remote, out и opt для каждого плагина. buf.yaml описывает модуль схем, buf.gen.yaml — что с ними делать.

1 / 6

Что дальше по курсу#

Тулчейн закрыт: рецепты генерации описаны декларативно, плагины — локальные с зафиксированными версиями или удалённые с нулём установки, managed mode держит схемы чистыми от языковых опций, а CI проверяет, что gen/ соответствует схемам. Осталась последняя деталь конвейера — откуда брать чужие схемы. В двенадцатом уроке встречаем Buf Schema Registry: модули buf.build, публикация buf push-ом, зависимости с buf.lock, готовые SDK для Go и TypeScript — и секция inputs в buf.gen.yaml, которая умеет генерировать прямо из опубликованного модуля. Ссылки для глубины: дисциплина охраны схем из урока про buf lint и breaking ложится поверх генерации как предохранитель, а эволюция контрактов руками — в уроке про совместимость proto курса gRPC. Программа всех двадцати пяти уроков — на странице курса.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • buf generate читает buf.yaml, собирает образ дескрипторов и передаёт его плагинам из buf.gen.yaml; результат раскладывается по каталогам out.
  • Локальный плагин — программа в PATH: protoc-gen-go и protoc-gen-connect-go ставятся go install-ом, protoc-gen-es — npm-ом.
  • Удалённый плагин (remote: buf.build/...) не требует установки и закрепляет версию в конфиге, но получает схему по сети — приватные схемы генерируют локально.
  • opt: paths=source_relative повторяет путь схемы в gen/, managed mode с go_package_prefix снимает go_package из протофайлов.
  • --path фильтрует вход по каталогу схемы, --template выбирает отдельный buf.gen-файл, clean: true удаляет осиротевшие артефакты.
  • Свежесть gen/ проверяет пара buf generate + git diff --exit-code в CI; генерация идемпотентна при зафиксированных версиях плагинов.
  • Секция inputs в buf.gen.yaml v2 умеет брать модуль прямо из BSR — мост к следующему уроку.

Домашнее задание

Разберите buf.gen.yaml своего проекта из второго урока и обновите его до осознанной версии: убедитесь, что version: v2, оба Go-плагина описаны с opt: paths=source_relative и out: gen, а третий плагин protoc-gen-es с target=ts вынесен в отдельный buf.gen.ts.yaml — проверьте, что buf generate и buf generate --template buf.gen.ts.yaml дают те же файлы, что раньше. Затем попробуйте удалённые плагины: соберите копию buf.gen.yaml с remote: buf.build/protocolbuffers/go и remote: buf.build/bufbuild/connect-go, сравните git diff после прогонов локального и удалённого вариантов — вывод должен совпасть (или объясните, почему нет: разные версии плагинов). Включите managed mode с override go_package_prefix: example.com/notes/gen, удалите go_package из своих схем и добейтесь, что buf lint и buf generate работают, а сервер собирается. Финальный шаг: добавьте в CI шаг buf generate + git diff --exit-code и проверьте на тестовом PR, что забытая регенерация реально валит сборку. Запишите в заметки, какие версии плагинов вы зафиксировали и почему.

Личные заметкиПусто
Хранятся только в вашем браузере (Connect · buf generate: кодогенерация)

Вопросы и ответы по уроку

buf generate перегенерирует всё, а мне нужен только один сервис. Это нормально?

Да, полный прогон — норма для CI: buf быстр, а идемпотентный вывод не создаёт шума в git. Для локальной работы используйте --path proto/eliza/v1 — перегенерируется только указанный каталог. Отдельные шаблоны через --template помогают, когда языки генерируются разными командами.

Какие плагины выбрать — local или remote?

Публичные схемы и быстрый онбординг — remote с закреплённой версией: ноль установки, одинаковый вывод у всей команды. Приватные коммерческие контракты — local: схема не покидает машину. В одном buf.gen.yaml их можно смешивать: например, Go-плагины локально, TypeScript — удалённо.

Коммитить ли сгенерированный каталог gen/ в git?

Для библиотек и API, которые потребляют через go get, — да: клиент получает рабочий код без тулчейна. Плюс ставьте в CI проверку buf generate + git diff --exit-code, чтобы артефакт не расходился со схемами. Не коммитить можно в монорепозиториях, где buf есть у всех и генерация встроена в сборку.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Сравните с альтернативами

Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.

Следующая ступень учебного пути

Fyne25 уроков~13 чпрактика25 уроков уже доступны

Fyne: десктопные и мобильные GUI на Go

Курс Fyne: окна, виджеты, контейнеры, canvas, темы, binding, сборка под мобильные и десктоп. 25 уроков и итоговый менеджер задач.

  • Создавать окна и интерфейсы из виджетов
  • Управлять компоновкой: Box, Grid, Form
  • Рисовать примитивами canvas и строить свои виджеты
  • Связывать данные через binding