Использование ldflags для установки информации о версии для приложений Go

Вступление

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

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

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

Предпосылки

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

Создание вашего образца приложения

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

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

mkdir app

Измените свой рабочий каталог на эту папку:

cd app

Затем, используя текстовый редактор по вашему выбору, создайте точку входа вашей программы,main.go:

nano main.go

Теперь заставьте ваше приложение распечатать информацию о версии, добавив следующее содержимое:

app/main.go

package main

import (
    "fmt"
)

var Version = "development"

func main() {
    fmt.Println("Version:\t", Version)
}

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

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

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

go build
./app

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

OutputVersion:     development

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

Использованиеldflags сgo build

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

go build -ldflags="-flag"

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

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

Внутри кавычек теперь есть опция-X иkey-value pair, представляющая изменяемую переменную и ее новое значение. Символ. разделяет путь к пакету и имя переменной, а одинарные кавычки используются во избежание разрыва символов в паре "ключ-значение".

Чтобы заменить переменную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 будет собран, запустите приложение:

./app

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

OutputVersion:     v1.0.0

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

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

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

Таргетинг переменных субпакета

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

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

mkdir -p build

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

nano build/build.go

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

app/build/build.go

package build

var Time string

var User string

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

Сохраните и выйдите из файла.

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

nano main.go

Внутриmain.go добавьте следующие выделенные строки:

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 выведетsymbols, задействованные в данном исполняемом файле, объектном файле или архиве. В этом случае символ относится к объекту в коде, такому как определенная или импортированная переменная или функция. Создав таблицу символов с помощьюnm и используяgrep для поиска переменной, вы можете быстро найти информацию о ее пути.

[.note] #Note: Командаnm не поможет вам найти путь к вашей переменной, если имя пакета не содержит https: //en.wikipedia.org/wiki/ASCII [ASCII ] или" или%, поскольку это ограничение самого инструмента.
#

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

go build

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

go tool nm ./app | grep app

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

Вы получите вывод, похожий на этот:

Output  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 и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)'"

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

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

./app

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

OutputVersion:     v1.0.0
build.Time:  Fri Oct  4 19:49:19 UTC 2019
build.User:  sammy

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

Заключение

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

Если вы хотите узнать больше о языке программирования Go, ознакомьтесь с нашими полнымиHow To Code in Go series. Если вы ищете другие решения для контроля версий, попробуйте наше справочное руководствоHow To Use Git.