Сборка приложений Go для разных операционных систем и архитектур

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

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

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

Требования

Для работы вам понадобится среда разработки Go:

Доступные платформы для GOOS и GOARCH

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

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

Чтобы просмотреть список платформ, выполните следующую команду:

go tool dist list

Вы получите подобный вывод:

aix/ppc64        freebsd/amd64   linux/mipsle   openbsd/386
android/386      freebsd/arm     linux/ppc64    openbsd/amd64
android/amd64    illumos/amd64   linux/ppc64le  openbsd/arm
android/arm      js/wasm         linux/s390x    openbsd/arm64
android/arm64    linux/386       nacl/386       plan9/386
darwin/386       linux/amd64     nacl/amd64p32  plan9/amd64
darwin/amd64     linux/arm       nacl/arm       plan9/arm
darwin/arm       linux/arm64     netbsd/386     solaris/amd64
darwin/arm64     linux/mips      netbsd/amd64   windows/386
dragonfly/amd64  linux/mips64    netbsd/arm     windows/amd64
freebsd/386      linux/mips64le  netbsd/arm64   windows/arm

Эти выходные данные представляют собой набор пар ключ-значение, разделенных символом /. Первая часть комбинации, перед слешем – это операционная система. В Go операционные системы являются доступными значениями для переменной среды GOOS, что означает Go Operating System. Вторая часть, после слеша, это архитектура. Эта часть показывает доступные значения для переменной среды GOARCH, что означает Go Architecture.

Давайте разберем одну из этих комбинаций, чтобы понять, что она означает и как она работает. Для примера можно взять пару linux/386. Пара ключ-значение начинается с GOOS, в этом примере это linux, что ссылается на ОС Linux. GOARCH здесь – 386, что указывает на микропроцессор Intel 80386.

Существует много платформ, доступных с помощью команды go build, но в основном в качестве значения GOOS вы будете использовать linux, windows или darwin. Они охватывают три большие платформы ОС: Linux, Windows и macOS, которая основаны на операционной системе Darwin и поэтому называются darwin. Однако Go может охватывать и менее распространенные платформы, такие как nacl, Google’s Native Client.

Когда вы запускаете команду типа go build, Go использует GOOS и GOARCH текущей платформы, чтобы определить, как собирать двоичный файл. Чтобы узнать, какая комбинация подходит вашей платформе, вы можете использовать команду go env и передать GOOS и GOARCH в качестве аргументов:

go env GOOS GOARCH

Мы запустили эту команду на macOS, на машине с архитектурой AMD64, поэтому мы получили такой вывод:

darwin
amd64

Здесь выходные данные команды говорят нам, что система имеет переменные GOOS=darwin и GOARCH=amd64.

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

Создание зависимой от платформы программы с помощью filepath.Join()

Прежде чем приступить к созданию бинарных файлов для других платформ, давайте создадим простую тестовую программу. Для этого можно использовать функцию Join, которая доступна в пакете path/filepath стандартной библиотеки Go. Эта функция принимает несколько строк и возвращает одну строку, которая соединяется правильным разделителем пути к файлу.

Это хорошая тестовая программа, потому что ее работа зависит от того, на какой ОС она запускается. В Windows в качестве разделителя пути используется обратный слеш, \, а в Unix-системах используется обычный слеш /.

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

Сначала в каталоге src создайте папку и назовите ее именем вашего приложения:

mkdir app

Перейдите в нее:

cd app

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

nano main.go

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

package main
import (
"fmt"
"path/filepath"
)
func main() {
s := filepath.Join("a", "b", "c")
fmt.Println(s)
}

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

Сохраните и закройте файл, затем запустите программу:

go run main.go

При запуске этой программы вы получите уникальный вывод – он зависит от используемой вами платформы. В Windows вы увидите строки, разделенные обратным слешем, \:

a\b\c

В системах Unix, таких как macOS и Linux, вы получите следующее:

a/b/c

Как видите, из-за разных протоколов файловой системы, используемых в этих операционных системах, программе придется создавать разный код для разных платформ. Но поскольку программа уже использует разные разделители файлов в зависимости от ОС, мы знаем, что filepath.Join() уже учитывает разницу в платформах. Это связано с тем, что цепочка инструментов Go автоматически обнаруживает GOOS и GOARCH вашего компьютера и использует эту информацию для выбора фрагмента кода с правильными тегами сборки и разделителем файлов.

