Настройка информации о версиях приложения Go с помощью ldflags

При развертывании приложений в производственной среде информация о версии и другие метаданные улучшают процессы мониторинга, логирования и отладки. В целом любая идентифицирующая информация в будущем поможет вам отслеживать ваши сборки.

Информация о версии обычно включает в себя высокодинамичные данные, такие как время сборки, машина или пользователь, собирающий двоичный файл, идентификатор коммита системы управления версиями (VCS), на основе которого он был создан, и многое другое. Поскольку эти значения постоянно меняются, кодировать их непосредственно в исходный код и менять перед каждой новой сборкой утомительно, кроме того, можно наделать ошибок: исходные файлы могут перемещаться, а переменные и константы будут заменить файлы в ходе разработки, нарушая процесс сборки.

Читайте также: Переменные и константы в Go

Одним из решений этой проблемы в Go является использование параметра -ldflags в команде go build для вставки динамической информации в двоичный файл во время сборки – при этом нет необходимости изменять исходный код. В этом флаге ld обозначает linker, компоновщик – это программа, которая связывает различные части скомпилированного исходного кода в финальный файл. Соответственно, ldflags – это флаги компоновщика. ldflags так называется потому, что он передает флаг базовому компоновщику цепочки инструментов Go, cmd/link, который позволяет изменять значения импортируемых пакетов во время сборки из командной строки.

В этом мануале вы научитесь использовать -ldflags, чтобы изменить значение переменных во время сборки и добавлять динамическую информацию в бинарный файл (на примере простого приложения, которое выводит информацию о версии на экран).

Требования

Для работы вам нужна рабочая среда Go. У нас есть ряд мануалов для разных систем, которые помогут вам создать такую среду:

1: Сборка тестового приложения

Прежде чем вы сможете использовать ldflags для представления динамических данных, вам сначала нужно создать простое тестовое приложение. Наше приложение изначально будет выводить только статическую информацию о версиях. Давайте создадим его.

В каталоге src создайте каталог с именем вашего приложения. В этом мануале мы назовем его  просто app:

mkdir app

Перейдите в этот каталог:

cd app

Затем в текстовом редакторе создайте точку входа main.go:

nano main.go

Теперь введите в этот файл код, с помощью которого приложение сможет выводить информацию о версии:

package main
import (
"fmt"
)
var Version = "development"
func main() {
fmt.Println("Version:\t", Version)
}

Внутри функции main() мы объявили переменную Version, затем ввели строку Version:, за которой следует символ табуляции \t и объявленная переменная.

Читайте также: Основы работы со строками в Go

На этом этапе переменная Version имеет значение development, эта версия будет версией по умолчанию для данного приложения. Позже мы изменим это значение на официальный номер версии, упорядоченный в соответствии с семантическим версионированием.

Сохраните и выйдите из файла. После этого соберите и запустите приложение, чтобы убедиться, что оно отображает правильную версию:

go build
./app

Вы увидите следующий вывод:

Version:     development

Теперь у вас есть приложение, которое выводит на экран информацию о версии по умолчанию, но пока что вы не можете передать ему информацию о текущей версии во время сборки. На следующем этапе мы попробуем использовать -ldflags и команду go build, чтобы решить эту проблему.

2: Использование параметра -ldflags

Как упоминалось ранее, ldflags используется для передачи флагов базовому компоновщику в цепочке инструментов Go. Параметр работает по следующему синтаксису:

В этом примере мы передали флаг flag базовой команде go tool link, которая выполняется как часть go build. Эта команда берет содержимое ldflags в двойные кавычки, чтобы избежать символов, которые командная строка может интерпретировать иначе. В этих кавычках вы можете передать много разных флагов link. В данном мануале мы будем использовать флаг -X для записи информации в переменную во время соединения, за которой следует путь пакета к переменной и ее новое значение:

go build -ldflags="-X 'package_path.variable_name=new_value'"

Итак, внутри кавычек теперь есть опция -X и пара ключ-значение, где указывается переменная, которую нужно изменить, и ее новое значение. Символ точки разделяет путь к пакету и имя переменной, а одинарные кавычки используются, чтобы избежать разделяющих символов в паре ключ-значение.

Чтобы заменить переменную Version в вашем приложении, используйте такой синтаксис в последнем блоке команд. Он позволяет передать новое значение и собрать новый файл:

go build -ldflags="-X 'main.Version=v1.0.0'"

В этой команде main – это путь пакета переменной Version, поскольку эта переменная находится в файле main.go. Version – это переменная, в которую вы пишете новые данные, а v1.0.0 – это ее новое значение.

