diff options
| author | Stefan Boberg <[email protected]> | 2026-03-16 10:52:45 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-16 10:52:45 +0100 |
| commit | 79e10a165cf09dc2cc120b3a226c51f87c235f20 (patch) | |
| tree | cf51b07e097904044b4bf65bc3fe0ad14134074f /scripts | |
| parent | Linux build improvements (#843) (diff) | |
| download | zen-79e10a165cf09dc2cc120b3a226c51f87c235f20.tar.xz zen-79e10a165cf09dc2cc120b3a226c51f87c235f20.zip | |
Enable cross compilation of Windows targets on Linux (#839)
This PR makes it *possible* to do a Windows build on Linux via `clang-cl`.
It doesn't actually change any build process. No policy change, just mechanics and some code fixes to clear clang compilation.
The code fixes are mainly related to #include file name casing, to match the on-disk casing of the SDK files (via xwin).
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/win_cross/get_win_sdk.sh | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/scripts/win_cross/get_win_sdk.sh b/scripts/win_cross/get_win_sdk.sh new file mode 100755 index 000000000..b22d1bf3a --- /dev/null +++ b/scripts/win_cross/get_win_sdk.sh @@ -0,0 +1,305 @@ +#!/bin/bash +# +# Downloads xwin and uses it to fetch the Windows SDK and MSVC CRT headers/libs +# needed for cross-compiling Windows binaries from Linux using clang-cl. +# +# Usage: +# ./get_win_sdk.sh [output_dir] +# +# Output defaults to ~/.xwin-sdk (override via $XWIN_SDK_DIR or first argument). + +set -euo pipefail + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +die() { echo "ERROR: $1" >&2; exit 1; } + +sdk_dir="${1:-${XWIN_SDK_DIR:-${HOME}/.xwin-sdk}}" + +if [[ "${sdk_dir}" == "--help" ]]; then + echo "usage: $(basename "${BASH_SOURCE[0]}") [output_dir]" + echo "" + echo "Downloads the Windows SDK and MSVC CRT via xwin for cross-compilation." + echo "Default output: ~/.xwin-sdk (override via \$XWIN_SDK_DIR or first argument)" + exit 0 +fi + +# If the directory already has SDK content, skip download +if [ -d "${sdk_dir}/sdk/include/um" ] && [ -d "${sdk_dir}/crt/include" ]; then + echo "SDK already present at '${sdk_dir}', skipping download." + echo "Delete the directory to force re-download." + # Still create the compat layout in case it's missing (e.g. script was updated) + CREATE_COMPAT_ONLY=true +else + CREATE_COMPAT_ONLY=false +fi + +if [ -e "${sdk_dir}" ]; then + # Allow re-use of existing empty or partial directory + if [ -d "${sdk_dir}" ]; then + : + else + die "'${sdk_dir}' exists but is not a directory" + fi +fi + +mkdir -p "${sdk_dir}" + +# ------------------------------------------------------------------------- +# Detect LLVM installation +# ------------------------------------------------------------------------- +LLVM_BIN="${LLVM_BIN_DIR:-}" +if [ -z "${LLVM_BIN}" ]; then + # Try common locations + for candidate in /usr/lib/llvm-19/bin /usr/lib/llvm-18/bin /usr/lib/llvm-17/bin; do + if [ -x "${candidate}/clang" ]; then + LLVM_BIN="${candidate}" + break + fi + done +fi +if [ -z "${LLVM_BIN}" ]; then + # Fallback: try to find clang on PATH + CLANG_PATH=$(command -v clang 2>/dev/null || true) + if [ -n "${CLANG_PATH}" ]; then + LLVM_BIN=$(dirname "$(readlink -f "${CLANG_PATH}")") + fi +fi +if [ -z "${LLVM_BIN}" ]; then + die "Could not find LLVM/clang installation. Set LLVM_BIN_DIR to the bin directory." +fi +echo "Using LLVM at: ${LLVM_BIN}" + +# ------------------------------------------------------------------------- +# Download xwin binary and fetch SDK (skip if already present) +# ------------------------------------------------------------------------- +if [ "${CREATE_COMPAT_ONLY}" = false ]; then + XWIN_VERSION="0.6.5" + XWIN_ARCHIVE="xwin-${XWIN_VERSION}-x86_64-unknown-linux-musl.tar.gz" + XWIN_URL="https://github.com/Jake-Shadle/xwin/releases/download/${XWIN_VERSION}/${XWIN_ARCHIVE}" + + TMPDIR=$(mktemp -d) + trap 'rm -rf "${TMPDIR}"' EXIT + + echo "Downloading xwin ${XWIN_VERSION}..." + if command -v wget &>/dev/null; then + wget -q --show-progress -O "${TMPDIR}/${XWIN_ARCHIVE}" "${XWIN_URL}" + elif command -v curl &>/dev/null; then + curl -fSL --progress-bar -o "${TMPDIR}/${XWIN_ARCHIVE}" "${XWIN_URL}" + else + die "Neither wget nor curl found" + fi + + echo "Extracting xwin..." + tar -xzf "${TMPDIR}/${XWIN_ARCHIVE}" -C "${TMPDIR}" + + XWIN_BIN="${TMPDIR}/xwin-${XWIN_VERSION}-x86_64-unknown-linux-musl/xwin" + if [ ! -x "${XWIN_BIN}" ]; then + die "xwin binary not found after extraction" + fi + + echo "Fetching Windows SDK and CRT (this may take a few minutes)..." + "${XWIN_BIN}" --accept-license splat --output "${sdk_dir}" +fi + +# ------------------------------------------------------------------------- +# Create tool wrapper scripts in bin/ +# ------------------------------------------------------------------------- +BIN_DIR="${sdk_dir}/bin" +mkdir -p "${BIN_DIR}" + +# clang-cl wrapper (since the host may not have a clang-cl symlink) +cat > "${BIN_DIR}/clang-cl" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/clang" --driver-mode=cl -D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH "\$@" +WRAPPER +chmod +x "${BIN_DIR}/clang-cl" + +# clang wrapper for GNU assembly (.S files) +cat > "${BIN_DIR}/clang" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/clang" "\$@" +WRAPPER +chmod +x "${BIN_DIR}/clang" + +# ------------------------------------------------------------------------- +# Create MSVC-compatible directory layout for xmake package builds. +# +# xmake's built-in msvc toolchain on Linux uses find_build_tools() which +# expects the following structure: +# <sdk>/VC/Tools/MSVC/<version>/include → CRT headers +# <sdk>/VC/Tools/MSVC/<version>/lib/<arch> → CRT libs +# <sdk>/Windows Kits/10/Include/<ver>/{ucrt,um,shared} → SDK headers +# <sdk>/Windows Kits/10/Lib/<ver>/{ucrt,um}/<arch> → SDK libs +# <sdk>/bin/<arch>/ → tool wrappers +# +# We create this layout using symlinks back to the xwin flat layout. +# ------------------------------------------------------------------------- +echo "Creating MSVC-compatible directory layout..." + +FAKE_VC_VER="14.0.0" +FAKE_SDK_VER="10.0.0.0" + +# --- VC Tools (CRT) --- +VC_DIR="${sdk_dir}/VC/Tools/MSVC/${FAKE_VC_VER}" +mkdir -p "${VC_DIR}" +ln -sfn "${sdk_dir}/crt/include" "${VC_DIR}/include" +mkdir -p "${VC_DIR}/lib" +ln -sfn "${sdk_dir}/crt/lib/x86_64" "${VC_DIR}/lib/x64" + +# --- Windows Kits (SDK headers) --- +WINSDK_INC="${sdk_dir}/Windows Kits/10/Include/${FAKE_SDK_VER}" +mkdir -p "${WINSDK_INC}" +ln -sfn "${sdk_dir}/sdk/include/ucrt" "${WINSDK_INC}/ucrt" +ln -sfn "${sdk_dir}/sdk/include/um" "${WINSDK_INC}/um" +ln -sfn "${sdk_dir}/sdk/include/shared" "${WINSDK_INC}/shared" + +# --- Windows Kits (SDK libs) --- +WINSDK_LIB="${sdk_dir}/Windows Kits/10/Lib/${FAKE_SDK_VER}" +mkdir -p "${WINSDK_LIB}/ucrt" "${WINSDK_LIB}/um" +ln -sfn "${sdk_dir}/sdk/lib/ucrt/x86_64" "${WINSDK_LIB}/ucrt/x64" +ln -sfn "${sdk_dir}/sdk/lib/um/x86_64" "${WINSDK_LIB}/um/x64" + +# --- Tool wrappers in bin/<arch>/ (for msvc toolchain PATH setup) --- +ARCH_BIN="${sdk_dir}/bin/x64" +mkdir -p "${ARCH_BIN}" + +# cl → clang-cl wrapper +cat > "${ARCH_BIN}/cl" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/clang" --driver-mode=cl -D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH "\$@" +WRAPPER +chmod +x "${ARCH_BIN}/cl" +cp "${ARCH_BIN}/cl" "${ARCH_BIN}/cl.exe" + +# link → lld-link (with /lib mode redirecting to llvm-lib for archiver use) +# xmake sets ar=link.exe for non-LTO MSVC builds and may pass linker-only flags +# like /opt:ref to the archiver. We detect /lib mode, filter those flags, and +# redirect to llvm-lib. Also handles response files (@file) that xmake uses +# when the argument list is too long. +cat > "${ARCH_BIN}/link" << WRAPPER +#!/bin/bash +ALL_ARGS=() +for arg in "\$@"; do + if [[ "\$arg" == @* ]]; then + rspfile="\${arg#@}" + while IFS= read -r line; do + [[ -n "\$line" ]] && ALL_ARGS+=("\$line") + done < "\$rspfile" + else + ALL_ARGS+=("\$arg") + fi +done +LIB_MODE=false +HAS_OUT_LIB=false +HAS_OBJ_ONLY=true +ARGS=() +for arg in "\${ALL_ARGS[@]}"; do + lower="\${arg,,}" + case "\$lower" in + /lib|-lib) LIB_MODE=true ;; + /out:*.lib|-out:*.lib) HAS_OUT_LIB=true; ARGS+=("\$arg") ;; + /opt:*|-opt:*) ;; + /subsystem:*|-subsystem:*) HAS_OBJ_ONLY=false; ARGS+=("\$arg") ;; + *.exe) HAS_OBJ_ONLY=false; ARGS+=("\$arg") ;; + *) ARGS+=("\$arg") ;; + esac +done +if [ "\$LIB_MODE" = true ] || ([ "\$HAS_OUT_LIB" = true ] && [ "\$HAS_OBJ_ONLY" = true ]); then + LIB_ARGS=() + for arg in "\${ARGS[@]}"; do + case "\${arg,,}" in + -nodefaultlib:*|/nodefaultlib:*) ;; + *) LIB_ARGS+=("\$arg") ;; + esac + done + exec "${LLVM_BIN}/llvm-lib" "\${LIB_ARGS[@]}" +else + exec "${LLVM_BIN}/lld-link" "\$@" +fi +WRAPPER +chmod +x "${ARCH_BIN}/link" +cp "${ARCH_BIN}/link" "${ARCH_BIN}/link.exe" + +# lib → llvm-lib +cat > "${ARCH_BIN}/lib" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/llvm-lib" "\$@" +WRAPPER +chmod +x "${ARCH_BIN}/lib" +cp "${ARCH_BIN}/lib" "${ARCH_BIN}/lib.exe" + +# rc → llvm-rc (with SDK include paths for winres.h etc.) +cat > "${ARCH_BIN}/rc" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/llvm-rc" /I "${sdk_dir}/crt/include" /I "${sdk_dir}/sdk/include/ucrt" /I "${sdk_dir}/sdk/include/um" /I "${sdk_dir}/sdk/include/shared" "\$@" +WRAPPER +chmod +x "${ARCH_BIN}/rc" +cp "${ARCH_BIN}/rc" "${ARCH_BIN}/rc.exe" + +# ml64 → llvm-ml (MASM-compatible assembler) +cat > "${ARCH_BIN}/ml64" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/llvm-ml" -m64 "\$@" +WRAPPER +chmod +x "${ARCH_BIN}/ml64" +cp "${ARCH_BIN}/ml64" "${ARCH_BIN}/ml64.exe" + +# clang-cl (for xmake's built-in clang-cl toolchain detection) +cat > "${ARCH_BIN}/clang-cl" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/clang" --driver-mode=cl -D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH "\$@" +WRAPPER +chmod +x "${ARCH_BIN}/clang-cl" + +# llvm-ar (cmake's clang-cl driver may use llvm-ar as archiver name but with +# MSVC-style flags like /nologo /out: — redirect to llvm-lib which handles these) +cat > "${ARCH_BIN}/llvm-ar" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/llvm-lib" "\$@" +WRAPPER +chmod +x "${ARCH_BIN}/llvm-ar" + +# lld-link (for LTO builds where clang-cl toolchain uses lld-link) +cat > "${ARCH_BIN}/lld-link" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/lld-link" "\$@" +WRAPPER +chmod +x "${ARCH_BIN}/lld-link" + +# mt → llvm-mt (manifest tool) +cat > "${ARCH_BIN}/mt" << WRAPPER +#!/bin/bash +exec "${LLVM_BIN}/llvm-mt" "\$@" +WRAPPER +chmod +x "${ARCH_BIN}/mt" +cp "${ARCH_BIN}/mt" "${ARCH_BIN}/mt.exe" + +# ------------------------------------------------------------------------- +# Create debug CRT lib symlinks (cmake's try_compile uses Debug config +# by default, which links against msvcrtd.lib etc. -- these don't exist +# in xwin since it only ships release libs. Symlink to release versions +# so cmake compiler tests pass.) +# ------------------------------------------------------------------------- +CRT_LIB="${sdk_dir}/crt/lib/x86_64" +for lib in msvcrt MSVCRT vcruntime msvcprt libcmt LIBCMT libcpmt libcpmt1 libconcrt libconcrt1 libvcruntime; do + release="${CRT_LIB}/${lib}.lib" + debug="${CRT_LIB}/${lib}d.lib" + if [ -f "${release}" ] && [ ! -e "${debug}" ]; then + ln -sfn "${lib}.lib" "${debug}" + fi +done + +echo "" +echo "Windows SDK installed to: ${sdk_dir}" +echo " SDK headers: ${sdk_dir}/sdk/include/um" +echo " SDK libs: ${sdk_dir}/sdk/lib/um/x86_64" +echo " CRT headers: ${sdk_dir}/crt/include" +echo " CRT libs: ${sdk_dir}/crt/lib/x86_64" +echo " Tool wrappers: ${BIN_DIR}/" +echo " MSVC compat: ${sdk_dir}/VC/ and ${sdk_dir}/Windows Kits/" +echo "" +echo "Usage:" +echo " xmake config -p windows -a x64 --toolchain=clang-cl --sdk=\${sdk_dir}" +echo "" +echo "Done" |