next.js/scripts/docker-native-build.sh
docker-native-build.sh160 lines6.3 KB
#!/usr/bin/env bash
# Inner build script run inside the next-swc-builder docker container.
#
# All toolchains (clang, lld, musl sysroots, node, rust, napi-cli,
# cargo-rustflags) are pre-installed in the image.
#
# RUSTFLAGS are resolved via `cargo rustflags` which merges .cargo/config.toml
# (mounted from the repo) with target-specific --config overrides for cross-
# compilation paths. This avoids duplicating config.toml flags in this script.
#
# Expected env vars (set by CI or docker-native-build.js):
#   TARGET     - Rust target triple (e.g. x86_64-unknown-linux-gnu)
#   ABI        - Target ABI (gnu or musl)
#   ARCH       - Target architecture (x86_64 or aarch64)
#   BUILD_TASK - Cargo/napi build task name (default: build-native-release)

set -xeo pipefail

BUILD_TASK="${BUILD_TASK:-build-native-release}"

# Node.js (installed via nodesource) is used only as a build tool (runs
# npm/napi-cli). The output .node shared library's linking is determined
# by cargo's --target, not the node binary.

# --- RUSTFLAGS via cargo-rustflags ---
# We use `linker=clang` + `linker-flavor=gnu-lld-cc` rather than
# `linker=rust-lld` directly. The actual linker is rust-lld in both
# cases, but clang acts as the driver: it handles finding crt files
# (crti.o, crtbeginS.o), libc, and libgcc, and translates --target /
# --sysroot into the correct library search paths.
#
# cargo-rustflags merges these with .cargo/config.toml (cfg(true) base flags,
# musl crt-static, etc.) so we don't duplicate them here.
CROSS_FLAGS="-Clinker=clang -Clinker-flavor=gnu-lld-cc -Clink-arg=-Wl,--icf=all"
HOST_ARCH=$(uname -m)

# Determine if we need --sysroot. Native GNU targets (host == target arch)
# don't need it — clang finds native multiarch headers automatically.
# Cross GNU targets and all musl targets need explicit sysroot paths.
IS_NATIVE_GNU=0
if [ "$ABI" = "gnu" ]; then
  case "${HOST_ARCH}-${ARCH}" in
    x86_64-x86_64|aarch64-aarch64) IS_NATIVE_GNU=1 ;;
  esac
fi

case "$TARGET" in
  x86_64-unknown-linux-gnu)
    CROSS_FLAGS="$CROSS_FLAGS -Clink-arg=--target=x86_64-linux-gnu"
    # For cross builds, DON'T pass --sysroot to the linker — lld can't
    # resolve absolute paths inside libc.so linker scripts when --sysroot
    # is set (it double-prepends the sysroot). Instead, pass -L to point
    # at the cross libs directly. --sysroot is passed via CFLAGS for C
    # compilation (build scripts like jemalloc).
    if [ "$IS_NATIVE_GNU" = "0" ]; then
      CROSS_FLAGS="$CROSS_FLAGS -Clink-arg=-L/usr/x86_64-linux-gnu/lib"
    fi
    ;;
  aarch64-unknown-linux-gnu)
    CROSS_FLAGS="$CROSS_FLAGS -Clink-arg=--target=aarch64-linux-gnu"
    if [ "$IS_NATIVE_GNU" = "0" ]; then
      CROSS_FLAGS="$CROSS_FLAGS -Clink-arg=-L/usr/aarch64-linux-gnu/lib"
    fi
    ;;
  x86_64-unknown-linux-musl)
    CROSS_FLAGS="$CROSS_FLAGS -Clink-arg=--target=x86_64-linux-musl -Clink-arg=--sysroot=/opt/x86_64-linux-musl-cross/x86_64-linux-musl -Clink-arg=--gcc-toolchain=/opt/x86_64-linux-musl-cross" ;;
  aarch64-unknown-linux-musl)
    CROSS_FLAGS="$CROSS_FLAGS -Clink-arg=--target=aarch64-linux-musl -Clink-arg=--sysroot=/opt/aarch64-linux-musl-cross/aarch64-linux-musl -Clink-arg=--gcc-toolchain=/opt/aarch64-linux-musl-cross" ;;
  *) echo "Unknown target: $TARGET"; exit 1 ;;
esac

# Build the --config argument as a TOML inline value
CROSS_CONFIG="target.${TARGET}.rustflags=[$(echo "$CROSS_FLAGS" | sed 's/\([^ ]*\)/"\1"/g; s/ /, /g')]"

