Cross Compiling With CMake — Mastering CMake (2025)

Cross-compiling a piece of software means that the software is builton one system, but is intended to run on a different system. Thesystem used to build the software will be called the “build host,” andthe system for which the software is built will be called the “targetsystem” or “target platform.” The target system usually runs adifferent operating system (or none at all) and/or runs on differenthardware. A typical use case is in software development for embeddeddevices like network switches, mobile phones, or engine controlunits. In these cases, the target platform doesn’t have or is not ableto run the required software development environment.

Cross-compiling is fully supported by CMake, ranging from cross-compilingfrom Linux to Windows; cross-compiling for supercomputers, through tocross-compiling for small embedded devices without an operating system (OS).

Cross-compiling has several consequences for CMake:

  • CMake cannot automatically detect the target platform.

  • CMake cannot find libraries and headers in the default systemdirectories.

  • Executables built during cross compiling cannot be executed.

Cross-compiling support doesn’t mean that all CMake-based projects canbe magically cross-compiled out-of-the-box (some are), but that CMakeseparates between information about the build platform and targetplatform and gives the user mechanisms to solve cross-compiling issueswithout additional requirements such as running virtual machines, etc.

To support cross-compiling for a specific software project, CMake mustto be told about the target platform via a toolchain file. TheCMakeLists.txt may have to be adjusted. It is aware that thebuild platform may have different properties than the target platform,and it has to deal with the instances where a compiled executabletries to execute on the build host.

Toolchain Files

In order to use CMake for cross-compiling, a CMake file that describesthe target platform has to be created, called the “toolchain file,”This file tells CMake everything it needs to know about the targetplatform. Here is an example that uses the MinGW cross-compiler forWindows under Linux; the contents will be explained line-by-lineafterwards.

# the name of the target operating systemset(CMAKE_SYSTEM_NAME Windows)# which compilers to use for C and C++set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)# where is the target environment locatedset(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc /home/alex/mingw-install)# adjust the default behavior of the FIND_XXX() commands:# search programs in the host environmentset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)# search headers and libraries in the target environmentset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Assuming that this file is saved with the name TC-mingw.cmake in yourhome directory, you instruct CMake to use this file by setting theCMAKE_TOOLCHAIN_FILE variable:

~/src$ cd build~/src/build$ cmake -DCMAKE_TOOLCHAIN_FILE=~/TC-mingw.cmake .....

CMAKE_TOOLCHAIN_FILE has to be specified only on the initial CMakerun; after that, the results are reused from the CMake cache. Youdon’t need to write a separate toolchain file for every piece ofsoftware you want to build. The toolchain files are per targetplatform; i.e. if you are building several software packages for thesame target platform, you only have to write one toolchain file thatcan be used for all packages.

What do the settings in the toolchain file mean? We will examine themone-by-one. Since CMake cannot guess the target operating system orhardware, you have to set the following CMake variables:

CMAKE_SYSTEM_NAME

This variable is mandatory; it sets the name of the target system,i.e. to the same value as CMAKE_SYSTEM_NAME would have if CMake wererun on the target system. Typical examples are “Linux” and“Windows.” It is used for constructing the file names of theplatform files like Linux.cmake or Windows-gcc.cmake. If your targetis an embedded system without an OS, set CMAKE_SYSTEM_NAME to“Generic.” Presetting CMAKE_SYSTEM_NAME this way instead of beingdetected, automatically causes CMake to consider the build across-compiling build and the CMake variableCMAKE_CROSSCOMPILING will be set to TRUE.CMAKE_CROSSCOMPILING is the variable that should be tested in CMakefiles to determine whether the current build is a cross-compiled build ornot.

CMAKE_SYSTEM_VERSION

Sets the version of your target system.

CMAKE_SYSTEM_PROCESSOR

This variable is optional; it sets the processor or hardware name ofthe target system. It is used in CMake for one purpose, to load the${CMAKE_SYSTEM_NAME}-COMPILER_ID-${CMAKE_SYSTEM_PROCESSOR}.cmakefile. This file can be used to modify settings such as compiler flagsfor the target. You should only have to set this variable if you areusing a cross-compiler where each target needs special buildsettings. The value can be chosen freely, so it could be, for example,i386, IntelPXA255, or MyControlBoardRev42.

In CMake code, the CMAKE_SYSTEM_XXX variables always describe thetarget platform. The same is true for the short WIN32,UNIX, APPLE variables. These variables can be usedto test the properties of the target. If it is necessary to test the buildhost system, there is a corresponding set of variables:CMAKE_HOST_SYSTEM, CMAKE_HOST_SYSTEM_NAME,CMAKE_HOST_SYSTEM_VERSION,CMAKE_HOST_SYSTEM_PROCESSOR; and also the short forms:CMAKE_HOST_WIN32, CMAKE_HOST_UNIX and CMAKE_HOST_APPLE.

