Настройка бинарных файлов Go с помощью тегов сборки

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

Давайте в качестве примера возьмем проблему определения различий между уровнями ров функций клиента. При написании некоторых приложений вам может потребоваться указать, какие функции следует включить в бинарный файл: например, приложение предлагает уровни Free, Pro и Enterprise. Когда клиент повышает уровень своей подписки в таком приложении, ему открывается больше функций. Чтобы решить эту проблему, вы можете поддерживать отдельные проекты и пытаться синхронизировать их друг с другом с помощью операторов import. Этот подход будет работать, но со временем он станет очень громоздким и в нем могут появиться ошибки. Альтернативный подход – использование тегов сборки.

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

Требования

Чтобы выполнить примеры из этого мануала, вам понадобится среда разработки Go. Инструкции по настройке вы найдете в этих мануалах:

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

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

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

mkdir app

Перейдите в эту папку:

cd app

Затем создайте новый текстовый файл main.go в выбранном вами редакторе:

nano main.go

Теперь мы определим бесплатную версию приложения. Добавьте следующее содержимое в main.go:

package main
import "fmt"
var features = []string{
"Free Feature #1",
"Free Feature #2",
}
func main() {
for _, f := range features {
fmt.Println(">", f)
}
}

Итак, мы создали программу, которая объявляет фрагмент features. Он содержит две строки, которые представляют бесплатные функции приложения. Функция main() использует цикл for для выбора диапазона функций и вывода всех доступных функций на экран.

Читайте также: Циклы For в Go

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

Соберите и запустите программу:

go build
./app

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

> Free Feature #1
> Free Feature #2

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

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

Добавление функций уровня Pro с помощью go build

Мы не будем вносить изменений в main.go, чтобы имитировать общую производственную среду, в которую код добавляется без изменения основного кода (чтоб избежать его возможного нарушения). Следовательно, поскольку мы не можем редактировать файл main.go, нам потребуется использовать другой механизм для добавления новых функций в срез features – и это можно сделать с помощью тегов сборки.

Давайте создадим новый файл pro.go, который будет использовать функцию init() для добавления новых функций в срез features:

nano pro.go

Когда редактор откроет файл, добавьте следующие строки:

package main
func init() {
features = append(features,
"Pro Feature #1",
"Pro Feature #2",
)
}

В этом коде мы использовали функцию init() для запуска кода перед функцией main() нашего приложения, а затем с помощью append() мы добавили функции Pro в срез features. Сохраните и закройте файл.

Скомпилируйте и запустите приложение, используя go build:

go build

Поскольку в нашем текущем каталоге есть два файла (pro.go и main.go), go build создаст двоичный файл из них обоих. Выполните этот двоичный файл:

./app

Он выведет следующий набор функций:

> Free Feature #1
> Free Feature #2
> Pro Feature #1

> Pro Feature #2

Как видите, теперь приложение включает в себя как функции Pro, так и бесплатные функции. Однако это нежелательное поведение: поскольку между версиями нет различий, бесплатная версия теперь включает те функции, которые должны быть доступны только в версии Pro. Чтобы это исправить, вы можете добавить код для управления различными уровнями приложения или использовать теги сборки, которые помогут указать инструментам Go, какие файлы .go нужно собирать, а какие – игнорировать. Давайте добавим теги сборки.

Добавление тегов сборки

Теперь вы можете использовать теги сборки, чтобы компилятор научился отличать версию Pro от бесплатной версии приложения.

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

// +build tag_name

Поместите этот тег в качестве первой строки вашего пакета и замените tag_name именем вашего тега сборки – и вы пометите этот пакет как код, который можно выборочно включить в окончательный двоичный файл. Давайте посмотрим, как это работает в действии: мы добавим тег сборки в файл pro.go, чтобы команда go build проигнорировала этот файл, если этот тег не указан. Откройте файл в текстовом редакторе:

nano pro.go

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

// +build pro
.
package main
func init() {
features = append(features,
"Pro Feature #1",
"Pro Feature #2",
)

}

В начало файла pro.go мы добавили // +build pro и затем оставили пустую строку. Символ новой строки здесь обязателен, в противном случае Go интерпретирует тег как комментарий. Объявления тегов сборки также должны находиться в самом верху файла .go. никакие другие элементы, даже комментарии, не могут находиться выше тегов сборки.

Объявление +build сообщает команде go build, что это не комментарий, а тег сборки. Вторая часть – это тег pro. Добавив этот тег в начало файла pro.go, вы сообщаете команде go build, что теперь она может включать файл pro.go только тогда, когда в ней ест тег pro.

Скомпилируйте и снова запустите приложение:

go build
./app

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

> Free Feature #1
> Free Feature #2

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

При запуске команды go build мы можем использовать флаг -tags для условного включения кода в скомпилированный исходник. Сам тег добавляется в качестве аргумента. Давайте попробуем добавить тег pro:

go build -tags pro

Это выведет следующий результат:

> Free Feature #1
> Free Feature #2
> Pro Feature #1
> Pro Feature #2

Теперь мы получаем дополнительные функции приложения только при сборке с использованием тега pro build.

Это хорошо, если у вас есть только две версии. Но когда вы добавляете больше версий и тегов, все становится сложнее. Чтобы добавить версию Enterprise, мы будем использовать несколько тегов сборки, объединенных с помощью булевой логики.

