From 381701ee258727b24031c09695c11bdf2cc7c854 Mon Sep 17 00:00:00 2001 From: Anso Date: Tue, 24 Mar 2026 09:32:58 -0400 Subject: [PATCH] fix(docker): fix xx cross-compilation sysroot for native modules (#80) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(docker): fix xx cross-compilation sysroot for node-pty and C++ modules Three issues in the prod-deps cross-compilation stage: 1. xx-apk had `gcc` (C only) instead of `g++` — all three native modules (bcrypt, better-sqlite3, node-pty) use C++, so libstdc++ headers must be present in the target sysroot for xx-clang++ to link against them. 2. Missing `linux-headers` in the target sysroot — node-pty requires and which live in the Linux kernel headers package. 3. Missing AR=xx-ar — node-gyp uses `ar` to create static archives during the native build; without this override it falls back to the host ar (amd64), producing wrong-arch .a files that fail at the link step. * fix(docker): use native g++ for same-platform builds, xx-clang only for cross xx-clang introduces sysroot flags that break node-gyp header resolution on Alpine when TARGETARCH == BUILDARCH (e.g. the docker-validate CI job which builds amd64-only). The native path now uses the standard g++ toolchain (identical to what worked before the xx refactor). The cross-compilation path (amd64 → arm64) continues to use xx-clang / xx-clang++ / xx-ar with the target sysroot populated via xx-apk. --- Dockerfile | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0fe489b2..9d5ba3bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,27 +44,46 @@ FROM --platform=$BUILDPLATFORM node:20-alpine AS prod-deps # Copy xx cross-compilation tools into this stage COPY --from=xx / / -ARG TARGETPLATFORM ARG TARGETARCH +ARG BUILDARCH WORKDIR /app -# clang/lld: the cross-compiler toolchain (runs natively on amd64). -# xx-apk adds the target-arch musl headers + libgcc to the sysroot so -# xx-clang can link native .node binaries for the target platform. -RUN apk add --no-cache clang lld python3 make && \ - xx-apk add --no-cache musl-dev gcc +# Two paths depending on whether we are cross-compiling: +# +# Native (TARGETARCH == BUILDARCH, e.g. amd64 → amd64): +# Standard g++ is used. xx-clang introduces sysroot flags that conflict with +# node-gyp's header resolution on Alpine for same-platform builds, so we +# bypass it entirely and let npm ci use the host compiler directly. +# +# Cross (TARGETARCH != BUILDARCH, e.g. amd64 → arm64): +# xx-clang targets the foreign architecture without QEMU. The target sysroot +# is populated via xx-apk: +# g++ — libstdc++ headers/libs (all three native modules use C++) +# musl-dev — musl libc headers for the target arch +# linux-headers — / required by node-pty +RUN if [ "$TARGETARCH" = "$BUILDARCH" ]; then \ + apk add --no-cache python3 make g++; \ + else \ + apk add --no-cache clang lld python3 make g++ && \ + xx-apk add --no-cache g++ musl-dev linux-headers; \ + fi COPY backend/package*.json ./ -# npm_config_arch=$TARGETARCH → tells prebuild-install / node-pre-gyp which -# pre-built binary to fetch (arm64 vs amd64). -# CC/CXX=xx-clang(++) → cross-compiles from source if no pre-built -# binary is available for the target arch. -RUN npm_config_arch=$TARGETARCH \ - CC=xx-clang \ - CXX=xx-clang++ \ - npm ci --omit=dev +# Native: plain npm ci — g++ compiles native modules for the host arch. +# Cross: npm_config_arch tells prebuild-install/node-pre-gyp which pre-built +# binary to attempt; CC/CXX/AR route compilation through xx-clang so +# the output targets the foreign arch without any QEMU emulation. +RUN if [ "$TARGETARCH" = "$BUILDARCH" ]; then \ + npm ci --omit=dev; \ + else \ + npm_config_arch=$TARGETARCH \ + CC=xx-clang \ + CXX=xx-clang++ \ + AR=xx-ar \ + npm ci --omit=dev; \ + fi # Stage 4: Production runtime # Runs on the TARGET platform — no compilation happens here.