Кросс-компиляция и запуск консольного Go‑приложения на Android, Windows, macOS и Linux
Я иногда пишу консольные утилиты на Go под Linux. Недавно я освоил кросс-компиляцию, и теперь они прекрасно работают на Android и Windows (и Linux само собой). В статье собран практический опыт кросс‑компиляции, подготовки релизной версии и развёртывания бинарника, плюс несколько подводных камней.
1. Консольные программы ещё живы
Они до сих пор в деле, и вряд ли это когда-то изменится:
сервисы, демоны;
утилиты для пайплайнов, cron, CI/CD;
криптоноды и майнеры.
Их легко:
оборачивать в shell‑/bat‑скрипты;
запускать в Docker;
дёргать из системных сервисов (
cron
,systemd
,launchd
и т.д.).
1.1. ARM‑процессоры и реальная мощность смартфонов
Современный ARM‑телефон по мощности одного ядра примерно соответствует одному логическому ядру десктопного компа. Меня восхищает архитектура ARM:
очень много регистров (в отличие от x86 и amd64);
ну и низкое энергопотребление.
Доступ к памяти DDR долгий, а к регистрам процессора практически мгновенный. Каждый, кто хоть раз программировал на ассемблере знает как сильно просаживается скорость программы с каждый обращением к памяти. На практике компиляторы редко выжимают из ARM > 30% от теоретического максимума (ИМХО) по причине их исторической заточки под архитектуры с меньшим числом регистров.
Кстати, возможно, высокая производительность эпловских процов M (arm64) — заслуга не только железа, но и компиляторов.
Но даже этих 20–30% от максимума хватает, чтобы консольный Go‑бинарник бодро крутился на телефоне и грел карман (в смысле денег, охлаждение всё-таки желательно).
Фактически, почти любой телефон можно превратить в сервер-малютку и запустить на нём что-то интересное. ARM-телефон, подключённый к розетке в режиме молотилки, потребляет всего около 7 Ватт (после того как зарядился).
2. Возможности Go по кросс‑компиляции
Go из коробки умеет кросс-компилировать без танцев бубном. Компилятор самодостаточен, ему не нужны gcc
/llvm
:
задаём платформу через переменную среды
GOOS
(linux
,windows
,darwin
,android
и др.);задаём архитектуру через переменную
GOARCH
(amd64
,arm64
,arm
и т.п.).
Простейший пример: собираем под Windows, находясь в Linux:
GOOS=windows GOARCH=amd64 go build -o app.exe console.go
Под Android arm64 (тот же исходник):
GOOS=android GOARCH=arm64 go build -o app-android console.go
2.1. Платформо‑специфичный код в Go
Go поддерживает платформо-специфичный код двумя основными способами:
build tags:
//go:build android
package main
суффиксы файлов:
net_windows.go
net_unix.go
net_android.go
Компилятор сам возьмёт нужный файл под нужную платформу, главное — корректно назвать.
2.2. Альтернативные компиляторы — Tinygo, gccgo, gollvm
Tinygo — совершенно прекрасная вещь, если вам нужно запустить Go на чайнике (микроконтроллере) или сделать компактный файл в WebAssembly (до 600КБ). Он генерит компактные бинарники. Сделан на бэкенде LLVM.
Обычный Go генерит слоноподобные бинарники для вебассемблера (от 7 МБ), которые, как правило, вообще не запускаются.
Хоть это особо не афишируется разработчиками (в справке поддерживаемых платформ нет, они боятся гнева Гугл) он умеет кросс-компилировать под amd64.
Но Tinygo поддерживает не все функции Go, например, есть проблемы с reflect
и парсингом JSON (json.Unmarshal). Я получал ошибку исполнения panic: reflect: unimplemented: AssignableTo with interface
.
Также он не поддерживает Go-ассемблер.
Формально есть ещё:
gccgo
gollvm
На текущий момент:
по уровню поддерживаемых фич они примерно на уровне Go 1.18 (Go-ассемблер поддерживается);
производительность — примерно в 9 раз медленнее обычного
go
;интеграция с экосистемой Go хуже.
Практического смысла в их использовании нет — штатного компилятора Go более чем достаточно. Задумывались с целью автоматического получения супероптимизаций (векторные регистры, bmi2) при компиляции от развитых бэкэндов gcc
и llvm
.
3. Кросс‑компиляция «голым» Go
3.1. Базовые переменные среды
Минимальный набор:
GOOS
— целевая ОС:linux
,windows
,darwin
,android
…GOARCH
— архитектура:amd64
,arm
,arm64
…
Примеры:
# Linux amd64
GOOS=linux GOARCH=amd64 go build -o app-linux-amd64 console.go
# Android arm (старые смартфоны)
GOOS=android GOARCH=arm go build -o app-android-arm console.go
# macOS (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o app-darwin-arm64 console.go
3.2. Проверить архитектуру скомпилированного бинарника
file myapp
# myapp: Mach-O 64-bit executable arm64
3.3. Полезные флаги компилятора
-a
— принудительно перекомпилировать без использования кеша (у меня выполняется 7 секунд):
GOOS=linux GOARCH=amd64 go build -a -o app console.go
Полезно при смене экспериментальных флагов, обновлении библиотек и т.п.
Переменная окружения
GOEXPERIMENT=greenteagc
— экспериментальный сборщик мусора (green tea GC):
GOEXPERIMENT=greenteagc GOOS=linux GOARCH=amd64 go build -o program console.go
По ощущениям новый сборщик мусора:
в среднем даёт ~6% ускорения;
иногда — до 30% в сценариях с активной аллокацией/GC.
Хоть у него экспериментальный статус, но в Гугле его активно используют и для своих утилит можно смело пробовать. У меня он работает отлично.
3.4. Уменьшаем размер и выкидываем личную информацию
3.4.1. -ldflags="-s -w"
GOOS=linux GOARCH=amd64 go build \
-ldflags="-s -w" \
-o app-small \
console.go
-s
— выкинуть символы отладчика;-w
— выкинуть DWARF‑информацию.
Результат — меньший бинарник, сложнее реверс-инжиниринг (хотя это и не обфускация).
3.4.2. -trimpath
go build -trimpath -o app-trim console.go
Удаляет локальные пути к исходникам из бинарника:
меньше «личных» путей вроде
/home/VasyaPupkin/project/...
;полезно, если распространяете бинарник.
Я заметного изменения размера/производительности не увидел, но в теории из бинарника убираются пути к вашим папкам разработки, из которых можно что-то понять личное о вас. Лично я эффекта от этой опции не увидел.
Как проверить:
strings ваш_бинарник | grep "Фамилия"
3.4. Пара слов о garble
garble
— инструмент для обфускации Go‑бинарников:
переименовывает символы;
режет отладочную информацию;
может усложнять реверс.
На практике:
идея хорошая;
но на момент написания мне не удалось заставить его оптимизировать/обфусцировать бинарник, собранный Go 1.25.3 —
garble
просто отказывался работать с исполняемыми файлами, собранными этой версией Go.
Если хотите использовать обфускацию от garble
, то придётся подбирать совместимую версию Go и не использовать экспериментальные флаги при компиляции.
4. Удобный инструмент для кросс‑компиляции: gox
gox
— маленькая утилита, которая:
параллельно собирает бинарники под разные
GOOS/GOARCH
;создаёт удобную структуру каталогов вида
os_arch/имя
.
Устанавливаем:
go install github.com/mitchellh/gox@latest
Она компилирует многопоточно — очень быстро под все нужные вам платформы.
4.1. Список платформ
Некоторые типичные значения для -osarch
:
gox -osarch "linux/amd64 linux/arm linux/arm64 windows/amd64 darwin/arm64" ...
4.2. Полезные ключи gox
Используем связку:
-osarch
— список таргетов;-output
— шаблон имени ({{.OS}}
,{{.Arch}}
);-ldflags
— передаются как есть вgo build
.
Пример:
GOEXPERIMENT=greenteagc gox \
-osarch "linux/amd64 linux/arm linux/arm64 windows/amd64 darwin/arm64" \
-ldflags="-s -w -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-output "cmd/console/builds/{{.OS}}_{{.Arch}}/app" \
console.go
4.3. Пример скрипта сборки с gox
Скрипт не самый маленький, поэтому в спойлере.
Скрипт gox для кросс-компиляции
#!/bin/bash
# Build script using gox for cross-compilation
# Creates: Linux amd64, Android 32-bit, Android 64-bit, Windows amd64
set -e # Exit on error
# Check if gox is installed
if ! command -v gox &> /dev/null; then
echo "gox is not installed. Installing..."
go install github.com/mitchellh/gox@latest
if [ $? -ne 0 ]; then
echo "Error: Failed to install gox"
exit 1
fi
echo "gox installed successfully"
fi
# Get version from config package
# Extract version from Go source file
VERSION=$(grep -E '^\s*Version\s*=\s*"[^"]+"' ../../config/app.go | sed 's/.*Version\s*=\s*"\([^"]*\)".*/\1/')
if [ -z "$VERSION" ]; then
echo "Error: Could not extract version from config/app.go"
exit 1
fi
echo "Building version: $VERSION"
# "0.1.0" -> "0_1_0"
VERSION_NUM=$(echo "$VERSION" | sed 's/\./_/g')
# Base directory for builds (in console directory)
BUILD_DIR="./builds/${VERSION}"
mkdir -p "$BUILD_DIR"
# Change to project root for building
cd ../..
# Output directory for gox (using gox structure: os_arch/filename)
OUTPUT_DIR="cmd/console/${BUILD_DIR}"
echo "Building with gox..."
# Build with greenteagc experiment and ldflags
GOEXPERIMENT=greenteagc gox \
-osarch "linux/amd64 linux/arm linux/arm64 windows/amd64 darwin/arm64" \
-ldflags="-s -w -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-output "$OUTPUT_DIR/{{.OS}}_{{.Arch}}/bm${VERSION_NUM}" \
./cmd/console
# Return to console directory
cd cmd/console
# Create zip archive
cd builds
zip bm${VERSION_NUM}.zip -r ${VERSION}
cd ..
echo ""
echo "Build complete! All executables are in: $BUILD_DIR"
echo "Version: $VERSION"
echo "Version number: $VERSION_NUM"
5. Особенности компиляции под Windows (и Windows 7 в частности)
Начиная с Go 1.21 официальная сборка Go перестала поддерживать Windows 7 / Windows Server 2008 R2. Для консольных утилит это до сих пор боль — много старых машин живут на этих ОС.
Практическое решение — использовать форк go-legacy-win7:
Это Go‑toolchain с:
поддержкой Windows 7/2008 R2;
откатами некоторых изменений рантайма/системных вызовов под старые WinAPI;
классическим поведением
go get
вне модулей;при этом в форк бэкпортируются фичи и фиксы из соответствующих версий официального Go.
Создатели сборки не рекомендуют на одной машине иметь 2 разные версии Go. Так как они могут начать портить друг другу совместные файлы (список модулей, кеш компиляции). Они рекомендуют использовать их версию, так как в неё уже портирован уроверь Go 1.25.4.
Я отказался от компиляции под Windows 7. Но если бы мне это было нужно, то настроил бы в контейнере.
6. Особенности Android
6.1. Основные архитектуры
Реально интересны две:
arm64
— ≈95% современных устройств (ARMv8‑A,arm64-v8a
);arm
— старые 32‑битные девайсы (ARMv7‑A,arm-v7a
), которые мало кому нужны, но безумно дёшевы (у большинства есть старые ненужные телефоны).
Соответственно, собираем, как минимум, под:
GOOS=android GOARCH=arm64 ...
GOOS=android GOARCH=arm ...
6.2. Termux — отличный эмулятор терминала под Android
Я запускаю Go‑бинарники на Android через Termux. Фактически, это целый Linux в вашем телефоне со своей системой пакетов. Но в Goole Play версия старая. Ставить нужно с GitHub. Ставить можно последнюю бету-версию — работает очень стабильно по моему опыту.
Android может «прибивать» процесс Termux, если вы на автономном питании, поэтому стоит добавить его в исключения по энергосбережению.
Termux живёт в своей песочнице /data/data/com.termux/...
— чтобы получить доступ из неё к «обычной» памяти (/storage/emulated/0/...
), нужно выполнить termux-setup-storage
.
6.3. Сеть, IPv6 и сертификаты
Если ваша Go-программа использует сеть, то 100%, что она не заработает в Termux с первого раза. Go на Android использует:
свою реализацию ДНС (через IP6), а не системный;
не имеет некоторых корневых сертификатов, например, те, которые нужны для Let’s Encrypt.
На практике это проявляется как:
невозможность разрешения ДНС-имён;
невозможность работы с защищёнными сайтами, подписанными Let's Encrypt.
Рабочий обходной путь:
использовать пакет
proot
и командуtermux-chroot
, чтобы подмонтировать нормальное окружение;явно указать
SSL_CERT_FILE
иSSL_CERT_DIR
на корректные сертификаты.
6.4. Инструкция по запуску консольных программ под Android/Termux (подробно)
Инструкция для «фанатов», которые хотят руками довести окружение до ума и запускать консольный Go‑бинарник
1. Установка Termux
Отключить Play Защиту (если мешает ставить apk не из Play):
Play Маркет → Профиль (иконка вверху справа) → Play Защита → отключить проверки (можно временно).
Скачать Termux не из Play Market, а с релизов GitHub:
https://github.com/termux/termux-app/releases
Выбрать apk по архитектуре:
arm64-v8a.apk
— для современных 64‑битных телефонов (99% случаев);v7a.apk
— для старых 32‑битных;если сомневаетесь —
universal.apk
.
Установить apk как обычное приложение.
Запустить Termux и по желанию обновить пакеты:
pkg update -y && pkg upgrade -y
2. Первичная инициализация Termux
Однократно выполняем:
termux-setup-storage # права на доступ к памяти устройства
pkg install termux-api # по желанию, для доступа к API Android
termux-wifi-connectioninfo # запросит разрешения, можно прервать Ctrl-C, если завис
Даём все запрошенные разрешения.
6.4.3. proot / chroot и сертификаты
Установить
proot
:
pkg install proot
Запустить «chroot‑подобную» среду:
termux-chroot
Настроить переменные окружения для сертификатов (пути могут отличаться, пример):
export SSL_CERT_FILE=/etc/tls/cert.pem
export SSL_CERT_DIR=/etc/tls
Дальше Go‑приложения будут использовать эти сертификаты для TLS.
4. Копируем бинарник на телефон
Для удобства можно использовать Total Commander (из Play Market):
Скачиваем с сервера/Telegram архив с бинарниками, например
app011.zip
.Открываем архив в Total Commander.
На второй панели (свайп влево/вправо) создаём папку, например
prg
в «Память устройства».Копируем соответствующий бинарник в папку
prg
:linux_arm64
/android64
— для новых смартфонов;linux_arm
— для старых.
Итог: в памяти устройства есть что‑то вроде:
/storage/emulated/0/prg/app011
5. Настраиваем запуск в Termux
В Termux:
cp /storage/emulated/0/prg/app011 app011
chmod +x app011
./app011
Если Android любит выгружать Termux:
добавляем приложение Termux в исключения по батарее (на Android 11+ это встречается часто).
6. Автоматизация
Идеально это всё завернуть в скрипт:
установить дополнение Termux:Widget (из F-Droid или Google Play);
положить shell‑скрипт в
~/.shortcuts
или~/.shortcuts/tasks
(в зависимости от версии);вызывать всё одной кнопкой на виджете.
Пример содержимого скрипта (идея, не окончательный вариант):
#!/data/data/com.termux/files/usr/bin/bash
termux-chroot
export SSL_CERT_FILE=/etc/tls/cert.pem
export SSL_CERT_DIR=/etc/tls
cd /data/data/com.termux/files/home
./app011
После первой настройки дальнейший запуск сводится к одному нажатию или клик по «стрелке вверх» в Termux, чтобы повторить последнюю команду.
7. Перезагрузка телефона
После перезагрузки:
переменные окружения Termux теряются;
termux-chroot
нужно снова запускать вручную;export SSL_CERT_FILE=...
иexport SSL_CERT_DIR=...
снова прописывать (или переложить в~/.bashrc
/ скрипт‑виджет).
6.5. Автоматическая настройка окружения в Go‑коде
Часть рутины можно переложить на саму программу: если она видит, что оно на ARM и переменные сертификатов не заданы, то выставляет их сама.
Пример:
package app
import (
"os"
"runtime"
)
func (app *App) setupEnvironment() {
// Проверяем архитектуру
arch := runtime.GOARCH
if arch != "arm64" && arch != "arm" {
return // Не ARM архитектура, ничего не делаем
}
// Проверяем, установлена ли переменная SSL_CERT_FILE
if os.Getenv("SSL_CERT_FILE") == "" {
// Устанавливаем переменные окружения
_ = os.Setenv("SSL_CERT_FILE", "/etc/tls/cert.pem")
_ = os.Setenv("SSL_CERT_DIR", "/etc/tls")
}
}
Так программа сама подстраивает окружение на Android/ARM, если пользователь забыл экспортировать переменные в Termux.
7. Заключение и основные моменты
Итак, консольные утилиты на Go живут отлично на Linux, Windows, macOS и, с оговорками, на Android. А встроенная кросс‑компиляция Go плюс gox
делает мультиплатформенную сборку тривиальной.
Компиляцию под Windows 7/2008 R2 нам поможет сделать форк go-legacy-win7.
На Android же для запуска консольных программ главное — дать права Termux, выставить переменные среды для SSL и запустить termux-chroot
.
Да пребудет с вами сила go build
!
© 2025 ООО «МТ ФИНАНС»