Up to Main Index Up to Annexed Works BUILDING THE GO TOOLCHAIN FROM SOURCE ON A RASPBERRY PI ZERO INTRODUCTION ‾‾‾‾‾‾‾‾‾‾‾‾ Building Go from source on a Raspberry Pi, especially a Raspberry Pi Zero, can be a challenge. These instructions are from my experience building the Go toolchain (compiler, other tools, standard library) on machines with limited resources. The toolchain sources are downloaded from https://golang.org. This is not about compiling Go programs from code you write… You are expected to have at least a little experience with the Linux command line. If you can open a terminal window and type commands you should be okay. These instructions may seem overly verbose. However, they attempt to explain what to do and why. This should make adapting the instructions for your setup, or for different tiny computers, much easier. The original Raspberry Pi Zero, and Zero W, are cheap and tiny computers with limited resources that are capable of running a full Linux distribution. They have a single core BCM2835 32-bit processor running at 1GHz with 512Mb RAM. The storage is usually limited to an SD card, although an SSD or thumb-drive can be attached via the single micro USB port using an OTG adapter. If Go can be built on these diminutive systems, then we should be able to build Go on any model of Raspberry Pi or similar single-board computer. For the build described here, a Raspberry Pi Zero W was used. The operating system used was an official Raspberry Pi OS image. The version of Go built was Go 1.23.3. The SDCard used in the Raspberry Pi was a 16Gb Class 10 A1 SanDisk. A previous build of Go 1.21.5 was used to build the Go 1.23.3 toolchain. If there is no previous or recent version of Go installed, the easiest method is to download a recent binary release of Go. If that is not possible then you are in for a long haul: · download and build the latest Go 1.4.x · use Go 1.4.x to build the latest Go 1.19.x · use Go 1.19.x to build the latest Go 1.20.x · use Go 1.20.x to build Go 1.23.3 Depending on which Go compiler versions you already have available, hopefully don’t have to build the whole Go 1.4, Go 1.19 & Go 1.20 chain of compilers :( Additional information on building Go from source, and which compiler versions are needed to compile different versions of Go, can be found on the official Go website: https://go.dev/doc/install/source Feel free to take and adapt these notes as you see fit. Feedback in the form of comments, improvements or errata is always welcome: diddymus@wolfmud.org OVERVIEW ‾‾‾‾‾‾‾‾ A quick overview of the following process: · Reduce video memory used · Setup swap space · Create temporary directory · Unpack Go toolchain source · Setup stacks · Build Go toolchain · Setup PATH and symlinks · Troubleshooting tips REDUCE VIDEO MEMORY ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ The first step should be to setup the RAM reserved for graphics to be as small as possible. This frees up more memory for the operating system and Go build to use. As root, the current gpu_mem setting can be checked using: >vcgencmd get_mem gpu gpu=16M > If it is not 16M then edit the /boot/config.txt file. Set the gpu_mem setting to 16, take note of its current value so it can be restored after the build: gpu_mem=16 If your config.txt does not contain a gpu_mem line, add it to the end of the file. A reboot is required for the change to take effect. After rebooting, check the value of gpu_mem again, reported by the vcgencmd command above, and make sure it has been updated. Once rebooted, as many background services as possible should be stopped to free up additional memory. Which services are running on your Raspberry Pi will depend on what software has been installed. Logging in to the Raspberry Pi from the console or over SSH, instead of running a graphical interface, is highly recommended. This will free up more memory for the build. The more free memory the quicker the build will be. SETUP SWAP SPACE ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ A small swap space will be provided by a memory backed zram device that compresses and decompresses its content automatically. Setup the swap space, as root, using: >swapoff -a >zramctl --find -a lz4 -s 32M /dev/zram0 >mkswap /dev/zram0 Setting up swapspace version 1, size = 32 MiB (33550336 bytes) no label, UUID=d43824c2-feab-4b48-b162-049d129dbac3 >swapon --priority 100 /dev/zram0 > Note that if the size of the zram device is too large, the build will spend more time swapping as less of the build can be kept in memory. Building Go will require more swap space than is available on the zram device alone. For additional swap space a temporary 1Gb swap file will be used. To create and mount a temporary swap file, as root, use: >dd if=/dev/zero of=/tmpswap bs=1M count=1024 2097152+0 records in 2097152+0 records out 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 558.15 s, 1.9 MB/s >mkswap /tmpswap Setting up swapspace version 1, size = 1024 MiB (1073737728 bytes) no label, UUID=45122e1d-9bcd-4bf4-81cb-0a5b3aec51ec >swapon /tmpswap > Check the zram and swap file are now being used: >swapon -s Filename Type Size Used Priority /dev/zram0 partition 32764 0 100 /tmpswap file 1048572 0 -2 > This allows frequent small swaps to memory with only the overflow going to the SDCard. This should speed up the build and help preserve the SDCard. TEMPORARY DIRECTORY ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ A temporary directory will be needed for the build. As /tmp is normally mapped to tmpfs and memory backed, /tmp is unsuitable. Instead a temporary directory in the user’s home directory is used. As a normal user: >mkdir ~/tmp > UNPACK TOOLCHAIN SOURCE ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ The Go source code should be downloaded and unpacked into a directory named as per the release being built. In this case go1.23.3 which has been downloaded from https://go.dev/dl/ to the user’s downloads folder: >cd ~ >mkdir go1.23.3 >cd go1.23.3 >tar --strip-components=1 -zxf ~/downloads/go1.23.3.src.tar.gz > Note: prior to Go 1.21.0 the first release of a Go version was named without a minor number. For example: instead of ‘go1.20.0’, ‘go1.20’ was used. We always want a minor number and append ‘.0’ to the directory name if required. See the later discussion on symbolic links for the reasoning as to why we always want to use directories with a minor number. SETUP STACKS ‾‾‾‾‾‾‾‾‾‾‾‾ By default, stacks are created with a size of 8Mb, however the limit can be lowered saving memory during the build. This will also reduce the build time considerably. A setting of 1Mb seems to work well. To set, and then check, the initial stack limit, as a normal user: >ulimit -s 1024 The current setting can be checked using: >ulimit -s 1024 > BUILD GO TOOLCHAIN ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ For the build we use the tee command so we can keep an eye on the progress and also have the output sent to ./compile.log in case we need to troubleshoot. To build Go, using the temporary ~/tmp directory, as a normal user: >cd ~/go1.23.3/src >time GO_TEST_TIMEOUT_SCALE=10 CGO_ENABLE=1 GOROOT_BOOTSTRAP=~/go1.21.5 \ GOARM=6 GOTMPDIR=~/tmp TMPDIR=~/tmp ./all.bash | tee ./compile.log : : wait 6 hours, the follow messages will start to appear during this time… : Building Go cmd/dist using /home/diddymus/go1.21.5. (go1.21.5 linux/arm) Building Go toolchain1 using /home/diddymus/go1.21.5. Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1. Building Go toolchain2 using go_bootstrap and Go toolchain1. Building Go toolchain3 using go_bootstrap and Go toolchain2. Building packages and commands for linux/arm. : : the toolchain is compiled, next the tests are run… : ##### Test execution environment. # GOARCH: arm # CPU: ARMv6-compatible processor rev 7 (v6l) # GOOS: linux # OS Version: Linux 6.1.21+ #1642 Mon Apr 3 17:19:14 BST 2023 armv6l ##### Testing packages. ok archive/tar 2.083s ok archive/zip 2.478s ok bufio 0.841s ok bytes 4.598s ok cmp 0.020s : : finally the build will finish… : ALL TESTS PASSED --- Installed Go for linux/arm in /home/diddymus/go1.23.3 Installed commands in /home/diddymus/go1.23.3/bin *** You need to add /home/diddymus/go1.23.3/bin to your PATH. real 351m12.649s user 298m32.391s sys 27m0.611s > Once the build has completed, revert the change to gpu_mem in /boot/config.txt and set gpu_mem back to its original value and reboot. After the reboot the changes to swap will be lost and ulimit settings returned to normal. As root the /tmpswap file can also be removed. If the compile fails, you can look at the build log in compile.log to find out what went wrong. See also the troubleshooting tips section below. TROUBLESHOOTING TIPS ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ I had to specify ARM=6 when building the toolchain for the Raspberry Pi Zero. However, this does not always work and I have had to use ARM=5 before. This seems to depend a lot on the current kernel, libraries and how they have been compiled for the current version of Raspberry Pi OS. If compiling with ARM=6 fails then try again with ARM=5. This weirdness can also be seen when compiling on a Raspberry Pi 4 with a 32-bit Raspberry Pi OS installed — a 64-bit CPU running a 32-bit OS. The compiler tries to build Go for the arm64 CPU but we need 32-bit binaries. Setting GOHOSTARCH=arm for the compile fixes this. I think this is also the reason why the runtime/vdso_test.go fails on a Raspberry Pi 4 with a 32-bit OS: vdso_test.go:124: did not use VDSO system call To work around this I edited src/runtime/vdso_test.go and at the end of the function TestUsingVDSO changed: // The Go program used the system call but the C // program did not. This is a VDSO failure for Go. t.Errorf("did not use VDSO system call") To be Log instead of Errorf: // The Go program used the system call but the C // program did not. This is a VDSO failure for Go. t.Log("did not use VDSO system call") Save the change and compile… again. PATH AND SYMLINKS ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ For myself, ~/golang is a symlink to a major Go version, currently Go 1.21 for this build, and this is a symlink to the latest minor Go version, in this case Go 1.21.5: >ls -ld ~/go* lrwxrwxrwx 1 diddymus diddymus 8 Dec 12 2023 go1.21 -> go1.21.5 drwxr-xr-x 10 diddymus diddymus 4096 Dec 11 2023 go1.21.5 lrwxrwxrwx 1 diddymus diddymus 6 Nov 9 17:29 golang -> go1.21 > For the newly compiled toolchain I symlink go1.23 to go1.23.3: >ln -s go1.23.3 go1.23 >ls -ld ~/go* lrwxrwxrwx 1 diddymus diddymus 8 Dec 12 2023 go1.21 -> go1.21.5 drwxr-xr-x 10 diddymus diddymus 4096 Dec 11 2023 go1.21.5 lrwxrwxrwx 1 diddymus diddymus 6 Dec 11 2023 golang -> go1.21 lrwxrwxrwx 1 diddymus diddymus 8 Nov 9 17:28 go1.23 -> go1.23.3 drwxrwxr-x 10 diddymus diddymus 4096 Nov 7 12:16 go1.23.3 > Then to switch from go1.21 to go1.23 I update the symlink for golang: >rm golang >ln -s go1.23 golang >ls -ld ~/golang lrwxrwxrwx 1 diddymus diddymus 8 Dec 12 2023 go1.21 -> go1.21.5 drwxr-xr-x 10 diddymus diddymus 4096 Dec 11 2023 go1.21.5 lrwxrwxrwx 1 diddymus diddymus 6 Nov 9 17:29 golang -> go1.23 lrwxrwxrwx 1 diddymus diddymus 8 Nov 9 17:28 go1.23 -> go1.23.3 drwxrwxr-x 10 diddymus diddymus 4096 Nov 7 12:16 go1.23.3 > The symlink ~/golang/bin should be added to the PATH environment variable in ~/.bashrc so that the Go version ~/golang currently points to can be found: export PATH=~/bin:$PATH:~/golang/bin This makes switching between Go versions very easy. To change major version I update the symlink for golang. To change minor version I update the symlink for the go1.x version. For example, to switch from Go 1.23 back to Go 1.21: >go version go version go1.23.3 linux/arm >rm golang >ln -s go1.21 golang >go version go version go1.21.5 linux/arm > For each new Go release the build is put into a go1.x.x directory and the go1.x symlink added or updated. The golang symlink is only updated for new go1.x releases, or if I need a specific version of Go for testing. FINISH ‾‾‾‾‾‾ The instructions presented here can be used for other models of Raspberry Pi and other small, low power, resource constrained systems. In which case you may not need additional swap, a separate ~/tmp directory or reduced stacks. These are the build times for Go 1.23.3, including running all the tests, on different Raspberry Pi models, with the tweaks used, and a desktop PC for comparison: System/OS/Memory Time HMS Tweaks Used -------------------- -------- ------------------------ RPi0 32-bit 512Mb: 05:51:12 tmpdir, ulimit, swapfile RPI3 32-bit 1Gb: 34:49 tmpdir, ulimit, swapfile RPi4 32-bit 4Gb: 25:55 tmpdir RPi4 64-bit 8Gb: 38:10 tmpdir Desktop 64-bit 64Gb: 3:10 Except for the Raspberry Pi Zero, which uses an SDCard, the other Raspberry Pi models used a USB to SATA adapter with a WD Green SSD. The desktop used was an Intel i9-12900T — 12 core/24 threads (8P/16T @ 4.8Ghz, 4E/8T @ 3.6Ghz) with 64Gb RAM. -- Diddymus Up to Main Index Up to Annexed Works