Cross-compilatie met Wails

Go staat erom bekend dat het cross-compilatie veel eenvoudiger maakt dan de meeste andere talen, en dat is grotendeels nog steeds waar bij het gebruik van frameworks zoals Wails. Het maken van uitvoerbare bestanden en/of installers voor verschillende platforms is soms zo eenvoudig als het opsommen van die platforms in een commando:

wails build -clean -nsis \
-platform 'linux/amd64,windows/amd64,windows/arm64,windows/386'

Het bovenstaande commando maakt de volgende bestanden aan:

build
└── bin
    ├── my-app-386.exe
    ├── my-app-amd64.exe
    ├── my-app-amd64-installer.exe
    ├── my-app-arm64.exe
    ├── my-app-arm64-installer.exe
    └── my-app-linux-amd64

Hiermee hebben we direct uitvoerbare bestanden en installers voor Linux en Windows. Ik heb MacOS niet in de lijst met platforms opgenomen omdat Apple over het algemeen geen cross-compilatie toestaat; ik denk niet dat Wails of Go daar iets aan kunnen doen. Wanneer ik darwin toevoeg aan de lijst, wordt het platform overgeslagen met het bericht: WARNING Crosscompiling to Mac not currently supported.

Wanneer ik per ongeluk een platform opgaf dat niet bestaat, kreeg ik een behulpzame melding met daarin de ondersteunde platforms: darwin, darwin/amd64, darwin/arm64, darwin/universal, linux, linux/amd64, linux/arm64, linux/arm, windows, windows/amd64, windows/arm64, windows/386.

Zijn ze statisch gekoppeld?

Volgens het Linux-commando ldd (dat gedeelde object-afhankelijkheden print), zijn de Windows-executables en installers statisch gekoppeld (statically linked) en is de Linux-versie dynamisch gekoppeld:

