I'm working on porting FreeGLUT for Android, and FreeGLUT is switching to CMake.

I'm more familiar with the GNU Autotools myself, and I'm used to cross-compile with a simple:

./configure --host=arm-linux-androideabi

Can we do that with CMake? Well, it's not well-documented, but yes :) Let's see how to do that for Android (smartphones) and MinGW (.exe for windows).


The principle is:

  • write a "toolchain" CMake script that defines the cross-compiler,

  • mention you're using a "GNU" compiler (a.k.a. GCC) so CMake automatically looks for prefixed cross-compilers tools such as arm-linux-androideabi-cpp or arm-linux-androideabi-ld.

  • pass the toolchain script to CMake using the predefined CMAKE_TOOLCHAIN_FILE variable.

  • REMOVE all the CMake-generated files when you change anything, CMake does not rebuild its cache when the toolchain script or PATH is modified, this creates misleading errors. Hence, building in a subdirectory is crucial.

Here's a simple toolchain script:

SET(CMAKE_SYSTEM_NAME Linux)  # Tell CMake we're cross-compiling
include(CMakeForceCompiler)
# Prefix detection only works with compiler id "GNU"
# CMake will look for prefixed g++, cpp, ld, etc. automatically
CMAKE_FORCE_C_COMPILER(arm-linux-androideabi-gcc GNU)
SET(ANDROID TRUE)

Note: SET(ANDROID TRUE) is not mandatory for CMake, it's just used to detect an Android build in the particular case of FreeGLUT development.

Here's how to invoke it:

PATH=/usr/src/ndk-standalone-9/bin:$PATH
cd /usr/src/freeglut-3.0.0/
mkdir cross-android/ && cd cross-android/
cmake \
  -D CMAKE_TOOLCHAIN_FILE=../android_toolchain.cmake \
  -D CMAKE_INSTALL_PREFIX=/freeglut \
  ..
make -j4
make install DESTDIR=$(pwd)

Now here's a slightly more complex toolchain for MinGW.

The main issue with MinGW is that there are at least 4 possible prefixes depending on which version and which distro you're using. So I ask the user to specify it with GNU_HOST:

SET(CMAKE_SYSTEM_NAME Windows)
include(CMakeForceCompiler)
IF("${GNU_HOST}" STREQUAL "")
    SET(GNU_HOST i586-mingw32msvc)
ENDIF()
# Prefix detection only works with compiler id "GNU"
CMAKE_FORCE_C_COMPILER(${GNU_HOST}-gcc GNU)
# CMake doesn't automatically look for prefixed 'windres', do it manually:
SET(CMAKE_RC_COMPILER ${GNU_HOST}-windres)

I wanted to print an error when GNU_HOST is not specified, but the toolchain script is loaded multiple times, and the command-line variables are not always set (CMake is not super-clean in that regard). So I defined a default, that works.

Sample invocation:

  apt-get install mingw-w64

  mkdir cross-woe/ && cd cross-woe/
  cmake \
    -D GNU_HOST=x86_64-w64-mingw32 \
    -D CMAKE_TOOLCHAIN_FILE=../mingw_cross_toolchain.cmake \
    -D CMAKE_INSTALL_PREFIX=/freeglut \
    ..
  make -j4
  make install DESTDIR=$(pwd)

I used this technique to cross-compile my projects, or other projects that use CMake. mingw-cross-env uses this technique (abeilt with explicit definition of all compiler tools) to provide pre-built windows libraries without altering the existing CMake scripts.

Do you people have tips or a better technique to cross-compile with CMake?