Introduction

Sometimes we can spend a lot of time finding a way to execute a simple task. In that case, I spent some hours to find a way to cross-compile a code hosted in my machine using Conan + CMake. Conan provides some good examples oriented to projects on GitHub, but none of then for hosted source code. Maybe there is a way to do what this tutorial presents only using Conan, but until this moment, I was not able to figure out how.

So, this tutorial intends to source a simple example of how to cross-compile a source code using a Linux machine with AMD64/x86 architecture to a Linux target for ARMv6/v7 using Conan and CMake. I’ve dealt with some issues trying to compile OpenSSL to ARMv8 and realized this is a bug being treated in the Conan. It is expected that the incredible Conan team handles this and fixes the bug as soon as possible.

Tutorial

The example was made on a computer with Ubuntu 18.04 for a target, a Raspberry Pi, with Raspbian Buster.

Example code and necessary files

The example used here is based on md5 hash calculator sourced by Conan with a slight difference in the string that will be hashed. If you have questions about the Conan setup, I strongly recommend the excellent documentation they provide.

Let’s go.

The first thing to do is define a profile for ARM.

amd64_to_arm
target_host=arm-linux-gnueabihf
toolchain=/opt/tools/raspberry/gcc-linaro
cc_compiler=gcc
cxx_compiler=g++

[settings]
os=Linux
arch=armv7
compiler=gcc
compiler.version=7.4
compiler.libcxx=libstdc++11
build_type=Release

[env]
CONAN_CMAKE_FIND_ROOT_PATH=$toolchain
PATH=[$toolchain/bin]
CHOST=$target_host
#AR=$target_host-ar
AS=$target_host-as
RANLIB=$target_host-ranlib
LD=$target_host-ld
STRIP=$target_host-strip
CC=$target_host-$cc_compiler
CXX=$target_host-$cxx_compiler
CXXFLAGS=-I"$toolchain/$target_host/lib/include"

Remember to adjust target_host and toolchain parameters according to your setup.

The followed three files are identical as sourced by Conan.

md5.cpp
 #include "Poco/MD5Engine.h"
 #include "Poco/DigestStream.h"

 #include <iostream>


 int main(int argc, char** argv)
 {
     Poco::MD5Engine md5;
     Poco::DigestOutputStream ds(md5);
     ds << "Hello World!";
     ds.close();
     std::cout << Poco::DigestEngine::digestToHex(md5.digest()) << std::endl;
     return 0;
 }

Necessary configuration files

conanfile.txt
[requires]
poco/1.10.1

[generators]
cmake
CMakeLists.txt
cmake_minimum_required(VERSION 2.8.12)
project(MD5Encrypter)

add_definitions("-std=c++11")

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()

add_executable(md5 md5.cpp)
target_link_libraries(md5 ${CONAN_LIBS})

The next file is needed for the cross-compilation, and documentation about can be found in CMake documentation.

Based on Linaro compiler for ARM (more explanation will be sourced here).

toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_SYSROOT /opt/tools/raspberry/sysroot)

# You can set the followed param to define where the binary code will be saved!
#set(CMAKE_STAGING_PREFIX /opt/tools/raspberry/sysroot)

set(tools /opt/tools/raspberry/gcc-linaro/)
set(CMAKE_C_COMPILER ${tools}/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/arm-linux-gnueabihf-g++)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

Create a build directory to generate the target code.

mkdir build && cd build

At this point we have this tree:

.
├── amd64_to_arm
├── build
├── CMakeLists.txt
├── conanfile.txt
├── md5.cpp
└── toolchain.cmake

1 directory, 5 files


Running Conan

The first step is to run Conan to solve code dependencies.

conan install .. --profile=../amd64_to_arm

Of course, we need to inform Conan about our new profile.

This step can take several minutes because Conan cannot source binary files for ARM of Poco, OpenSSL, and Zlib. If this occurs, you can handle that feeding Conan to compile all necessary code on your machine.

conan install . --profile ../amd64_to_arm --build zlib --build openssl --build poco 
Configuration:
[settings]
arch=armv7
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=7.4
os=Linux
[options]
[build_requires]
[env]
AS=arm-linux-gnueabihf-as
CC=arm-linux-gnueabihf-gcc
CHOST=arm-linux-gnueabihf
CONAN_CMAKE_FIND_ROOT_PATH=/opt/tools/raspberry/gcc-linaro
CXX=arm-linux-gnueabihf-g++
CXXFLAGS=-I"/opt/tools/raspberry/gcc-linaro/arm-linux-gnueabihf/lib/include"
LD=arm-linux-gnueabihf-ld
PATH=[/opt/tools/raspberry/gcc-linaro/bin]
RANLIB=arm-linux-gnueabihf-ranlib
STRIP=arm-linux-gnueabihf-strip
conanfile.txt: Installing package
Requirements
    openssl/1.0.2t from 'conan-center' - Cache
    poco/1.10.1 from 'conan-center' - Cache
    zlib/1.2.11 from 'conan-center' - Cache
Packages
    openssl/1.0.2t:dd504a67e1f68cd2e981f39999ff7304a3d7980e - Cache
    poco/1.10.1:3c3626390a39fc0562eed042ce49388656074cd7 - Cache
    zlib/1.2.11:d0d510d7fe50fdfcb7278cb9eacbaf324235cdc3 - Cache

Cross-build from 'Linux:x86_64' to 'Linux:armv7'
Installing (downloading, building) binaries...
zlib/1.2.11: Already installed!
openssl/1.0.2t: Already installed!
poco/1.10.1: Already installed!
conanfile.txt: Generator cmake created conanbuildinfo.cmake
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo

Running CMake

Now it is time to run CMake. Here is the moment where the magic happens.

cmake ..  -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake

Some attention here: the toolchain.cmake file must be set.

Lastly,

cmake --build .
Scanning dependencies of target md5
[ 50%] Building CXX object CMakeFiles/md5.dir/md5.cpp.o
[100%] Linking CXX executable bin/md5
[100%] Built target md5

Generated code

Inside the build directory

.
├── bin
│   └── md5
├── CMakeCache.txt
├── CMakeFiles
│   ├── 3.10.2
│   ├── cmake.check_cache
│   ├── CMakeDirectoryInformation.cmake
│   ├── CMakeOutput.log
│   ├── CMakeTmp
│   ├── feature_tests.bin
│   ├── feature_tests.c
│   ├── feature_tests.cxx
│   ├── Makefile2
│   ├── Makefile.cmake
│   ├── md5.dir
│   ├── progress.marks
│   └── TargetDirectories.txt
├── cmake_install.cmake
├── conanbuildinfo.cmake
├── conanbuildinfo.txt
├── conaninfo.txt
├── conan.lock
├── graph_info.json
└── Makefile

The code was generated in the bin folder and it is ready to run on the target.