Переменные времени сборки в Go
Установка переменных во время сборки может предоставить нашему приложению ценные метаданные, которые не были доступны при написании кода или даже во время выполнения. Мы можем контролировать флаги функций или информацию о сборке, например номер версии, без постоянного обновления кода Go.
Управление версиями проекта Go
Представьте, что у нас есть version/version.go
в основном пакете github.com/benweidig/goapp
package version
import (
"fmt"
"time"
)
var (
Version = "dev"
CommitHash = "n/a"
BuildTimestamp = "n/a"
)
func BuildVersion() string {
return fmt.Sprintf("%s-%s (%s)", Version, CommitHash, BuildTimestamp)
}
Два свойства определяют любую сборку:
Version
: Идентификатор версии вашего приложения.CommitHash
: Для воспроизводимых сборок важно знать, какая фиксация использовалась для создания определенного двоичного файла.BuildTimestamp
: Еще одно динамическое значение, зависящее от самой сборки.
Конечно, мы могли бы просто обновлять версию каждый раз, прежде чем создавать новый двоичный файл. Но CommitHash
- это динамическое значение, которое нельзя установить до фиксации кода: хэш фиксации, используемый для создания приложения, не будет тем, который задан в коде. Отметка времени сборки также зависит от самой сборки и не может быть установлена заранее.
Все эти свойства необходимы для создания воспроизводимых сборок. Поэтому мы установим их во время сборки вместо того, чтобы обновлять наш код перед его компиляцией с помощью флагов компоновщика.
Флаги компоновщика приходят на помощь
Процесс сборки Go go build
- поддерживает флаги компоновщика, которые будут переданы базовому вызову go tool link
, что позволяет нам устанавливать общедоступные переменные в вашем коде во время сборки.
Чтобы передать флаг компоновщика, мы должны добавить аргумент -ldflags
:
go build -ldflags="-linkerflag"
Нас интересует флаг компоновщика -X
, который устанавливает переменную во время компоновки:
go build -ldflags="-X 'full_package_path.variable=value'"
Это большое предостережение заключается в том, что разрешены только значения string
, как вы, возможно, уже догадались из того, как эти значения цитируются.
В случае с нашим файлом version.go
вызов будет выглядеть так:
go build -ldflags = "- X 'github.com/benweidig/goapp/version.Version=1.0.0'"
Вместо того, чтобы писать версию непосредственно в вызове, мы должны использовать сценарий для автоматического получения требуемых значений.
Скрипты сборки
Пока что мы просто перенесли бремя записи текущей версии из файла Go на команду сборки. Давайте автоматизируем этот шаг с помощью сценария git
. Другие свойства также могут быть получены в таком скрипте:
#!/usr/bin/env bash
# STEP 1: Determinate the required values
PACKAGE="github.com/benweidig/goapp"
VERSION="$(git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')"
COMMIT_HASH="$(git rev-parse --short HEAD)"
BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')
# STEP 2: Build the ldflags
LDFLAGS=(
"-X '${PACKAGE}/version.Version=${VERSION}'"
"-X '${PACKAGE}/version.CommitHash=${COMMIT_HASH}'"
"-X '${PACKAGE}/version.BuildTime=${BUILD_TIMESTAMP}'"
)
# STEP 3: Actual Go build process
go build -ldflags="${LDFLAGS[*]}"
Общая концепция сценария должна легко адаптироваться к вашим собственным потребностям CI/CD. Лично я использую Makefile
для своего Git-helper Tortuga
.
Заключение
Внедрение динамических значений во время сборки с помощью флагов компоновщика - мощное дополнение к вашему набору инструментов. Вы можете предоставить информацию о сборке, как показано в статье, или использовать ее для управления флагами функций или чего-либо еще, что зависит от самого контекста сборки, а не от информации, доступной во время выполнения.