CMake
xclang's CMake package makes the toolchain the compiler of a build, for the host or another target. It gives the build import std, without CMake's experimental switches.
Requires: CMake 3.28 or later, Ninja 1.11 or later, and the Ninja or Ninja Multi-Config generator.
Set Up a Project
The project is examples/cmake, which the quick start also builds:
cmake_minimum_required(VERSION 3.28)
project(hello LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(xclang REQUIRED CONFIG)
add_executable(hello main.cpp)
target_link_libraries(hello PRIVATE xclang::std)import std;
int main() {
std::vector<std::string> targets{"windows", "linux", "macos"};
std::ranges::sort(targets);
try {
throw std::runtime_error(std::format("{} targets, the first {}", targets.size(), targets[0]));
} catch (const std::exception& e) {
std::println("hello from xclang: {}", e.what());
}
}With xclang's bin/ first in PATH, as pixi shell has it, name the compiler and build:
cmake -G Ninja -B build -DCMAKE_CXX_COMPILER=clang++
cmake --build build
./build/helloIt prints hello from xclang: 3 targets, the first linux.
CMake does not pick clang by itself, so the compiler has to be named; a project with C names -DCMAKE_C_COMPILER=clang too. find_package(xclang) comes after project(), and finds the package through PATH. Without PATH, use the toolchain file of the toolchain directory, $XCLANG below. It sets the compilers, the binary tools and the location of the package at once:
cmake -G Ninja -B build --toolchain $XCLANG/lib/cmake/xclang/toolchain.cmakeBuild for Another Target
XCLANG_TARGET names the target, with the toolchain file:
cmake -G Ninja -B build-aarch64-w64-mingw32 --toolchain $XCLANG/lib/cmake/xclang/toolchain.cmake \
-DXCLANG_TARGET=aarch64-w64-mingw32
cmake --build build-aarch64-w64-mingw32- Linux and MinGW targets build on every host. So do the MSVC targets, with the Windows SDK that the toolchain's
xclangfetched, and the macOS targets: with Xcode's SDK on macOS hosts (why), and with the fetched SDK on Linux and Windows hosts. On a macOS host, the other macOS architecture isCMAKE_OSX_ARCHITECTURESto CMake, not cross-compiling. - CMake looks for the libraries, headers and packages of another target in its sysroot only. Name a dependency built for the target with
<Package>_DIR, or add its prefix toCMAKE_FIND_ROOT_PATH. A library found on the host has the wrong architecture or OS. The link then fails late or, worse, succeeds against the wrong headers. xclang::stdis built for the target too.- Tests built for another target run on a machine of that target, not on the host.
Build for MSVC Targets
x86_64-pc-windows-msvc and aarch64-pc-windows-msvc build with the Windows SDK that the toolchain's xclang fetched, $XCLANG/bin/xclang sdk fetch windows --accept-license (MSVC targets). Without it, the toolchain file stops and says how to fetch it:
cmake -G Ninja -B build-msvc --toolchain $XCLANG/lib/cmake/xclang/toolchain.cmake \
-DXCLANG_TARGET=x86_64-pc-windows-msvc
cmake --build build-msvc- The compilers are clang and clang++, not clang-cl.
WIN32is true,MSVCfalse, andCMAKE_CXX_SIMULATE_IDisMSVC, so a project'sif(MSVC)options, written for cl's command line, stay out (why). - The C runtime is the hybrid CRT in every configuration:
CMAKE_MSVC_RUNTIME_LIBRARYisMultiThreadedunless set. Other values work too; CMake's own default would load the VC runtime's DLLs. xclang::stdis thestdandstd.compatof Microsoft's STL.- A link writes a PDB when it has
-g: Debug and RelWithDebInfo do; a target given-gin another configuration needs it intarget_link_optionstoo.
Build for macOS from Linux or Windows
aarch64-apple-darwin and x86_64-apple-darwin build on Linux and Windows hosts with Apple's SDK that the toolchain's xclang fetched, $XCLANG/bin/xclang sdk fetch macos --accept-license (macOS). Without it, the toolchain file stops and says how to fetch it:
cmake -G Ninja -B build-macos --toolchain $XCLANG/lib/cmake/xclang/toolchain.cmake \
-DXCLANG_TARGET=aarch64-apple-darwin
cmake --build build-macosAPPLEis true,CMAKE_SYSTEM_NAMEisDarwin, andCMAKE_OSX_ARCHITECTURESis the target's architecture. No CMake step needsxcrunor Xcode:install_name_tool,lipoandlibtoolare the toolchain'sllvm-install-name-tool,llvm-lipoandllvm-libtool-darwin, andxclang_debug_symbolsmakes the dSYM with itsdsymutil.CMAKE_OSX_SYSROOTis the SDK, as on a macOS host: the one given, elseSDKROOT, else the toolchain'ssdk/macos. CMake passes it as-isysroot, and looks for libraries, headers and packages in it only.CMAKE_OSX_DEPLOYMENT_TARGETworks as on a macOS host; without it, programs run on macOS 13.0 and later.- One build is one architecture, since each has its config file. A universal program is two builds and
llvm-lipo -create. - Objective-C (
OBJC,OBJCXX) compiles with the toolchain's clang. - Tests run on a Mac, not on the host.
Without xclang Installed
A project can download the toolchain itself, before project(), so it configures on a machine with nothing but CMake and Ninja. FetchContent fetches this repository at latest, a branch at the tag of the newest release, and packages/cmake/xclang.cmake downloads the host toolchain of that release. A release's tag, as GIT_TAG, keeps that release:
cmake_minimum_required(VERSION 3.28)
include(FetchContent)
FetchContent_Declare(xclang
GIT_REPOSITORY https://github.com/clice-io/xclang
GIT_TAG latest)
FetchContent_MakeAvailable(xclang)
include(${xclang_SOURCE_DIR}/packages/cmake/xclang.cmake)
project(hello LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(xclang REQUIRED CONFIG)
add_executable(hello main.cpp)
target_link_libraries(hello PRIVATE xclang::std)cmake -G Ninja -B build
cmake --build build
./build/helloxclang.cmake checks the archive against the SHA256SUMS of the release, and unpacks it into the cache of the user, once per release and host. Another build tree of the same release and host downloads nothing but the checkout. -DXCLANG_TARGET=<target> builds for another target here too:
cmake -G Ninja -B build-x86_64-w64-mingw32 -DXCLANG_TARGET=x86_64-w64-mingw32
cmake --build build-x86_64-w64-mingw32| variable | |
|---|---|
XCLANG_CACHE_DIR | where toolchains are unpacked; also an environment variable |
XCLANG_ROOT | an unpacked toolchain to use instead of downloading one |
XCLANG_URL | a mirror of the release |
The rest is in the CMake API. GIT_TAG may also name a later commit, with XCLANG_VERSION the release whose toolchain it downloads; an earlier release works too.
Use C++20 Modules and import std
xclang::std is a static library of the std and std.compat modules of libc++, compiled from the sources that the module manifest of the compiler names, for the target of the build. Link it, and import std works. The C++ modules of the program are a FILE_SET CXX_MODULES, scanned by clang-scan-deps. cmake_minimum_required(VERSION 3.28) turns on the scanning of C++20 targets (CMP0155):
add_library(math STATIC)
target_sources(math PUBLIC FILE_SET CXX_MODULES FILES math.cppm math-ops.cppm)
target_link_libraries(math PUBLIC xclang::std)C++20 modules has the whole project.
clang refuses a module file built with other language options than its importer's (matching options). So:
xclang::stdis built with the settings of the directory that calledfind_package(xclang), as they are at the end of itsCMakeLists.txt:CMAKE_CXX_STANDARD,CMAKE_CXX_EXTENSIONS,CMAKE_CXX_FLAGSandadd_compile_options(). Set them project-wide, not per CMake target.It asks its importers for its standard: C++23, or the
CMAKE_CXX_STANDARDit was built with if that is 20 or later.A CMake target with other language options links a
stdof its own, whosePUBLICoptions reach its importers. It linksstd_noexceptinstead ofxclang::std:cmakexclang_add_std(std_noexcept) target_compile_options(std_noexcept PUBLIC -fno-exceptions)
Before putting ccache in front of the compiler, read build caches and modules.
Ship Debug Symbols
xclang_debug_symbols(<program>) makes the debug symbols of a program after each of its links: <program>.gsym next to it, and for a macOS target <program>.dSYM too.
add_executable(tool tool.cpp)
target_compile_options(tool PRIVATE -g)
xclang_debug_symbols(tool)GSYM_ARGS --merged-functions keeps every name of the functions that identical code folding merged. For a macOS target, the link keeps the objects of ThinLTO in <build dir>/tool.lto, which dsymutil reads. Debugging has the whole example.
Link libclang
A tool on libclang finds it with find_package(Clang), with CMAKE_PREFIX_PATH naming the unpacked libclang archive (libclang).
Speed Up libclang Links
Name a directory for the ThinLTO cache when configuring, -DXCLANG_THINLTO_CACHE=/var/tmp/xclang-thinlto, and a link after the first takes seconds (libclang has a whole tool built this way). find_package(xclang) makes the directory at configure time. It adds the cache flag to every link of the directory that called it, and of its subdirectories (the ThinLTO cache).
Troubleshooting
C++26 was disabled in precompiled file: a CMake target asks for a newer standard, or other language options, thanxclang::stdwas built with. Set them project-wide, or usexclang_add_std.- A toolchain file is already set:
xclang.cmakestops if the build has one, such as vcpkg's. Chain-load xclang's from vcpkg instead, withVCPKG_CHAINLOAD_TOOLCHAIN_FILE. - A library is not found for another target: CMake searches the sysroot only. Build the library for the target, and name it with
<Package>_DIR. - More symptoms are in the FAQ.
Not Yet Supported
| status | |
|---|---|
| Relative paths in debug information, as Bazel builds have | Planned |
| Fetched targets beyond the six | Planned |
Known Limitations
- C++20 modules build only with the Ninja and Ninja Multi-Config generators.
- ccache never caches a module interface, and before 4.14 it serves the stale object of an importer (build caches and modules).