Since CMake cannot guess the target system, it cannot guess whichcompiler it should use. Setting the following variables defines whatcompilers to use for the target system.

CMAKE_C_COMPILER

This specifies the C compiler executable as either a full path orjust the filename. If it is specified with full path, then this pathwill be preferred when searching for the C++ compiler and the othertools (binutils, linker, etc.). If the compiler is a GNUcross-compiler with a prefixed name (e.g. “arm-elf-gcc”), CMake willdetect this and automatically find the corresponding C++ compiler(i.e. “arm-elf-c++”). The compiler can also be set via the CCenvironment variable. Setting CMAKE_C_COMPILER directly in atoolchain file has the advantage that the information about thetarget system is completely contained in this file, and it does notdepend on environment variables.

CMAKE_CXX_COMPILER

This specifies the C++ compiler executable as either a full path orjust the filename. It is handled the same way asCMAKE_C_COMPILER. If the toolchain is a GNU toolchain, it shouldsuffice to set only CMAKE_C_COMPILER; CMake should find thecorresponding C++ compiler automatically. As for CMAKE_C_COMPILER,also for C++ the compiler can be set via the CXX environmentvariable.

Finding External Libraries, Programs and Other Files

Most non-trivial projects make use of external libraries ortools. CMake offers the find_program, find_library,find_file, find_path, and find_packagecommands for this purpose. They search the file system in common places forthese files and return the results. find_package is a bitdifferent in that it does not actually search itself, but executesFind<*>.cmake modules, which in turn callthe find_program, find_library, find_file,and find_path commands.

