This article focuses on the cross-compilation of C and C++ projects for Beaglebone Black, which is a popular embedded Linux board available in the market. I am using Manjaro Linux with an Intel x86_64 processor as the host computer for cross-compilation. The same steps should be applicable for macOS and Windows, though, I haven’t personally tested it myself.

What is cross-compilation?

Compiled languages such as C, C++ produces a binary file at the end of the compilation for the program you write. The binary file contains all the assembly instructions specific to a certain type of computer architecture such as ARM, x86, etc. So, if I use my laptop which is x86_64 architecture to compile a program, I can’t run it in a computer-based on ARM’s architecture such as a BeagleBone Black. And, vice versa.

Commonly used embedded Linux boards are based on ARM processors, and they are generally low on resources such as 512 MB RAM, etc. To save time during development, it is a common practice to use a powerful host computer to cross-compile for smaller embedded Linux boards. It means that we use a cross-compiler in our host machine that produces a binary based on ARM’s instruction set so that it can be directly deployed in our embedded target.

Sometimes setting up the toolchain for cross-compilation might be difficult because your OS does not have a package out of the box. And also it can be frustrating when some system library gets in the way during the cross-compilation process resulting in a build failure. So it is good to isolate your host environment when you want to cross-compile. There are different ways of achieving that such as a VM, but in this article, I will go over Docker as they are lightweight and fast to set up and use.

Using Docker for cross-compilation

Docker is a tool to run your applications in an isolated environment called containers. These containers act as if they are an individual operating system, but only with the software you need inside, which makes it lightweight and fast to use. To start with cross-compilation, make sure Docker is installed correctly on your computer. In this example, I will mainly show the toolchain that is used to cross-compile for a Beaglebone black (armv7hf architecture). You could replace them with appropriate toolchains based on your target board.

Let us assume your folder structure looks as follows:

Projects
├── <your-repo>/

<your-repo> folder contains the source files that you want to cross-compile. Now introduce a Dockerfile in the Projects folder. Your Projects folder should now look like this:

Projects
├── Dockerfile
└── <your-repo>/

Dockerfile is like a recipe file that tells docker about the software and libraries that your container should have. In principle, Dockerfile can be located anywhere. It is even better if you put it as a part of your repository and keep it under version control. But if you don’t want it under version control, you could put it one level above your repository of interest as shown in this example.

Copy the contents below to your Dockerfile:

FROM ubuntu:xenial
RUN apt-get update && apt-get -y install \
                        gcc-arm-linux-gnueabihf \ 
                        g++-arm-linux-gnueabihf \
                        cmake 

With the Dockerfile, we need to create the Docker image that we will be using for cross-compilation. In your terminal, type the command shown below to build a docker image.

~/Projects $ docker build -t <name-of-your-container> . 

(You may need to start the docker service via systemctl if you are in linux. You can start it by typing this: systemctl start docker.service)

That’s it. Now we are ready to run our docker image to cross-compile applications for the Beaglebone Black. We will mount the required directories to our docker image.

To run your container, type the command in your shell from Projects directory as follows:

~/Projects $ docker run -it -v "$PWD":"$PWD" -w "$PWD"/<your-repo> <name-of-your-container>

We gave some extra options to run the container. Let’s see what each flag means in this context.

-it: Opens up an interactive shell inside your container image.

-v: Mount the $PWD(present working directory) into the container image. This is like your OS and docker will be sharing the same files from your $PWD. Read more about it in the docker documentation.

-w: Set the working directory of my container image. The interactive shell will start in this directory when it opens.

The container shell will pop up in <your-repo> directory and it will contain all your repository files in them. All that is left now is to use your favorite tool of choice to build the repository. I usually use CMake as my meta-build tool to generate the Makefiles. With CMake, a CMAKE_TOOLCHAIN_FILE is provided to choose arm-gcc compiler. You will notice during compilation that CMake will use the compiler that resides in your container, and not from your host machine’s OS.

If you are not using CMake, you can follow your usual steps with any other build tool to cross-compile inside this container.

Dependency Management

There are multiple ways of handling dependencies for your project. As cross-compilation requires the third-party library to be compiled in target architecture (armv7hf in this case), putting a pre-compiled version of the libraries inside a separate folder is a common practice. (This is mostly applicable for C and C++).

But I would strongly advise against that. We as a community should start using modern tools to deal with dependencies. One popular tool that I use for my project these days is Conan. It is a C and C++ package manager. It is like pip for python.

The best part about Conan is that one recipe can be used for multiple targets. It will take care of correctly linking the libraries corresponding to the architecture you want to cross-compile. It also has the option of building the libraries from the source if the pre-compiled version is missing for your machine. No need of any manual intervention1. Read more about Conan here .

Conclusion

In this article, we saw how to use Docker for isolated cross-compilation. This can come in handy if multiple programmers are working on the same codebase to ensure consistent builds across all laptops.

Most companies use Virtual Desktop Environment for building the project in a remote server. But if you are a part of a small hobby club or you are working on some personal project, Docker is the best way to achieve isolated builds in your laptop without any incurring any extra costs (both in terms of money and time).

The same technique can be followed for a Raspberry Pi board as well but make sure to use the correct toolchain inside the Dockerfile. There are pre-built Docker images already available in Docker hub that can be used for cross-compilation. Conan site also provides Conan docker images that can be easily used for cross-compilation.

Hope you enjoyed the article. Happy cross-compiling!

1 Though Conan has more than 100,000 packages available, sometimes the package you are looking for might not be there.