1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
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"
|