When cross-compiling, these commands become more complicated. Forexample, when cross-compiling to Windows on a Linux system, getting/usr/lib/libjpeg.so` as the result of the commandfind_package(JPEG) would be useless, since this would be the JPEGlibrary for the host system and not the target system. In some cases, youwant to find files that are meant for the target platform; in other casesyou will want to find files for the build host. The following variables aredesigned to give you the flexibility to change how the typical findcommands in CMake work, so that you can find both build host andtarget files as necessary.

The toolchain will come with its own set of libraries and headers forthe target platform, which are usually installed under a commonprefix. It is a good idea to set up a directory where all the softwarethat is built for the target platform will be installed, so that thesoftware packages don’t get mixed up with the libraries that come withthe toolchain.

The find_program command is typically used to find a program thatwill be executed during the build, so this should still search in the hostfile system, and not in the environment of the targetplatform. find_library is normally used to find a library that isthen used for linking purposes, so this command should only search in thetarget environment. For find_path and find_file, it isnot so obvious; in many cases, they are used to search for headers, so bydefault they should only search in the target environment. The following CMakevariables can be set to adjust the behavior of the find commands forcross-compiling.

CMAKE_FIND_ROOT_PATH

This is a list of the directories that contain the targetenvironment. Each of the directories listed here will be prependedto each of the search directories of every find command. Assumingyour target environment is installed under /opt/eldk/ppc_74xx andyour installation for that target platform goes to~/install-eldk-ppc74xx, set CMAKE_FIND_ROOT_PATH to thesetwo directories. Then find_library(JPEG_LIB jpeg) will search in/opt/eldk/ppc_74xx/lib, /opt/eldk/ppc_74xx/usr/lib,~/install-eldk-ppc74xx/lib, ~/install-eldk-ppc74xx/usr/lib, andshould result in /opt/eldk/ppc_74xx/usr/lib/libjpeg.so.

By default, CMAKE_FIND_ROOT_PATH is empty. If set, first thedirectories prefixed with the path given in CMAKE_FIND_ROOT_PATHwill be searched, and then the unprefixed versions of the samedirectories will be searched.

By setting this variable, you are basically adding a new set ofsearch prefixes to all of the find commands in CMake, but for somefind commands you may not want to search the target or hostdirectories. You can control how each find command invocation worksby passing in one of the three following optionsNO_CMAKE_FIND_ROOT_PATH, ONLY_CMAKE_FIND_ROOT_PATH, orCMAKE_FIND_ROOT_PATH_BOTH when you call it. You can also control howthe find commands work using the following three variables.

CMAKE_FIND_ROOT_PATH_MODE_PROGRAM

This sets the default behavior for the find_program command. Itcan be set to NEVER, ONLY, or BOTH. When set to NEVER,CMAKE_FIND_ROOT_PATH will not be used forfind_program calls except where it is enabled explicitly. Ifset to ONLY, only the search directories with the prefixes coming fromCMAKE_FIND_ROOT_PATH will be used by find_program.The default is BOTH, which means that first the prefixed directories andthen the unprefixed directories, will be searched.

In most cases, find_program is used to search for anexecutable which will then be executed, e.g. usingexecute_process or add_custom_command. So inmost cases an executable from the build host is required, so settingCMAKE_FIND_ROOT_PATH_MODE_PROGRAM to NEVER is normallypreferred.

CMAKE_FIND_ROOT_PATH_MODE_LIBRARY

This is the same as above, but for the find_library command. Inmost cases this is used to find a library which will then be used forlinking, so a library for the target is required. In most cases, itshould be set to ONLY.

CMAKE_FIND_ROOT_PATH_MODE_INCLUDE

This is the same as above and used for both find_path andfind_file. In most cases, this is used for finding includedirectories, so the target environment should be searched. In mostcases, it should be set to ONLY. If you also need to find files inthe file system of the build host (e.g. some data files that will beprocessed during the build); you may need to adjust the behavior forthose find_path or find_file calls using theNO_CMAKE_FIND_ROOT_PATH, ONLY_CMAKE_FIND_ROOT_PATH andCMAKE_FIND_ROOT_PATH_BOTH options.

With a toolchain file set up as described, CMake now knows how tohandle the target platform and the cross-compiler. We should now ableto build software for the target platform. For complex projects, thereare more issues that must to be taken care of.

System Inspection

Most portable software projects have a set of system inspection testsfor determining the properties of the (target) system. The simplestway to check for a system feature with CMake is by testingvariables. For this purpose, CMake provides the variablesUNIX, WIN32, and APPLE. Whencross-compiling, these variables apply to the target platform, fortesting the build host platform there are correspondingvariables CMAKE_HOST_UNIX, CMAKE_HOST_WIN32,and CMAKE_HOST_APPLE.

If this granularity is too coarse, the variablesCMAKE_SYSTEM_NAME, CMAKE_SYSTEM,CMAKE_SYSTEM_VERSION, andCMAKE_SYSTEM_PROCESSOR can be tested, along with theircounterparts CMAKE_HOST_SYSTEM_NAME,CMAKE_HOST_SYSTEM, CMAKE_HOST_SYSTEM_VERSION,and CMAKE_HOST_SYSTEM_PROCESSOR, which contain the sameinformation, but for the build host and not for the target system.

if(CMAKE_SYSTEM MATCHES Windows) message(STATUS "Target system is Windows")endif()if(CMAKE_HOST_SYSTEM MATCHES Linux) message(STATUS "Build host runs Linux")endif()

Using Compile Checks

In CMake, there are macros such ascheck_include_files andcheck_c_source_runs that are used to testthe properties of the platform. Most of these macros internally useeither the try_compile or the try_run commands.The try_compile command works as expected whencross-compiling; it tries to compile the piece of code with thecross-compiling toolchain, which will give the expected result.

All tests using try_run will not work since the createdexecutables cannot normally run on the build host. In some cases, thismight be possible (e.g. using virtual machines, emulation layers likeWine or interfaces to the actual target) as CMake does not depend onsuch mechanisms. Depending on emulators during the build process wouldintroduce a new set of potential problems; they may have a differentview on the file system, use other line endings, require specialhardware or software, etc.

If try_run is invoked when cross-compiling, it will firsttry to compile the software, which will work the same way as when notcross compiling. If this succeeds, it will check the variableCMAKE_CROSSCOMPILING to determine whether the resultingexecutable can be executed or not. If it cannot, it will create twocache variables, which then have to be set by the user or via theCMake cache. Assume the command looks like this:

try_run(SHARED_LIBRARY_PATH_TYPE SHARED_LIBRARY_PATH_INFO_COMPILED ${PROJECT_BINARY_DIR}/CMakeTmp ${PROJECT_SOURCE_DIR}/CMake/SharedLibraryPathInfo.cxx OUTPUT_VARIABLE OUTPUT ARGS "LDPATH" )

In this example, the source file SharedLibraryPathInfo.cxx will becompiled and if that succeeds, the resulting executable should beexecuted. The variable SHARED_LIBRARY_PATH_INFO_COMPILED will be setto the result of the build, i.e. TRUE or FALSE. CMake will create acache variable SHARED_LIBRARY_PATH_TYPE and preset it toPLEASE_FILL_OUT-FAILED_TO_RUN. This variable must be set to what theexit code of the executable would have been if it had been executed onthe target. Additionally, CMake will create a cache variableSHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT and preset it toPLEASE_FILL_OUT-NOTFOUND. This variable should be set to the outputthat the executable prints to stdout and stderr if it were executed onthe target. This variable is only created if the try_runcommand was used with the RUN_OUTPUT_VARIABLE or theOUTPUT_VARIABLE argument. You have to fill in the appropriatevalues for these variables. To help you with this CMake tries its bestto give you useful information. To accomplish this CMake creates a file${CMAKE_BINARY_DIR}/TryRunResults.cmake, which you can see anexample of here:

# SHARED_LIBRARY_PATH_TYPE# indicates whether the executable would have been able to run# on its target platform. If so, set SHARED_LIBRARY_PATH_TYPE# to the exit code (in many cases 0 for success), otherwise# enter "FAILED_TO_RUN".# SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT# contains the text the executable would have printed on# stdout and stderr. If the executable would not have been# able to run, set SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT# empty. Otherwise check if the output is evaluated by the# calling CMake code. If so, check what the source file would# have printed when called with the given arguments.# The SHARED_LIBRARY_PATH_INFO_COMPILED variable holds the build# result for this TRY_RUN().## Source file: ~/src/SharedLibraryPathInfo.cxx# Executable : ~/build/cmTryCompileExec-SHARED_LIBRARY_PATH_TYPE# Run arguments: LDPATH# Called from: [1] ~/src/CMakeLists.cmakeset(SHARED_LIBRARY_PATH_TYPE "0" CACHE STRING "Result from TRY_RUN" FORCE)set(SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT "" CACHE STRING "Output from TRY_RUN" FORCE)

You can find all of the variables that CMake could not determine, fromwhich CMake file they were called, the source file, the arguments forthe executable, and the path to the executable. CMake will also copythe executables to the build directory; they have the namescmTryCompileExec-<name of the variable>, e.g. in this casecmTryCompileExec-SHARED_LIBRARY_PATH_TYPE. You can then try to runthis executable manually on the actual target platform and check theresults.

Once you have these results, they have to be put into the CMakecache. This can be done by using ccmake or cmake-gui.and editing the variables directly in the cache. It is not possible toreuse these changes in another build directory or if CMakeCache.txt isremoved.

The recommended approach is to use the TryRunResults.cmake filecreated by CMake. You should copy it to a safe location (i.e. where itwill not be removed if the build directory is deleted) and give it auseful name, e.g. TryRunResults-MyProject-eldk-ppc.cmake. The contentsof this file have to be edited so that the set commands set therequired variables to the appropriate values for the targetsystem. This file can then be used to preload the CMake cache by usingthe -C option of cmake:

src/build/ $ cmake -C ~/TryRunResults-MyProject-eldk-ppc.cmake .

You do not have to use the other CMake options again as they are nowin the cache. This way you can useMyProjectTryRunResults-eldk-ppc.cmake in multiple build trees, and itcan be distributed with your project so that it is easier for otherusers to cross compile it.

Running Executables Built in the Project

In some cases it is necessary that during a build, an executable isinvoked that was built earlier in the same build; this is usually thecase for code generators and similar tools. This does not work whencross compiling, as the executables are built for the target platformand cannot run on the build host (without the use of virtual machines,compatibility layers, emulators, etc.). With CMake, these programs arecreated using add_executable, and executed withadd_custom_command or add_custom_target. Thefollowing three options can be used to support these executables withCMake. The old version of the CMake code could look something like this

add_executable(mygen gen.c)get_target_property(mygenLocation mygen LOCATION)add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/generated.h" COMMAND ${mygenLocation} -o "${CMAKE_CURRENT_BINARY_DIR}/generated.h" )

Now we will show how this file can be modified so that it works whencross-compiling. The basic idea is that the executable is built onlywhen doing a native build for the build host, and is then exported asan executable target to a CMake script file. This file is thenincluded when cross-compiling, and the executable target for theexecutable mygen will be loaded. An imported target with the same nameas the original target will be created. add_custom_commandrecognizes target names as executables, so for the command inadd_custom_command, simply the target name can be used; it is notnecessary to use the LOCATION property to obtain the path of the executable:

if(CMAKE_CROSSCOMPILING) find_package(MyGen)else() add_executable(mygen gen.c) export(TARGETS mygen FILE "${CMAKE_BINARY_DIR}/MyGenConfig.cmake")endif()add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/generated.h" COMMAND mygen -o "${CMAKE_CURRENT_BINARY_DIR}/generated.h" )

With the CMakeLists.txt modified like this, the project can becross-compiled. First, a native build has to be done in order tocreate the necessary mygen executable. After that, the cross-compilingbuild can begin. The build directory of the native build has to begiven to the cross-compiling build as the location of the MyGenpackage, so that find_package(MyGen) can find it:

mkdir build-native; cd build-nativecmake ..makecd ..mkdir build-cross; cd build-crosscmake -DCMAKE_TOOLCHAIN_FILE=MyToolchain.cmake \ -DMyGen_DIR=~/src/build-native/ ..make

Cross-Compiling Hello World

Now let’s actually start with the cross-compiling. The first step isto install a cross-compiling toolchain. If this is already installed,you can skip the next paragraph.

There are many different approaches and projects that deal withcross-compiling for Linux, ranging from free software projects workingon Linux-based PDAs to commercial embedded Linux vendors. Most ofthese projects come with their own way to build and use the respectivetoolchain. Any of these toolchains can be used with CMake; the onlyrequirement is that it works in the normal file system and does notexpect a “sandboxed” environment, like for example the Scratchboxproject.

An easy-to-use toolchain with a relatively complete target environmentis the Embedded Linux Development Toolkit(http://www.denx.de/wiki/DULG/ELDK). It supports ARM, PowerPC, andMIPS as target platforms. ELDK can be downloaded fromftp://ftp.sunet.se/pub/Linux/distributions/eldk/. The easiest way isto download the ISOs, mount them, and then install them:

mkdir mount-iso/sudo mount -tiso9660 mips-2007-01-21.iso mount-iso/ -o loopcd mount-iso/./install -d /home/alex/eldk-mips/...Preparing... ########################################### [100%] 1:appWeb-mips_4KCle ########################################### [100%]Donels /opt/eldk-mips/bin eldk_init etc mips_4KC mips_4KCle usr var version

ELDK (and other toolchains) can be installed anywhere, either in thehome directory or system-wide if there are more users working withthem. In this example, the toolchain will now be located in/home/alex/eldk-mips/usr/bin/ and the target environment is in/home/alex/eldk-mips/mips_4KC/.

Now that a cross-compiling toolchain is installed, CMake has to be setup to use it. As already described, this is done by creating atoolchain file for CMake. In this example, the toolchain file lookslike this:

# the name of the target operating systemset(CMAKE_SYSTEM_NAME Linux)# which C and C++ compiler to useset(CMAKE_C_COMPILER /home/alex/eldk-mips/usr/bin/mips_4KC-gcc)set(CMAKE_CXX_COMPILER /home/alex/eldk-mips/usr/bin/mips_4KC-g++)# location of the target environmentset(CMAKE_FIND_ROOT_PATH /home/alex/eldk-mips/mips_4KC /home/alex/eldk-mips-extra-install )# adjust the default behavior of the FIND_XXX() commands:# search for headers and libraries in the target environment,# search for programs in the host environmentset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

The toolchain files can be located anywhere, but it is a good idea toput them in a central place so that they can be reused in multipleprojects. We will save this file as~/Toolchains/Toolchain-eldk-mips4K.cmake. The variables mentionedabove are set here: CMAKE_SYSTEM_NAME, the C/C++compilers, and CMAKE_FIND_ROOT_PATH to specify wherelibraries and headers for the target environment are located. The findmodes are also set up so that libraries and headers are searched forin the target environment only, whereas programs are searched for in the host environment only. Now we will cross-compile the hello world project from Chapter 2

project(Hello)add_executable(Hello Hello.c)

Run CMake, this time telling it to use the toolchain file from above:

mkdir Hello-eldk-mipscd Hello-eldk-mipscmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchains/Toolchain-eldk-mips4K.cmake ..make VERBOSE=1

This should give you an executable that can run on the targetplatform. Thanks to the VERBOSE=1 option, you should see that thecross-compiler is used. Now we will make the example a bit moresophisticated by adding system inspection and install rules. We willbuild and install a shared library named Tools, and then build theHello application which links to the Tools library.

include(CheckIncludeFiles)check_include_files(stdio.h HAVE_STDIO_H)set(VERSION_MAJOR 2)set(VERSION_MINOR 6)set(VERSION_PATCH 0)configure_file(config.h.in ${CMAKE_BINARY_DIR}/config.h)add_library(Tools SHARED tools.cxx)set_target_properties(Tools PROPERTIES VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH} SOVERSION ${VERSION_MAJOR})install(FILES tools.h DESTINATION include)install(TARGETS Tools DESTINATION lib)

There is no difference in a normal CMakeLists.txt; no specialprerequisites are required for cross-compiling. The CMakeLists.txtchecks that the header stdio.h is available and sets the versionnumber for the Tools library. These are configured into config.h,which is then used in tools.cxx. The version number is also used toset the version number of the Tools library. The library and headersare installed to ${CMAKE_INSTALL_PREFIX}/lib and${CMAKE_INSTALL_PREFIX}/include respectively. Running CMake gives thisresult:

mkdir build-eldk-mipscd build-eldk-mipscmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchains/Toolchain-eldk-mips4K.cmake \ -DCMAKE_INSTALL_PREFIX=~/eldk-mips-extra-install ..-- The C compiler identification is GNU-- The CXX compiler identification is GNU-- Check for working C compiler: /home/alex/eldk-mips/usr/bin/mips_4KC-gcc-- Check for working C compiler: /home/alex/eldk-mips/usr/bin/mips_4KC-gcc -- works-- Check size of void*-- Check size of void* - done-- Check for working CXX compiler: /home/alex/eldk-mips/usr/bin/mips_4KC-g++-- Check for working CXX compiler: /home/alex/eldk-mips/usr/bin/mips_4KC-g++ -- works-- Looking for include files HAVE_STDIO_H-- Looking for include files HAVE_STDIO_H - found-- Configuring done-- Generating done-- Build files have been written to: /home/alex/src/tests/Tools/build-mipsmake installScanning dependencies of target Tools[100%] Building CXX object CMakeFiles/Tools.dir/tools.oLinking CXX shared library libTools.so[100%] Built target ToolsInstall the project...-- Install configuration: ""-- Installing /home/alex/eldk-mips-extra-install/include/tools.h-- Installing /home/alex/eldk-mips-extra-install/lib/libTools.so

As can be seen in the output above, CMake detected the correctcompiler, found the stdio.h header for the target platform, andsuccessfully generated the Makefiles. The make command was invoked,which then successfully built and installed the library in thespecified installation directory. Now we can build an executable thatuses the Tools library and does some system inspection

project(HelloTools)find_package(ZLIB REQUIRED)find_library(TOOLS_LIBRARY Tools)find_path(TOOLS_INCLUDE_DIR tools.h)if(NOT TOOLS_LIBRARY OR NOT TOOLS_INCLUDE_DIR) message FATAL_ERROR "Tools library not found")endif()set(CMAKE_INCLUDE_CURRENT_DIR TRUE)set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE TRUE)include_directories("${TOOLS_INCLUDE_DIR}" "${ZLIB_INCLUDE_DIR}")add_executable(HelloTools main.cpp)target_link_libraries(HelloTools ${TOOLS_LIBRARY} ${ZLIB_LIBRARIES})set_target_properties(HelloTools PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE)install(TARGETS HelloTools DESTINATION bin)

Building works in the same way as with the library; the toolchain filehas to be used and then it should just work:

cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchains/Toolchain-eldk-mips4K.cmake \ -DCMAKE_INSTALL_PREFIX=~/eldk-mips-extra-install ..-- The C compiler identification is GNU-- The CXX compiler identification is GNU-- Check for working C compiler: /home/alex/denx-mips/usr/bin/mips_4KC-gcc-- Check for working C compiler: /home/alex/denx-mips/usr/bin/mips_4KC-gcc -- works-- Check size of void*-- Check size of void* - done-- Check for working CXX compiler: /home/alex/denx-mips/usr/bin/mips_4KC-g++-- Check for working CXX compiler: /home/alex/denx-mips/usr/bin/mips_4KC-g++ -- works-- Found ZLIB: /home/alex/denx-mips/mips_4KC/usr/lib/libz.so-- Found Tools library: /home/alex/denx-mips-extra-install/lib/libTools.so-- Configuring done-- Generating done-- Build files have been written to: /home/alex/src/tests/HelloTools/build-eldk-mipsmake[100%] Building CXX object CMakeFiles/HelloTools.dir/main.oLinking CXX executable HelloTools[100%] Built target HelloTools

Obviously CMake found the correct zlib and also libTools.so, which hadbeen installed in the previous step.

Cross-Compiling for a Microcontroller

CMake can be used for more than cross-compiling to targets withoperating systems, it is also possible to use it in development fordeeply-embedded devices with small microcontrollers and no operatingsystem at all. As an example, we will use the Small Devices C Compiler(http://sdcc.sourceforge.net), which runs under Windows, Linux, andMac OS X, and supports 8 and 16 Bit microcontrollers. For driving thebuild, we will use MS NMake under Windows. As before, the first stepis to write a toolchain file so that CMake knows about the targetplatform. For sdcc, it should look something like this

set(CMAKE_SYSTEM_NAME Generic)set(CMAKE_C_COMPILER "c:/Program Files/SDCC/bin/sdcc.exe")

The system name for targets that do not have an operating system,“Generic,” should be used as the CMAKE_SYSTEM_NAME. The CMake platformfile for “Generic” doesn’t set up any specific features. All that itassumes is that the target platform does not support shared libraries,and so all properties will depend on the compiler andCMAKE_SYSTEM_PROCESSOR. The toolchain file above does not set theFIND-related variables. As long as none of the find commands is usedin the CMake commands, this is fine. In many projects for smallmicrocontrollers, this will be the case. The CMakeLists.txt shouldlook like the following

project(Blink C)add_library(blink blink.c)add_executable(hello main.c)target_link_libraries(hello blink)

There are no major differences in other CMakeLists.txt files. Oneimportant point is that the language “C” is enabled explicitly usingthe project command. If this is not done, CMake will alsotry to enable support for C++, which will fail as sdcc only has supportfor C. Running CMake and building the project should work as usual:

cmake -G"NMake Makefiles" \ -DCMAKE_TOOLCHAIN_FILE=c:/Toolchains/Toolchain-sdcc.cmake ..-- The C compiler identification is SDCC-- Check for working C compiler: c:/program files/sdcc/bin/sdcc.exe-- Check for working C compiler: c:/program files/sdcc/bin/sdcc.exe -- works-- Check size of void*-- Check size of void* - done-- Configuring done-- Generating done-- Build files have been written to: C:/src/tests/blink/buildnmakeMicrosoft (R) Program Maintenance Utility Version 7.10.3077Copyright (C) Microsoft Corporation. All rights reserved.Scanning dependencies of target blink[ 50%] Building C object CMakeFiles/blink.dir/blink.relLinking C static library blink.lib[ 50%] Built target blinkScanning dependencies of target hello[100%] Building C object CMakeFiles/hello.dir/main.relLinking C executable hello.ihx[100%] Built target hello

This was a simple example using NMake with sdcc with the defaultsettings of sdcc. Of course, more sophisticated project layouts arepossible. For this kind of project, it is also a good idea to set upan install directory where reusable libraries can be installed, so itis easier to use them in multiple projects. It is normally necessaryto choose the correct target platform for sdcc; not everybody usesi8051, which is the default for sdcc. The recommended way to do thisis via setting CMAKE_SYSTEM_PROCESSOR.

This will cause CMake to search for and load the platform filePlatform/Generic-SDCC-C-${CMAKE_SYSTEM_PROCESSOR}.cmake. As thishappens, right before loading Platform/Generic-SDCC-C.cmake, it can beused to set up the compiler and linker flags for the specific targethardware and project. Therefore, a slightly more complex toolchainfile is required

get_filename_component(_ownDir "${CMAKE_CURRENT_LIST_FILE}" PATH)set(CMAKE_MODULE_PATH "${_ownDir}/Modules" ${CMAKE_MODULE_PATH})set(CMAKE_SYSTEM_NAME Generic)set(CMAKE_C_COMPILER "c:/Program Files/SDCC/bin/sdcc.exe")set(CMAKE_SYSTEM_PROCESSOR "Test_DS80C400_Rev_1")# here is the target environment locatedset(CMAKE_FIND_ROOT_PATH "c:/Program Files/SDCC" "c:/ds80c400-install" )# adjust the default behavior of the FIND_XXX() commands:# search for headers and libraries in the target environment# search for programs in the host environmentset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

This toolchain file contains a few new settings; it is also about themost complicated toolchain file you should ever need.CMAKE_SYSTEM_PROCESSOR is set to Test_DS80C400_Rev_1, anidentifier for the specific target hardware. This has the effect thatCMake will try to loadPlatform/Generic-SDCC-C-Test_DS80C400_Rev_1.cmake. As this file doesnot exist in the CMake system module directory, the CMake variableCMAKE_MODULE_PATH has to be adjusted so that this file canbe found. If this toolchain file is saved toc:/Toolchains/sdcc-ds400.cmake, the hardware-specific file should besaved in c:/Toolchains/Modules/Platform/. An example of this is shownbelow:

set(CMAKE_C_FLAGS_INIT "-mds390 --use-accelerator")set(CMAKE_EXE_LINKER_FLAGS_INIT "")

This will select the DS80C390 as the target platform and addthe –use-accelerator argument to the default compile flags. In thisexample the “NMake Makefiles” generator was used. In the same waye.g. the “MinGW Makefiles” generator could be used for a GNU make fromMinGW, or another Windows version of GNU make, are available. At leastversion 3.78 is required, or the “Unix Makefiles” generator underUNIX. Also, any Makefile-based, IDE-project generators could be used;e.g. the Eclipse, CodeBlocks, or the KDevelop3 generator.

Cross-Compiling a Complex Project - VTK

Building a complex project is a multi-step process. Complex in thiscase means that the project uses tests that run executables, and thatit builds executables which are used later in the build to generatecode (or something similar). One such project is VTK, which usesseveral try_run tests and creates several code generators.When running CMake on the project, every try_run commandwill produce an error message; at the end there will be aTryRunResults.cmake file in the build directory. You need to gothrough all of the entries of this file and fill in the appropriatevalues. If you are uncertain about the correct result, you can alsotry to execute the test binaries on the real target platform, wherethey are saved in the binary directory.

VTK contains several code generators, one of which isProcessShader. These code generators are added usingadd_executable; get_target_property(LOCATION) is usedto get the locations of the resulting binaries, which are then usedin add_custom_command or add_custom_targetcommands. Since the cross-compiled executables cannot be executedduring the build, the add_executable calls aresurrounded by if (NOT CMAKE_CROSSCOMPILING) commands and theexecutable targets are imported into the project using theadd_executable command with the IMPORTED option. Theseimport statements are in the file VTKCompileToolsConfig.cmake,which does not have to be created manually, but it is created by anative build of VTK.

In order to cross-compile VTK, you need to:

  • Install a toolchain and create a toolchain file for CMake.

  • Build VTK natively for the build host.

  • Run CMake for the target platform.

  • Complete TryRunResults.cmake.

  • Use the VTKCompileToolsConfig.cmake file from the native build.

  • Build.

So first, build a native VTK for the build host using the standardprocedure.

cd VTKmkdir build-native; cd build-nativeccmake ..make

Ensure that all required options are enabled using ccmake; e.g. if youneed Python wrapping for the target platform, you must enable Pythonwrapping in build-native/. Once this build has finished, therewill be a VTKCompileToolsConfig.cmake file inbuild-native/. If this succeeded, we can continue to crosscompiling the project, in this example for an IBM BlueGenesupercomputer.

cd VTKmkdir build-bgl-gcccd build-bgl-gcccmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchains/Toolchain-BlueGeneL-gcc.cmake \ -DVTKCompileTools_DIR=~/VTK/build-native/ ..

This will finish with an error message for each try_run and aTryRunResults.cmake file, that you have to complete as describedabove. You should save the file to a safe location, or it will beoverwritten on the next CMake run.

cp TryRunResults.cmake ../TryRunResults-VTK-BlueGeneL-gcc.cmakeccmake -C ../TryRunResults-VTK-BlueGeneL-gcc.cmake ....make

On the second run of ccmake, all the other arguments can be skipped asthey are now in the cache. It is possible to point CMake to the builddirectory that contains a CMakeCache.txt, so CMake will figure outthat this is the build directory.

Some Tips and Tricks

Dealing with try_run tests

In order to make cross compiling your project easier, try to avoidtry_run tests and use other methods to test something instead. Ifyou cannot avoid try_run tests, try to use only theexit code from the run and not the output of the process. That way itwill not be necessary to set both the exit code and the stdout andstderr variables for the try_run test whencross-compiling. This allows the OUTPUT_VARIABLE or theRUN_OUTPUT_VARIABLE options for try_run to be omitted.

If you have done that, created and completed a correctTryRunResults.cmake file for the target platform, you mightconsider adding this file to the sources of the project, so that itcan be reused by others. These files are per-target, per-toolchain.

Target platform and toolchain issues

If your toolchain is unable to build a simple program without specialarguments, like e.g. a linker script file or a memory layout file, thetests CMake does initially will fail. To make it work the CMake moduleCMakeForceCompiler offers the following macros:

CMAKE_FORCE_SYSTEM(name version processor),CMAKE_FORCE_C_COMPILER(compiler compiler_id sizeof_void_p)CMAKE_FORCE_CXX_COMPILER(compiler compiler_id).

These macros can be used in a toolchain file so that the requiredvariables will be preset and the CMake tests avoided.

RPATH handling under UNIX

For native builds, CMake builds executables and libraries by defaultwith RPATH. In the build tree, the RPATH is set so that theexecutables can be run from the build tree; i.e. the RPATH points intothe build tree. When installing the project, CMake links theexecutables again, this time with the RPATH for the install tree,which is empty by default.

When cross-compiling you probably want to set up RPATH handlingdifferently. As the executable cannot run on the build host, it makesmore sense to build it with the install RPATH right from thestart. There are several CMake variables and target properties foradjusting RPATH handling.

set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)set(CMAKE_INSTALL_RPATH "<whatever you need>")

With these two settings, the targets will be built with the installRPATH instead of the build RPATH, which avoids the need to link themagain when installing. If you don’t need RPATH support in yourproject, you don’t need to set CMAKE_INSTALL_RPATH; it is empty bydefault.

Setting CMAKE_INSTALL_RPATH_USE_LINK_PATH to TRUE is usefulfor native builds, since it automatically collects the RPATH from alllibraries against which a targets links. For cross-compiling it shouldbe left at the default setting of FALSE, because on the target theautomatically generated RPATH will be wrong in most cases; it willprobably have a different file system layout than the build host.

Cross Compiling With CMake — Mastering CMake (2025)

References

Top Articles
Latest Posts
Recommended Articles
Article information

Author: Nicola Considine CPA

Last Updated:

Views: 6044

Rating: 4.9 / 5 (49 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Nicola Considine CPA

Birthday: 1993-02-26

Address: 3809 Clinton Inlet, East Aleisha, UT 46318-2392

Phone: +2681424145499

Job: Government Technician

Hobby: Calligraphy, Lego building, Worldbuilding, Shooting, Bird watching, Shopping, Cooking

Introduction: My name is Nicola Considine CPA, I am a determined, witty, powerful, brainy, open, smiling, proud person who loves writing and wants to share my knowledge and understanding with you.