Tutorial

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

GoDevelopment

Введение

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

В Go эту проблему можно решить с помощью опции -ldflags команды go build. Эта опция вставляет в двоичный файл во время сборки динамическую информацию, не требуя изменения исходного кода. В этой опции ld означает программу linker, которая связывает разные фрагменты скомпилированного исходного кода в двоичном файле. Таким образом, название опции ldflags означает флаги linker. Данная опция передает файл в инструмент linker цепи инструментов 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, затем распечатали строку Version: с символом табуляции \t, а затем декларированную переменную.

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

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

  • go build
  • ./app

Результат будет выглядеть следующим образом:

Output
  • Version: development

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

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

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

  • go build -ldflags="-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 запустите приложение:

  • ./app

Результат будет выглядеть следующим образом:

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

Примечание: команда nm не поможет найти путь к переменной, если имя пакета содержит любые символы, кроме символов 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)'"

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

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

  • ./app

В системе Unix эта команда выводит примерно следующие результаты:

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

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

Заключение

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

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

Creative Commons License