Merge branch 'v3-alpha' into refactor/runtime-init-scripts

This commit is contained in:
Lea Anthony 2026-01-28 08:46:30 +11:00 committed by GitHub
commit ee2dbd289f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 480 additions and 54 deletions

423
.github/workflows/build-cross-image.yml vendored Normal file
View file

@ -0,0 +1,423 @@
name: Build Cross-Compiler Image
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch containing Dockerfile'
required: true
default: 'v3-alpha'
sdk_version:
description: 'macOS SDK version'
required: true
default: '14.5'
zig_version:
description: 'Zig version'
required: true
default: '0.14.0'
image_version:
description: 'Image version tag'
required: true
default: 'latest'
skip_tests:
description: 'Skip cross-compilation tests'
required: false
default: 'false'
type: boolean
push:
branches:
- v3-alpha
paths:
- 'v3/internal/commands/build_assets/docker/Dockerfile.cross'
env:
REGISTRY: ghcr.io
IMAGE_NAME: wailsapp/wails-cross
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image_tag: ${{ steps.vars.outputs.image_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch || github.ref }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set build variables
id: vars
run: |
echo "sdk_version=${{ inputs.sdk_version || '14.5' }}" >> $GITHUB_OUTPUT
echo "zig_version=${{ inputs.zig_version || '0.14.0' }}" >> $GITHUB_OUTPUT
echo "image_version=${{ inputs.image_version || 'latest' }}" >> $GITHUB_OUTPUT
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest
type=raw,value=${{ steps.vars.outputs.image_version }}
type=raw,value=sdk-${{ steps.vars.outputs.sdk_version }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: v3/internal/commands/build_assets/docker
file: v3/internal/commands/build_assets/docker/Dockerfile.cross
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: |
${{ steps.meta.outputs.labels }}
io.wails.zig.version=${{ steps.vars.outputs.zig_version }}
io.wails.sdk.version=${{ steps.vars.outputs.sdk_version }}
build-args: |
ZIG_VERSION=${{ steps.vars.outputs.zig_version }}
MACOS_SDK_VERSION=${{ steps.vars.outputs.sdk_version }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Test cross-compilation for all platforms
test-cross-compile:
needs: build
if: ${{ inputs.skip_tests != 'true' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# Darwin targets (Zig + macOS SDK) - no platform emulation needed
- os: darwin
arch: arm64
platform: ""
expected_file: "Mach-O 64-bit.*arm64"
- os: darwin
arch: amd64
platform: ""
expected_file: "Mach-O 64-bit.*x86_64"
# Linux targets (GCC) - need platform to match architecture
- os: linux
arch: amd64
platform: "linux/amd64"
expected_file: "ELF 64-bit LSB.*x86-64"
- os: linux
arch: arm64
platform: "linux/arm64"
expected_file: "ELF 64-bit LSB.*ARM aarch64"
# Windows targets (Zig + mingw) - no platform emulation needed
- os: windows
arch: amd64
platform: ""
expected_file: "PE32\\+ executable.*x86-64"
- os: windows
arch: arm64
platform: ""
expected_file: "PE32\\+ executable.*Aarch64"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch || github.ref }}
- name: Set up QEMU
if: matrix.platform != ''
uses: docker/setup-qemu-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create test CGO project
run: |
mkdir -p test-project
cd test-project
# Create a minimal CGO test program
cat > main.go << 'EOF'
package main
/*
#include <stdlib.h>
int add(int a, int b) {
return a + b;
}
*/
import "C"
import "fmt"
func main() {
result := C.add(1, 2)
fmt.Printf("CGO test: 1 + 2 = %d\n", result)
}
EOF
cat > go.mod << 'EOF'
module test-cgo
go 1.21
EOF
- name: Build ${{ matrix.os }}/${{ matrix.arch }} (CGO)
run: |
cd test-project
PLATFORM_FLAG=""
if [ -n "${{ matrix.platform }}" ]; then
PLATFORM_FLAG="--platform ${{ matrix.platform }}"
fi
docker run --rm $PLATFORM_FLAG \
-v "$(pwd):/app" \
-e APP_NAME="test-cgo" \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag || 'latest' }} \
${{ matrix.os }} ${{ matrix.arch }}
- name: Verify binary format
run: |
cd test-project/bin
ls -la
# Find the built binary
if [ "${{ matrix.os }}" = "windows" ]; then
BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }}.exe 2>/dev/null || ls *.exe | head -1)
else
BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }} 2>/dev/null || ls test-cgo* | grep -v '.exe' | head -1)
fi
echo "Binary: $BINARY"
FILE_OUTPUT=$(file "$BINARY")
echo "File output: $FILE_OUTPUT"
# Verify the binary format matches expected
if echo "$FILE_OUTPUT" | grep -qE "${{ matrix.expected_file }}"; then
echo "✅ Binary format verified: ${{ matrix.os }}/${{ matrix.arch }}"
else
echo "❌ Binary format mismatch!"
echo "Expected pattern: ${{ matrix.expected_file }}"
echo "Got: $FILE_OUTPUT"
exit 1
fi
- name: Check library dependencies (Linux only)
if: matrix.os == 'linux'
run: |
cd test-project/bin
BINARY=$(ls test-cgo-${{ matrix.os }}-${{ matrix.arch }} 2>/dev/null || ls test-cgo* | grep -v '.exe' | head -1)
echo "## Library Dependencies for $BINARY"
echo ""
# Use readelf to show dynamic dependencies
echo "### NEEDED libraries:"
readelf -d "$BINARY" | grep NEEDED || echo "No dynamic dependencies (statically linked)"
# Verify expected libraries are linked
echo ""
echo "### Verifying required libraries..."
NEEDED=$(readelf -d "$BINARY" | grep NEEDED)
MISSING=""
for lib in libwebkit2gtk-4.1.so libgtk-3.so libglib-2.0.so libc.so; do
if echo "$NEEDED" | grep -q "$lib"; then
echo "✅ $lib"
else
echo "❌ $lib MISSING"
MISSING="$MISSING $lib"
fi
done
if [ -n "$MISSING" ]; then
echo ""
echo "ERROR: Missing required libraries:$MISSING"
exit 1
fi
# Test non-CGO builds (pure Go cross-compilation)
test-non-cgo:
needs: build
if: ${{ inputs.skip_tests != 'true' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- os: darwin
arch: arm64
expected_file: "Mach-O 64-bit.*arm64"
- os: darwin
arch: amd64
expected_file: "Mach-O 64-bit.*x86_64"
- os: linux
arch: amd64
expected_file: "ELF 64-bit LSB"
- os: linux
arch: arm64
expected_file: "ELF 64-bit LSB.*ARM aarch64"
- os: windows
arch: amd64
expected_file: "PE32\\+ executable.*x86-64"
- os: windows
arch: arm64
expected_file: "PE32\\+ executable.*Aarch64"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch || github.ref }}
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create test non-CGO project
run: |
mkdir -p test-project
cd test-project
# Create a pure Go test program (no CGO)
cat > main.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("Pure Go cross-compilation test")
}
EOF
cat > go.mod << 'EOF'
module test-pure-go
go 1.21
EOF
- name: Build ${{ matrix.os }}/${{ matrix.arch }} (non-CGO)
run: |
cd test-project
# For non-CGO, we can use any platform since Go handles cross-compilation
# We set CGO_ENABLED=0 to ensure pure Go build
docker run --rm \
-v "$(pwd):/app" \
-e APP_NAME="test-pure-go" \
-e CGO_ENABLED=0 \
--entrypoint /bin/sh \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build.outputs.image_tag || 'latest' }} \
-c "GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -o bin/test-pure-go-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.os == 'windows' && '.exe' || '' }} ."
- name: Verify binary format
run: |
cd test-project/bin
ls -la
# Find the built binary
if [ "${{ matrix.os }}" = "windows" ]; then
BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}.exe"
else
BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}"
fi
echo "Binary: $BINARY"
FILE_OUTPUT=$(file "$BINARY")
echo "File output: $FILE_OUTPUT"
# Verify the binary format matches expected
if echo "$FILE_OUTPUT" | grep -qE "${{ matrix.expected_file }}"; then
echo "✅ Binary format verified: ${{ matrix.os }}/${{ matrix.arch }} (non-CGO)"
else
echo "❌ Binary format mismatch!"
echo "Expected pattern: ${{ matrix.expected_file }}"
echo "Got: $FILE_OUTPUT"
exit 1
fi
- name: Check library dependencies (Linux only)
if: matrix.os == 'linux'
run: |
cd test-project/bin
BINARY="test-pure-go-${{ matrix.os }}-${{ matrix.arch }}"
echo "## Library Dependencies for $BINARY (non-CGO)"
echo ""
# Non-CGO builds should have minimal dependencies (just libc or statically linked)
echo "### NEEDED libraries:"
readelf -d "$BINARY" | grep NEEDED || echo "No dynamic dependencies (statically linked)"
# Verify NO GTK/WebKit libraries (since CGO is disabled)
NEEDED=$(readelf -d "$BINARY" | grep NEEDED || true)
if echo "$NEEDED" | grep -q "libwebkit\|libgtk"; then
echo "❌ ERROR: Non-CGO binary should not link to GTK/WebKit!"
exit 1
else
echo "✅ Confirmed: No GTK/WebKit dependencies (expected for non-CGO)"
fi
# Summary job
test-summary:
needs: [build, test-cross-compile, test-non-cgo]
if: always() && inputs.skip_tests != 'true'
runs-on: ubuntu-latest
steps:
- name: Check test results
run: |
echo "## Cross-Compilation Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.test-cross-compile.result }}" = "success" ]; then
echo "✅ **CGO Tests**: All passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **CGO Tests**: Failed" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.test-non-cgo.result }}" = "success" ]; then
echo "✅ **Non-CGO Tests**: All passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Non-CGO Tests**: Failed" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Tested Platforms" >> $GITHUB_STEP_SUMMARY
echo "| Platform | Architecture | CGO | Non-CGO |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------------|-----|---------|" >> $GITHUB_STEP_SUMMARY
echo "| Darwin | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY
echo "| Darwin | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY
echo "| Linux | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY
echo "| Linux | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY
echo "| Windows | arm64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY
echo "| Windows | amd64 | ✅ | ✅ |" >> $GITHUB_STEP_SUMMARY
# Fail if any test failed
if [ "${{ needs.test-cross-compile.result }}" != "success" ] || [ "${{ needs.test-non-cgo.result }}" != "success" ]; then
echo ""
echo "❌ Some tests failed. Check the individual job logs for details."
exit 1
fi

