Cross-compiling of a local source code using Conan + CMake
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.