Cross Compile With CMake

Dori Exterman
Dori Exterman reading time: 5 minutes
January 25, 2021

Wondering what the Pied Piper image is doing here? Well, just imagine the Pied Piper as CMake and the C++ based projects as Children.

The parents among us would relate to the amount of nurturing and care required to bring up a child. Similar is the difficulty in getting a C++ project cross-compiled. A nanny would make things simpler. Analogously we have CMake to make things simpler in the cross-compilation world.

Cross Compile? Care to Explain More?

Sure.  Think of a well-known application like Microsoft Paint. Before it becomes an executable, it exists as a set of source code files. Compiling the source code and linking object codes to a single executable is the job of a compiler.  In a normal scenario:

Cross compile - Target Operating system same as the running one

The source code of Microsoft Paint exists as a set of (no doubt C++) files that get compiled within Microsoft windows using (no doubt Microsoft Visual Studio) compiler that begets Microsoft Paint. Now take the cross compile scenario:

Cross compile - Target Operating system different from the running one

So cross compilation happens when the source code compiler is targeting a different operating system than the one it is currently hosted in. A bit clearer should this picture make:

Cross compile - A different operating system

It should now be obvious where the term cross compilation comes from… Cross platform software development is not easy as each operating system has its idiosyncrasies. Source code written for windows cannot normally be compiled for Linux and vice versa. This is where frameworks like Qt and standards like POSIX come into play.

Can I Not Live With a Simple Compilation?

Not always. Take for example an app that a developer is planning for Android. He would use the computational power of his development machine (Windows, Linux, or Mac) to cross compile the app for Android. The same would be the situation if the developer is targeting a platform like Raspberry Pi. It is much easier to set up the cross-compilation environment on a powerful development machine and copy the binaries to Raspberry Pi than spending too much time running the compilation on such a low power machine.

Another use case for cross compilation is the complexity of getting dependencies to build using the target machine’s native compiler. For example, take an open-source project like Chromium (base for both Microsoft Edge browser and Google Chrome browser) that is supported on multiple platforms. Because of the multiple dependencies, it is not advisable to attempt the build of Chromium natively. Rather, docker images are setup that brings up a Linux container that hosts GNU GCC to cross compile the source.

I Have Never Attempted a Cross Compilation. Can You Please Give an Example?

Windows 10 desktop operating system comes in two different flavors:

  • Windows 10 for Intel CPUs (Both 32 Bit and 64 Bit versions)
  • Windows 10 for ARM CPUs (Both ARM32 and ARM64 bit versions)

Programs compiled for Intel CPUs are not compatible with ARM CPUs and vice-versa. Microsoft Visual Studio comes with the following command tools some of which are used for cross compilation:

  • VS2015 x64 ARM Cross Tools Command
  • VS2015 x64 Native Tools Command
  • VS2015 x64 x86 Cross Tools Command
  • VS2015 x86 ARM Cross Tools Command
  • VS2015 x86 Native Tools Command
  • VS2015 x86 x64 Cross Tools Command

We are interested in the Cross Tools compiler. Let us try to create a simple “Hello, World!” C++ program:

#include <iostream>int main(int argc, char** argv){std::cout << "Hello, World!" << std::endl;return 0;}

On an x64 Native command line, compiling the above program gives:

Cross compile - x64 Native command line

On an x64 ARM cross tools command prompt, compiling the same program gives:

Cross compile - x64 ARM cross tools command prompt

Pay special attention to the /machine:arm output. The compilation is successful and an executable is generated, but it is not for the current machine as can be seen by running the executable:

Cross compile - Running the executable

Congratulations! You have successfully cross compiled the simplest C++ program for a different machine.

Thanks! So Why CMake Cross Compile?

Compiling for different CPUs supported by the same operating system is the simplest form of cross compilation. What if you wanted to be in Linux and wants to generate an executable that supports Windows? Better still, you want to use, if possible, the same codebase to generate both the Linux and Windows executables.

Let us try this out. I have a Windows 10 machine that runs Windows Subsystem for Linux where I have installed Ubuntu. A simple

sudo apt-get install mingw-w64

will install the mingw cross compiler toolchain for windows. We create CMakeLists.txt file like so:

# set minimum cmake versioncmake_minimum_required(VERSION 3.5 FATAL_ERROR)

 

# project name and languageproject(HelloWorld LANGUAGES CXX)

 

set(CMAKE_CXX_STANDARD 11)set(CMAKE_CXX_EXTENSIONS OFF)set(CMAKE_CXX_STANDARD_REQUIRED ON)

 

include(GNUInstallDirs)set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})

 

# define executable and its source fileadd_executable(HelloWorld main.cpp)

Let us create a toolchain file for CMake that tells some information about the cross-compilation toolchain. Like so:

# the name of the target operating systemset(CMAKE_SYSTEM_NAME Windows)

 

# which compilers to useset(CMAKE_C_COMPILER i686-w64-mingw32-gcc)set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)


set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)

# adjust the default behavior of the find commands:# search headers and libraries in the target environmentset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

 

# search programs in the host environmentset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

 

Save this file as cross-compilation.cmake and keep it in the same folder as CMakeLists.txt (given above) and Main.cpp. Create a new folder called build and change to that directory. Now issue the commands:

 

cmake .. -DCMAKE_TOOLCHAIN_FILE=../cross-compilation.cmakecmake --build .

 

This should create a HelloWorld.exe as specified in the CMakeLists.txt in the build/bin folder. What have we achieved? We have created an executable for Windows entirely working under Linux. Different toolchain files and compilers should allow the same CMakeLists.txt target to be created for multiple platforms.

 

Cross Platform Software Development Using CMake

CMake is a great tool for cross platform software development. It uses a set of utilities called a toolchain to drive the build. There are two main scenarios of using CMake for builds:

  • Normal builds where CMake is responsible for choosing the toolchain
  • Cross platform builds where the user specifies a toolchain file

The simple example above shows how we can create a toolchain file and tell CMake to use that toolchain file to drive the build. In real-world cross platform software development, the stakeholders would carefully select frameworks that aid such a development – for example Qt.

By the way, If you’d like to read more about CMake please visit our blog post about CMake vs Make.

Conclusion

Did the pied piper’s story have a happy ending? Some versions of the legend say piper took the children to a beautiful land. CMake as a choice for cross platform software development should also result in such a happy ending. The End ?

Dori Exterman
Dori Exterman reading time: 5 minutes minutes January 25, 2021
January 25, 2021

Table of Contents

Related Posts

5 minutes 8 Reasons Why You Need Build Observability

Read More  

5 minutes These 4 advantages of caching are a game-changer for development projects

Read More  

5 minutes What Level of Build Observability Is Right for You?

Read More