|
| 1 | +# Cross toolchain configuration for using clang-cl on non-Windows hosts to |
| 2 | +# target MSVC. |
| 3 | +# |
| 4 | +# Usage: |
| 5 | +# cmake -G Ninja |
| 6 | +# -DCMAKE_TOOLCHAIN_FILE=/path/to/this/file |
| 7 | +# -DHOST_ARCH=[aarch64|arm64|armv7|arm|i686|x86|x86_64|x64] |
| 8 | +# -DLLVM_NATIVE_TOOLCHAIN=/path/to/llvm/installation |
| 9 | +# -DLLVM_WINSYSROOT=/path/to/win/sysroot |
| 10 | +# -DMSVC_VER=vc tools version folder name |
| 11 | +# -DWINSDK_VER=windows sdk version folder name |
| 12 | +# |
| 13 | +# HOST_ARCH: |
| 14 | +# The architecture to build for. |
| 15 | +# |
| 16 | +# LLVM_NATIVE_TOOLCHAIN: |
| 17 | +# *Absolute path* to a folder containing the toolchain which will be used to |
| 18 | +# build. At a minimum, this folder should have a bin directory with a |
| 19 | +# copy of clang-cl, clang, clang++, and lld-link, as well as a lib directory |
| 20 | +# containing clang's system resource directory. |
| 21 | +# |
| 22 | +# MSVC_VER/WINSDK_VER: |
| 23 | +# (Optional) if not specified, highest version number is used if any. |
| 24 | +# |
| 25 | +# LLVM_WINSYSROOT and MSVC_VER work together to define a folder layout that |
| 26 | +# containing MSVC headers and system libraries. The layout of the folder |
| 27 | +# matches that which is intalled by MSVC 2017 on Windows, and should look like |
| 28 | +# this: |
| 29 | +# |
| 30 | +# ${LLVM_WINSYSROOT}/VC/Tools/MSVC/${MSVC_VER}/ |
| 31 | +# include |
| 32 | +# vector |
| 33 | +# stdint.h |
| 34 | +# etc... |
| 35 | +# lib |
| 36 | +# x64 |
| 37 | +# libcmt.lib |
| 38 | +# msvcrt.lib |
| 39 | +# etc... |
| 40 | +# x86 |
| 41 | +# libcmt.lib |
| 42 | +# msvcrt.lib |
| 43 | +# etc... |
| 44 | +# |
| 45 | +# For versions of MSVC < 2017, or where you have a hermetic toolchain in a |
| 46 | +# custom format, you must use symlinks or restructure it to look like the above. |
| 47 | +# |
| 48 | +# LLVM_WINSYSROOT and WINSDK_VER work together to define a folder layout that |
| 49 | +# matches that of the Windows SDK installation on a standard Windows machine. |
| 50 | +# It should match the layout described below. |
| 51 | +# |
| 52 | +# Note that if you install Windows SDK to a windows machine and simply copy the |
| 53 | +# files, it will already be in the correct layout. |
| 54 | +# |
| 55 | +# ${LLVM_WINSYSROOT}/Windows Kits/10/ |
| 56 | +# Include |
| 57 | +# ${WINSDK_VER} |
| 58 | +# shared |
| 59 | +# ucrt |
| 60 | +# um |
| 61 | +# windows.h |
| 62 | +# etc... |
| 63 | +# Lib |
| 64 | +# ${WINSDK_VER} |
| 65 | +# ucrt |
| 66 | +# x64 |
| 67 | +# x86 |
| 68 | +# ucrt.lib |
| 69 | +# etc... |
| 70 | +# um |
| 71 | +# x64 |
| 72 | +# x86 |
| 73 | +# kernel32.lib |
| 74 | +# etc |
| 75 | +# |
| 76 | +# IMPORTANT: In order for this to work, you will need a valid copy of the Windows |
| 77 | +# SDK and C++ STL headers and libraries on your host. Additionally, since the |
| 78 | +# Windows libraries and headers are not case-correct, this toolchain file sets |
| 79 | +# up a VFS overlay for the SDK headers and case-correcting symlinks for the |
| 80 | +# libraries when running on a case-sensitive filesystem. |
| 81 | + |
| 82 | +include_guard(GLOBAL) |
| 83 | + |
| 84 | +# When configuring CMake with a toolchain file against a top-level CMakeLists.txt, |
| 85 | +# it will actually run CMake many times, once for each small test program used to |
| 86 | +# determine what features a compiler supports. By default, none of these |
| 87 | +# invocations share a CMakeCache.txt with the top-level invocation, meaning they |
| 88 | +# won't see the value of any arguments the user passed via -D. Since these are |
| 89 | +# necessary to properly configure MSVC in both the top-level configuration as well as |
| 90 | +# all feature-test invocations, we include them in CMAKE_TRY_COMPILE_PLATFORM_VARIABLES, |
| 91 | +# so that they get inherited by child invocations. |
| 92 | +list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES |
| 93 | + HOST_ARCH |
| 94 | + LLVM_NATIVE_TOOLCHAIN |
| 95 | + LLVM_WINSYSROOT |
| 96 | + MSVC_VER |
| 97 | + WINSDK_VER |
| 98 | + msvc_lib_symlinks_dir |
| 99 | + winsdk_lib_symlinks_dir |
| 100 | + winsdk_vfs_overlay_path |
| 101 | + ) |
| 102 | + |
| 103 | +function(generate_winsdk_vfs_overlay winsdk_include_dir output_path) |
| 104 | + set(include_dirs) |
| 105 | + file(GLOB_RECURSE entries LIST_DIRECTORIES true "${winsdk_include_dir}/*") |
| 106 | + foreach(entry ${entries}) |
| 107 | + if(IS_DIRECTORY "${entry}") |
| 108 | + list(APPEND include_dirs "${entry}") |
| 109 | + endif() |
| 110 | + endforeach() |
| 111 | + |
| 112 | + file(WRITE "${output_path}" "version: 0\n") |
| 113 | + file(APPEND "${output_path}" "case-sensitive: false\n") |
| 114 | + file(APPEND "${output_path}" "roots:\n") |
| 115 | + |
| 116 | + foreach(dir ${include_dirs}) |
| 117 | + file(GLOB headers RELATIVE "${dir}" "${dir}/*.h") |
| 118 | + if(NOT headers) |
| 119 | + continue() |
| 120 | + endif() |
| 121 | + |
| 122 | + file(APPEND "${output_path}" " - name: \"${dir}\"\n") |
| 123 | + file(APPEND "${output_path}" " type: directory\n") |
| 124 | + file(APPEND "${output_path}" " contents:\n") |
| 125 | + |
| 126 | + foreach(header ${headers}) |
| 127 | + file(APPEND "${output_path}" " - name: \"${header}\"\n") |
| 128 | + file(APPEND "${output_path}" " type: file\n") |
| 129 | + file(APPEND "${output_path}" " external-contents: \"${dir}/${header}\"\n") |
| 130 | + endforeach() |
| 131 | + endforeach() |
| 132 | +endfunction() |
| 133 | + |
| 134 | +function(generate_winsdk_lib_symlinks winsdk_um_lib_dir output_dir) |
| 135 | + execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${output_dir}") |
| 136 | + file(GLOB libraries RELATIVE "${winsdk_um_lib_dir}" "${winsdk_um_lib_dir}/*") |
| 137 | + foreach(library ${libraries}) |
| 138 | + string(TOLOWER "${library}" all_lowercase_symlink_name) |
| 139 | + if(NOT library STREQUAL all_lowercase_symlink_name) |
| 140 | + execute_process(COMMAND "${CMAKE_COMMAND}" |
| 141 | + -E create_symlink |
| 142 | + "${winsdk_um_lib_dir}/${library}" |
| 143 | + "${output_dir}/${all_lowercase_symlink_name}") |
| 144 | + endif() |
| 145 | + |
| 146 | + get_filename_component(name_we "${library}" NAME_WE) |
| 147 | + get_filename_component(ext "${library}" EXT) |
| 148 | + string(TOLOWER "${ext}" lowercase_ext) |
| 149 | + set(lowercase_ext_symlink_name "${name_we}${lowercase_ext}") |
| 150 | + if(NOT library STREQUAL lowercase_ext_symlink_name AND |
| 151 | + NOT all_lowercase_symlink_name STREQUAL lowercase_ext_symlink_name) |
| 152 | + execute_process(COMMAND "${CMAKE_COMMAND}" |
| 153 | + -E create_symlink |
| 154 | + "${winsdk_um_lib_dir}/${library}" |
| 155 | + "${output_dir}/${lowercase_ext_symlink_name}") |
| 156 | + endif() |
| 157 | + endforeach() |
| 158 | +endfunction() |
| 159 | + |
| 160 | +function(generate_msvc_lib_symlinks msvc_lib_dir output_dir) |
| 161 | + execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${output_dir}") |
| 162 | + file(GLOB libraries RELATIVE "${msvc_lib_dir}" "${msvc_lib_dir}/*.lib") |
| 163 | + foreach(library ${libraries}) |
| 164 | + get_filename_component(name_wle "${library}" NAME_WLE) |
| 165 | + get_filename_component(ext "${library}" LAST_EXT) |
| 166 | + string(TOLOWER "${ext}" lowercase_ext) |
| 167 | + string(TOUPPER "${name_wle}" all_uppercase_symlink_name_wle) |
| 168 | + set(uppercase_symlink_name "${all_uppercase_symlink_name_wle}${lowercase_ext}") |
| 169 | + if(NOT library STREQUAL uppercase_symlink_name) |
| 170 | + execute_process(COMMAND "${CMAKE_COMMAND}" |
| 171 | + -E create_symlink |
| 172 | + "${msvc_lib_dir}/${library}" |
| 173 | + "${output_dir}/${uppercase_symlink_name}") |
| 174 | + endif() |
| 175 | + endforeach() |
| 176 | +endfunction() |
| 177 | + |
| 178 | +function(get_highest_version the_dir the_ver) |
| 179 | + file(GLOB entries LIST_DIRECTORIES true RELATIVE "${the_dir}" "${the_dir}/[0-9.]*") |
| 180 | + foreach(entry ${entries}) |
| 181 | + if(IS_DIRECTORY "${the_dir}/${entry}") |
| 182 | + set(${the_ver} "${entry}" PARENT_SCOPE) |
| 183 | + endif() |
| 184 | + endforeach() |
| 185 | +endfunction() |
| 186 | + |
| 187 | +set(CMAKE_SYSTEM_NAME Windows) |
| 188 | +set(CMAKE_SYSTEM_VERSION 10.0) |
| 189 | +set(CMAKE_SYSTEM_PROCESSOR AMD64) |
| 190 | + |
| 191 | +if(NOT HOST_ARCH) |
| 192 | + set(HOST_ARCH x86_64) |
| 193 | +endif() |
| 194 | +if(HOST_ARCH STREQUAL "aarch64" OR HOST_ARCH STREQUAL "arm64") |
| 195 | + set(TRIPLE_ARCH "aarch64") |
| 196 | + set(WINSDK_ARCH "arm64") |
| 197 | +elseif(HOST_ARCH STREQUAL "armv7" OR HOST_ARCH STREQUAL "arm") |
| 198 | + set(TRIPLE_ARCH "armv7") |
| 199 | + set(WINSDK_ARCH "arm") |
| 200 | +elseif(HOST_ARCH STREQUAL "i686" OR HOST_ARCH STREQUAL "x86") |
| 201 | + set(TRIPLE_ARCH "i686") |
| 202 | + set(WINSDK_ARCH "x86") |
| 203 | +elseif(HOST_ARCH STREQUAL "x86_64" OR HOST_ARCH STREQUAL "x64") |
| 204 | + set(TRIPLE_ARCH "x86_64") |
| 205 | + set(WINSDK_ARCH "x64") |
| 206 | +else() |
| 207 | + message(SEND_ERROR "Unknown host architecture ${HOST_ARCH}. Must be aarch64 (or arm64), armv7 (or arm), i686 (or x86), or x86_64 (or x64).") |
| 208 | +endif() |
| 209 | + |
| 210 | +# Do some sanity checking to make sure we can find a native toolchain and |
| 211 | +# that the Windows SDK / MSVC STL directories look kosher. |
| 212 | +if(NOT EXISTS "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" OR |
| 213 | + NOT EXISTS "${LLVM_NATIVE_TOOLCHAIN}/bin/lld-link") |
| 214 | + message(SEND_ERROR |
| 215 | + "LLVM_NATIVE_TOOLCHAIN folder '${LLVM_NATIVE_TOOLCHAIN}' does not " |
| 216 | + "point to a valid directory containing bin/clang-cl and bin/lld-link " |
| 217 | + "binaries") |
| 218 | +endif() |
| 219 | + |
| 220 | +if (NOT MSVC_VER) |
| 221 | + get_highest_version("${LLVM_WINSYSROOT}/VC/Tools/MSVC" MSVC_VER) |
| 222 | +endif() |
| 223 | + |
| 224 | +if (NOT WINSDK_VER) |
| 225 | + get_highest_version("${LLVM_WINSYSROOT}/Windows Kits/10/Include" WINSDK_VER) |
| 226 | +endif() |
| 227 | + |
| 228 | +if (NOT LLVM_WINSYSROOT OR NOT MSVC_VER OR NOT WINSDK_VER) |
| 229 | + message(SEND_ERROR |
| 230 | + "Must specify CMake variable LLVM_WINSYSROOT, MSVC_VER and WINSDK_VER") |
| 231 | +endif() |
| 232 | + |
| 233 | +set(ATLMFC_LIB "${LLVM_WINSYSROOT}/VC/Tools/MSVC/${MSVC_VER}/atlmfc/lib") |
| 234 | +set(MSVC_INCLUDE "${LLVM_WINSYSROOT}/VC/Tools/MSVC/${MSVC_VER}/include") |
| 235 | +set(MSVC_LIB "${LLVM_WINSYSROOT}/VC/Tools/MSVC/${MSVC_VER}/lib") |
| 236 | +set(WINSDK_INCLUDE "${LLVM_WINSYSROOT}/Windows Kits/10/Include/${WINSDK_VER}") |
| 237 | +set(WINSDK_LIB "${LLVM_WINSYSROOT}/Windows Kits/10/Lib/${WINSDK_VER}") |
| 238 | + |
| 239 | +if (NOT EXISTS "${MSVC_INCLUDE}" OR NOT EXISTS "${MSVC_LIB}") |
| 240 | + message(SEND_ERROR |
| 241 | + "CMake variable LLVM_WINSYSROOT and MSVC_VER must point to a folder " |
| 242 | + "containing MSVC system headers and libraries") |
| 243 | +endif() |
| 244 | + |
| 245 | +if(NOT EXISTS "${WINSDK_INCLUDE}" OR NOT EXISTS "${WINSDK_LIB}") |
| 246 | + message(SEND_ERROR |
| 247 | + "CMake variable LLVM_WINSYSROOT and WINSDK_VER must resolve to a " |
| 248 | + "valid Windows SDK installation") |
| 249 | +endif() |
| 250 | + |
| 251 | +if(NOT EXISTS "${WINSDK_INCLUDE}/um/Windows.h") |
| 252 | + message(SEND_ERROR "Cannot find Windows.h") |
| 253 | +endif() |
| 254 | +if(NOT EXISTS "${WINSDK_INCLUDE}/um/WINDOWS.H") |
| 255 | + set(case_sensitive_filesystem TRUE) |
| 256 | +endif() |
| 257 | + |
| 258 | +set(CMAKE_C_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" CACHE FILEPATH "") |
| 259 | +set(CMAKE_CXX_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" CACHE FILEPATH "") |
| 260 | +set(CMAKE_LINKER "${LLVM_NATIVE_TOOLCHAIN}/bin/lld-link" CACHE FILEPATH "") |
| 261 | +set(CMAKE_AR "${LLVM_NATIVE_TOOLCHAIN}/bin/llvm-lib" CACHE FILEPATH "") |
| 262 | +set(CMAKE_ASM_MASM_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/llvm-ml" CACHE FILEPATH "") |
| 263 | + |
| 264 | +# Even though we're cross-compiling, we need some native tools (e.g. llvm-tblgen), and those |
| 265 | +# native tools have to be built before we can start doing the cross-build. LLVM supports |
| 266 | +# a CROSS_TOOLCHAIN_FLAGS_NATIVE argument which consists of a list of flags to pass to CMake |
| 267 | +# when configuring the NATIVE portion of the cross-build. By default we construct this so |
| 268 | +# that it points to the tools in the same location as the native clang-cl that we're using. |
| 269 | +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_ASM_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang") |
| 270 | +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_C_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang") |
| 271 | +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_CXX_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang++") |
| 272 | + |
| 273 | +# These flags are used during build time. So if CFLAGS/CXXFLAGS/LDFLAGS is set |
| 274 | +# for the target, makes sure these are unset during build time. |
| 275 | +set(CROSS_TOOLCHAIN_FLAGS_NATIVE "${_CTF_NATIVE_DEFAULT}" CACHE STRING "") |
| 276 | + |
| 277 | +set(COMPILE_FLAGS |
| 278 | + -D_CRT_SECURE_NO_WARNINGS |
| 279 | + --target=${TRIPLE_ARCH}-windows-msvc |
| 280 | + -fms-compatibility-version=19.28 |
| 281 | + -vctoolsversion ${MSVC_VER} |
| 282 | + -winsdkversion ${WINSDK_VER} |
| 283 | + -winsysroot ${LLVM_WINSYSROOT}) |
| 284 | + |
| 285 | +if(case_sensitive_filesystem) |
| 286 | + # Ensure all sub-configures use the top-level VFS overlay instead of generating their own. |
| 287 | + if(NOT winsdk_vfs_overlay_path) |
| 288 | + set(winsdk_vfs_overlay_path "${CMAKE_BINARY_DIR}/winsdk_vfs_overlay.yaml") |
| 289 | + generate_winsdk_vfs_overlay("${WINSDK_INCLUDE}" "${winsdk_vfs_overlay_path}") |
| 290 | + endif() |
| 291 | + list(APPEND COMPILE_FLAGS |
| 292 | + -Xclang -ivfsoverlay -Xclang "${winsdk_vfs_overlay_path}") |
| 293 | +endif() |
| 294 | + |
| 295 | +string(REPLACE ";" " " COMPILE_FLAGS "${COMPILE_FLAGS}") |
| 296 | +string(APPEND CMAKE_C_FLAGS_INIT " ${COMPILE_FLAGS}") |
| 297 | +string(APPEND CMAKE_CXX_FLAGS_INIT " ${COMPILE_FLAGS}") |
| 298 | +if(TRIPLE_ARCH STREQUAL "x86_64") |
| 299 | + string(APPEND CMAKE_ASM_MASM_FLAGS_INIT " -m64") |
| 300 | +endif() |
| 301 | + |
| 302 | +set(LINK_FLAGS |
| 303 | + # Prevent CMake from attempting to invoke mt.exe. It only recognizes the slashed form and not the dashed form. |
| 304 | + /manifest:no |
| 305 | + |
| 306 | + -libpath:"${ATLMFC_LIB}/${WINSDK_ARCH}" |
| 307 | + -libpath:"${MSVC_LIB}/${WINSDK_ARCH}" |
| 308 | + -libpath:"${WINSDK_LIB}/ucrt/${WINSDK_ARCH}" |
| 309 | + -libpath:"${WINSDK_LIB}/um/${WINSDK_ARCH}") |
| 310 | + |
| 311 | +if(case_sensitive_filesystem) |
| 312 | + # Ensure all sub-configures use the top-level symlinks dir instead of generating their own. |
| 313 | + if(NOT winsdk_lib_symlinks_dir) |
| 314 | + set(winsdk_lib_symlinks_dir "${CMAKE_BINARY_DIR}/winsdk_lib_symlinks") |
| 315 | + generate_winsdk_lib_symlinks("${WINSDK_LIB}/um/${WINSDK_ARCH}" "${winsdk_lib_symlinks_dir}") |
| 316 | + endif() |
| 317 | + list(APPEND LINK_FLAGS |
| 318 | + -libpath:"${winsdk_lib_symlinks_dir}") |
| 319 | + if(NOT msvc_lib_symlinks_dir) |
| 320 | + set(msvc_lib_symlinks_dir "${CMAKE_BINARY_DIR}/msvc_lib_symlinks") |
| 321 | + generate_msvc_lib_symlinks("${MSVC_LIB}/${WINSDK_ARCH}" "${msvc_lib_symlinks_dir}") |
| 322 | + endif() |
| 323 | + list(APPEND LINK_FLAGS |
| 324 | + -libpath:"${msvc_lib_symlinks_dir}") |
| 325 | +endif() |
| 326 | + |
| 327 | +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.25") |
| 328 | + list(TRANSFORM LINK_FLAGS PREPEND "${CMAKE_CXX_LINKER_WRAPPER_FLAG}") |
| 329 | +endif() |
| 330 | + |
| 331 | +string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}") |
| 332 | +string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " ${LINK_FLAGS}") |
| 333 | +string(APPEND CMAKE_MODULE_LINKER_FLAGS_INIT " ${LINK_FLAGS}") |
| 334 | +string(APPEND CMAKE_SHARED_LINKER_FLAGS_INIT " ${LINK_FLAGS}") |
| 335 | + |
| 336 | +# CMake populates these with a bunch of unnecessary libraries, which requires |
| 337 | +# extra case-correcting symlinks and what not. Instead, let projects explicitly |
| 338 | +# control which libraries they require. |
| 339 | +set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "" FORCE) |
| 340 | +set(CMAKE_CXX_STANDARD_LIBRARIES "" CACHE STRING "" FORCE) |
| 341 | + |
| 342 | +# Allow clang-cl to work with macOS paths. |
| 343 | +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_LIST_DIR}/ClangClCMakeCompileRules.cmake") |
0 commit comments