Golang compile for windows

Время на прочтение9 мин

Количество просмотров114K

Несмотря на то, что кроссплатформенность стала фактически стандартным атрибутом практически всех современных языков и библиотек, создавать по-настоящему кроссплатформенный продукт, всё равно было непросто. Компилируемые языки и сопутствующие библиотеки требовали сложной установки и настройки среды сборки и библиотек, а интерпретируемые — обязывали иметь или деплоить в составе необходимую версию интерпретатора. Есть немало проектов, пытающихся сделать этот процесс чуть более простым, но зачастую единственным решением оставалось устанавливать отдельный сервер и компилировать нативно.

В Go кросс-платформенность вышла на тот уровень, когда впервые можно смело отказаться от compile farms, специально настроенных dev-сред, виртуальных машин для сборки или chroot/docker-dev решений. И это ещё один серьезный game-changer, подробнее о котором я и хочу рассказать и показать на примерах
Поехали.

Как известно, в Go сознательно отказались от динамической линковки — по ряду причин, основная из которых очень схожа с обычным объяснением дизайна почти любого аспекта Go — «преимущества [динамической линковки] намного меньше её недостатков и сложности, которая она привносит в архитектуру». Что ж, главной причиной появления dynamic linking было желание экономить ресурсы — прежде всего диcковое пространство и память — которые сейчас достаточно дешевы, не только на серверах, но и в embedded-устройствах (коптеры, к примеру, несут на борту уже по 1-2 Гб RAM!). Вобщем, перечислять плюсы и минусы отдельного способа линковки — это потянет на отдельный пост, так что пока просто принимаем, как есть — в Go на выходе всегда имеем статический бинарник.

На данный момент для актуальной версии Go 1.4.1 реализована поддержка следующих платформ:

  • Linux 2.61 и выше — amd64, 386, arm
  • MacOS X 10.6 и выше — amd64, 386
  • Windows XP и выше — amd64, 386
  • FreeBSD 8 и выше — amd64, 386, arm
  • NetBSD — amd64, 386, arm
  • OpenBSD — amd64, 386
  • DragonFly BSD — amd64, 386
  • Plan 9 — amd64, 386
  • Google Native Client — amd64p32, 386
  • Android — arm

1 — официально поддерживаются ядра 2.6.23 и выше, но в реальности всё работает и на более ранних ядрах ветки 2.6 — к примеру немало людей используют Go на RHEL5/CentOS5 с 2.6.18.

В Go 1.5 ожидается поддержка iOS.
Еще примечательно, что изначально поддержки Windows в Go не было — команда маленькая, и

пачкать руки

заниматься имплементацией кода для Windows было некому, но благодаря тому, что проект открыли для open-source разработки — порт для Windows был очень быстро написан сторонними людьми и интегрирован в официальную кодовую базу.

Хотя описанные далее процессы будут абсолютно одинаковы для всех платформ (за исключеним, разве что, Android и Native Client (NaCl), для которых нужны лишние телодвижения), далее в статье будет по-умолчанию считаться, что вы используете одну из трех самых популярных десктопных платформ — Linux, MacOS X или Windows. Кроме того, для большей простоты я буду подразумевать, что мы пишем и используем исключительно Go-код, без необходимости линковаться с С-библиотеками (и, тем самым, без необходимости использовать cgo/gcc). Есть еще отдельный кейс — когда нужно использовать ряд функций из стандартной библиотеки, завязанных на cgo, но об этом я напишу отдельной главой в конце.

Подготовка toolchain

Первый шаг, который необходимо выполнить — это собрать toolchain для нужной платформы.

Переходим в директорию с исходным кодом Go (она же $GOROOT/src, она же всегда есть у вас на машине) и пересобираем под нужную платформу, скажем Windows/amd64:

cd $(go env GOROOT)/src
sudo GOOS=windows GOARCH=amd64 CGO_ENABLED=0 ./make.bash --no-clean

Процесс занимает на Macbook Air 2012 около 26 секунд. Скрипт make.bash — это стандартный скрипт сборки Go, которым бы вы инсталлировали Go, если бы ставили из исходников. Он собирает, собственно, Go, и всю стандартную библиотеку, только в этот раз — для платформы windows/amd64.
Также, по упомянутой выше причине, мы отключили поддержку CGO.

Значения GOOS и GOARCH

Таблица значений GOOS (если кто знает, как на Хабре сделать таблица в 50% ширины — подскажите):

OS $GOOS
Linux linux
MacOS X darwin
Windows windows
FreeBSD freebsd
NetBSD netbsd
OpenBSD openbsd
DragonFly BSD dragonfly
Plan 9 plan9
Native Client nacl
Android android

И GOARCH:

Architecture $GOARCH
x386 386
AMD64 amd64
AMD64 с 32-указателями amd64p32
ARM arm

Пример 1. Веб-сервер, написанный и собранный в Linux для Windows

Напишем простенький веб-сервер, который в Go писать проще, чем в некоторых языках/библиотеках парсить командную строку.

package main

import (
	"log"
	"net/http"
)

func Handler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, world\n"))
}

func main() {
	http.HandleFunc("/", Handler)

	log.Println("Starting HTTP server on :1234")
	log.Fatal(http.ListenAndServe(":1234", nil))
}

И соберем его для Windows 32- и 64-bit:

GOOS=windows GOARCH=386 go build -o http_example.exe
GOOS=windows GOARCH=amd64 go build -o http_example64.exe

Проверяем:

$ file http_example*.exe
http_example.exe:   PE32 executable for MS Windows (console) Intel 80386 32-bit
http_example64.exe: PE32+ executable for MS Windows (console) Mono/.Net assembly

Думаю, не нужно говорить, что оба бинарника готовы к копированию на целевую Windows-систему и будут работать.

Пример 2. Кросс-компиляция под ARM для телефона Nokia N9