Теги сборки и булева логика

Когда в пакете Go находится несколько тегов сборки, они взаимодействуют друг с другом с помощью логики. Чтобы продемонстрировать это, мы добавим уровень Enterprise, используя теги pro и enterprise.

Чтобы создать двоичный файл Enterprise, нам нужно включить как функции по умолчанию и функции уровня Pro, так и новый набор функций для Enterprise. Сначала откройте редактор и создайте новый файл enterprise.go, в который будут добавлены новые функции Enterprise:

nano enterprise.go

Содержимое enterprise.go будет выглядеть почти идентично файлу pro.go, но будет содержать новые функции. Добавьте следующие строки в файл:

package main
func init() {
features = append(features,
"Enterprise Feature #1",
"Enterprise Feature #2",
)
}

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

В настоящее время файл enterprise.go не имеет никаких тегов сборки, и, как вы уже видели при добавлении pro.go, это означает, что эти функции будут добавлены в бесплатную версию при выполнении команды go.build. Для уровня pro.go вы добавили тег (// +build pro) и новую строку в начало файла, чтобы сообщить команде go build, что этот файл должен включаться только при использовании -tags pro. В этой ситуации для достижения цели вам понадобился только один тег сборки. Однако при добавлении новых функций Enterprise у вас сначала должны быть функции уровня Pro.

Давайте сначала добавим поддержку тега pro build в файл enterprise.go. Откройте файл в текстовом редакторе:

nano enterprise.go

Затем добавьте тег сборки перед объявлением пакета package main  и обязательно оставьте новую строку после тега сборки:

// +build pro
.
package main
func init() {
features = append(features,
"Enterprise Feature #1",
"Enterprise Feature #2",
)
}

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

Скомпилируйте и запустите приложение без каких-либо тегов:

go build
./app

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

> Free Feature #1
> Free Feature #2

Функции Enterprise не отображаются в бесплатной версии. Теперь давайте добавим тег pro build, соберем и снова запустим приложение:

go build -tags pro
./app

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

> Free Feature #1
> Free Feature #2
> Enterprise Feature #1
> Enterprise Feature #2
> Pro Feature #1
> Pro Feature #2

Как видите, это не совсем то, что нам нужно: возможности Enterprise теперь открываются раньше, в версии Pro. Чтобы решить эту проблему, нам нужно использовать еще один тег сборки. В отличие от тега pro, этот тег должен убедиться, что доступны как функции pro, так и enterprise.

Система сборки Go учитывает эту ситуацию, позволяя использовать базовую булеву логику в системе тегов сборки.

Давайте снова откроем enterprise.go:

nano enterprise.go

Добавьте еще один тег сборки, enterprise, в ту же строку, что и тег pro:

// +build pro enterprise
.
package main
func init() {
features = append(features,
"Enterprise Feature #1",
"Enterprise Feature #2",
)
}

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

Теперь давайте скомпилируем и запустим приложение с новым тегом сборки enterprise.

go build -tags enterprise
./app

Это вернет следующее:

> Free Feature #1
> Free Feature #2
> Enterprise Feature #1
> Enterprise Feature #2

Теперь мы потеряли функции Pro. Это произошло потому, что когда мы помещаем несколько тегов сборки в одну строку в файле .go, go build интерпретирует их как логику OR. После добавления строки // +build pro enterprise файл enterprise.go будет создан при наличии тега pro build или enterprise build. Нам нужно правильно настроить теги сборки, чтобы требовать оба тега и использовать здесь логику AND, а не OR.

Если мы разместим теги в разных строках, а не в одной строке, то go build будет интерпретировать эти теги с помощью логики AND.

Откройте файл enterprise.go еще раз и разделите теги сборки на несколько строк.

// +build pro

// +build enterprise

.
package main
func init() {
features = append(features,
"Enterprise Feature #1",
"Enterprise Feature #2",
)
}

Теперь скомпилируйте и запустите приложение с тегом сборки enterprise.

go build -tags enterprise
./app

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

> Free Feature #1
> Free Feature #2

Приложение все еще работате не совсем так: поскольку оператор AND требует, чтобы оба элемента считались истинными, нам необходимо использовать теги сборки pro и enterprise.

Давайте попробуем еще раз:

go build -tags "enterprise pro"
./app

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

> Free Feature #1
> Free Feature #2
> Enterprise Feature #1
> Enterprise Feature #2
> Pro Feature #1
> Pro Feature #2

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

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

Синтаксис тега сборки Пример тега Логическое выражение
Разделенные пробелами элементы // +build pro enterprise pro OR enterprise
Разделенные запятыми элементы // +build pro,enterprise pro AND enterprise
Восклицательный знак // +build !pro NOT pro

Заключение

В этом мануале вы научились использовать теги сборки, чтобы контролировать компиляцию кода в двоичные файлы. Сначала вы объявили теги сборки и использовали их в команде go build, затем вы объединили несколько тегов с помощью булевой логики. Также вы создали программу, которая представляла различные наборы функций в версиях Free, Pro и Enterprise. Теперь с помощью тегов вы можете контролировать версии и функции вашего проекта.

Больше информации о тегах можно найти в документации Golang.

Tags: ,