View file

@ -1,7 +1,8 @@
# Cross-compile Wails v3 apps to any platform
#
# Uses Zig as C compiler + macOS SDK for darwin targets
# Supports both amd64 and arm64 host architectures
# Darwin: Zig + macOS SDK
# Linux: GCC (use --platform to match target arch)
# Windows: Zig + bundled mingw
#
# Usage:
# docker build -t wails-cross -f Dockerfile.cross .
@ -15,13 +16,22 @@
# Multi-arch build:
# docker buildx build --platform linux/amd64,linux/arm64 -t wails-cross -f Dockerfile.cross .
FROM golang:1.25-alpine
FROM golang:1.25-bookworm
RUN apk add --no-cache curl xz nodejs npm
ARG TARGETARCH
# Install base tools, GCC, and GTK/WebKit dev packages
RUN apt-get update && apt-get install -y --no-install-recommends \
curl xz-utils nodejs npm pkg-config gcc libc6-dev \
libgtk-3-dev libwebkit2gtk-4.1-dev \
libgtk-4-dev libwebkitgtk-6.0-dev \
&& rm -rf /var/lib/apt/lists/*
# Note: For Linux cross-compilation, use --platform to match target architecture
# e.g., docker run --platform linux/amd64 ... for amd64 targets
# Install Zig - automatically selects correct binary for host architecture
ARG ZIG_VERSION=0.14.0
ARG TARGETARCH
RUN ZIG_ARCH=$(case "${TARGETARCH}" in arm64) echo "aarch64" ;; *) echo "x86_64" ;; esac) && \
curl -L "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-${ZIG_ARCH}-${ZIG_VERSION}.tar.xz" \
| tar -xJ -C /opt \
@ -35,7 +45,9 @@ RUN curl -L "https://github.com/joseluisq/macosx-sdks/releases/download/${MACOS_
ENV MACOS_SDK_PATH=/opt/macos-sdk
# Create zig cc wrappers for each target
# Create Zig CC wrappers for Darwin and Windows targets
# (Linux uses native GCC with --platform flag for architecture matching)
# Darwin arm64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-darwin-arm64
#!/bin/sh
@ -76,45 +88,7 @@ exec zig cc -fno-sanitize=all -target x86_64-macos-none -isysroot /opt/macos-sdk
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-darwin-amd64
# Linux amd64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-linux-amd64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target x86_64-linux-musl $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-linux-amd64
# Linux arm64
COPY <<'ZIGWRAP' /usr/local/bin/zcc-linux-arm64
#!/bin/sh
ARGS=""
SKIP_NEXT=0
for arg in "$@"; do
if [ $SKIP_NEXT -eq 1 ]; then
SKIP_NEXT=0
continue
fi
case "$arg" in
-target) SKIP_NEXT=1 ;;
*) ARGS="$ARGS $arg" ;;
esac
done
exec zig cc -target aarch64-linux-musl $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-linux-arm64
# Windows amd64
# Windows amd64 - uses Zig's bundled mingw
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-amd64
#!/bin/sh
ARGS=""
@ -134,7 +108,7 @@ exec zig cc -target x86_64-windows-gnu $ARGS
ZIGWRAP
RUN chmod +x /usr/local/bin/zcc-windows-amd64
# Windows arm64
# Windows arm64 - uses Zig's bundled mingw
COPY <<'ZIGWRAP' /usr/local/bin/zcc-windows-arm64
#!/bin/sh
ARGS=""
@ -163,13 +137,42 @@ OS=${1:-darwin}
ARCH=${2:-arm64}
case "${OS}-${ARCH}" in
darwin-arm64|darwin-aarch64) export CC=zcc-darwin-arm64; export GOARCH=arm64; export GOOS=darwin ;;
darwin-amd64|darwin-x86_64) export CC=zcc-darwin-amd64; export GOARCH=amd64; export GOOS=darwin ;;
linux-arm64|linux-aarch64) export CC=zcc-linux-arm64; export GOARCH=arm64; export GOOS=linux ;;
linux-amd64|linux-x86_64) export CC=zcc-linux-amd64; export GOARCH=amd64; export GOOS=linux ;;
windows-arm64|windows-aarch64) export CC=zcc-windows-arm64; export GOARCH=arm64; export GOOS=windows ;;
windows-amd64|windows-x86_64) export CC=zcc-windows-amd64; export GOARCH=amd64; export GOOS=windows ;;
*) echo "Usage: <os> <arch>"; echo " os: darwin, linux, windows"; echo " arch: amd64, arm64"; exit 1 ;;
darwin-arm64|darwin-aarch64)
export CC=zcc-darwin-arm64
export GOARCH=arm64
export GOOS=darwin
;;
darwin-amd64|darwin-x86_64)
export CC=zcc-darwin-amd64
export GOARCH=amd64
export GOOS=darwin
;;
linux-arm64|linux-aarch64)
export CC=gcc
export GOARCH=arm64
export GOOS=linux
;;
linux-amd64|linux-x86_64)
export CC=gcc
export GOARCH=amd64
export GOOS=linux
;;
windows-arm64|windows-aarch64)
export CC=zcc-windows-arm64
export GOARCH=arm64
export GOOS=windows
;;
windows-amd64|windows-x86_64)
export CC=zcc-windows-amd64
export GOARCH=amd64
export GOOS=windows
;;
*)
echo "Usage: <os> <arch>"
echo " os: darwin, linux, windows"
echo " arch: amd64, arm64"
exit 1
;;
esac
export CGO_ENABLED=1

View file

@ -69,7 +69,7 @@ tasks:
Docker image '{{.CROSS_IMAGE}}' not found.
Build it first: wails3 task setup:docker
cmds:
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
- docker run --rm --platform linux/{{.DOCKER_ARCH}} -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
- mkdir -p {{.BIN_DIR}}
- mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"