golang

Кросс-компиляция и запуск консольного Go‑приложения на Android, Windows, macOS и Linux

  • суббота, 22 ноября 2025 г. в 00:00:05
https://habr.com/ru/companies/ruvds/articles/968178/
Компилятор Go хорошо и быстро кросс-компилирует
Компилятор Go хорошо и быстро кросс-компилирует

Я иногда пишу консольные утилиты на 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

Пример компиляции gccgo с оптимизацией под текущую архитектуру:

# Компиляция через прямой вызов gccgo
go build -compiler=gccgo -tags=release  -gccgoflags '-march=native -O3' -o app app.go./cmd/console

На текущий момент:

  • по уровню поддерживаемых фич они примерно на уровне 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.3garble просто отказывался работать с исполняемыми файлами, собранными этой версией 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

  1. Отключить Play Защиту (если мешает ставить apk не из Play):

    • Play Маркет → Профиль (иконка вверху справа) → Play Защита → отключить проверки (можно временно).

  2. Скачать Termux не из Play Market, а с релизов GitHub: https://github.com/termux/termux-app/releases

  3. Выбрать apk по архитектуре:

    • arm64-v8a.apk — для современных 64‑битных телефонов (99% случаев);

    • v7a.apk — для старых 32‑битных;

    • если сомневаетесь — universal.apk.

  4. Установить apk как обычное приложение.

  5. Запустить 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 и сертификаты

  1. Установить proot:

pkg install proot
  1. Запустить «chroot‑подобную» среду:

termux-chroot
  1. Настроить переменные окружения для сертификатов (пути могут отличаться, пример):

export SSL_CERT_FILE=/etc/tls/cert.pem
export SSL_CERT_DIR=/etc/tls

Дальше Go‑приложения будут использовать эти сертификаты для TLS.

4. Копируем бинарник на телефон

Для удобства можно использовать Total Commander (из Play Market):

  1. Скачиваем с сервера/Telegram архив с бинарниками, например app011.zip.

  2. Открываем архив в Total Commander.

  3. На второй панели (свайп влево/вправо) создаём папку, например prg в «Память устройства».

  4. Копируем соответствующий бинарник в папку 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 ООО «МТ ФИНАНС»