Stream: contributing

Topic: Help with Go platform cross-compile


view this post on Zulip Luke Boswell (Apr 28 2024 at 03:24):

I'm just wondering if there are any Go users who may be able to assist with the Go platform example?

I was exploring changes so that the platform builds the pre-built binaries itself, and also cross-compiles to the various roc supported targets.

If I compile for my native target (macos arm64) everything is happy and I get a c-archive for the host which links nicely with Roc and I can use with roc dev --prebuilt-platform main.roc.

But as soon as I go to cross-compile for another target I get an issue build constraints exclude all Go files which seems strange as we don't specify any build constraints. I've been following various guides online and this doesn't seem to be a common thing.

I've left a build.sh script in that branch to help debug this.

GOOS=linux GOARCH=arm64 go build -C host -buildmode=c-archive -o libhost.a
cp host/libhost.a platform/macos-arm64.a

roc dev --prebuilt-platform main.roc

Also, below is the WIP roc build script for building the platform for various architectures.

main =

    buildGoTarget! MacosArm64

    # TODO -- why is this failing with "build constraints exclude all Go files"?
    buildGoTarget! MacosX64

    # buildGoTarget! LinuxArm64
    # etc

    Stdout.line "DONE"

buildGoTarget : SupportedTarget -> Task {} _
buildGoTarget = \target ->

    (goos, goarch, prebuiltBinary) = when target is
        MacosArm64 -> ("darwin", "arm64", "macos-arm64.a")
        MacosX64 -> ("darwin", "amd64", "macos-x64")
        LinuxArm64 -> ("linux", "arm64", "linux-arm64.a")
        LinuxX64 -> ("linux", "amd64", "linux-x64.a")
        WindowsArm64 -> ("windows", "arm64", "windows-arm64.a")
        WindowsX64 -> ("windows", "amd64", "windows-x64")

    Cmd.new "go"
    |> Cmd.envs [("GOOS", goos), ("GOARCH", goarch)]
    |> Cmd.args ["build", "-C", "host", "-buildmode=c-archive", "-o","libhost.a"]
    |> Cmd.status
    |> Task.mapErr! \err -> BuildErr goos goarch (Inspect.toStr err)

    Cmd.exec "cp" ["host/libhost.a", "platform/$(prebuiltBinary)"]
    |> Task.mapErr! \err -> CpErr (Inspect.toStr err)

view this post on Zulip Oskar Hahn (Apr 28 2024 at 08:20):

Could you tell exactly, what commands you are running and which returns the error message build constraints exclude all Go files?

The first command, you have to call is roc build --lib main.roc --output host/libapp.so. I can not see this in your build-scripts.

You also have to call roc preprocess-host main.roc. This command internally calls roc gen-stub-lib which does something similar then roc build --lib, but is broken in some cases. This means, that roc preprocess-host creates a broken libapp.so file. You have to call roc build --lib every time after calling roc preprocess-host to replace the broken libapp.so.

There are some strange things in your branch. Why are you using go 1.23 ? The latest go version is 1.22. Some time ago, they changed the syntax of the go.mod file. You have to specify a miner version. For example go 1.22.0 or go 1.22.1.

You removed the LDFLAGS from the main.go file: #cgo LDFLAGS: -L. -lapp I don't think it works without it.

Is there a reason, you are using -buildmode=c-archive instead of -buildmode=pie?

view this post on Zulip Oskar Hahn (Apr 28 2024 at 08:26):

You could also try to use zig as the c-compiler. If you want go cross compile cgo, you could get better results with it:

CC="zig cc" go build [...]

If you look for "cgo cross compile zig" you find some articles, that explain, how zig helps to cross compile cgo.

view this post on Zulip Luke Boswell (Apr 28 2024 at 08:55):

Thank you for your comments. I probably should have provided some more context for what I am trying to achieve and my intent here.

I'm not necessarily wanting to merge that PR... I've been researching and working towards achieving https://github.com/roc-lang/roc/issues/6414

The intent is to remove the "Rebuilding platform..." from roc cli. So all platforms will be responsible for building their own prebuilt binaries and roc only needs to link with either a library (using the legacy linker) or where available the preprocessed host (surgical linker).

I was updating the examples and the current Go example doesn't work using the ci/all_tests.sh script for me. I started investigating that... and got sidetracked learning about the go build system and then started thinking about the changes for glue, and then thinking about how I could make a tool which scaffolds out starter platforms for various languages. It's a bit of a random thought process... but where the PR is at is kind of where I got to. I've almost got all the pieces that I could make a tool that scaffolds out a zig, rust, and now basic go platform that can cross compile to all of the roc supported targets.

So, I've changed the example to build a c-archive and produce a libhost.a library instead of the dynamic library. That is why you can't see any libapp.so, because it's now doing something very different.

I wasn't aware that 1.23 isn't the latest. I just had an error when I try to build that says I need to upgrade to 1.23. It must be something that homebrew installed for me? :shrug:

I removed the LDFLAGS as it didn't seem to be required any more, and included #cgo CFLAGS: -Wno-main-return-type as I was getting warnings for that. I needed to add //export main so that the static library kept the main symbol in the artefact.

Using zig for the c-compiler sounds interesting. I'll explore that further.

Thank you for your assistance.

view this post on Zulip Richard Feldman (Apr 28 2024 at 11:25):

btw you can set some environment variables to prevent Homebrew from automatically updating things:

export HOMEBREW_NO_AUTO_UPDATE=1
export HOMEBREW_NO_INSTALL_UPGRADE=1

view this post on Zulip Luke Boswell (Apr 28 2024 at 11:41):

A bit of a follow up to the above. @Oskar Hahn and I did some more investigation into this. The issue is related to how I've set up the cross compilation with Go in that PR. Using environment variables CGO_ENABLED=1 and setting CC to a cross compiler looks to be the solution. I didnt quite get to the point of fully working static library due to obscure issues, but I've convinced myself that someone familiar with go could do this without too much trouble.

My take away though from this exploration is that it will be helpful to have an example or template with a good developer experience suited for the native toolchain. The current example is really helpful, I'dlike to add something like a build script maybe.

Anyone who would like to build binaries for multiple archs and OS should be able to use things like GH actions or docker buildx as other alternatives.


Last updated: Jul 06 2025 at 12:14 UTC