fix(docker): fix xx cross-compilation sysroot for native modules (#80)

* 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
   <pty.h> and <termios.h> 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.
This commit is contained in:
Anso
2026-03-24 09:32:58 -04:00
committed by GitHub
parent 6338122253
commit 381701ee25
+33 -14
View File
@@ -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 — <pty.h> / <termios.h> 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.