Repository navigation
clangcl_linux_sysroot
title: Building the VS2022 (v143) target from Linux with clang-cl (draft) description: A Windows x64 cross toolchain on Linux without Wine, from clang/lld, a packaged MSVC + Windows SDK sysroot and a case-insensitive VFS overlay, linking the stock 2026q4_external_v143_x64 externals published: true date: 2026-10-31T00:00:00.000Z tags: editor: markdown dateCreated: 2026-10-31T00:00:00.000Z
Status: trial validated (2026-10-31). A program compiled and linked by clang-cl and lld-link in an Ubuntu 26.04 container, against the unchanged
2026q4_external_v143_x64package, runs natively on Windows: zlib and libpng through their import libraries, Lua, the MSVC-built luabind static library with STL types in its interface, the MS STL and the UCRT. Ryzom Core itself has not been built this way yet; see "Open items". Companion pages: VS2008 under Wine and VC6 under Wine for the two legacy toolchains, which have no Wine-free option because those compilers only exist as Windows binaries. This page is the modern counterpart: native Linux, parallel, no Wine. {.is-warning}
clang-cl is not a different ABI. It targets x86_64-pc-windows-msvc: MSVC name mangling, calling conventions,
class layout, RTTI and exception model, the same UCRT, vcruntime140 and msvcp140, the same MS STL headers,
CodeView debug information, and lld-link writes PDBs. Objects from clang-cl link against MSVC-built libraries and
the result debugs in Visual Studio like any MSVC build (Chromium and Firefox ship Windows builds exactly so). What a
Linux host needs is only the data side of the Microsoft toolchain: the MSVC headers and libraries and the Windows
SDK. Everything executable (compiler, linker, librarian, resource compiler, manifest tool) comes from LLVM.
toolchain_v143_sysroot.7z (285 MiB, 3.3 GB unpacked, 7,587 files) holds the headers and libraries in the
Visual Studio layout that clang's /winsysroot option reads:
| Path in the archive | From the Windows machine |
|---|---|
VC/Tools/MSVC/14.44.35207/include, atlmfc/include
|
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.44.35207\… |
VC/Tools/MSVC/14.44.35207/lib/{x64,x86}, atlmfc/lib/{x64,x86}
|
same |
Windows Kits/10/Include/10.0.26100.0/{ucrt,um,shared,winrt,cppwinrt} |
C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0 |
Windows Kits/10/Lib/10.0.26100.0/{ucrt,um}/{x64,x86} |
C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0 |
The versions are the ones the v143 externals were built with (MSVC 14.44.35207, SDK 10.0.26100.0): the STL headers
a consumer compiles against must match the ones the package's C++ libraries were built with, as between two MSVC
builds. Building the archive is a robocopy /E of those directories into the layout above, then 7-Zip. Nothing is
generated; a newer toolchain is a new archive.
Alternatives that fetch the same payload without a Windows machine: xwin
(xwin splat, reads the Visual Studio installer manifest, downloads only the CRT/STL and SDK packages, unpacks with
lowercase aliases) and msvc-wine (same download, also able to run the real
cl.exe through Wine).
The SDK is written for a case-insensitive filesystem and relies on it: windows.h includes "DriverSpecs.h" for
a file named driverspecs.h, and MSVC-built objects carry /DEFAULTLIB:MSVCRT directives for msvcrt.lib.
/winsysroot finds the right directories but file lookup on ext4 stays case-sensitive, and lowercase alias
symlinks (the xwin approach, or ryzomcore's tool/wine_vs2008/lowercase_alias.py) only cover includes spelled in
lowercase. The complete fix is a clang VFS overlay with case-sensitive: false that lists every directory and
file of the sysroot with its real name; clang-cl takes it with -vfsoverlay <yaml>, lld-link with
/vfsoverlay:<yaml>, and both then match any spelling. The overlay is generated once per sysroot (1.3 MB of JSON for
this one):
"""gen_vfs.py <sysroot dir> <output yaml>: case-insensitive clang VFS overlay for a Windows sysroot."""
import os, sys, json
root = os.path.abspath(sys.argv[1])
def entry(path):
items = []
for name in sorted(os.listdir(path)):
full = os.path.join(path, name)
if os.path.islink(full):
continue
if os.path.isdir(full):
items.append(entry(full))
else:
items.append({"type": "file", "name": name, "external-contents": full})
return {"type": "directory", "name": os.path.basename(path) if path != root else root, "contents": items}
json.dump({"version": 0, "case-sensitive": "false", "use-external-names": True, "roots": [entry(root)]},
open(sys.argv[2], "w"))Note the spelling: the compiler-side option is clang's generic -vfsoverlay <file> (clang-cl has no
/vfsoverlay: form), the linker-side one is lld-link's /vfsoverlay:<file>.
# toolchain-clangcl-x64.cmake: Windows x64 from Linux with clang-cl + lld-link; WINSYSROOT in the environment
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_VERSION 10.0)
set(CMAKE_SYSTEM_PROCESSOR AMD64)
set(_sysroot "$ENV{WINSYSROOT}")
set(_vfs "${_sysroot}.yaml")
set(CMAKE_C_COMPILER clang-cl)
set(CMAKE_CXX_COMPILER clang-cl)
set(CMAKE_C_COMPILER_TARGET x86_64-pc-windows-msvc)
set(CMAKE_CXX_COMPILER_TARGET x86_64-pc-windows-msvc)
set(CMAKE_LINKER lld-link)
set(CMAKE_AR llvm-lib)
set(CMAKE_RC_COMPILER llvm-rc)
set(CMAKE_MT llvm-mt)
set(CMAKE_C_FLAGS_INIT "/winsysroot \"${_sysroot}\" -vfsoverlay \"${_vfs}\"")
set(CMAKE_CXX_FLAGS_INIT "/winsysroot \"${_sysroot}\" -vfsoverlay \"${_vfs}\"")
set(CMAKE_EXE_LINKER_FLAGS_INIT "/winsysroot:\"${_sysroot}\" /vfsoverlay:\"${_vfs}\"")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "/winsysroot:\"${_sysroot}\" /vfsoverlay:\"${_vfs}\"")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "/winsysroot:\"${_sysroot}\" /vfsoverlay:\"${_vfs}\"")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)CMake 3.31 and 4.x detect MSVC 19.33-compatible clang-cl, pick the Ninja/MSVC flag set (/MD, /MDd,
/Zi), and run their vs_link_exe wrapper with llvm-mt for manifests. The externals of the v143 package are
found through CMAKE_PREFIX_PATH with their own CMake config files (ZLIBConfig.cmake, BoostConfig.cmake) or
the standard Find modules; a FindLua.cmake that understands Lua 5.5's numeric version macros is needed (the one in
the XP externals' cmake/ directory).
FROM ubuntu:resolute
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
clang lld llvm cmake ninja-build python3 p7zip-full ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /mnt/nel /opt/sysrootsUbuntu 26.04 ships clang/lld 21.1 and CMake 4.2; clang-cl, lld-link, llvm-lib, llvm-rc and llvm-mt are
in those packages. The sysroot archive is extracted into a volume (/opt/sysroots/toolchain_v143_sysroot) and the
overlay generated next to it (/opt/sysroots/toolchain_v143_sysroot.yaml); the build sets WINSYSROOT and uses the
toolchain file. The same shape as ryzombuild_wine_amd64 with its _prefixes volume.
-
hello.exe(MS STL,GetTickCount64) and the externals test (zlib 1.3.2 libpng 1.6.59, a luabind-registered function called from Lua 5.5) build on Linux and run on Windows against the package's DLLs. - CMake's default flags work unchanged; linking the MSVC-built static luabind into a clang-cl executable works, which is the C++ ABI interoperability question in one step.
-
A real luabind bug surfaced:
scope.hppchose the comma-operator overloads by__cplusplus >= 201103L. MSVC reports199711Lunless/Zc:__cplusplusis given, so the MSVC-built library exported the C++03 overload while clang-cl (real__cplusplus) called the C++11 one: an unresolved external. Fixed in ryzom/luabind (bb5e4fd) by keying on the Boost-derivedLUABIND_NO_RVALUE_REFERENCES, which is the same on both sides of the ABI. Any header that keys ABI-relevant choices on__cplusplusis a risk for mixed MSVC/clang-cl builds; MSVC consumers built with/Zc:__cplusplushit the same mismatch.
- Configure and build Ryzom Core itself with this toolchain against
2026q4_external_v143_x64: MSVC-only constructs to expect are MASM-syntax inline assembly in the x86 paths, a few pragmas, and precompiled-header behaviour. clang-cl defines_MSC_VER, so the existing#ifdefstructure applies as is. - The MFC tools and the Max plugins:
atlmfcheaders and libraries are in the sysroot and clang-cl compiles ATL/MFC (#importtype libraries are the known gap); the 3ds Max SDK would be a second payload, as in the Wine prefix. - Qt: the package's Qt DLLs link fine from clang-cl; only a clang-cl build of Qt itself would matter for the static variants.
- 32-bit (
/machine:x86): the sysroot carries the x86 libraries too; untested. - Quick start integration: a
ryzombuild_clangcl_amd64image and atoolchain_v143_sysrootvolume filled byconfigure_docker.pyfrom the CDN archive or from the host's own Visual Studio, a toolchain layer undertool/clangcl_v143/, and thev143/x64externals detected the waydump_wine.pyfinds them in prefixes.