From 9fb390830746077e906bd5b55f36c11a42d38f5d Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 12 Sep 2025 15:03:27 +0200 Subject: [PATCH] xx-go: zig support Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- README.md | 22 +++++++ src/test-go.bats | 97 ++++++++++++++++++++++++++++ src/test_helper.bash | 4 ++ src/xx-go | 150 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 258 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 833d3cb..ea56764 100644 --- a/README.md +++ b/README.md @@ -342,6 +342,28 @@ RUN go build -o hello hello.go && \ xx-verify hello ``` +`xx-go` can also drive [Zig](https://ziglang.org/) as the underlying C +toolchain for CGo builds. This can be useful when you want a single +cross-compiling toolchain that works across multiple platforms, or when you +already rely on Zig in your build pipeline. + +Zig support is **opt-in** and is enabled by setting the `XX_GO_PREFER_C_COMPILER` +environment variable to `zig`. When this variable is set and the `zig` binary +is available in `PATH`, `xx-go` will configure CGo to use Zig as the C compiler +for the current target. + +```dockerfile +FROM --platform=$BUILDPLATFORM golang:alpine +RUN apk add zig +# ... +ARG TARGETPLATFORM +RUN xx-apk add musl-dev +ENV CGO_ENABLED=1 +ENV XX_GO_PREFER_C_COMPILER=zig +RUN xx-go build -o hello ./hello.go && \ + xx-verify hello +``` + ## Rust Building Rust can be achieved with the `xx-cargo` wrapper that automatically diff --git a/src/test-go.bats b/src/test-go.bats index 56f599c..5cd3fcc 100755 --- a/src/test-go.bats +++ b/src/test-go.bats @@ -511,6 +511,96 @@ testHelloCGO() { assert_output --partial "PKG_CONFIG=$(xx-info triple)-pkg-config" } +testHelloCGOZig() { + if ! supportZig; then + skip "Zig not supported" + fi + export CGO_ENABLED=1 + export XX_GO_PREFER_C_COMPILER=zig + add zig + run xx-go build -x -o /tmp/a.out ./fixtures/hello_cgo.go + assert_success + run xx-verify /tmp/a.out + assert_success + if ! xx-info is-cross; then + run /tmp/a.out + assert_success + assert_output "hello cgo" + fi +} + +@test "native-hellocgo-zig" { + unset TARGETARCH + testHelloCGOZig +} + +@test "amd64-hellocgo-zig" { + export TARGETARCH=amd64 + testHelloCGOZig +} + +@test "arm64-hellocgo-zig" { + export TARGETARCH=arm64 + testHelloCGOZig +} + +@test "arm-hellocgo-zig" { + export TARGETARCH=arm + testHelloCGOZig +} + +@test "ppc64le-hellocgo-zig" { + export TARGETARCH=ppc64le + testHelloCGOZig +} + +@test "riscv64-hellocgo-zig" { + if ! supportRiscVCGo; then + skip "RISC-V CGO not supported" + fi + export TARGETARCH=riscv64 + testHelloCGOZig +} + +@test "loong64-hellocgo-zig" { + if ! supportLoong64CGo; then + skip "LOONGARCH64 not supported" + fi + if [ -f /etc/alpine-release ]; then + # FIXME: loong64-hellocgo issue on alpine < 3.21 + # ld.lld: error: unknown emulation: elf64loongarch + # ld.lld: error: /loongarch64-alpine-linux-musl/usr/lib/gcc/loongarch64-alpine-linux-musl/14.2.0/crtbeginS.o:(.text+0x0): unknown relocation (102) against symbol + # error: unknown target triple 'loongarch64-alpine-linux-musl', please use -triple or -arch + alpineRelease=$(cat /etc/alpine-release) + if ! grep PRETTY_NAME /etc/os-release | cut -d '=' -f 2 | tr -d '"' | grep -q "edge$" || [ "$(semver compare "$alpineRelease" "3.21.0")" -lt 0 ]; then + skip + fi + fi + export TARGETARCH=loong64 + testHelloCGOZig +} + +@test "386-hellocgo-zig" { + export TARGETARCH=386 + testHelloCGOZig +} + +@test "arm64-cgoenv-zig" { + if ! supportZig; then + skip "Zig not supported" + fi + export TARGETARCH=arm64 + export XX_GO_PREFER_C_COMPILER=zig + export CGO_ENABLED=1 + + add zig + # single/double quotes changed in between go versions + run sh -c "xx-go env | sed 's/[\"'\'']//g'" + assert_success + assert_output --partial "CC=zig cc -target" + assert_output --partial "CXX=zig c++ -target" +} + @test "wrap-unwrap" { target="arm64" if [ "$(xx-info arch)" = "arm64" ]; then target="amd64"; fi @@ -535,3 +625,10 @@ testHelloCGO() { assert_success assert_output "$nativeArch" } + +@test "clean-packages" { + if ! supportZig; then + skip "Zig not supported" + fi + del zig +} diff --git a/src/test_helper.bash b/src/test_helper.bash index 3675d78..6f3b256 100644 --- a/src/test_helper.bash +++ b/src/test_helper.bash @@ -142,3 +142,7 @@ supportLoong64CGo() { supportRC() { command -v llvm-rc >/dev/null 2>&1 } + +supportZig() { + [ -f /etc/alpine-release ] && versionGTE "$(xx-info os-version | cut -d'.' -f1-2)" "3.20" +} diff --git a/src/xx-go b/src/xx-go index 6e777e7..3136c0b 100755 --- a/src/xx-go +++ b/src/xx-go @@ -6,6 +6,9 @@ for l in $(xx-info env); do export "${l?}" done +: "${XX_GO_PREFER_C_COMPILER=clang}" +: "${XX_ZIG_TRIPLE=unknown-unknown-none}" + export GOOS="${TARGETOS}" export GOARCH="${TARGETARCH}" @@ -24,6 +27,28 @@ case "$TARGETARCH" in ;; esac fi + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="x86_64-linux-musl" + else + XX_ZIG_TRIPLE="x86_64-linux-gnu" + fi + if [ "$TARGETOS" = "darwin" ]; then + XX_ZIG_TRIPLE="x86_64-macos-none" + elif [ "$TARGETOS" = "windows" ]; then + XX_ZIG_TRIPLE="x86_64-windows-gnu" + fi + ;; + "arm64") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="aarch64-linux-musl" + else + XX_ZIG_TRIPLE="aarch64-linux-gnu" + fi + if [ "$TARGETOS" = "darwin" ]; then + XX_ZIG_TRIPLE="aarch64-macos-none" + elif [ "$TARGETOS" = "windows" ]; then + XX_ZIG_TRIPLE="aarch64-windows-gnu" + fi ;; "arm") if [ -z "$GOARM" ]; then @@ -39,6 +64,94 @@ case "$TARGETARCH" in ;; esac fi + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="arm-linux-musleabihf" + else + XX_ZIG_TRIPLE="arm-linux-gnueabihf" + fi + if [ "$TARGETVARIANT" = "v6" ]; then + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="arm-linux-musleabi" + else + XX_ZIG_TRIPLE="arm-linux-gnueabi" + fi + fi + if [ "$TARGETVARIANT" = "v5" ]; then + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="arm-linux-musleabi" + else + XX_ZIG_TRIPLE="arm-linux-gnueabi" + fi + fi + if [ "$TARGETOS" = "windows" ]; then + XX_ZIG_TRIPLE="arm-windows-gnu" + fi + ;; + "riscv64") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="riscv64-linux-musl" + else + XX_ZIG_TRIPLE="riscv64-linux-gnu" + fi + ;; + "ppc64le") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="powerpc64le-linux-musl" + else + XX_ZIG_TRIPLE="powerpc64le-linux-gnu" + fi + ;; + "s390x") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="s390x-linux-musl" + else + XX_ZIG_TRIPLE="s390x-linux-gnu" + fi + ;; + "loong64") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="loongarch64-linux-musl" + else + XX_ZIG_TRIPLE="loongarch64-linux-gnu" + fi + ;; + "386") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="x86-linux-musl" + else + XX_ZIG_TRIPLE="x86-linux-gnu" + fi + if [ "$TARGETOS" = "windows" ]; then + XX_ZIG_TRIPLE="x86-windows-gnu" + fi + ;; + "mips") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="mips-linux-musl" + else + XX_ZIG_TRIPLE="mips-linux-gnueabi" + fi + ;; + "mipsle") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="mipsel-linux-musl" + else + XX_ZIG_TRIPLE="mipsel-linux-gnueabi" + fi + ;; + "mips64") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="mips64-linux-musl" + else + XX_ZIG_TRIPLE="mips64-linux-gnuabi64" + fi + ;; + "mips64le") + if [ "$XX_LIBC" = "musl" ]; then + XX_ZIG_TRIPLE="mips64el-linux-musl" + else + XX_ZIG_TRIPLE="mips64el-linux-gnuabi64" + fi ;; esac @@ -76,26 +189,33 @@ if command -v "$XX_TRIPLE-g++" >/dev/null 2>/dev/null; then cxx_set=1 fi -if command -v clang >/dev/null 2>/dev/null; then - triple=$(xx-clang --print-target-triple || true) - if [ -n "$triple" ]; then - export CC="$triple-clang" - export CXX="$triple-clang++" +if [ "$XX_GO_PREFER_C_COMPILER" = "clang" ]; then + if command -v clang >/dev/null 2>/dev/null; then + triple=$(xx-clang --print-target-triple || true) + if [ -n "$triple" ]; then + export CC="$triple-clang" + export CXX="$triple-clang++" + c_set=1 + cxx_set=1 + fi + fi + if command -v "$XX_TRIPLE-ar" >/dev/null 2>/dev/null; then + export AR="$XX_TRIPLE-ar" + ar_set=1 + fi + if command -v "$XX_TRIPLE-pkg-config" >/dev/null 2>/dev/null; then + export PKG_CONFIG="$XX_TRIPLE-pkg-config" + pkgconfig_set=1 + fi +elif [ "$XX_GO_PREFER_C_COMPILER" = "zig" ]; then + if command -v "zig" >/dev/null 2>/dev/null; then + export CC="zig cc -target $XX_ZIG_TRIPLE" c_set=1 + export CXX="zig c++ -target $XX_ZIG_TRIPLE" cxx_set=1 fi fi -if command -v "$XX_TRIPLE-ar" >/dev/null 2>/dev/null; then - export AR="$XX_TRIPLE-ar" - ar_set=1 -fi - -if command -v "$XX_TRIPLE-pkg-config" >/dev/null 2>/dev/null; then - export PKG_CONFIG="$XX_TRIPLE-pkg-config" - pkgconfig_set=1 -fi - if [ -z "$GOBIN" ] && [ -n "$GOPATH" ] && [ -n "$GOARCH" ] && [ -n "$GOOS" ]; then export PATH="${GOPATH}/bin/${GOOS}_${GOARCH}:${PATH}" fi