$ ldd build/bin/*
build/bin/my-app-386.exe:
not a dynamic executable
build/bin/my-app-amd64.exe:
not a dynamic executable
build/bin/my-app-amd64-installer.exe:
not a dynamic executable
build/bin/my-app-arm64.exe:
not a dynamic executable
build/bin/my-app-arm64-installer.exe:
not a dynamic executable
build/bin/my-app-linux-amd64:
linux-vdso.so.1 (0x00007ffdbe4c7000)
libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x000079e7a3ff0000)
libwebkit2gtk-4.0.so.37 => /lib/x86_64-linux-gnu/libwebkit2gtk-4.0.so.37 (0x000079e7a0400000)
libgtk-3.so.0 => /lib/x86_64-linux-gnu/libgtk-3.so.0 (0x000079e79fc00000)
...

Het file-commando bevestigt dit:

$ file build/bin/*
build/bin/my-app-386.exe:             PE32 executable (GUI) Intel 80386, for MS Windows, 7 sections
build/bin/my-app-amd64.exe:           PE32+ executable (GUI) x86-64, for MS Windows, 9 sections
build/bin/my-app-amd64-installer.exe: PE32 executable (GUI) Intel 80386 (stripped to external PDB), for MS Windows, Nullsoft Installer self-extracting archive, 7 sections
build/bin/my-app-arm64.exe:           PE32+ executable (GUI) Aarch64, for MS Windows, 7 sections
build/bin/my-app-arm64-installer.exe: PE32 executable (GUI) Intel 80386 (stripped to external PDB), for MS Windows, Nullsoft Installer self-extracting archive, 7 sections
build/bin/my-app-linux-amd64:         ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8ce9189206c3fc10a0fdfb874b0a5cd7b5f61ec7, for GNU/Linux 3.2.0, stripped

Ik heb geprobeerd om statische koppeling voor het Linux-uitvoerbare bestand af te dwingen door -ldflags '-extldflags "-static"' te gebruiken, maar ik kreeg veel foutmeldingen, waaronder:

/usr/bin/ld: /tmp/go-link-3304063632/000004.o: in function cgo77133bf98b3aC2funcgetaddrinfo': /tmp/go-build/cgounixcgo.cgo2.c:60:(.text+0x37): warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

Er is wellicht een manier om statische koppeling voor het Linux-bestand af te dwingen, maar dat is mij op dit moment niet bekend.

Hoe zit het met linux/arm64?

Helaas werkt cross-compilatie van linux/amd64 naar linux/arm64 momenteel niet (zie onderstaande output). Ik heb in de Wails-repository gezocht en gevonden dat anderen hetzelfde probleem hebben. Er lijkt geen workaround te zijn. Hier is de output:

$ wails build -platform 'linux/arm64'
Wails CLI v2.11.0
# Build Options
Platform(s)        | linux/arm64
Compiler           | /usr/local/go/bin/go
Skip Bindings      | false
Build Mode         | production
Devtools           | false
Frontend Directory | /home/chris/repos/my-app/frontend
Obfuscated         | false
Skip Frontend      | false
Compress           | false
Package            | true
Clean Bin Dir      | false
LDFlags            |
Tags               | []
Race Detector      | false
# Building target: linux/arm64
• Generating bindings: Done.
• Installing frontend dependencies: Done.
• Compiling frontend: Done.
• Compiling application: # runtime/cgo
gcc_arm64.S: Assembler messages:
gcc_arm64.S:30: Error: no such instruction: `stp x29,x30,[sp,'
gcc_arm64.S:34: Error: operand size mismatch for `mov'
gcc_arm64.S:36: Error: no such instruction: `stp x19,x20,[sp,'
gcc_arm64.S:39: Error: no such instruction: `stp x21,x22,[sp,'
gcc_arm64.S:42: Error: no such instruction: `stp x23,x24,[sp,'
gcc_arm64.S:45: Error: no such instruction: `stp x25,x26,[sp,'
gcc_arm64.S:48: Error: no such instruction: `stp x27,x28,[sp,'
gcc_arm64.S:52: Error: operand size mismatch for `mov'
gcc_arm64.S:53: Error: operand size mismatch for `mov'
gcc_arm64.S:54: Error: operand size mismatch for `mov'
gcc_arm64.S:56: Error: no such instruction: `blr x20'
gcc_arm64.S:57: Error: no such instruction: `blr x19'
gcc_arm64.S:59: Error: no such instruction: `ldp x27,x28,[sp,'
gcc_arm64.S:62: Error: no such instruction: `ldp x25,x26,[sp,'
gcc_arm64.S:65: Error: no such instruction: `ldp x23,x24,[sp,'
gcc_arm64.S:68: Error: no such instruction: `ldp x21,x22,[sp,'
gcc_arm64.S:71: Error: no such instruction: `ldp x19,x20,[sp,'
gcc_arm64.S:74: Error: no such instruction: `ldp x29,x30,[sp],'
ERROR   exit status 1

Ik krijg dezelfde foutmelding wanneer ik de -compiler optie gebruik met go1.24.11 of go1.23.12. (Men kan meerdere versies van Go installeren).

De dAppServer/wails-build-action GitHub Action

Op de website van Wails staat een voorbeeld van een GitHub action voor het bouwen van executables, die gebruikmaakt van de dAppServer/wails-build-action. Ik heb het voorbeeld getest met de actions bijgewerkt naar hun nieuwste stabiele versies.

Enkele zaken die iemand kunnen doen aarzelen om dAppServer/wails-build-action te gebruiken:

  • Het voorbeeld op de site van Wails faalt met de foutmelding: Cannot get the real path for source './build/bin/App.app'.
  • Fixes voor deze bug zijn ingediend als pull requests in de dAppServer/wails-build-action repo, maar worden al 5 maanden (en langer) genegeerd (voorbeeld: https://github.com/Snider/build/pull/49/files).
  • Het heeft een copyleft-licentie.
  • Een groot deel van de documentatie van het project is geschreven door een LLM.

De eerder genoemde foutmelding verschijnt als je darwin/universal in de lijst met doelplatforms laat staan. (De Linux- en Windows-builds waren succesvol tijdens mijn test).

GoReleaser

GoReleaser is een fantastische tool die releases automatiseert, inclusief het maken van executables en installers. Het is me echter niet gelukt om Wails ermee te laten werken.

Hier is een deel van de .goreleaser.yaml die ik heb geprobeerd:

builds:
- id: "wails-build"
  goos:
  - linux
  - windows
  tool: "go1.23.12"
  # how: https://go.dev/doc/manage-install
  # why: https://github.com/wailsapp/wails/issues/4605
hooks:
  post:
  - wails build -clean -nsis -compiler 'go1.23.12' -platform 'linux/amd64,linux/arm64,windows/amd64,windows/arm64,windows/386'
  - cp ./build/bin/. ./dist

Ik heb dit getest met goreleaser release --snapshot --clean en kreeg deze fout:

⨯ release failed after 6s
error=
│ post hook failed: shell: 'wails build -clean -nsis -compiler 'go1.23.12' -platform linux/amd64,linux/arm64,windows/amd64,windows/arm64,windows/386': exit status 1: Wails CLI v2.11.0
│ # Build Options
│
│ Platform(s)        | linux/amd64,linux/arm64,windows/amd64,windows/arm64,windows/386
│ Compiler           | /home/chris/go/bin/go1.23.12
│ Skip Bindings      | false
│ Build Mode         | production
│ Devtools           | false
│ Frontend Directory | /home/chris/repos/my-app/frontend
│ Obfuscated         | false
│ Skip Frontend      | false
│ Compress           | false
│ Package            | true
│ Clean Bin Dir      | true
│ LDFlags            |
│ Tags               | []
│ Race Detector      | false
│ # Building target: linux/amd64
│
│   • Generating bindings:   ERROR
│
│           fork/exec /tmp/wailsbindings: no such file or directory
│   ERROR
│
│           fork/exec /tmp/wailsbindings: no such file or directory

Ik kreeg dezelfde fout met go1.24.11, en met Go v1.25.5 wanneer ik de tool en compiler niet specificeerde. Ik heb in de Wails-repo gezocht naar issues of PR's die hierbij zouden kunnen helpen, maar kon niets veelbelovends vinden. Er is misschien een manier om het werkend te krijgen, maar voor nu maak ik releases handmatig.

Voor het geval de fout alleen optreedt wanneer ik goreleaser release --snapshot --clean lokaal draai en niet in GitHub Actions, heb ik dat laatste ook geprobeerd. De foutmelding veranderde, maar er lijkt nog steeds geen workaround te zijn:

⨯ release failed after 5s
error=
Error:     │ build failed: exit status 1: main.go:11:12: pattern all:frontend/dist: no matching files found
target=linux_386_sse2
Error: The process '/opt/hostedtoolcache/goreleaser-action/2.13.1/x64/goreleaser' failed with exit code 1