Технологии

Кросс-компиляция и запуск консольного 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 ООО «МТ ФИНАНС»

Фильтры и сортировка