# Resolve merged RUSTFLAGS (config.toml base + cross overrides)
RUSTFLAGS=$(cargo rustflags --target "$TARGET" --config "$CROSS_CONFIG")
export RUSTFLAGS

# --- rust-lld symlink ---
# rustc's gcc-ld/ dir has ld.lld but no 'ld' shim. gnu-lld-cc passes
# -B<gcc-ld-dir> to clang, which looks for 'ld' there.
SYSROOT=$(rustc --print sysroot)
GCC_LD="$SYSROOT/lib/rustlib/${TARGET}/bin/gcc-ld"
if [ -d "$GCC_LD" ] && [ ! -e "$GCC_LD/ld" ]; then
  ln -sf ../rust-lld "$GCC_LD/ld"
fi

# --- CC/CXX for build scripts (jemalloc, ring, etc.) ---
TARGET_US=$(echo "$TARGET" | tr '-' '_')
unset "CC_${TARGET_US}" "CXX_${TARGET_US}" "CFLAGS_${TARGET_US}"

export "CC_${TARGET_US}=clang"
export "CXX_${TARGET_US}=clang++"

case "$TARGET" in
  x86_64-unknown-linux-gnu)
    if [ "$HOST_ARCH" = "x86_64" ]; then
      export "CFLAGS_${TARGET_US}=--target=x86_64-linux-gnu"
    else
      export "CFLAGS_${TARGET_US}=--target=x86_64-linux-gnu --sysroot=/usr/x86_64-linux-gnu"
    fi ;;
  aarch64-unknown-linux-gnu)
    if [ "$HOST_ARCH" = "aarch64" ]; then
      export "CFLAGS_${TARGET_US}=--target=aarch64-linux-gnu"
    else
      export "CFLAGS_${TARGET_US}=--target=aarch64-linux-gnu --sysroot=/usr/aarch64-linux-gnu"
    fi ;;
  x86_64-unknown-linux-musl)
    export "CFLAGS_${TARGET_US}=--target=x86_64-linux-musl --sysroot=/opt/x86_64-linux-musl-cross/x86_64-linux-musl --gcc-toolchain=/opt/x86_64-linux-musl-cross" ;;
  aarch64-unknown-linux-musl)
    export "CFLAGS_${TARGET_US}=--target=aarch64-linux-musl --sysroot=/opt/aarch64-linux-musl-cross/aarch64-linux-musl --gcc-toolchain=/opt/aarch64-linux-musl-cross" ;;
esac

# aarch64 needs larger page size for jemalloc
if [ "$ARCH" = "aarch64" ]; then
  export JEMALLOC_SYS_WITH_LG_PAGE=16
fi

# Verify sccache is available if RUSTC_WRAPPER is set to it.
# Cached docker images may not have sccache installed yet.
if [ "${RUSTC_WRAPPER:-}" = "sccache" ] && ! command -v sccache &>/dev/null; then
  echo "WARNING: RUSTC_WRAPPER=sccache but sccache not found — disabling"
  unset RUSTC_WRAPPER
fi

echo "--- Build environment ---"
node -v
rustc --version
echo "Target: $TARGET"
echo "RUSTFLAGS: $RUSTFLAGS"
echo "RUSTC_WRAPPER: ${RUSTC_WRAPPER:-<unset>}"
echo "-------------------------"

rustup target add "$TARGET"
cd packages/next-swc
npm run "$BUILD_TASK" -- --target "$TARGET"
llvm-strip -x native/next-swc.*.node

# Show sccache stats if available
if command -v sccache &>/dev/null; then
  echo "--- sccache stats ---"
  sccache --show-stats || true
fi

# Post-build verification
echo "--- Dynamic libraries ---"
readelf -d native/next-swc.*.node | grep NEEDED

case "$ABI" in
  gnu)
    echo "--- GLIBC symbols by version ---"
    objdump -T native/next-swc.*.node \
      | grep 'GLIBC_' \
      | sed 's/.*\(GLIBC_[^ ]*\) \+/\1 /' \
      | sort -t. -k2,2n -k3,3n \
      | awk '{vers[$1] = vers[$1] ? vers[$1] ", " $2 : $2} END {for (v in vers) print v ": " vers[v]}' \
      | sort -t. -k2,2n -k3,3n
    ;;
esac
Quest for Codev2.0.0
/
SIGN IN