Внедрение переменных на этапе компиляции в Go: использование go build -ldflags -X

Предыстория
Недавно, работая над корпоративным проектом, я искал способ внедрять переменные на этапе компиляции без использования .env.
Я считал, что это будет очень удобно, так как достаточно одного файла без .env и множества конфигурационных файлов.
Сначала я искал CLI-инструмент на основе go:generate, который генерирует файл констант из .env, но такого не нашлось.
Впрочем, если уж так, то проще просто изменить constants, и не было необходимости усложнять процесс генерацией. Затем я обнаружил, что в Rust есть возможность внедрять значения на этапе компиляции, например, с помощью макроса include_bytes!.
Внедрение переменных на этапе компиляции
Использование очень простое.
1package main
2
3import "fmt"
4
5var Foo string
6
7func main() {
8 fmt.Println(Foo)
9}
Если имеется код, подобный приведенному выше, то при сборке можно внедрить значение следующим образом.
1go build -ldflags="-X 'main.Foo=bar'"
Если передать аргумент через ldflag, то значение bar будет внедрено в переменную Foo на этапе компиляции. Это просто.
Если в проекте одинаковые имена пакетов или переменных встречаются в нескольких местах, то достаточно указать путь к пакету.
1// github.com/myproject/config/config.go
2package config
3
4var Version string
5
6// github.com/myproject/internal/config/config.go
7package config
8
9var BuildTime string
В таком случае можно различать их и внедрять следующим образом:
1go build -ldflags="-X 'github.com/myproject/config.Version=v1.0.0' -X 'github.com/myproject/internal/config.BuildTime=2025-05-27'"
Если в одном пакете несколько переменных, достаточно передать дополнительные аргументы.
1go build -ldflags="-X 'main.Version=v1.0.0' -X 'main.BuildTime=2025-05-27' -X 'main.GitCommit=abc123'"
Особенности таковы:
- Переменная не обязана быть публичной: можно внедрять и переменные, начинающиеся с маленькой буквы
- Поддерживается только тип string: другие типы не поддерживаются