Сразу скажу, что сейчас я с embedded-девайсами плотно не работаю, поэтому могу какие-то детали не знать — так что постараюсь не углубляться в эту тему, но в целом за ситуацией с Go на embedded слежу. Вообще говоря, Go не позиционировался как язык для embedded-платформ, что, впрочем, не помешало народу активно начать его использовать в этой области. Возможно, причина в том, что embedded-индустрия сделала скачок вперед, и теперь «встраиваемое» устройство уже не означает критически малое количество ресурсов, а возможно компромиссы не в пользу экономии памяти в Go оказались гораздо менее ощутимыми на практике, но факт есть факт — для Go уже создано масса проектов вроде Gobot (robotics-фреймворк для целой кучи платформ — от Arduino, Raspberry PI и Beaglebone Back до LeapMotion, Pebble и ArDrone) или EMBD (фреймворк для работы с hobby-бордами), а PayPal уже пару лет использует Go в своем beacon-девайсе для беспроводных чекинов и платежей.

Для примера возьмем Nokia N9 (или N950, кому повезло) — и соберем вышеприведенный пример для него:

GOOS=linux GOARCH=arm go build -o http_example_arm
scp http_example_arm developer@192.168.2.16:/home/user/

Вот так просто, да.

Для ARM-платформ, на самом деле, может понадобиться еще указывать флаг GOARM, но тут, если версия по-умолчанию не подходит, бинарник на целевой платформе выдаст понятное сообщение, вроде такого:

runtime: this CPU has no floating point hardware, so it cannot 
run this GOARM=7 binary. Recompile using GOARM=5.

Автоматизируем процесс

Казалось бы, что может быть проще указания одной переменной перед go build. Но есть ситуации, когда код нужно собирать и деплоить на разные платформы по 100 раз в день. Для таких задач есть несколько проектов, для автоматизации процессов подготовки toolchain-ов и, непосредственно, сборки кода под нужную платформу.

Gox

Ссылка: github.com/mitchellh/gox
Инсталляция и подготовка сразу всех возможных toolchain-ов:

go get github.com/mitchellh/gox
gox -build-toolchain
...

Теперь, вместо «go build», пишем «gox»:

$ gox
Number of parallel builds: 4

-->      darwin/386: github.com/mitchellh/gox
-->    darwin/amd64: github.com/mitchellh/gox
-->       linux/386: github.com/mitchellh/gox
-->     linux/amd64: github.com/mitchellh/gox
-->       linux/arm: github.com/mitchellh/gox
-->     freebsd/386: github.com/mitchellh/gox
-->   freebsd/amd64: github.com/mitchellh/gox
-->     openbsd/386: github.com/mitchellh/gox
-->   openbsd/amd64: github.com/mitchellh/gox
-->     windows/386: github.com/mitchellh/gox
-->   windows/amd64: github.com/mitchellh/gox
-->     freebsd/arm: github.com/mitchellh/gox
-->      netbsd/386: github.com/mitchellh/gox
-->    netbsd/amd64: github.com/mitchellh/gox
-->      netbsd/arm: github.com/mitchellh/gox
-->       plan9/386: github.com/mitchellh/gox

Можно указывать конкретный пакет или конкретную платформу:

gox -os="linux"
gox -osarch="linux/amd64"
gox github.com/divan/gorilla-xmlrpc/xml

Остальные аргументы командной строки идентичны go build. Достаточно интуитивно.

GoCX

GoCX — это один из самых известных врапперов вокруг фич кросс-компиляции, но с упором на пакаджинг (умеет делать .deb даже) и различные плюшки для автоматизированных сборок. Сам не пользовал, поэтому, кому интересно, смотрите сайт и документацию.
github.com/laher/goxc

Разбираемся с CGO

Если кто-то смотрел видео с конференции GopherCon 2014, которая проходила прошлой весной в Денвере, то, возможно, помнит выступление Alan Shreve «Build Your Developer Tools in Go» — и одну из вещей, которую он говорит достаточно категорично: «не используйте кросс-компиляцию, компилируйте нативно». Дальше идет объяснение — причина в Cgo. Если вам не нужно использовать cgo — все окей. И на самом деле, очень малая часть очень специфичного кода в Go нуждается в сторонних С-библиотеках. В чем же проблема?

Проблема в том, что некоторые функции стандартной библиотеки зависят от cgo. Тоесть, если мы собираем Go с CGO_ENABLED=0, они просто не будут доступны и на этапе компиляции мы получим ошибку. Несмотря на то, что тут есть очень удобный и красивый workaround, давайте разберемся, что же именно в стандартной библиотеке зависит от cgo.

К счастью, сделать это просто:

# cd $(go env GOROOT)/src/
# grep  -re "^// +build.*[^\!]cgo" *
crypto/x509/root_cgo_darwin.go:// +build cgo
net/cgo_android.go:// +build cgo,!netgo
net/cgo_linux.go:// +build !android,cgo,!netgo
net/cgo_netbsd.go:// +build cgo,!netgo
net/cgo_openbsd.go:// +build cgo,!netgo
net/cgo_unix_test.go:// +build cgo,!netgo
os/user/lookup_unix.go:// +build cgo
runtime/crash_cgo_test.go:// +build cgo

Вкратце пройдемся по этим файлам:

  • crypto/x509/root_cgo_darwin.go — имплементирует одну функцию для получения корневых X.509 сертификатов в MacOS X. Если вы не используете явно эту фичу — ничего страшного, без cgo у вас все будет работать.
  • net/cgo_android/linux/netbsd/openbsd/cgo_unix_test.go — код необходимый для использования нативного DNS-резолвера в разных unix-ах. Чуть ниже подробности.
  • os/user/lookup_unix.go — функции из пакета os/user — для получения информации о текущем юзере (uid, gid, username). Используется getpwuid_r() для чтения passwd-записей
  • runtime/crash_cgo_test.go — файл с тестами для хендлинга крешей, ничего релевантного

