Tutorial

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

GoDevelopment

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

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

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

Предварительные требования

Для выполнения примера из этой статьи вам потребуется следующее:

Возможные платформы GOOS и GOARCH

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

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

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

  • go tool dist list

Вы получите примерно следующий результат:

Output
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, произносится “goose”, название которой означает операционная система Go. Вторая часть, идущая после /, — это архитектура. Как и в случае с операционной системой, это все возможные значения для переменной среды: GOARCH. Это сокращение произносится “gore-ch” и означает архитектура Go.

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

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

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

  • go env GOOS GOARCH

При тестировании этого примера мы запускаем эту команду в macOS на компьютере с архитектурой AMD64, поэтому мы получим следующее:

Output
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:

  • nano main.go

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

src/app/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 вы увидите, что строки будут разделены \:

Output
a\b\c

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

Output
a/b/c

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

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

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

В результате вы увидите содержимое path_unix.go. Найдите следующую часть файла:

/usr/local/go/os/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

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

/usr/local/go/os/path_unix.go
. . .
package os

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

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

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

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

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

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

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

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

  • nano main.go

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

src/app/main.go
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 получает ряд частей и объединяет их вместе с помощью метода strings.Join() из пакета strings для конкатенации частей, используя PathSeparator.

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

nano path.go

Определите PathSeparator и установите его равным разделителю пути файла Unix /:

src/app/path.go
package main

const PathSeparator = "/"

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

  • go build
  • ./app

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

Output
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

Добавьте в файл выделенный маркер сборки:

src/app/path.go
// +build !windows

package main

const PathSeparator = "/"

Маркеры сборки Go позволяют использовать инвертирование, что означает, что вы можете указать Go выполнить сборку этого файла для любой платформы, кроме Windows. Чтобы инвертировать маркер доступа, добавьте ! перед маркером.

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

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

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

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

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

  • nano windows.go

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

src/app/windows.go
// +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, создайте бинарный файл windows для app, установив для переменной среды GOOS значение windows при запуске команды go build:

  • GOOS=windows go build

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

  • ls

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

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

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

  • file app.exe

Вы получите следующий результат:

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

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

  • GOOS=linux GOARCH=ppc64 go build

Теперь ваш исполняемый файл app будет заменен файлом для другой архитектуры. Запустите команду 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, чтобы ваш файл выглядел следующим образом:

path_windows.go
package main

const PathSeparator = "\\"

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

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

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

Заключение

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

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

Creative Commons License