Давайте посмотрим, где функция filepath.Join() находит разделитель. Запустите следующую команду, чтобы проверить соответствующий фрагмент из стандартной библиотеки Go:

less /usr/local/go/src/os/path_unix.go

Эта команда вернет содержимое path_unix.go. Найдите в нем эту часть:

. . .
// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package os
const (
PathSeparator     = '/' // OS-specific path separator
PathListSeparator = ':' // OS-specific path list separator
)
. . .

В этом разделе определяется PathSeparator для всех разновидностей Unix-подобных систем, которые поддерживает Go. Обратите внимание на все теги сборки в верхней части, которые отображают все доступные платформы Unix GOOS,. Когда GOOS соответствует этим условиям, ваша программа выдает разделитель пути к файлу в стиле Unix.

Нажмите q, чтобы вернуться в командную строку.

Затем откройте файл, который определяет поведение filepath.Join() в Windows:

less /usr/local/go/src/os/path_windows.go

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

. . .
package os
const (
PathSeparator     = '\\' // OS-specific path separator
PathListSeparator = ';'  // OS-specific path list separator
)
. . .

Хотя значение PathSeparator здесь \\, код будет отображать один обратный слеш (\), необходимый для файловых путей Windows, а первый слеш здесь используется только в качестве escape-символа.

Обратите внимание: в отличие от файла Unix, в верхней части этого файла нет тегов сборки. Это связано с тем, что GOOS и GOARCH можно также передать в сборку, добавив подчеркивание (_) и значение переменной среды в качестве суффикса к имени файла (о чем мы поговорим подробнее в следующих разделах мануала). Здесь часть _windows в path_windows.go заставляет файл действовать так, как если бы он имел тег сборки // +build windows  в верхней части файла. Поэтому когда программа запускается в Windows, она будет использовать константы PathSeparator и PathListSeparator из фрагмента кода path_windows.go.

Чтобы вернуться в командную строку, нажмите q.

Итак, вы создали программу, которая показала, как Go автоматически преобразует GOOS и GOARCH в теги сборки. Имея это в виду, вы теперь можете обновить свою программу и написать собственную реализацию функции filepath.Join(), используя теги сборки, чтобы вручную установить правильный PathSeparator для платформ Windows и Unix.

Реализация функции, зависимой от платформы

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

Откройте файл main.go:

nano main.go

Замените содержимое main.go на следующее, используя вашу собственную функцию Join():

package main
import (
"fmt"
"strings"
)
func Join(parts ...string) string {
return strings.Join(parts, PathSeparator)
}
func main() {
s := Join("a", "b", "c")
fmt.Println(s)
}

Функция Join принимает несколько parts и соединяет их вместе, используя метод strings.Join() из пакета strings, чтобы объединить части пути в одну строку с помощью PathSeparator.

Вы еще не определили PathSeparator. Сделайте это сейчас в другом файле. Сохраните и выйдите из main.go, откройте ваш редактор и создайте новый файл path.go:

nano path.go

Определите PathSeparator и задайте разделитель пути файла Unix, /:

package main
const PathSeparator = "/"

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

go build
./app

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

a/b/c

Все работает, и вы можете получить путь к файлу в стиле Unix. Но это не все, чего мы хотим: сейчас выводится только a/b/c, независимо от того, на какой платформе работает программа. Чтобы добавить  функциональность для создания файловых путей в стиле Windows, вам необходимо добавить версию PathSeparator для Windows и сообщить команде go build, какую версию использовать. В следующем разделе мы применим теги сборки для достижения этой цели.

Использование тегов сборки GOOS или GOARCH

Для учета платформ Windows мы создадим альтернативный файл path.go и будем использовать теги сборки, чтобы фрагменты кода выполнялись только тогда, когда в GOOS и GOARCH указана подходящая платформа.

Сначала добавьте тег сборки в path.go, чтобы обозначить, что он собирается для всех платформ, кроме Windows. Откройте файл:

nano path.go

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

// +build !windows
package main
const PathSeparator = "/"

Теги сборки Go позволяют инвертировать (то есть вы можете настроить Go для сборки этого файла для любой платформы, кроме Windows). Чтобы инвертировать тег сборки, поместите перед ним восклицательный знак.

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

Теперь, если вы запустите эту программу в Windows, вы получите следующую ошибку:

./main.go:9:29: undefined: PathSeparator

В этом случае Go не может включить path.go для определения переменной PathSeparator.

Теперь, когда вы убедились, что path.go не будет работать, если в GOOS указана система Windows, добавьте новый файл windows.go:

nano windows.go

В windows.go определите PathSeparator, а также тег сборки, чтобы команда go build знала, что это реализация Windows:

// +build windows
package main
const PathSeparator = "\\"

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

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

Использование локальных переменных GOOS и GOARCH

Ранее вы запускали команду go env GOOS GOARCH, чтобы выяснить, с какой ОС и архитектурой вы работали. Запущенная нами команда go env искала две переменные окружения GOOS и GOARCH; если она находит значения, она их использует, но если их нет, Go установит в них информацию для текущей платформы. Так вы можете изменить GOOS или GOARCH и установить значения, которые не относятся к вашей локальной ОС и архитектуре по умолчанию.

Команда go build ведет себя аналогично команде go env. Вы можете установить переменные среды GOOS или GOARCH для сборки для другой платформы, используя go build.

Если на локальной машине вы не используете систему Windows, создайте двоичный файл приложения, установив для переменной среды GOOS значение windows при запуске команды go build:

GOOS=windows go build

Теперь просмотрите файлы в вашем текущем каталоге:

ls

Вывод показывает, что в каталоге проекта теперь есть исполняемый файл app.exe для Windows:

app  app.exe  main.go  path.go  windows.go

С помощью команды file вы можете получить больше информации об этом файле, в том числе и о его сборке:

file app.exe
app.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

Вы также можете установить одну или обе переменные среды во время сборки. Запустите следующее:

GOOS=linux GOARCH=ppc64 go build

Ваш исполняемый файл приложения будет заменен файлом для другой архитектуры. Запустите команду file для этого двоичного файла:

file app

Вы получите подобный вывод:

app: ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, not stripped

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

Суффиксы имен GOOS и GOARCH

Как вы видели ранее, стандартная библиотека Go активно использует теги сборки для упрощения кода путем разделения реализаций разных платформ на разные файлы. Когда вы открыли файл os/path_unix.go, вы видели тег сборки, перечисляющий все возможные комбинации, которые считаются Unix-подобными платформами. Однако файл os/path_windows.go не содержал тегов сборки, поскольку суффикса имени файла было достаточно, чтобы указать Go, для какой платформы предназначен файл.

Давайте посмотрим на синтаксис этой функции. При именовании файла .go вы можете добавить GOOS и GOARCH в качестве суффиксов к имени файла в указанном порядке. Значение должно разделяться подчеркиванием (_). Если у вас есть файл Go по имени filename.go, вы можете указать ОС и архитектуру, изменив имя файла на filename_GOOS_GOARCH.go. Например, если вы хотите скомпилировать файл для Windows с 64-битной архитектурой ARM, вы должны указать имя filename_windows_arm64.go. Это соглашение об именах помогает поддерживать код в чистоте и порядке.

Обновите вашу программу, добавьте суффиксы имен файлов вместо тегов сборки. Сначала переименуйте файлы path.go и windows.go, чтобы использовать соглашение пакета os:

mv path.go path_unix.go
mv windows.go path_windows.go

Изменив имена двух фалов, вы можете удалить тег сборки, который вы добавили в path_windows.go:

nano path_windows.go

Удалите // + build windows, чтобы ваш файл выглядел так:

package main
const PathSeparator = "\\"

Поскольку unix не является действительным значением GOOS, суффикс _unix.go ничего не значит для компилятора Go. Однако он передает предполагаемую цель файла. Как и в os/path_unix.go, в файле path_unix.go должны использоваться теги сборки, поэтому оставьте этот файл без изменений.

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

Заключение

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

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

Tags: ,