Чтобы использовать ldflags, значение, которое вы хотите изменить, должно существовать и быть переменной уровня пакета с типом string. Это может быть как экспортированная, так и неэкспортированная переменная. Значение не может быть const или устанавливаться в результате вызова функции. Наша переменная Version соответствует всем этим требованиям: она уже объявлена ​​как переменная в файле main.go, а ее текущее значение (development) и требуемое значение (v1.0.0) являются строками.

Как только ваш новый бинарный файл будет собран, запустите приложение:

./app

Вы получите следующий вывод:

Version:     v1.0.0

Используя -ldflags, вы успешно изменили значение переменной Version с development на v1.0.0.

У нас получилось изменить строковую переменную внутри простого приложения во время сборки. Используя ldflags, вы можете с помощью командной строки встраивать в двоичный файл сведения о версии, информацию о лицензировании и многое другое.

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

3: Изменение переменных в подпакетах

В последнем разделе мы изменили переменную Version, которая была в пакете верхнего уровня. Но это не всегда так. Часто размещать переменные удобнее в другом пакете, так как main не является импортируемым пакетом. Чтобы смоделировать такую ситуацию, мы создадим новый подпакет app/build, который будет хранить информацию о времени сборки бинарного файла и имя пользователя, который выполнил команду build.

Чтобы добавить новый подпакет, сначала добавьте в проект новый каталог build:

mkdir -p build

Затем создайте новый файл build.go для хранения новых переменных:

nano build/build.go

В текстовом редакторе добавьте новые переменные Time и User:

package build
var Time string
var User string

Переменная Time будет содержать строковое представление времени, в которое был создан двоичный файл. Переменная User будет содержать имя пользователя, который собрал этот файл. Поскольку эти две переменные всегда будут иметь значения, вам не нужно устанавливать в них значения по умолчанию, как это было с Version.

Сохраните и закройте файл.

Затем откройте main.go, чтобы добавить новые переменные в ваше приложение:

nano main.go

Добавьте следующие выделенные строки:

package main
import (
"app/build"
"fmt"
)
var Version = "development"
func main() {
fmt.Println("Version:\t", Version)
fmt.Println("build.Time:\t", build.Time)

fmt.Println("build.User:\t", build.User)

}

Эти строки сначала импортируют пакет app/build, а затем выводят build.Time и build.User – так же, как было в случае с Version.

Сохраните файл, затем выйдите из текстового редактора.

Чтобы захватить эти переменные с помощью ldflags, вы можете использовать путь импорта app/build, а за ним указать .User или .Time (поскольку вы уже знаете путь импорта). Однако для моделирования более сложной ситуации, в которой путь к переменной неочевиден, вместо этого нужно использовать команду nm цепочки инструментов Go.

Команда go tool nm выведет символы, относящиеся к данному исполняемому файлу, объектному файлу или архиву. В этом случае символ относится к объекту в коде, такому как определенная или импортированная переменная или функция. Генерируя таблицу символов с помощью nm и используя grep для поиска переменной, вы можете быстро найти информацию о ее пути.

Примечание. Команда nm не поможет вам найти путь к вашей переменной, если имя пакета содержит какие-либо символы, отличные от ASCII, или символы «или%», поскольку это ограничение самого инструмента.

Чтобы использовать эту команду, сначала соберите файл приложения:

go build

Теперь, когда приложение app собрано, можно использовать инструмент nm и выполнить поиск по выводу:

go tool nm ./app | grep app

Инструмент nm выведет много данных. потому в команде мы используем символ |, который направит вывод в команду grep. Она найдет в выводе все строки, в заголовке которых было приложение верхнего уровня app.

Вы получите примерно такой вывод:

55d2c0 D app/build.Time

55d2d0 D app/build.User

4069a0 T runtime.appendIntStr
462580 T strconv.appendEscapedRune
. . .

Первые две строки содержат пути к двум переменным, которые вы ищете: app/build.Time и app/build.User.

Теперь, когда вы знаете нужные пути, соберите приложение еще раз, на этот раз изменяя переменные Version, User, and Time во время сборки. Для этого передайте несколько флагов -X в парпметре -ldflags:

go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"

Здесь вы передали команду id -u -n для отображения текущего пользователя и команду date для отображения текущей даты.

Как только исполняемый файл соберется, запустите программу:

./app

Эта команда при запуске в системе Unix сгенерирует примерно такой вывод:

Version:     v1.0.0
build.Time:  Fri Oct  4 19:49:19 UTC 2019
build.User:  8host

Теперь у вас есть бинарный файл, который содержит информацию о версиях и сборке. Эти данные могут пригодиться при устранении неполадок.

Заключение

В мануале вы научились пользоваться параметром ldflags. Этот полезный инструмент позволяет добавлять в сборку файлов информацию о версии приложения. С его помощью вы можете управлять флагами функций, информацией о среде и о версиях и многими другими данными, не внося изменений в исходный код.

Tags: ,

Добавить комментарий