Cross-compiling CMake projects for Windows
I sometimes find myself wanting to cross-compile programs with CMake on a Linux machine such that I get a standalone .exe that can be given to mostly non-technical Windows users to run. This isn’t hard, but finding the right options is a little bit of a challenge every time, so now I’m recording the procedure here; both as a reminder to myself, and to provide a quick recipe that future searchers can use.
Cross-compiling for Windows will of course need an appropriate toolchain, which these days tends to be mingw-w64. Many Linux distributions provide packages for it: mingw-w64
on Debian (including Ubuntu and variants), mingw-w64-gcc
on Arch and similar for other distributions.
CMake documents how to specify cross-compilation options, but it’s not terribly clear which settings are mandatory. For simple needs, only three variables must be set:
CMAKE_SYSTEM_NAME
being set implies that you’re cross-compiling, which will prevent cmake from trying to do things like run the binaries it builds (unless they’re tool binaries being built for the host, rather than the target).CMAKE_C_COMPILER
is the name of the compiler to useCMAKE_CXX_COMPILER
is the name of the compiler to use for C++ sources
Targeting mingw-64 then, the cmake invocation looks like this:
cmake -DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=i686-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=i686-w64-mingw32-g++ \
path_to_sources
The i686-
prefixed compiler builds 32-bit binaries, which I usually prefer to build because they’ll work both on 32- and 64-bit Windows. If no support for 32-bit Windows is required, the x86_64-
-prefixed tools will build 64-bit binaries instead (eg, x86_64-w64-mingw32-gcc
).
When building binaries to share it’s probably helpful to do a non-debug build by also setting CMAKE_BUILD_TYPE
, perhaps to MinSizeRel
or RelWithDebInfo
.
Libraries
Libraries that you might depend on can be built in the same way, and installed to a chosen directory by setting CMAKE_INSTALL_PREFIX
then building the install target. For instance building a libpng static library
(which itself provides options to enable a shared library and tests which we turn off):
cmake -DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=i686-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=i686-w64-mingw32-g++ \
-DCMAKE_INSTALL_PREFIX=$HOME/windows_binaries \
-DPNG_SHARED=OFF -DPNG_TESTS=OFF \
path_to_sources
cmake --build . --target install
As I’ve observed previously, you can then link against these libraries by setting CMAKE_PREFIX_PATH
. So if we want to build against the libpng that was just built and generate a “release” binary:
cmake -DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_C_COMPILER=i686-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=i686-w64-mingw32-g++ \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_PREFIX_PATH=$HOME/windows_binaries \
path_to_sources
libpng notes
While libpng (used as an example above) provides a nice CMake configuration that cross-compiles cleanly, it depends on zlib which does not. However zlib does distribute a Makefile intended for targeting GNU tools on Windows. So before building libpng I first compile zlib with a particular incantation:
make -f win32/Makefile.gcc \
BINARY_PATH=${INSTALL_DIR}/bin \
INCLUDE_PATH=${INSTALL_DIR}/include \
LIBRARY_PATH=${INSTALL_DIR}/lib \
SHARED_MODE=0 \
PREFIX=i686-w64-mingw32- \
install
Because this is just a Makefile, sadly the appropriate options need to be discovered by reading the source: projects that natively use CMake are much easier to support!