Imagine this: You're on a new M1 MacBook1. You need to compile a CMake project to run on your friend's Windows x86-64 PC. How do you do that? Use zig cc and friends! πŸš€

Here's an example project that only works on Windows:

main.c
#include <windows.h>

int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow)
{
    return MessageBox(NULL, "hello, world", "caption", 0);
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.29)
project(hello-world LANGUAGES C)
add_executable(hello-world WIN32 main.c)

So how can we compile it for Windows x86-64 from our M1 MacBook1? We need to add some supporting infrastructure first. CMAKE_AR="zig;ar" and CMAKE_RANLIB="zig;ranlib" don't work. CMAKE_AR and CMAKE_RANLIB don't support commands with arguments so we need to use a wrapper ./zig-ar script instead. πŸ€·β€β™€οΈ

zig-ar (chmod +x)
exec zig ar "$@"
zig-ar.cmd
@zig ar %*
zig-ranlib (chmod +x)
exec zig ranlib "$@"
zig-ranlib.cmd
@zig ranlib %*

You now have everything ready to run the CMake configure & build steps! πŸŽ‰ Don't forget to pass all the appropriate flags to compile for Windows x86-64 using the Zig toolchain! πŸ˜‰

ASM="zig cc" \
CC="zig cc" \
CXX="zig c++" \
cmake \
  -DCMAKE_SYSTEM_NAME="Windows" \
  -DCMAKE_SYSTEM_PROCESSOR="x86_64" \
  -DCMAKE_ASM_COMPILER_TARGET="x86_64-windows-gnu" \
  -DCMAKE_C_COMPILER_TARGET="x86_64-windows-gnu" \
  -DCMAKE_CXX_COMPILER_TARGET="x86_64-windows-gnu" \
  -DCMAKE_AR="$PWD/zig-ar" \
  -DCMAKE_RANLIB="$PWD/zig-ranlib" \
  -B build
-- The C compiler identification is Clang 18.1.6
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - failed
-- Check for working C compiler: /somewhere/zig/zig
-- Check for working C compiler: /somewhere/zig/zig - works
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done (5.5s)
-- Generating done (0.0s)
-- Build files have been written to: /somewhere/hello-world/build

πŸ’‘ You can change the CMAKE_C_COMPILER_TARGET and friends to any supported Zig target. Use zig targets to see them all.

Do I have to specify the CMAKE_SYSTEM_NAME and CMAKE_SYSTEM_PROCESSOR? Yeah. It triggers a cascade of configuration that sets the .exe output suffix, sets WIN32=1, uses .lib and .dll library suffixes, and a lot more.

What about platform-specific file extensions for ./zig-ar? Windows will helpfully scan all extensions from %PATHEXT% (.COM;.EXE;.BAT;.CMD;...) for you. That's why you can run zig in your PowerShell/CMD prompt when the actual file is called zig.exe.

How does CMAKE_C_COMPILER_TARGET make its way into zig cc --target? zig cc is detected as Clang which is known by CMake to support a --target option.

β„Ή CMAKE_AR does not search $PATH. CMAKE_AR="zig-ar" is resolved as $PWD/zig-ar. πŸ€·β€β™€οΈ gitlab.kitware.com/cmake/cmake#18087

Why can't we do AR="./zig-ar" like CC and CXX? Unfixed issue. gitlab.kitware.com/cmake/cmake#18712

Now that the project is configured with all the compiler settings and other magic✨ we can run the build step:

cmake --build build
[ 50%] Building C object CMakeFiles/hello-world.dir/main.c.obj
[100%] Linking C executable hello-world.exe
[100%] Built target hello-world

Tada! πŸ₯³ You have now built a Windows x86-64 .exe file from your M1 MacBook1!

./build/hello-world.exe

hello world alert box

You can use this on any ASM/C/C++ CMake project! πŸš€ Cross-compiling has never been easier than with zig cc and friends. Use it in your CI pipeline to build binaries for more than just The Big 5 (Windows x86-64, macOS x86-64 & ARM64, Linux x86-64 & ARM64). You can see a bunch of Zig targets via zig targets.

  1. It doesn't have to be an M1 MacBook. The point is to emphasize that a cross-compiler is required. ↩