Теперь подробнее про DNS-resolver.
Каждый файл из того списка (который скомпилируется только для своей платформы благодаря тегам // +build) содержит имплементацию единственной функции cgoAddrInfoFlags(), которая, в свою очередь, используется в cgoLookupIP(), которая, используется в dnsclient_unix.go, в котором мы находим функцию goLookupIP(), которая служит fallback-вариантом при отсутствии cgo-enabled кода, и тут же находим объяснение:

// goLookupIP is the native Go implementation of LookupIP.
// Used only if cgoLookupIP refuses to handle the request
// (that is, only if cgoLookupIP is the stub in cgo_stub.go).
// Normally we let cgo use the C library resolver instead of
// depending on our lookup code, so that Go and C get the same
// answers.

goLookupIP фактически резолвит только по Hosts-файлу и по DNS-протоколу, что для большинства систем — ок. Но могут быть проблемы, если в системе будут использоваться нестандартные методы резолвинга имён. Полагаю, что в 99% случаев, hosts и dns будут более, чем достаточно.

В сухом остатке имеем — если ваш код не использует С/С++-библиотеки через Cgo, и не использует следующие две вещи:

  • проверку x.509 сертификатов, которая должна работать на MacOS X
  • гарантированно получать системную информацию о текущем юзере

то на все заморочки с Cgo можно забить.

Первая часть (с X.509) на самом деле не такая уж редкая. Если я правильно понимаю — этот код нужен, если ваша программа использует стандартный net/http.StartAndListenTLS() — и вы используете реальные сертификаты, которые реально нужно проверять.

Поэтому вкратце о простом workaround вокруг этой темы — называется он gonative, и делает одну простую вещь — скачивает с официального сайта бинарные версии golang нужной версии для нужной платформы, в которой уже есть скомпилированные бинарники всех стандартных пакетов и, фактически, завершает процесс «собрать toolchain с cgo-кодом».
Всё что нужно сделать, это установить её (go get github.com/inconshreveable/gonative) и выполнить одну простую команду:

gonative

И дальше использовать стандартные процедуры кросскомпиляции, как и раньше, ручками или через gox/gocx.
Подробнее о gonative тут: inconshreveable.com/04-30-2014/cross-compiling-golang-programs-with-native-libraries

Практическое применение

Теперь о главном — применении на практике. Я использовал в продакшене пока только три схемы — «сборка на darwin/amd64 -> деплой на linux/386», «linux/amd64 -> linux/386» и «linux/amd64 -> windows/amd64». Это продукты, которые уже больше года полноценно работают. Третий случай (деплой на windows) тогда меня вообще застал врасплох — был сервер, успешно бегущий на Linux, и тут вдруг резко понадобилось его запускать на Windows. Причем «вот срочно надо». Вспоминая бессонные ночи опыта с кросс- — да что там кросс, просто с компиляцией Qt для деплоя на Windows — 60-секундный процесс «гуглим как это делается → сборка toolchain → перекомпиляция проекта → деплой на windows» — стал просто шоком, я тогда даже не поверил глазам.

Но тут возникает следующий момент — раз кросс-компиляция и деплой становятся такими простыми и быстрыми, появляется стимул все зависимости от файлов — будь-то конфиги, сертификаты или что угодно еще — встраивать в бинарник тоже. Впрочем, это достаточно простая задача, даже для сторонних библиотек, благодаря эффективному использованию io.Reader интерфейса и пакету go-bindata, но это уже тема для отдельной статьи.

Надеюсь, ничего из главного не упустил.
Но в целом это на самом деле очень существенная разница со всем предыдущим опытом кросс-сборки. Если честно, я до сих пор не привык к этой перемене. Больше не нужны виртуалки с настроенной dev-средой, не нужны докер-имиджи для сборки — да вообще dev-environment отпадает как таковой. Это слишком резкий game changer, чтобы так быстро привыкнуть.

Ссылки

dave.cheney.net/2012/09/08/an-introduction-to-cross-compilation-with-go
blog.hashbangbash.com/2014/04/linking-golang-statically
www.limitlessfx.com/cross-compile-golang-app-for-windows-from-linux.html

  1. Documentation

  2. Download and install

  3. Installing Go from source

This topic describes how to build and run Go from source code.
To install with an installer, see Download and install.

Introduction

Go is an open source project, distributed under a
BSD-style license.
This document explains how to check out the sources,
build them on your own machine, and run them.

Most users don’t need to do this, and will instead install
from precompiled binary packages as described in
Download and install,
a much simpler process.
If you want to help develop what goes into those precompiled
packages, though, read on.

There are two official Go compiler toolchains.
This document focuses on the gc Go
compiler and tools.
For information on how to work on gccgo, a more traditional
compiler using the GCC back end, see
Setting up and using gccgo.

The Go compilers support the following instruction sets:

amd64, 386
The x86 instruction set, 64- and 32-bit.
arm64, arm
The ARM instruction set, 64-bit (AArch64) and 32-bit.
loong64
The 64-bit LoongArch instruction set.
mips64, mips64le, mips, mipsle
The MIPS instruction set, big- and little-endian, 64- and 32-bit.
ppc64, ppc64le
The 64-bit PowerPC instruction set, big- and little-endian.
riscv64
The 64-bit RISC-V instruction set.
s390x
The IBM z/Architecture.
wasm
WebAssembly.

The compilers can target the AIX, Android, DragonFly BSD, FreeBSD,
Illumos, Linux, macOS/iOS (Darwin), NetBSD, OpenBSD, Plan 9, Solaris,
and Windows operating systems (although not all operating systems
support all architectures).

A list of ports which are considered «first class» is available at the
first class ports
wiki page.

The full set of supported combinations is listed in the
discussion of environment variables below.

See the Go Wiki MinimumRequirements page for the
overall system requirements.

Install Go compiler binaries for bootstrap

The Go toolchain is written in Go. To build it, you need a Go compiler installed.
The scripts that do the initial build of the tools look for a «go» command
in $PATH, so as long as you have Go installed in your
system and configured in your $PATH, you are ready to build Go
from source.
Or if you prefer you can set $GOROOT_BOOTSTRAP to the
root of a Go installation to use to build the new Go toolchain;
$GOROOT_BOOTSTRAP/bin/go should be the go command to use.

The minimum version of Go required depends on the target version of Go:

  • Go <= 1.4: a C toolchain.
  • 1.5 <= Go <= 1.19: a Go 1.4 compiler.
  • 1.20 <= Go <= 1.21: a Go 1.17 compiler.
  • 1.22 <= Go <= 1.23: a Go 1.20 compiler.
  • Going forward, Go version 1.N will require a Go 1.M compiler,
    where M is N-2 rounded down to an even number. Example: Go 1.24 and 1.25 require Go 1.22.

There are four possible ways to obtain a bootstrap toolchain:

  • Download a recent binary release of Go.
  • Cross-compile a toolchain using a system with a working Go installation.
  • Use gccgo.
  • Compile a toolchain from Go 1.4, the last Go release with a compiler written in C.

These approaches are detailed below.

Bootstrap toolchain from binary release

To use a binary release as a bootstrap toolchain, see
the downloads page or use any other
packaged Go distribution meeting the minimum version requirements.

Bootstrap toolchain from cross-compiled source

To cross-compile a bootstrap toolchain from source, which is
necessary on systems Go 1.4 did not target (for
example, linux/ppc64le), install Go on a different system
and run bootstrap.bash.

When run as (for example)

$ GOOS=linux GOARCH=ppc64 ./bootstrap.bash

bootstrap.bash cross-compiles a toolchain for that GOOS/GOARCH
combination, leaving the resulting tree in ../../go-${GOOS}-${GOARCH}-bootstrap.
That tree can be copied to a machine of the given target type
and used as GOROOT_BOOTSTRAP to bootstrap a local build.

Bootstrap toolchain using gccgo

To use gccgo as the bootstrap toolchain, you need to arrange
for $GOROOT_BOOTSTRAP/bin/go to be the go tool that comes
as part of gccgo 5. For example on Ubuntu Vivid:

$ sudo apt-get install gccgo-5
$ sudo update-alternatives --set go /usr/bin/go-5
$ GOROOT_BOOTSTRAP=/usr ./make.bash

Bootstrap toolchain from C source code

To build a bootstrap toolchain from C source code, use
either the git branch release-branch.go1.4 or
go1.4-bootstrap-20171003.tar.gz,
which contains the Go 1.4 source code plus accumulated fixes
to keep the tools running on newer operating systems.
(Go 1.4 was the last distribution in which the toolchain was written in C.)
After unpacking the Go 1.4 source, cd to
the src subdirectory, set CGO_ENABLED=0 in
the environment, and run make.bash (or,
on Windows, make.bat).

Once the Go 1.4 source has been unpacked into your GOROOT_BOOTSTRAP directory,
you must keep this git clone instance checked out to branch
release-branch.go1.4. Specifically, do not attempt to reuse
this git clone in the later step named «Fetch the repository.» The go1.4
bootstrap toolchain must be able to properly traverse the go1.4 sources
that it assumes are present under this repository root.

Note that Go 1.4 does not run on all systems that later versions of Go do.
In particular, Go 1.4 does not support current versions of macOS.
On such systems, the bootstrap toolchain must be obtained using one of the other methods.

Install Git, if needed

To perform the next step you must have Git installed. (Check that you
have a git command before proceeding.)

If you do not have a working Git installation,
follow the instructions on the
Git downloads page.

(Optional) Install a C compiler

To build a Go installation
with cgo support, which permits Go
programs to import C libraries, a C compiler such as gcc
or clang must be installed first. Do this using whatever
installation method is standard on the system.

To build without cgo, set the environment variable
CGO_ENABLED=0 before running all.bash or
make.bash.

Fetch the repository

Change to the directory where you intend to install Go, and make sure
the goroot directory does not exist. Then clone the repository
and check out the latest release tag or release branch (go1.22.0, or release-branch.go1.22,
for example):

$ git clone https://go.googlesource.com/go goroot
$ cd goroot
$ git checkout <tag>

Where <tag> is the version string of the release.

Go will be installed in the directory where it is checked out. For example,
if Go is checked out in $HOME/goroot, executables will be installed
in $HOME/goroot/bin. The directory may have any name, but note
that if Go is checked out in $HOME/go, it will conflict with
the default location of $GOPATH.
See GOPATH below.

Reminder: If you opted to also compile the bootstrap binaries from source (in an
earlier section), you still need to git clone again at this point
(to checkout the latest <tag>), because you must keep your
go1.4 repository distinct.

(Optional) Switch to the master branch

If you intend to modify the go source code, and
contribute your changes
to the project, then move your repository
off the release tag, and onto the master (development) branch.
Otherwise, skip this step.

$ git checkout master

Install Go

To build the Go distribution, run

$ cd src
$ ./all.bash

(To build under Windows use all.bat.)

If all goes well, it will finish by printing output like:

ALL TESTS PASSED

---
Installed Go for linux/amd64 in /home/you/go.
Installed commands in /home/you/go/bin.
*** You need to add /home/you/go/bin to your $PATH. ***

where the details on the last few lines reflect the operating system,
architecture, and root directory used during the install.

For more information about ways to control the build, see the discussion of
environment variables below.
all.bash (or all.bat) runs important tests for Go,
which can take more time than simply building Go. If you do not want to run
the test suite use make.bash (or make.bat)
instead.

Testing your installation

Check that Go is installed correctly by building a simple program.

Create a file named hello.go and put the following program in it:

package main

import "fmt"

func main() {
	fmt.Printf("hello, world\n")
}

Then run it with the go tool:

$ go run hello.go
hello, world

If you see the «hello, world» message then Go is installed correctly.

Set up your work environment

You’re almost done.
You just need to do a little more setup.

How to Write Go Code
Learn how to set up and use the Go tools

The How to Write Go Code document
provides essential setup instructions for using the Go tools.

The source code for several Go tools (including gopls)
is kept in the golang.org/x/tools repository.
To install one of the tools (gopls in this case):

$ go install golang.org/x/tools/gopls@latest

The usual community resources listed on the help page have
active developers that can help you with problems with your installation or your
development work.
For those who wish to keep up to date,
there is another mailing list, golang-checkins,
that receives a message summarizing each checkin to the Go repository.

Bugs can be reported using the Go issue tracker.

Keeping up with releases

New releases are announced on the
golang-announce
mailing list.
Each announcement mentions the latest release tag, for instance,
go1.9.

To update an existing tree to the latest release, you can run:

$ cd go/src
$ git fetch
$ git checkout <tag>
$ ./all.bash

Where <tag> is the version string of the release.

Optional environment variables

The Go compilation environment can be customized by environment variables.
None is required by the build, but you may wish to set some
to override the defaults.

  • $GOROOT

    The root of the Go tree, often $HOME/go1.X.
    Its value is built into the tree when it is compiled, and
    defaults to the parent of the directory where all.bash was run.
    There is no need to set this unless you want to switch between multiple
    local copies of the repository.

  • $GOROOT_FINAL

    The value assumed by installed binaries and scripts when
    $GOROOT is not set explicitly.
    It defaults to the value of $GOROOT.
    If you want to build the Go tree in one location
    but move it elsewhere after the build, set
    $GOROOT_FINAL to the eventual location.

  • $GOPATH

    The directory where Go projects outside the Go distribution are typically
    checked out. For example, golang.org/x/tools might be checked out
    to $GOPATH/src/golang.org/x/tools. Executables outside the
    Go distribution are installed in $GOPATH/bin (or
    $GOBIN, if set). Modules are downloaded and cached in
    $GOPATH/pkg/mod.

    The default location of $GOPATH is $HOME/go,
    and it’s not usually necessary to set GOPATH explicitly. However,
    if you have checked out the Go distribution to $HOME/go,
    you must set GOPATH to another location to avoid conflicts.

  • $GOBIN

    The directory where executables outside the Go distribution are installed
    using the go command. For example,
    go install golang.org/x/tools/gopls@latest downloads, builds,
    and installs $GOBIN/gopls. By default, $GOBIN is
    $GOPATH/bin (or $HOME/go/bin if GOPATH
    is not set). After installing, you will want to add this directory to
    your $PATH so you can use installed tools.

    Note that the Go distribution’s executables are installed in
    $GOROOT/bin (for executables invoked by people) or
    $GOTOOLDIR (for executables invoked by the go command;
    defaults to $GOROOT/pkg/$GOOS_$GOARCH) instead of
    $GOBIN.

  • $GOOS and $GOARCH

    The name of the target operating system and compilation architecture.
    These default to the values of $GOHOSTOS and
    $GOHOSTARCH respectively (described below).

    Choices for $GOOS are
    android, darwin, dragonfly,
    freebsd, illumos, ios, js,
    linux, netbsd, openbsd,
    plan9, solaris, wasip1,
    and windows.

    Choices for $GOARCH are
    amd64 (64-bit x86, the most mature port),
    386 (32-bit x86), arm (32-bit ARM), arm64 (64-bit ARM),
    ppc64le (PowerPC 64-bit, little-endian), ppc64 (PowerPC 64-bit, big-endian),
    mips64le (MIPS 64-bit, little-endian), mips64 (MIPS 64-bit, big-endian),
    mipsle (MIPS 32-bit, little-endian), mips (MIPS 32-bit, big-endian),
    s390x (IBM System z 64-bit, big-endian), and
    wasm (WebAssembly 32-bit).

    The valid combinations of $GOOS and $GOARCH are:

    $GOOS $GOARCH
    aix ppc64
    android 386
    android amd64
    android arm
    android arm64
    darwin amd64
    darwin arm64
    dragonfly amd64
    freebsd 386
    freebsd amd64
    freebsd arm
    illumos amd64
    ios arm64
    js wasm
    linux 386
    linux amd64
    linux arm
    linux arm64
    linux loong64
    linux mips
    linux mipsle
    linux mips64
    linux mips64le
    linux ppc64
    linux ppc64le
    linux riscv64
    linux s390x
    netbsd 386
    netbsd amd64
    netbsd arm
    openbsd 386
    openbsd amd64
    openbsd arm
    openbsd arm64
    plan9 386
    plan9 amd64
    plan9 arm
    solaris amd64
    wasip1 wasm
    windows 386
    windows amd64
    windows arm
    windows arm64
  • $GOHOSTOS and $GOHOSTARCH

    The name of the host operating system and compilation architecture.
    These default to the local system’s operating system and
    architecture.

    Valid choices are the same as for $GOOS and
    $GOARCH, listed above.
    The specified values must be compatible with the local system.
    For example, you should not set $GOHOSTARCH to
    arm on an x86 system.

  • $GO386 (for 386 only, defaults to sse2)

    This variable controls how gc implements floating point computations.

    • GO386=softfloat: use software floating point operations; should support all x86 chips (Pentium MMX or later).
    • GO386=sse2: use SSE2 for floating point operations; has better performance but only available on Pentium 4/Opteron/Athlon 64 or later.
  • $GOARM (for arm only; default is auto-detected if building
    on the target processor, 7 if not)

    This sets the ARM floating point co-processor architecture version the run-time
    should target. If you are compiling on the target system, its value will be auto-detected.

    • GOARM=5: use software floating point; when CPU doesn’t have VFP co-processor
    • GOARM=6: use VFPv1 only; default if cross compiling; usually ARM11 or better cores (VFPv2 or better is also supported)
    • GOARM=7: use VFPv3; usually Cortex-A cores

    If in doubt, leave this variable unset, and adjust it if required
    when you first run the Go executable.
    The GoARM page
    on the Go community wiki
    contains further details regarding Go’s ARM support.

  • $GOAMD64 (for amd64 only;
    default is v1)

    This sets the microarchitecture level for which to compile.
    Valid values are v1 (default), v2, v3,
    v4.
    See the Go wiki
    MinimumRequirements page for more information.

  • $GOMIPS (for mips and mipsle only)
    $GOMIPS64 (for mips64 and mips64le only)

    These variables set whether to use floating point instructions. Set to «hardfloat» to use floating point instructions; this is the default. Set to «softfloat» to use soft floating point.

  • $GOPPC64 (for ppc64 and ppc64le only)

    This variable sets the processor level (i.e. Instruction Set Architecture version)
    for which the compiler will target. The default is power8.

    • GOPPC64=power8: generate ISA v2.07 instructions
    • GOPPC64=power9: generate ISA v3.00 instructions
  • $GORISCV64 (for riscv64 only)

    This variable sets the RISC-V user-mode application profile for which to compile. The default is rva20u64.

    • GORISCV64=rva20u64: only use RISC-V extensions that are mandatory in the RVA20U64 profile
    • GORISCV64=rva22u64: only use RISC-V extensions that are mandatory in the RVA22U64 profile
  • $GOWASM (for wasm only)

    This variable is a comma separated list of experimental WebAssembly features that the compiled WebAssembly binary is allowed to use.
    The default is to use no experimental features.

    • GOWASM=satconv: generate saturating (non-trapping) float-to-int conversions
    • GOWASM=signext: generate sign-extension operators

Note that $GOARCH and $GOOS identify the
target environment, not the environment you are running on.
In effect, you are always cross-compiling.
By architecture, we mean the kind of binaries
that the target environment can run:
an x86-64 system running a 32-bit-only operating system
must set GOARCH to 386,
not amd64.

If you choose to override the defaults,
set these variables in your shell profile ($HOME/.bashrc,
$HOME/.profile, or equivalent). The settings might look
something like this:

export GOARCH=amd64
export GOOS=linux

although, to reiterate, none of these variables needs to be set to build,
install, and develop the Go tree.

Updated on February 7, 2024

Go programs can be easily cross-compiled for various operating systems like
Windows, macOS, and Linux using GOARCH and GOOS environment variables, which
denote the architecture and target OS respectively.

By default, they’re set to your system’s architecture (GOHOSTARCH) and OS
(GOHOSTOS):

command
go env

output
. . .
GOARCH="amd64"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
. . .

Cross-compile Go for Windows

You can cross-compile Go for 64-bit Windows machines with:

command
GOOS=windows GOARCH=amd64 go build -o bin/app-amd64.exe app.go

For 32-bit, change GOARCH to 386:

output
GOOS=windows GOARCH=386 go build -o bin/app-386.exe app.go

Cross-compile Go for macOS

Use GOOS=darwin for macOS, applicable for both 64-bit and 32-bit:

command
GOOS=darwin GOARCH=amd64 go build -o bin/app-amd64-darwin app.go # 64-bit

command
GOOS=darwin GOARCH=386 go build -o bin/app-386-darwin app.go # 32-bit

For Arm-based Macs (Apple Silicon), supply GOARCH=arm64:

command
GOOS=darwin GOARCH=arm64 go build -o bin/app-arm64-darwin app.go # Apple Silicon

Cross-compile Go for Linux

To build your Go programs for Linux, specify GOOS=linux:

command
GOOS=linux GOARCH=amd64 go build -o bin/app-amd64-linux app.go # 64-bit

command
GOOS=linux GOARCH=386 go build -o bin/app-386-linux app.go # 32-bit

Cross-compiling for other platforms

Go supports many other OS and CPU combinations. Use the command below to view
them all:

command
go tool dist list

This command lists combinations in GOOS/GOARCH format, like this:

output
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
freebsd/arm64
freebsd/riscv64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/loong64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
netbsd/386
netbsd/amd64
netbsd/arm
netbsd/arm64
openbsd/386
openbsd/amd64
openbsd/arm
openbsd/arm64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
wasip1/wasm
windows/386
windows/amd64
windows/arm
windows/arm64

You can then choose the relevant values for the platform you’re interested in
and cross-compile as before:

command
GOOS=android GOARCH=arm64 go build -o bin/app-arm64-android app.go # Android

Thanks for reading, and happy coding!

  • #golang

One of Golang’s strengths is that it supports cross-compiling to any common Operating System and CPU architecture out of the box. However, that’s only true for projects written in pure Go. Normally that’s not a problem, but sometimes we depend on 3rd-party C libraries… and that’s when things tend to get complicated.

In this post, I will explain how you can cross-compile your cgo project for multiple operating systems and architectures in a clear and structured manner.

Some Background

Over the last few years, I’ve been writing software mainly for remote controlling our Amateur Radio station over the Internet. Most of these programs are written in Go and published on my Github profile under permissive Open Source licenses. The most prominent program is remoteAudio, a low latency, multi-user audio streaming application.

While one can achieve a lot in native Go, sometimes there is just no way around using C libraries. One could re-implement those libraries in Go, but often it’s just not feasible. In the case of remoteAudio, I’m using for example the OPUS audio codec and Portaudio for handling cross-platform Audio APIs. Both of them are written in C.

Assumptions

I personally like Debian flavored Linux systems. That’s what this article has been written for. However, it should be also valid for all other Linux distros. In most cases, you just have to replace the apt-get package manager commands with the ones from your distro.

Two important terms have to be defined:

Term Meaning
Host System This is the system on which the compiler and the tools are run.
Target System This is the system for which we generate the programs/libraries.

In my case, the Host System is Linux/amd64.

Get a Cross Compiler

You could build the (GCC based) cross compiler yourself,
but fortunately, the popular Linux distributions provide them pre-compiled with all dependencies through their respective package managers.

To get started you need at least the cross-compiler and the platform-specific standard library (libc). The corresponding packages are:

Operating System Architecture Needed package(s)
Linux armhf gcc-arm-linux-gnueabihf
libc6-dev-armhf-cross
Linux arm64 gcc-aarch64-linux-gnu
libc6-dev-arm64-cross
Linux i386 gcc-multilib
libc6-dev-i386
linux-libc-dev:i386
Windows amd64 gcc-mingw-w64-x86-64
Windows i386 gcc-mingw-w64-i686

If your cgo program only uses the standard C library this would be all you need. However, we often depend on additional libraries. For example, remoteAudio depends also on libopus and libportaudio. Since our target architecture (e.g. arm64) differs from our host’s architecture (amd64), we cannot simply install the needed libraries with apt-get. This would just install the amd64 versions of those libraries.

Add support for target architectures

Debian-based distributions come fortunately with Multiarch support. This allows us to install library packages for multiple architectures on the same machine. Dependencies will be automatically correctly resolved.

To my knowledge, Debian supports at least the following architectures:
amd64, i386, armel, armhf, arm64, mips, powerpc and ppc64el.

With the help of dpkg, the Debian Package Manager we can add additional architectures (e.g. arm64) to our host system:


$ dpkg --add-architecture arm64

After updating the packages sources with apt-get update we can install now pre-compiled library packages for the target system. These packages are distinguished by the :<architecture> suffix. So installing the opus and portaudio libraries (and header files) for arm64 looks like this:


$ apt-get update
$ apt-get install -y libopus-dev:arm64 \
                    libopus0:arm64 \
                    libportaudio19-dev:arm64 \
                    libportaudio2:arm64

The Debian package manager will store the installed files on our host system in folders named after the respective architecture. For the case above, we can find the arm64 libraries in /usr/lib/aarch64-linux-gnu/ and the corresponding header files in /usr/include/aarch64-linux-gnu.

Preparing the environment

Before we can cross-compile our (c)go program, we have to set a few environment variables. We could all set them inline when calling the go compiler, but for the sake of clarity, we will set and discuss the variables one by one.


export GOOS=linux
export GOARCH=arm64
export CGO_ENABLED=1
export CC=aarch64-linux-gnu-gcc
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig

With GOOS and GOARCH, we tell the go compiler the operating system and architecture of our target system. Since our program contains C code, we have to explicitly enable cgo by setting CGO_ENABLED (cgo is disabled by default for cross-compiling). Instead of using the host’s default C compiler (GCC), we want to use the previously installed cross compiler for arm64. This is accomplished by setting the CC variable to the C compiler of choice. Finally, we have to set the PKG_CONFIG_PATH so that pkg-config uses the pkg-config files for our target architecture. Just in case you wonder, the pkg-config files are installed automatically with the corresponding package library.

Compile

Once all the preparations have been done, it’s just a matter of calling

In case you don’t use pkg-config and haven’t specified LDFLAGS with a cgo directive in your source code, you can specify the locations of the libraries and header files with the -ldflags flag:


$ go build -ldflags="-Ipath/to/headerfile -Lpath/to/lib"

Containerize it

While we could certainly install everything required for cross-compiling on our host system,
I prefer to keep my host system as clean as possible. Over the years, Docker has become one of my indispensable tools. It’s the perfect tool for our cross-compilation chains.

For remoteAudio I created the repository remoteAudio-xcompile which contains Dockerfiles for each combination of os/architecture I support. These containers can be build either locally or downloaded from the Docker Hub registry.

Below you can find the Dockerfile with remoteAudio’s arm64 cross-compilation chain.


FROM golang:1.13-buster
LABEL os=linux
LABEL arch=arm64

ENV GOOS=linux
ENV GOARCH=arm64
ENV CGO_ENABLED=1
ENV CC=aarch64-linux-gnu-gcc
ENV PATH="/go/bin/${GOOS}_${GOARCH}:${PATH}"
ENV PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig

# install build & runtime dependencies
RUN dpkg --add-architecture arm64 \
    && apt update \
    && apt install -y --no-install-recommends \
        protobuf-compiler \
        upx \
        gcc-aarch64-linux-gnu \
        libc6-dev-arm64-cross \
        pkg-config \
        libsamplerate0:arm64 \
        libsamplerate0-dev:arm64 \
        libopusfile0:arm64 \
        libopusfile-dev:arm64 \
        libopus0:arm64 \
        libopus-dev:arm64 \
        libportaudio2:arm64 \
        portaudio19-dev:arm64 \
    && rm -rf /var/lib/apt/lists/*

# install build dependencies (code generators)
RUN GOARCH=amd64 go get github.com/gogo/protobuf/protoc-gen-gofast \
    && GOARCH=amd64 go get github.com/GeertJohan/go.rice/rice \
    && GOARCH=amd64 go get github.com/micro/protoc-gen-micro

To compile the program for arm64 you just need to call from the source directory:


$ docker run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp dh1tw/remoteaudio-xcompile:linux-arm64 /bin/sh -c 'go build'

With the -v flag we mount the current directory of our host ($PWD) into the container under the path /usr/src/myapp. We select the same directory as our working directory with the -w. After generating our binary we automatically delete the container with the --rm flag.

After the successful compilation, we find the binary in the current directory of our host system and we can confirm that it has been built for arm64 (aarch64).


$ file ./remoteAudio
./remoteAudio: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, no section header

Microsoft Windows

Cross-compiling cgo applications from Linux host systems for Microsoft Windows is a bit more complicated and requires a few extra steps. MinGW (Minimalist GNU for Windows) provides a development environment for native Microsoft Windows applications. MinGW is available on Linux and Windows. MinGW works out of the box if no 3rd-party C-Runtime libraries (DLLs) are involved… and that’s unfortunately again a problem since remoteAudio depends on 3rd-party libraries like opus and portaudio. How cool would it be if we could also install our dependencies with a package manager like Debian’s apt-get ?

MSYS2 & Pacman to the rescue!

As it turns out, some smart folks have solved this problem and created MSYS2 which is a software distro and building platform for Windows. MSYS2 is built around the MinGW-w64 toolchain and comes with a ported version of Arch Linux’ package manager Pacman. MSYS2 provides ready-to-go installers for Windows, but installing MSYS2 on Linux is a bit more work.

Fortunately, Gary Kramlich who is one of Pidgin’s core developers, has done all the hard work for us and created the Debian based msys2-cross Container. Using this container as the base image, I created containers to compile remoteAudio for windows/amd64 and windows/i386 directly from Linux. You can find the Dockerfiles in the respective folders of the remoteAudio-xcompile repository.

This is the Dockerfile for windows/amd64:


FROM rwgrim/msys2-cross
LABEL os=windows
LABEL arch=amd64

ENV GOVERSION="1.13.4"
ENV GOOS=windows
ENV GOARCH=amd64
ENV GOPATH=/go
ENV CGO_ENABLED=1
ENV CC=x86_64-w64-mingw32-gcc
ENV CXX=x86_64-w64-mingw32-g++
ENV PATH="/go/bin:/usr/local/go/bin:${PATH}"
ENV PKG_CONFIG_PATH=/windows/mingw64/lib/pkgconfig
ENV MSYS2_ARCH=x86_64

# install build dependencies
RUN set -ex \
    && apt-get update \
    && apt-get install -y --no-install-recommends \
        build-essential \
        gcc-mingw-w64-x86-64 \
        git \
        protobuf-compiler \
        upx \
        pkg-config \
    && rm -rf /var/lib/apt/lists/*

# install golang
RUN set -ex \
    && wget -P /tmp -q https://dl.google.com/go/go$GOVERSION.linux-amd64.tar.gz \
    && tar -C /usr/local -xzf /tmp/go$GOVERSION.linux-amd64.tar.gz

# install build dependencies
RUN set -ex \
    && pacman --noconfirm --needed -Sy mingw-w64-$MSYS2_ARCH-libsamplerate \
    && pacman --noconfirm --needed -Sy mingw-w64-$MSYS2_ARCH-portaudio \
    && pacman --noconfirm --needed -Sy mingw-w64-$MSYS2_ARCH-opus \
    && pacman --noconfirm --needed -Sy mingw-w64-$MSYS2_ARCH-opusfile \
    && pacman --noconfirm -Sc

# install runtime dependencies (DLLs)
RUN set -ex \
    && pacman-cross --noconfirm --needed -Sy mingw-w64-$MSYS2_ARCH-libsamplerate \
    && pacman-cross --noconfirm --needed -Sy mingw-w64-$MSYS2_ARCH-portaudio \
    && pacman-cross --noconfirm --needed -Sy mingw-w64-$MSYS2_ARCH-opus \
    && pacman-cross --noconfirm --needed -Sy mingw-w64-$MSYS2_ARCH-opusfile \
    && pacman-cross --noconfirm -Sc

# install build dependencies (code generators)
RUN set -ex \
    && GOOS=linux GOOARCH=amd64 go get github.com/gogo/protobuf/protoc-gen-gofast \
    && GOOS=linux GOOARCH=amd64 go get github.com/GeertJohan/go.rice/rice \
    && GOOS=linux GOOARCH=amd64 go get github.com/micro/protoc-gen-micro

COPY ./scripts /scripts

Here we are installing the 3rd-party dependencies twice. Once for linking & compiling and once for the pre-build DLLs which we later extract conveniently with a script.

So to compile our (c)go program for windows/amd64 and extract the dlls, we just execute:


$ docker run --rm -v "$PWD":/usr/src/myapp -w /usr/src/myapp dh1tw/remoteaudio-xcompile:windows-amd64 /bin/sh -c 'go build && /scripts/getlibs.sh .'
[output not shown]

$ ls -al | grep '*.exe\|*.dll'
-rwxr-xr-x   1 user  staff    85136 Dec 12 14:57 libgcc_s_seh-1.dll
-rwxr-xr-x   1 user  staff    44544 Dec 12 14:57 libogg-0.dll
-rwxr-xr-x   1 user  staff   406177 Dec 12 14:57 libopus-0.dll
-rwxr-xr-x   1 user  staff    54568 Dec 12 14:57 libopusfile-0.dll
-rwxr-xr-x   1 user  staff   183508 Dec 12 14:57 libportaudio-2.dll
-rwxr-xr-x   1 user  staff  1498448 Dec 12 14:57 libsamplerate-0.dll
-rwxr-xr-x   1 user  staff    55808 Dec 12 14:57 libwinpthread-1.dll
-rwxr-xr-x@  1 user  staff  9446400 Dec 12 14:57 remoteAudio.exe

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

Conclusion

In this article, I’ve explained my approach on how to cross-compile Go applications that depend on C libraries for different architectures & operating systems. Putting the Cross-compilation chains into Docker containers does not only keep our host system clean, but it also documents the build process and shows the needed dependencies. As a bonus, the containers integrate nicely in our Continous Integration & Continous Delivery (CI/CD) Pipeline so that we can generate releases for a variety of platforms without any additional effort.

The Automatic (but unsupported) Way: winstrap

The winstrap tool is used by the Go project to turn a fresh Windows VM image into a Windows builder. It installs all necessary dependencies. It’s sometimes out of date, though, as it’s only updated when we need to update the Windows base image for the Go continuous build.

To use winstrap, download the latest version of winstrap.exe from the winstrap page and run it.

It will download some installers to your desktop, which you should run. Just click through; all the defaults are fine.

Then it will check out Go and place it in c:\Users\%USER%\goroot and build it.

That’s it.

Note however that winstrap is not supported. It’s considered an internal tool used for occasional setup of new Windows builder images and is not actively maintained until we need it ourselves.

The Manual Way

Install MinGW/MSYS

Download and save the latest version of the automated MinGW installer executable (exe) file from SourceForge.

http://sourceforge.net/projects/mingw/files/OldFiles/mingw-get-inst/

Open and run the saved automated MinGW installer executable file, which is named mingw-get-inst-yyyymmdd.exe, where yyyymmdd is the version date stamp. For example, mingw-get-inst-20110530.exe.

The MinGW Setup Wizard window will open with the title “Setup — MinGW-Get”. Except for the following, accept the setup defaults, unless it’s necessary to change them.

For Repository Catalogues, check the Download latest repository catalogues button.

For Select Components, the MinGW Compiler Suite, the C Compiler box is automatically checked. Scroll down to the bottom of the list and check the MinGW Developer Toolkit box, which includes the MSYS Basic System.

For Ready to Install, review and verify the installation settings, which should look similar this:

    Installing:
        mingw-get
        pkginfo
        C Compiler
        MSYS Basic System
        MinGW Developer Toolkit 
    Downloading latest repository catalogues 
    Destination location:
        C:\MinGW 

When the installation settings are correct, Install.

The installation loads the package installation catalogues and downloads and installs the files. The installation may take some time, largely depending on the download speed.

The MSYS terminal window may be opened by opening and running the C:\MinGW\msys\1.0\msys.bat batch file.

Build

git clone https://go.googlesource.com/go
cd go\src
all.bat

64-bit Notes

  1. Ensure you are able to compile a working 32-bit Go first.
  2. Grab the latest zip from http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Automated%20Builds/ and extract it over the MinGW directory, so that for example the .exe files end up in the same location as the 32-bit ones.
  3. Replace gcc.exe and ar.exe with their 64-bit counterparts.
  4. Set GOARCH=amd64 and away you go!

This content is part of the Go Wiki.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Как обновлять драйвера на windows 10 nvidia
  • Windows затереть пустое место
  • Nvidia quadro 2000 driver windows 10
  • Как сменить драйвер видеокарты windows 10
  • Службе установщика windows не удается обновить один или несколько защищенных файлов windows