Part 2: Go
The Go source we want to compile is functionally equivalent to the C source.
Setting up the Build System
For Go, we’ll want a go.mod
file that defines our project’s Go dependencies:
module github.com/flyx/Zicross/examples/go
go 1.18
require github.com/veandco/go-sdl2 v0.4.25
We’ll also need a corresponding go.sum
, which can be created via go mod tidy
.
Now let’s look how the go-sdl2
wrapper links to SDL2:
//#cgo windows LDFLAGS: -lSDL2
//#cgo linux freebsd darwin openbsd pkg-config: sdl2
import "C"
pkg-config
, how convenient – we’ve already set that up.
Except for Windows, we’ll handle that later.
To compile natively, we only need to ensure that SDL2
is available via pkg-config
.
Let’s write a flake.nix
that compiles our code:
{
inputs = {
zicross.url = github:flyx/Zicross;
nixpkgs.url = github:NixOS/nixpkgs/nixos-22.05;
utils.url = github:numtide/flake-utils;
};
outputs = {self, zicross, nixpkgs, utils}:
with utils.lib; eachSystem allSystems (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [
zicross.overlays.zig
zicross.overlays.go
zicross.overlays.debian
zicross.overlays.windows
];
};
pname = "zicross_demo_go";
version = "0.1.0";
mySDL2 = if pkgs.stdenv.isDarwin then (pkgs.SDL2.override {
x11Support = false;
}) else pkgs.SDL2;
postUnpack = ''
mv "$sourceRoot" source
sourceRoot=source
'';
in rec {
packages = rec {
demo = pkgs.buildGoModule {
inherit pname version;
src = ./.;
subPackages = [ "zicross_demo_go" ];
vendorSha256 = "5cfp25rEhmnLI/pQXE1+e6kjiYnb7T3nEuoLw2AfEoM=";
nativeBuildInputs = [ pkgs.pkg-config ];
buildInputs = with pkgs; [ mySDL2 ];
targetSharePath="${placeholder "out"}/share";
# workaround for buildGoModule not being able to take sources in a `go`
# directory as input
overrideModAttrs = (_: {
inherit postUnpack;
});
inherit postUnpack;
postConfigure = ''
cat <<EOF >zicross_demo_go/generated.go
package main
const LogoPath = "$targetSharePath/logo.txt";
EOF
'';
preInstall = ''
mkdir -p $out/share
cp ${zicross.lib.logo_data} $out/share/logo.txt
'';
meta = {
maintainers = [ "Felix Krause <contact@flyx.org>" ];
description = "Zicross Demo App (in Go)";
};
};
};
};
}
This is pretty similar to what we did for C.
The additional overlay zicross.overlays.go
overrides the standard pkgs.buildGoModule
with a version that uses Zig as C compiler.
The mySDL2
package is a workaround for macOS.
Since go-sdl2
links to much more parts of SDL2 than our C source did, we can run into an error since X11 is by default enabled for macOS in Nixpkgs.
We create a modified SDL2 package that has X11 disabled to avoid this error.
The vendorSha256
is something Nix needs to ensure that the Go dependencies, specified in go.mod
, are what it expects them to be.
Initially, you can give pkgs.lib.fakeSha256
to get an error message that tells you what the actual checksum is, and then substitute that.
Don’t mind the postUnpack
workaround, this mitigates a problem arising from the sources being located in the directory examples/go
.
buildGoModule
dislikes the base directory being named go
which is what the workaround fixes.
Now with this in place, we can compile our Go application natively and test it:
$ nix build .#demo
$ result/bin/zicross_demo_go
Cross-Compiling for Debian on Raspberry Pi
Like before, let’s add an additional package that builds a .deb
file:
rpiDeb = pkgs.packageForDebian (demo.overrideAttrs (origAttrs: {
GOOS = "linux";
GOARCH = "arm";
})) {
targetSystem = "armv7l-hf-multiplatform";
pkgConfigPrefix = "/usr/lib/arm-linux-gnueabihf/pkgconfig";
includeDirs = [ "/usr/include" "/usr/include/arm-linux-gnueabihf" ];
name = "zicross-demo-go";
inherit version;
deps = {
sdl2 = {
path = "debian/pool/main/libs/libsdl2/libsdl2-2.0-0_2.0.14+dfsg2-3_armhf.deb";
sha256 = "1z3bcjx225gp6lcbcd7h15cvhjik089y5pgivl2v3kfp61zm9wv4";
dev = {
path = "debian/pool/main/libs/libsdl2/libsdl2-dev_2.0.14+dfsg2-3_armhf.deb";
sha256 = "17d8qms1p7961kl0g7hgmkn0qx9avjnxwlmsvx677z5xb8vchl3y";
};
packageName = "libsdl2-2.0-0";
minVersion = "2.0.0";
};
libcrypt = {
path = "debian/pool/main/libx/libxcrypt/libcrypt1_4.4.18-4_armhf.deb";
sha256 = "0mcr0s5dwcj8rlr70sf6n3271pg7h73xk6zb8r7xvhp2fm51fyri";
packageName = "libcrypt1";
minVersion = "1:4.4.18";
};
libx11-dev = {
path = "debian/pool/main/libx/libx11/libx11-dev_1.7.2-1_armhf.deb";
sha256 = "0n0r21z7lp582pk51fp8dwaymz3jz54nb26xmfwls7q4xbj5f7wz";
};
x11proto-dev = {
path = "debian/pool/main/x/xorgproto/x11proto-dev_2020.1-1_all.deb";
sha256 = "1xb5ll2fg3as128m5vi6w5kwbcyc732hljy16i66dllsgmc8smnm";
};
};
};
We’re using packageForDebian
just like we did for C.
As we know, this configures our zig cc
compiler.
However we also need to configure the Go compiler for cross-compiling, and we do that via overrideAttrs
– we need to set GOOS
and GOARCH
to the correct values for the target system.
Since go-sdl2
includes significantly more SDL2 headers than our C code did, we need to add additional dependencies that provide header files that are included by some of the SDL2 headers – these are transitive dependencies of libsdl2-dev
.
They don’t have a packageName
since we don’t want to add them as dependencies of the created package.
Everything else looks similar to what we did for C. Now let’s test it:
$ nix build .#rpiDeb
$ dpkg -I
new Debian package, version 2.0.
size 470990 bytes: control archive=281 bytes.
235 bytes, 9 lines control
Package: zicross-demo-go
Version: 0.1.0
Section: base
Priority: optional
Architecture: armhf
Depends: libcrypt1 (>= 1:4.4.18), libsdl2-2.0-0 (>= 2.0.0)
Maintainer: Felix Krause <contact@flyx.org>
Description: Zicross Demo App (in Go)
Looks good.
Cross-Compiling for x86_64 Windows
Remember how go-sdl2
doesn’t use pkg-config
when compiling for Windows?
There are two ways we can remedy this:
- We can patch
go-sdl2
. This is possible during the vendoring phase which downloads all dependencies. - We can manually add the flags provided by
pkg-config
toCGO_LDFLAGS
We will go for the latter option since it is less intrusive. Here’s our package:
win64Zip = pkgs.packageForWindows (demo.overrideAttrs (origAttrs: {
GOOS = "windows";
GOARCH = "amd64";
postConfigure = origAttrs.postConfigure + ''
export CGO_LDFLAGS="$CGO_LDFLAGS $(pkg-config --libs sdl2)"
'';
})) {
targetSystem = "x86_64-windows";
deps = {
sdl2 = {
tail = "SDL2-2.0.22-1-any.pkg.tar.zst";
sha256 = "13v4wavbxzdnmg6b7qrv7031dmdbd1rn6wnsk9yn4kgs110gkk90";
postPatch = ''
${pkgs.gnused}/bin/sed -i "s:-lSDL2main:$out/clang64/lib/libSDL2main.a:g" upstream/clang64/lib/pkgconfig/sdl2.pc
'';
};
iconv = {
tail = "libiconv-1.16-2-any.pkg.tar.zst";
sha256 = "0kwc5f60irrd5ayjr0f103f7qzll9wghcs9kw1v17rj5pax70bxf";
};
vulkan = {
tail = "vulkan-loader-1.3.211-1-any.pkg.tar.zst";
sha256 = "0n9wnrcclvxj7ay14ia679s2gcj5jyjgpg53j51yfdn48wlqi40l";
};
libcpp = {
tail = "libc++-14.0.3-1-any.pkg.tar.zst";
sha256 = "1r73zs9naislzzjn7mr3m8s6pikgg3y4mv550hg09gcsjc719kzz";
};
unwind = {
tail = "libunwind-14.0.3-1-any.pkg.tar.zst";
sha256 = "1lxb0qgnl9fbdmkmj53zjg8i9q5hv0pa83bkmraf2raflpm2yrs5";
};
};
};
};
As before, we set GOOS
and GOARCH
appropriately.
As discussed, we add the pkg-config
libs in postConfigure
.
The libraries we link are the same as for C.
Let’s build and check the result:
$ nix build .#win64Zip
$ unzip -Z1 result
zicross_demo_go-0.1.0-win64/
zicross_demo_go-0.1.0-win64/bin/
zicross_demo_go-0.1.0-win64/bin/libcharset-1.dll
zicross_demo_go-0.1.0-win64/bin/libc++.dll
zicross_demo_go-0.1.0-win64/bin/libiconv-2.dll
zicross_demo_go-0.1.0-win64/bin/libvulkan-1.dll
zicross_demo_go-0.1.0-win64/bin/SDL2.dll
zicross_demo_go-0.1.0-win64/bin/libunwind.dll
zicross_demo_go-0.1.0-win64/bin/zicross_demo_go.exe
zicross_demo_go-0.1.0-win64/share/
zicross_demo_go-0.1.0-win64/share/logo.txt
Conclusion
The pkg-config
setup for C works quite well for Go.
The only additional thing needed is setting up the Go compiler for cross-compiling.
There are some quirks, but generally it works quite well.