One of “Go”’s strengths is its capability to build your “Go” project
cross-platform for all kinds of operating systems and architectures,
e. g. windows/amd64
(Windows), linux/386
(Linux), darwin/amd64
(MacOS). But setting up the environment for this is not that easy. This article
is a small “HOWTO” listing all the steps required to set up a cross-platform
“Go” environment.
To make it easier for readers to follow along without any operating system issues, I use a “Docker” image as a working environment. As “Docker” is quite common these days, this article expects readers to be comfortable with it. But the instructions given below shall work on most Linux distributions as well.
Prepare build environment
First, let’s pull down the official “Docker” image of the “Alpine Linux” project. This is a very small “Docker” image which can be easily extended with additional packages.
docker pull alpine
# => Using default tag: latest
# => latest: Pulling from library/alpine
# => 88286f41530e: Pull complete
# => Digest: sha256:1072e499f3f655a032e88542330cf75b02e7bdf673278f701d7ba61629ee3ebe
# => Status: Downloaded newer image for alpine:latest
After the download has finished run the image and start a shell in the container context.
docker run -it --rm --name go-build-1 alpine sh
# => / #
Subsequently we need to update apk
’s package index. Otherwise it does not find any
package we’re looking for.
apk update
# => fetch http://dl-cdn.alpinelinux.org/alpine/v3.6/main/x86_64/APKINDEX.tar.gz
# => fetch http://dl-cdn.alpinelinux.org/alpine/v3.6/community/x86_64/APKINDEX.tar.gz
# => v3.6.2-41-g11187df7f2 [http://dl-cdn.alpinelinux.org/alpine/v3.6/main]
# => v3.6.2-32-g6f53cfcccd [http://dl-cdn.alpinelinux.org/alpine/v3.6/community]
# => OK: 8436 distinct packages available
Install pre-build go
To start building our very own “Go”, we need to install a pre-build “Go” first. We chose to use the “Go” package coming with “Alpine Linux” for this.
apk add go
# [...]
# => OK: 266 MiB in 24 packages
Setup packages
We need some more tools and libraries installed along with the pre-build “Go” package to run the build script. So, let’s add them to the container.
apk add make gcc git libc-dev bash
# [...]
# => OK: 298 MiB in 33 packages
Run the shell
Nowadays most Linux distributions use some kind of an advanced shell – e.g. “Bash”, “Zsh” – as the default one. To “simulate” a normal Linux distribution, we install the “Bash” shell first.
apk add bash
# => OK: 306 MiB in 38 packages
Next, we run it.
bash
# => bash-4.3#
Clone the repository
After we cloned the repository using the git
command, we check out the
version of “Go” we want to build.
-
Assign the install path to a variable to make building the project easier:
go_lang_install_path=~/.local/share/go-source
-
Clone the “Git” repository of the “Go” project:
git clone https://github.com/golang/go $go_lang_install_path # => Cloning into '/root/.local/share/go-source'... # [...] cd $go_lang_install_path
-
Find the “Go” version to checkout:
git tag | grep 1.8 # => v1.8.3
-
Check out the source code tagged with version tag:
git checkout go1.8.3 # => Note: checking out 'go1.8.3'. # [...]
-
Switch to
src
directory:cd src
Build binaries
-
Find out the path where your pre-build “Go” libraries are stored:
We use the
apk
command for this as it knows the contents of all installed packages. As we can see, the relevant parts of the pre-build “Go” can be found in/usr/lib/go
.apk info -L go | head # => usr/bin/gofmt # => usr/bin/go # => usr/lib/go/bin/gofmt # => usr/lib/go/bin/go # => usr/lib/go/lib/time/zoneinfo.zip # => usr/lib/go/lib/time/update.bash # [...]
-
Bootstrap “Go” for all needed platforms:
We use
GOROOT_BOOTSTRAP
to set the path where the libraries of the pre-buildgo
binary can be found. You need to configure the operating system using theGOOS
-variable. The architecture is controlled by theGOARCH
-variable. In the context of this article a platform is a combination of an operating system and an architecture. You can find a full list of supported platforms at [1].GOOS=windows GOARCH=amd64 GOROOT_BOOTSTRAP=/usr/lib/go ./make.bash # [...] # => Installed Go for windows/amd64 in /root/.local/share/go-source # => Installed commands in /root/.local/share/go-source/bin GOOS=darwin GOARCH=amd64 GOROOT_BOOTSTRAP=/usr/lib/go ./make.bash # [...] # => Installed Go for darwin/amd64 in /root/.local/share/go-source # => Installed commands in /root/.local/share/go-source/bin GOOS=linux GOARCH=amd64 GOROOT_BOOTSTRAP=/usr/lib/go ./make.bash # [...] # => Installed Go for linux/amd64 in /root/.local/share/go-source # => Installed commands in /root/.local/share/go-source/bin
Setup shell environment for use of go
Next, we need to set up our shell environment. In this article we choose
~/.local/share/go-packages
as GOPATH
to contain all installed
“Go”-packages - use whatever directory suites your needs best. Additionally we
modifed PATH
to make the shell look for go
and binaries from installed “Go” packages
in the configured paths first. Given you’re using the “Bash” shell, you can use
the following snippet to configure it.
echo "export GOPATH=~/.local/share/go-packages" >> ~/.bashrc
echo "export PATH=~/.local/share/go-source/bin:$PATH" >> ~/.bashrc
echo "export PATH=~/.local/share/go-packages/bin:$PATH" >> ~/.bashrc
After that source
the .bashrc
to activate the configuration in your current session.
source ~/.bashrc
Test your installation
After you have built your own “Go” and set up your shell environment, it’s time to
test the installation. We use a small “helloworld” project for this to
build the sources for three different platforms: linux/amd64
(Linux),
darwin/amd64
(MacOS) and windows/amd64
(Windows).
-
Create a directory for the project in
$GOPATH
:package_dir=~/.local/share/go-packages/src/github.com/fedux-org/hello-world mkdir -p $package_dir cd $package_dir
-
Create a file named
helloworld.go
in$package_dir
:First start the “Vi” editor.
vi helloworld.go
After that, press
i
, paste this code snippet into the editor and stop the “insert” mode with<Esc>
. Save and close the file using:wq
.package main import "fmt" func main() { fmt.Printf("hello, world\n") }
-
Build the tool for each platform:
GOOS=windows GOARCH=amd64 go build -o helloworld.windows.exe helloworld.go GOOS=darwin GOARCH=amd64 go build -o helloworld.mac helloworld.go GOOS=linux GOARCH=amd64 go build -o hellworld.linux helloworld.go
-
Check the built binaries:
First of all we need to add the
file
tool to our container. Again we useapk
for this.apk add file # [...] # => OK: 311 MiB in 40 packages
Afterwards we try to find out, if all binaries are built for the correct platform using the
file
command. As we can see, everything is fine. Hooray!file helloworld.windows.exe # => helloworld.windows.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows file helloworld.mac # => helloworld.mac: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS> file hellworld.linux # => hellworld.linux: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, not stripped, with debug_info
Conclusion
Given you have “Go” bootstrapped for all required platforms, it’s that easy to build projects cross-platform. I hope you enjoyed reading this article and I see you again next time.
References
- [1] Installing Go from source
- [2] Building windows go programs on linux
- [3] 10 Alpine Linux apk Command Examples
- [3] Alpine Linux package management