aboutsummaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-16 10:52:45 +0100
committerGitHub Enterprise <[email protected]>2026-03-16 10:52:45 +0100
commit79e10a165cf09dc2cc120b3a226c51f87c235f20 (patch)
treecf51b07e097904044b4bf65bc3fe0ad14134074f /scripts
parentLinux build improvements (#843) (diff)
downloadzen-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-xscripts/win_cross/get_win_sdk.sh305
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"