Содержание урока · 11 разделов
- buf.gen.yaml: анатомия рецепта
- local: плагин — просто программа в PATH
- remote: плагины, которые не нужно ставить
- managed mode: go_package без ручной возни
- Точечно и по шаблонам: --template, --path, clean
- gen/ и git: коммитить или проверять свежесть
- Локальные и удалённые плагины: сравнение
- Что дальше по курсу
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Команду 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 курса: три плагина, три языка
GOversion: 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 на удалённых плагинах с закреплённой версией
GOversion: 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
GOversion: 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-плагина и саму команду.
Путь одного прогона buf generate
Расставьте события от правки схемы до пересобранного сервера.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Заведите в Makefile или justfile цель generate: buf lint, buf breaking --against '.git#branch=main' и buf generate одной командой — та же последовательность, что в CI из десятого урока, только локально. Пока команда набирается по памяти, кто-то обязательно пропустит lint перед генерацией и закоммитит код, который линтер не пропустит. Одна цель — ноль пропусков, а новый разработчик узнаёт правильный порядок, просто читая Makefile.
Конфиги и команды из этого урока — настоящие: YAML и shell можно копировать в проект из второго урока один в один. Браузерная песочница сайта файловые команды и плагины не исполняет, поэтому исполняемых блоков в курсе нет — интерактивы работают прямо на странице и дают XP, а генерацию проверяйте локально на своей схеме.
Карточки: buf generate
Что дальше по курсу#
Тулчейн закрыт: рецепты генерации описаны декларативно, плагины — локальные с зафиксированными версиями или удалённые с нулём установки, 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, что забытая регенерация реально валит сборку. Запишите в заметки, какие версии плагинов вы зафиксировали и почему.
Личные заметкиПусто
Вопросы и ответы по уроку
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 есть у всех и генерация встроена в сборку.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Сравните с альтернативами
Практический вопрос после первых уроков: а почему именно эта библиотека? Мы разобрали конкурентов по одним критериям, с кодом одной задачи рядом и персональным подбором под ваши приоритеты.
Изучайте дальше
Следующая ступень учебного пути
Fyne: десктопные и мобильные GUI на Go
Курс Fyne: окна, виджеты, контейнеры, canvas, темы, binding, сборка под мобильные и десктоп. 25 уроков и итоговый менеджер задач.
- Создавать окна и интерфейсы из виджетов
- Управлять компоновкой: Box, Grid, Form
- Рисовать примитивами canvas и строить свои виджеты
- Связывать данные через binding