UP | HOME

Using Conan and cross-compile to EV3

Table of Contents

robot.jpg

Overview

I'll start with a disclaimer. The goal of this project was to be able to create an environment which I could easily cross-compile c++ code and run on my Lego-Mindstorm running ev3dev (debian linux). I wanted to use conan, for two reasons , one to learn, and the other is that I like the idea of having a packagemanager for c++. I have struggled enough with trying to find and build libraries, and a package manager will make life much eassier. I am by no means a expert on this field. There might be easier and/or better way of doing things, but this is the way I found through trail-error and reading. And docker, well, I like the container idea, but again I did bare mininmum to get things to work..

Ok, so what is this? A couple of years ago I bought a lego-mindstorm kit to my kids, the reason was that when I was a kid I started using commondore and add things like lights and buttons to some interface it had in the back, and I manage to get it to blink. How I knew how to do, I have no idea, I probably read it in a computer magazine somewhere, and started fiddling with it. Anyhow , it was a great memory, and the whole thing got stuck to me while growing up. So computer became my profession, though maybe not in a straight line, but somehow I ended up doing what I though was fun then. So my simple minded thought was , since I loved fiddling with computer when I was a kid, my kids might like it too… Was I ever wrong, they looked at it and said "Nah, lets play roblox". WTF!! There I was, with my painfully expensive robot-kit which my kids dismissed. So it got stuck in a corner for some year, until one day I read about ev3dev. I added the image to a old sd-card and Voilá. It ran linux.. I did some python programs to have it do stuff, but honestly Im not a big fan of python. I wanted c++… So this article is how I did it, using cross-compiler in a not so painful fashion..Well..It could be worse. My kids…Yes, they been part of the development, well, they named it.."Gorby" thats about it.

Giving Gorby a (debian) life

Gorby needs a system, we already talked about discussed some in the overview. I will stick to the supported one, but it is kind of old. For example the installed glibc is 2.24 which was released in 2016. So quite old, to compare with my host machine which is on version 2.32. When it comes to c++ the std libraries are on version 6.0.22, which means the c++ standard it supports is c++14, which is a bit old. But maybe in another post i will upgrade the EV3 to a later kernel and libc. There is a upgrade page which I guess you can find newer images. The docker image page also provides away to make a image of a newer distribution. This article I will just use the supported image. Which can be downloaded from here.

Adding image to microSD-card

The download image needs to be unzipped and locate the image file (*.img) Time to add it to the MicroSd-card.

sudo dd if=ev3dev-stretch-ev3-generic-2020-04-10.img of=/dev/sdf bs=4Mb

Assuming that sdf is your inserted device name , you can determine that its the right device by opening a terminal and run journalctl -fk and then insert your microSD card. .eg

$> sudo journactl -fk
.
.  <inser SD card>
....  sd 12:0:0:2: [sdf] 124735488 512-byte logical blocks: (63.9 GB/59.5 GiB)

1 When the image has been written to the card , just put it into the slot on the EV3 and boot it up. If everyting is done correctly the ev3 should boot up, and a EV3DEV logo should be seen.

Connection to EV3

We now need to connect the EV3 so we can ssh to it. I want do a big tutorial on this subject, since the existing alredy covers most of it. I was keen to get everything up and running as fast as I could, so I used bluetooth, and it worked like a charm.

I could now connect to Gorby and make some initial assessment.

Check the Gorby hardware

So lets first checkout what kind of network devices that Gorby has and if any ip addresses are connected to them.

ip addr | awk '/[0-9]: / {printf("%s %s\n",$2,$9)} /inet / {printf(" %s\n", $2)}'
lo: UNKNOWN
 127.0.0.1/8
sit0@NONE: DOWN
usb0: DOWN
usb1: DOWN
bnep0: UNKNOWN
 10.42.0.250/24

Just for information, what cpu information can we optain.

lscpu
Architecture:          armv5tejl
Byte Order:            Little Endian
CPU(s):                1
On-line CPU(s) list:   0
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             1
Model:                 5
Model name:            ARM926EJ-S rev 5 (v5l)
BogoMIPS:              148.88
Flags:                 swp half thumb fastmult edsp java

There are some things that we can take note of here. First of all its a single core (32 bit), and we can basically only run active thread for that core. That doesn't mean we can't use more threads, it only means that just one instruction at a time, and they need to switch between the threads, the utlization can still be better since many times threads just waits, thats when other threads can do some work.

The Byte order is default (little endian ) so we actually don't need the compiler argument -mlittle-endian, but its good to know.

Checking the family name of the models reveals that it belongs to the ARM9E family which is quite old (2001). Though it enables the direct execution of 8-bit Java bytecode in hardware. The gcc compiler option for this processor is -march=armv5

There is no fpu in the flag field, this means that we don't have a fpu (floating point) unit, which means if we going to use it we need to emulate it, this is a compiler argument (-mfloat-abi=soft), but I don't think the docker has a floating point library installed. So it will be set to hard.

The processor supports thumb instruction, which is used to to improve compiled code-density, as i understand it , it maps directly to the arm instructions, but as far as I understand it it creates smaller binaries with the same functionality. Though its not necessary in this case. Lets checkout the available disk size:

df -h
Filesystem Size Used Avail Use% Mounted on
udev 26M 0 26M 0% /dev  
tmpfs 20M 336K 20M 2% /run  
/dev/mmcblk0p2 7.3G 1015M 5.9G 15% /  
tmpfs 29M 0 29M 0% /dev/shm  
tmpfs 5.0M 4.0K 5.0M 1% /run/lock  
tmpfs 29M 0 29M 0% /sys/fs/cgroup  
/dev/mmcblk0p1 48M 224K 48M 1% /boot/flash  
tmpfs 5.7M 0 5.7M 0% /run/user/1000  

The 5.9 Gb available is quite enough to just don't care about the thumb instruction.

The half means that the processors, well, its supports store and load register signed 8 bit bytes and 16 bit-halfwords.Unsigned halfword loads are zero-extended to 32 bits. I don't think we need to care about this one. (gcc -mthumb or -marm)

The edsp is digital signal processing unit offer high performance signal processing. We don't need to care about this either, its a feature in the processor.

Lets skip the rest, since its nothing we want to use anyway. But one thing that might be good to know is that we should probably provide the -mtune=arm926ej-s to the compiler, that might do some performace gains.

cat /proc/meminfo | awk '/(MemTotal:|MemFree:|MemAvailable:|SwapTotal:|SwapFree:)/ {printf("%s %s %s %s\n",$1,$2,$3,$2/1024)}'
Name Val Type Mb
MemTotal: 57620 kB 56.2695
MemFree: 1436 kB 1.40234
MemAvailable: 32988 kB 32.2148
SwapTotal: 98300 kB 95.9961
SwapFree: 94716 kB 92.4961

Here is a small explanation:

Memtotal
Total usable memory
MemFree
The amount of physical memory not used by the system
MemAvailable
An estimate of how much memory is available for starting new applications, without swapping.
SwapTotal
Total swap space available
SwapFree
The remaining swap space available

57Mb total memory with 32Mb for running applications is not very much, so we are on scarce resources in comparison to ie. pc. It is necessary to make sure that we don't have things running which are not in use. Though things could have been worse.

One more thing needs to be fixed. Right now it does't seem to have a proper resolver, so running a update on the system with apt doesn't work. Usually this is fixed by editing /etc/resolv.conf But for some reason it didn't work. After some investigation I found this:

systemctl cat systemd-resolved
# /lib/systemd/system/systemd-resolved.service
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Network Name Resolution
Documentation=man:systemd-resolved.service(8)
Documentation=http://www.freedesktop.org/wiki/Software/systemd/resolved
Documentation=http://www.freedesktop.org/wiki/Software/systemd/writing-network-configuration-managers
Documentation=http://www.freedesktop.org/wiki/Software/systemd/writing-resolver-clients
After=systemd-networkd.service network.target

# On kdbus systems we pull in the busname explicitly, because it
# carries policy that allows the daemon to acquire its name.
Wants=org.freedesktop.resolve1.busname
After=org.freedesktop.resolve1.busname

[Service]
Type=notify
Restart=always
RestartSec=0
ExecStart=/lib/systemd/systemd-resolved
WatchdogSec=3min
CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_NET_RAW CAP_NET_BIND_SERVICE
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=full
ProtectHome=yes
ProtectControlGroups=yes
ProtectKernelTunables=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
SystemCallFilter=~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io

[Install]
WantedBy=multi-user.target

# /lib/systemd/system/systemd-resolved.service.d/resolvconf.conf
# tell resolvconf about resolved's builtin DNS server, so that DNS servers
# picked up via networkd are respected when using resolvconf, and that software
# like Chrome that does not do NSS (libnss-resolve) still gets proper DNS
# resolution; do not remove the entry after stop though, as that leads to
# timeouts on shutdown via the resolvconf hooks (see LP: #1648068)
[Service]
ExecStartPost=+/bin/sh -c '[ ! -e /run/resolvconf/enable-updates ] || echo "nameserver 127.0.0.53" | /sbin/resolvconf -a systemd-resolved'
ReadWritePaths=-/run/resolvconf

Actually, its right at the end we kind of find the problem, the nameserver is pointing towards our localhost.. it seems to look for a file and if the file /run/resolvconf/enable-updates exiss the resolver provides the address to the localhost. Well, the sbin/resolvconf doesn't seem to exists?…So that seems abit odd? Well lets fix so that we can get updates.

mkdir -p /etc/systemd/resolved.conf.d
cat <<EOF > /etc/systemd/resolved.conf.d/dns_server.conf
[Resolv]
DNS=192.168.0.1
EOF

systemctl daemon-reload systemd-resolved

That should do the trick. Ok, thats it.. for some system configuration for now.

Runing with python

ev3dev python interface..

As I already mentioned , I'm not a big fan of python. Its great to do some prototyping and see that everying works, but doing anything more elaborate, it quickly becomes a mess, it isn't a type safe programming language which means the compiler want validate the types during compile time. But its nice to be able to do a fast check I can control the vehicle and recieve data from the sensors.

Using tramp

I Haven't installed the ev3dev python library on my machine, the reason is that I just wanted to test out that Gorby reacts on my commands and I don't actually care about making anything substantial python. But the documentation is quite good for ev3dev python library. Since I'm a big fan of emacs , and org-mode I can make small scripts and execute it straight away using tramp 2. Or i could just edit a file on gorby using emacs and tramp.

Here is an example:

from time import sleep

from ev3dev2.motor import LargeMotor, OUTPUT_B,OUTPUT_C, SpeedPercent, MoveTank
from ev3dev2.sensor import INPUT_1
from ev3dev2.sensor.lego import TouchSensor
from ev3dev2.led import Leds
from ev3dev2.sound import Sound

sound = Sound()
sound.speak('Hello I am a robot')

tank_drive = MoveTank(OUTPUT_B, OUTPUT_C)
tank_drive.on_for_rotations(SpeedPercent(75), SpeedPercent(75), 10)

here is the actual org-source code when using tramp:

#+NAME: hello-python
#+HEADER: :results none :eval never-export
#+begin_src python :dir "/ssh:robot@10.42.0.250:/home/robot"
from time import sleep

from ev3dev2.motor import LargeMotor, OUTPUT_B,OUTPUT_C, SpeedPercent, MoveTank
from ev3dev2.sensor import INPUT_1
from ev3dev2.sensor.lego import TouchSensor
from ev3dev2.led import Leds
from ev3dev2.sound import Sound

sound = Sound()
sound.speak('Hello I am a robot')

tank_drive = MoveTank(OUTPUT_B, OUTPUT_C)
tank_drive.on_for_rotations(SpeedPercent(75), SpeedPercent(75), 10)

#+end_src

This makes it much easier to work with. I'm not going to turn this in to an emacs tutorial, this is just an alternative way to test things on target instead of using ssh and edit or alternatively edit on host and use scp.

In this regard python is great , minimal effort to make really small test utilities. On the other hand, the EV3 is quite slow both in regards of cpu speed and memory, that means it takes several seconds before python script actually executes. And as the application becomes bigger the more time and memory it will use to execute.

Using c++

The c++ is a different beast altogheter, to be able to use the c++ we need to cross-compile. Cross compilation means that the compiler is producing binary code which is not executable on the host machine. For example running a x86_64 host machine the compiler needs to produce a binary for .eg arm which is nativly on the target. This means that when the compiler on the host machine has produced a binary it it needs to be able to run on the target machine. The binary then needs to be transffered to the target where the machine set is the same. Alternativly one could install development and build tools on target and compile there. But that is really not such a great idea. There are (at least) two major reasons why this is a bad idea.

  1. It will take up alot of disk space to install a complete development enviroment
  2. the EV3 is really slow in comparison to my pc (see check hardware section). It would take ages to compile.

With those reasons, it makes sense to setup a cross-compiler toolchain on the local machine and use that to produce the ELF (arm binary) output. elf.gif

Setting up a toolchain for cross compilaton can be quite cumbersome. Fortunatly there is a docker with (almost) all the necessary tools installed.

Setting up Docker

First its necessary to pull the docker image to be able to run it.

docker pull ev3dev/debian-stretch-cross

This command will essentially download the ev3dev docker image to the local machine. We can check that the image is in fact on our host by

docker images | awk '/ev3dev/ {printf("%s\t%s\t%s\n",$1,$2,$3)}'
Repository Tag Image Id
ev3dev/debian-stretch-cross latest 8e3b0aa8f243

This container is a fresh new image, what we want is to create an create a writable container based on the above.

The docker create command creates a writeable container layer over the specified image and prepares it for running the specified command

an image is basically a blueprint of what a container should look like, it contains all the necessary binaries,source codes,tools and more. It is immutable (unchangable). A container is using a image as a blueprint to create a specific container. A container can be mutable.

A Docker container is a virtualized run-time environment where users can isolate applications from the underlying system

The above qoute is exactly the kind of thing that we want when building and using the cross-compiler toolchain. We don't want to mess up the host system with arm-binaries. Another benefit is that a docker container already exists, which means we can create a container that basically works out of the box.

To create a docker, the docker create command is used. Here is an example:

docker create -ti -u $(id -u):$(id -g) --name GorbyBuild -v $HOME:/src -w /src ev3dev/debian-stretch-cross /bin/bash
906f0913cefe3575a4a0f1bab49dda7876d4779f993cfeafeb9edfdb7ad74973

We can now check that the container exists.

docker container ls --all | awk '/(CONTAINER|GorbyBuild)/ {print $0}'
CONTAINER ID   IMAGE                         COMMAND       CREATED         STATUS                         PORTS     NAMES
2751b793d5fb   ev3dev/debian-stretch-cross   "/bin/bash"   4 seconds ago   Created                                  GorbyBuild

Now its time to start it up. Since we gave it a name (GorbyBuild), we don't need to remeber the container id.

docker start GorbyBuild
GorbyBuild

And finally we can start working with the GorbyBuild container. I prefer to work with the container executing /bin/bash at the beginning to have a more controlled enviroment. But its possible to just run a single command inside the docker. docker exec will execute a command inside the docker.Here is an example:

docker exec -t GorbyBuild cat /etc/hostname
2751b793d5fb

Maybe not the most creative name for a host….If one wants to have several commands executed like a bash script, its necessary to use the shell command though this requires that the container has some kind of shell installed.

docker exec -it GorbyBuild /bin/bash -c "
cd /etc/
cat os-release | grep PRETTY_NAME | sed 's/.*=//'
"
"Debian GNU/Linux 9 (stretch)"

And since we do have a bash installed we might just use that shell to get inside the container.

> docker exec -t GorbyBuild /bin/bash
compiler@2751b793d5fb:/src$

gorbybuild.gif

since its a debian distribution we can use apt to upgrade the system.

docker exec -it GorbyBuild bash -c "
sudo apt update
sudo apt upgrade
"

Now lets see if we can compile something using the provided arm-linux-gnueabi-gcc from the container.

docker exec -t GorbyBuild /bin/bash -c "
cat <<'EOF' > /tmp/main.c
#include <stdio.h>

int main()
{
    printf(\"Hello Gorby\");
    return 0;
}
EOF

arm-linux-gnueabi-gcc -Wall /tmp/main.c -o /src/hello_gorby
"

file ~/hello_gorby

/home/calle/hello_gorby: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 3.2.0, BuildID[sha1]=4d5671d991215dde587cffce20ee08ae2a22b9f5, not stripped

This example creates a small c-program on the dockers /tmp directory and compiles it inside the docker, then outside it checks the file. And since the my home directory is mounted inside the docker the file is available from my host machine.

This concludes the docker setup.

Installing conan

Conan is a c++ packagemanager based on python. Unfortunatly python is not installed on the ev3dev image and there for not part of our newly created container (GorbyBuild). That needs to be fixed. Its also required to have pip3 install for python package management.

docker exec -t GorbyBuild /bin/bash -c "
sudo apt install python3
sudo apt install python3-pip
"

Now we need to install conan using pip3.

docker exec -t GorbyBuild /bin/bash -c "
python3 -m pip install conan
"

There are one more thing todo before the setup is complete. The container is all setup, but I want to use my editor and my configuration. This can be setup in the docker container but its kind of impractical. I also want to run unit tests, that is not possible in the docker or even on my host (actually it is possible to use qemu but thats outside this article). That means I need to be able to build both for x86_64 and arm and since my host (without using docker) is all setup a build environment (clang,gcc,cmake and so on) there should be no reason to not use that but that requires that i also install conan on my host.

python3 -m pip install conan

This will install conan on the user local directory. So to be able to use conan we need to add the path somehow, we do this by adding the path to the

docker exec -t GorbyBuild /bin/bash -c '
echo "PATH=\$HOME/.local/bin:\$PATH" >> $HOME/.bashrc
echo "PROFILE_DIR=/src/.conan/profiles" >> $HOME/.bashrc
'

Conan software package manager

We start with a small overview how conan works, I already described how to install conan.

Conan is a software package manager which is intended for C and C++ developers.

Conan is universal and portable. It works in all operating systems including Windows, Linux, OSX, FreeBSD, Solaris, and others, and it can target any platform, including desktop, server, and cross-building for embedded and bare metal devices. It integrates with other tools like Docker, MinGW, WSL, and with all build systems such as CMake, MSBuild, Makefiles, Meson, SCons. It can even integrate with any proprietary build systems.

When a package is installed it keeps the source, build and deploy structure in a repository and generates build files with different generators to include in the project. The generators could be for example cmake,make,visual studio,scons,qmake and many more, there is even a markdown generator that generates useful information on each of the requirement i.e: conan-markdown_generator.png It is also possible to create your own package and install that into the local repository. If a generator doesn't exists its easy to create your own. This will just be short overview , I will leave to the reader to gain more information on conan documentation.

A conan recipe is an instruction how to build a package and what dependencies a packages has and is defined by a "conanfile.py" file. A conan package on the other hand is source and pre-compiled binaries for any possible platform and/or configuration. These binaries can be created and uploaded to a server with the same comands in all platforms. Installation of a package from a server is also very efficient. Only the necessary binaries for the current platforma and configuration are downloaded. Its possible to setup its own remote server, but I leave that to some other time and place.

Conan remotes

Remotes are places where conan will search for packages when argument --remote=... is applied. That is remote sites which contains many (or few) conan packages/recipes. One such remote site is conan center where all kinds of different packages can be found. Another is Bincrafters remote repository, both of these remote sites have interface on the web to search for specific packages. But commony though one will use the CLI to search for packages. To add a new remote:

conan remote add Example https://example.com

This will add a remote repository. All repositories are added to the ~/.conan/remotes.json file. To list the remote added repositories:

conan remote list
conan-center: https://conan.bintray.com [Verify SSL: True]
bincrafters: https://api.bintray.com/conan/bincrafters/public-conan [Verify SSL: True]
Example: https://example.com [Verify SSL: True]

Its also possible to edit the json file directly

cat ~/.conan/remotes.json
{
    "remotes": [
        {
            "verify_ssl": true,
            "url": "https://conan.bintray.com",
            "name": "conan-center"
        },
        {
            "verify_ssl": true,
            "url": "https://api.bintray.com/conan/bincrafters/public-conan",
            "name": "bincrafters"
        },
        {
            "verify_ssl": true,
            "url": "https://example.com",
            "name": "Example"
        }
    ]
}

To remove one can either use the CLI or just edit the ~/.conan/remotes.json by hand

conan remote remove Example

These are the most common arguments, there are many other that can be found here.

Conan Search

The search command is used to search for packages on a server or even locally installed packages. It searches for both recipes and binaries. Unless a --remote is specified the local cache is search.

Lets say I want to use fmt in my c++ project. Since I don't have it installed on my local cache I need to search for it remotely. The search pattern can include "*" for to obtain multiple for example:

conan search "fmt/6*" --remote=all
Existing package recipes:

Remote 'bincrafters':
fmt/6.0.0@bincrafters/stable
Remote 'conan-center':
fmt/6.0.0
fmt/6.0.0@bincrafters/stable
fmt/6.1.0
fmt/6.1.1
fmt/6.1.2
fmt/6.2.0
fmt/6.2.1

In this example we used all our remotes (see conan remotes) and searched for the fmt package. We could just used one by using its name in the remote field --remote=conan-center.

I can also inspect a specific package to gain more information.

conan inspect "fmt/6.1.0" --remote=conan-center
Downloading conanmanifest.txt
Downloading conanfile.py
Downloading conan_export.tgz
name: fmt
version: 6.1.0
url: https://github.com/conan-io/conan-center-index
homepage: https://github.com/fmtlib/fmt
license: MIT
author: None
description: A safe and fast alternative to printf and IOStreams.
topics: ('conan', 'fmt', 'format', 'iostream', 'printf')
generators: cmake
exports: None
exports_sources: ['CMakeLists.txt', 'patches/**']
short_paths: False
apply_env: True
build_policy: None
revision_mode: hash
settings: ('os', 'compiler', 'build_type', 'arch')
options:
    fPIC: [True, False]
    header_only: [True, False]
    shared: [True, False]
    with_fmt_alias: [True, False]
default_options:
    fPIC: True
    header_only: False
    shared: False
    with_fmt_alias: False

Here we can see that there are options for using header only and building shared and which default options are used.

You can also check what different binaries exists in the repository, that is , if for example a binary is compiled for different platform or with different setting that is shown by doing.

conan search fmt/6.2.1@ --remote=all
Existing packages for recipe fmt/6.2.1:

Existing recipe in remote 'conan-center':

    Package_ID: 03098e9ca3b3217acdb827caba0356e229ff04a1
        [options]
            header_only: False
            shared: True
        [settings]
            arch: x86_64
            build_type: Release
            compiler: clang
            compiler.libcxx: libc++
            compiler.version: 3.9
            os: Linux
        Outdated from recipe: True

    Package_ID: 0361dffd8f43a76c9aa894d20cb58fd062ae22cc
        [options]
            fPIC: True
            header_only: False
            shared: False
        [settings]
            arch: x86_64
            build_type: Debug
            compiler: clang
            compiler.libcxx: libc++
            compiler.version: 6.0
            os: Linux
        Outdated from recipe: True

    Package_ID: 038baac88f4c7bfa972ce5adac1616bed8fe2ef4
        [options]
            fPIC: True
            header_only: False
            shared: False
        [settings]
            arch: x86_64
            build_type: Debug
            compiler: gcc
            compiler.libcxx: libstdc++11
            compiler.version: 9
            os: Linux
        Outdated from recipe: True

    Package_ID: 038f8796e196b3dba76fcc5fd4ef5d3d9c6866ec
        [options]
            fPIC: True
            header_only: False
            shared: False
        [settings]
            arch: x86_64
            build_type: Debug
            compiler: gcc
            compiler.libcxx: libstdc++11
            compiler.version: 8
            os: Linux
        Outdated from recipe: True

    Package_ID: 21206bd73c3636750497f5d1a65364cc6adec885
        [options]
            header_only: False
            shared: True
        [settings]
            arch: x86_64
            build_type: Debug
            compiler: Visual Studio
            compiler.runtime: MTd
            compiler.version: 16
            os: Windows
        Outdated from recipe: True
    Package_ID: 95b87e2c9261497d05b76244c015fbde06fe50b3
        [options]
            fPIC: True
            header_only: False
            shared: True
        [settings]
            arch: x86_64
            build_type: Release
            compiler: apple-clang
            compiler.libcxx: libc++
            compiler.version: 10.0
            os: Macos
        Outdated from recipe: True

I removed some, since the I believe i counted to 221 different ways and settings that fmt/6.2.1 was compiled.

The search command needs a specific target and a @ sign at the end to retrieve the different binary specifications.

Conan install

Now that we found out what package we want to use, its time to download it locally to our local cache. Firs we need to create a ~conanfile.txt' file, this file includes all the packages that we want, and also options and settings.

 1: cat<<EOF > conanfile.txt
 2: [requires]
 3:  fmt/[>6.0.0 <7.0.0]
 4: 
 5: [build_requires]
 6:  cmake/[>3.17]
 7: 
 8: [options]
 9:  fmt:shared=True
10: 
11: [generators]
12:  cmake
13:  compiler_args
14:  virtualenv
15: 
16: [imports]
17: lib, *.so* -> ./lib
18: include, *.h -> ./include
19: EOF
20: 

Before we continue lets discuss some part of the conanfile.txt

Line 2 [require]
This is the libraries and binaries that we requries, in this case we just needed fmt and we want the version to be \(6.0.0> version < 7.0.0\)
Line 5 [build_requies]
These are tools that are needed, for example in this case we said that cmake is required to be at least of version 3.17.
Line 9 [options]
As we could see in the inspect commands each package possibly has some extra options, these one can be set here. For fmt I decided to work with shared library.
Line 11 [generators]
Are build artifacts that can be used by the build system. for example cmake generator will create a conanbuildinfo.cmake that can be included by cmake files to get the include paths,library paths and link information. We get to that later in this article. The compiler_args generator, creates a file with the content that can be used as compiler arguments (at least for gcc and clang). The last generator: virtualenv is kind of like python virtualenv where you can activate it, and it will sets enviroment variables to be able to use ie. cmake binary.
Line 16 [imports]
List of files to be imported to a local directory. It kind of states what to copy in to a local file directory, if one to use .eg LD_LIBRARY_PATH or specifying the include path. In the above example all "*.so" files are copied to the ./lib directory and all the headers to ./include which we will see later.

Now that we created the file lets uses conan install to install the package.

mkdir install ; cd install
conan install .. --build=fmt --build=openssl

The generated files will be located in the directory in which the conan install is executed. Here are the outputs of that directory

.
├── activate.ps1
├── activate.sh
├── conanbuildinfo.args
├── conanbuildinfo.cmake
├── conanbuildinfo.txt
├── conan_imports_manifest.txt
├── conaninfo.txt
├── conan.lock
├── deactivate.ps1
├── deactivate.sh
├── environment.ps1.env
├── environment.sh.env
├── graph_info.json
├── include
│   ├── fmt
│   └── openssl
└── lib
    ├── libfmt.so -> libfmt.so.6
    ├── libfmt.so.6 -> libfmt.so.6.2.1
    └── libfmt.so.6.2.1

4 directories, 16 files

I omitted the header files to keep the list smaller.

I used a version range for the fmt and for some reason it used 6.1.2, which is somewhere in between the ranges that I specified , one can wonder why 6.2.1 wouldn't be a better fit? Anyhow its possible to specify explicitly what version to use. In this case we also provided the --build=fmt to actually build the package. This due to that there were no binaries on the server that matched my build options and settings, so we had to build it locally so the binaries would be added to the local cache. On the other hand , the next time we need the same requirements it will check the local cache, where it would find the binaries and therefor no build has to be executed.

Conan new/create

Now that we got this far we could start using conan, but before doing that there is one more thing that I want to explain. Sometimes (as in this example) there might be libraries which are not part of conan remote repository (or local for that matter). It would be nice if we could create our own package, either just for local use or to be added into the remote resository. For this we need to use conan create on a python file which states how to download, configure,build and sometimes deploy a package. You can real all about it in the conan getting started.

For this article I will use e3dev-cpp to create a package for local use. I will skip the part of using a test, but if one wants to upload a reciepe to a remote server I guess that is kind of necessary.

As mentioned earlier to be able to create a package we need a python file conanfile.py, doing this from scratch would most definitely make some/most developers throw in the towel, so conan has a command to make a new template conanfile.py.

conan new ev3dev/latest
File saved: conanfile.py

This file needs to be edited, but most of the stuff is already at hand.

The diff will show the minimum effort that was needed to create a new package.

7,11c7,11
<     license = "MIT"
<     author = "Carl Olsen <olsen.carl@g****.com>"
<     url = "https://github.com/ddemidov/ev3dev-lang-cpp.git"
<     description = "Ev3dev cpp library for sensors and motors"
<     topics = ("ev3", "mindstorm", "sensors", "motors")
---
>     license = "<Put the package license here>"
>     author = "<Put your name here> <And your email here>"
>     url = "<Package recipe repository url here, for issues about the package>"
>     description = "<Description of Ev3dev here>"
>     topics = ("<Put some tag here>", "<here>", "<and here>")
14a15
>     generators = "cmake"
21c22,29
<         self.run("git clone https://github.com/ddemidov/ev3dev-lang-cpp.git")
---
>         self.run("git clone https://github.com/conan-io/hello.git")
>         # This small hack might be useful to guarantee proper /MT /MD linkage
>         # in MSVC if the packaged project doesn't have variables to set it
>         # properly
>         tools.replace_in_file("hello/CMakeLists.txt", "PROJECT(HelloWorld)",
>                               '''PROJECT(HelloWorld)
> include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
> conan_basic_setup()''')
25c33
<         cmake.configure(source_folder="ev3dev-lang-cpp")
---
>         cmake.configure(source_folder="hello")
34,35c42,43
<         self.copy("*.h", dst="include", src="ev3dev-lang-cpp")
<         self.copy("*.a", dst="lib", keep_path=False)
---
>         self.copy("*.h", dst="include", src="hello")
>         self.copy("*hello.lib", dst="lib", keep_path=False)
40,42d47
<         # Copy the demo too
<         self.copy("*", src="bin", dst="bin")
<
45c50
<         self.cpp_info.libs = ["ev3dev"]
---
>         self.cpp_info.libs = ["hello"]
47,48d51
<     def deploy(self):
<         self.copy("*",src="bin",dst="bin")

Now that we edited the python file we can use conan create to create the ev3dev-cpp package into our local cache. But before that lets explain a bit about user/channel.

We have seen from our searches that packages are named package/version. There is however two more attributes that could be added to the package. One can see them as a fork of a existing package, or as a git branch.

user
A user
channel
It’s a completely arbitrary string meaning that authors can put whatever they want
conan create . cool/beta
Exporting package recipe
ev3dev/latest@cool/beta: A new conanfile.py version was exported
ev3dev/latest@cool/beta: Folder: /home/calle/.conan/data/ev3dev/latest/cool/beta/export
ev3dev/latest@cool/beta: Exported revision: bca5ddeaed80faa66f4bc5f97258b11f
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++
compiler.version=10
os=Linux
os_build=Linux
[options]
[build_requires]
[env]

ev3dev/latest@cool/beta: Forced build from source
Installing package: ev3dev/latest@cool/beta
Requirements
    ev3dev/latest@cool/beta from local cache - Cache
Packages
    ev3dev/latest@cool/beta:82ef5eac51c38971dea2fd342dd55ddf2ddfbbc3 - Build

Installing (downloading, building) binaries...
ev3dev/latest@cool/beta: Configuring sources in /home/calle/.conan/data/ev3dev/latest/cool/beta/source
ev3dev/latest@cool/beta: Copying sources to build folder
ev3dev/latest@cool/beta: Building your package in /home/calle/.conan/data/ev3dev/latest/cool/beta/build/82ef5eac51c38971dea2fd342dd55ddf2ddfbbc3
ev3dev/latest@cool/beta: Generator txt created conanbuildinfo.txt
ev3dev/latest@cool/beta: Calling build()
-- The C compiler identification is GNU 10.2.0
-- The CXX compiler identification is GNU 10.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/calle/.conan/data/ev3dev/latest/cool/beta/build/82ef5eac51c38971dea2fd342dd55ddf2ddfbbc3
Scanning dependencies of target api_tests
Scanning dependencies of target ev3dev
[  5%] Building CXX object CMakeFiles/ev3dev.dir/ev3dev.cpp.o
[ 10%] Building CXX object tests/CMakeFiles/api_tests.dir/api_tests.cpp.o
[ 15%] Building CXX object tests/CMakeFiles/api_tests.dir/__/ev3dev.cpp.o
[ 21%] Linking CXX static library libev3dev.a
[ 21%] Built target ev3dev
Scanning dependencies of target ev3dev-lang-test
Scanning dependencies of target button-test
Scanning dependencies of target sound-test
Scanning dependencies of target ev3dev-lang-demo
Scanning dependencies of target remote-control
Scanning dependencies of target drive-test
Scanning dependencies of target remote_control-test
[ 26%] Building CXX object demos/CMakeFiles/ev3dev-lang-test.dir/ev3dev-lang-test.cpp.o
[ 31%] Building CXX object demos/CMakeFiles/sound-test.dir/sound-test.cpp.o
[ 36%] Building CXX object demos/CMakeFiles/button-test.dir/button-test.cpp.o
[ 42%] Building CXX object demos/CMakeFiles/ev3dev-lang-demo.dir/ev3dev-lang-demo.cpp.o
[ 47%] Building CXX object demos/CMakeFiles/drive-test.dir/drive-test.cpp.o
[ 52%] Building CXX object demos/CMakeFiles/remote_control-test.dir/remote_control-test.cpp.o
[ 57%] Building CXX object demos/CMakeFiles/remote-control.dir/remote-control.cpp.o
[ 63%] Linking CXX executable button-test
[ 68%] Linking CXX executable remote_control-test
[ 68%] Built target button-test
[ 68%] Built target remote_control-test
[ 73%] Linking CXX executable sound-test
[ 78%] Linking CXX executable remote-control
[ 78%] Built target sound-test
[ 78%] Built target remote-control
[ 84%] Linking CXX executable drive-test
[ 84%] Built target drive-test
[ 89%] Linking CXX executable ev3dev-lang-test
[ 89%] Built target ev3dev-lang-test
[ 94%] Linking CXX executable ev3dev-lang-demo
[ 94%] Built target ev3dev-lang-demo
[100%] Linking CXX executable api_tests
[100%] Built target api_tests
ev3dev/latest@cool/beta: Package '82ef5eac51c38971dea2fd342dd55ddf2ddfbbc3' built
ev3dev/latest@cool/beta: Build folder /home/calle/.conan/data/ev3dev/latest/cool/beta/build/82ef5eac51c38971dea2fd342dd55ddf2ddfbbc3
ev3dev/latest@cool/beta: Generated conaninfo.txt
ev3dev/latest@cool/beta: Generated conanbuildinfo.txt
ev3dev/latest@cool/beta: Generating the package
ev3dev/latest@cool/beta: Package folder /home/calle/.conan/data/ev3dev/latest/cool/beta/package/82ef5eac51c38971dea2fd342dd55ddf2ddfbbc3
ev3dev/latest@cool/beta: Calling package()
ev3dev/latest@cool/beta package(): Packaged 1 '.h' file: ev3dev.h
ev3dev/latest@cool/beta package(): Packaged 1 '.a' file: libev3dev.a
ev3dev/latest@cool/beta: Package '82ef5eac51c38971dea2fd342dd55ddf2ddfbbc3' created
ev3dev/latest@cool/beta: Created package revision dde36583e10cd07371f9eb1f1dbe6217

Its possible to omit user and channel, in that case it will only show up as ev3dev/latest and if we add the user and channel, it will become ev3dev/latest@cool/beta. This could be used for example if we had different branches that we want to add , or if for example we have a beta, and then later when done testing we want it to become stable (see conan copy).

One way that I use it to make a copy of an already existing release Lets say fmt, and then change some things in the package, for example adding some options.

I can now search in my local cache for .eg user name.

conan search "*ev3dev*cool*"
Existing package recipes:

ev3dev/latest@cool/beta

Obviously im just scratching the surface here . More information can be found here

Conan profiles

So far we have the packages installed and we it seems as if we have all the necessary toolchains setup. But somehow we need to tell conan which compiler and architecture we like to build for.

This can be done through conan profile. Usually there already exists a default profile , where the system default compiler and libraries are used. But what happens if we want to use something else? The profiles are located in ~/.conan/profiles directory. In my case there is a default profile.

cat default
[settings]
os=Linux
os_build=Linux
arch=x86_64
arch_build=x86_64
compiler=gcc
compiler.version=10
compiler.libcxx=libstdc++
build_type=Release
[options]
[build_requires]
[env]

Here we can see all the default used compiler and versions.

But what happens if the default is lost? No worries, its easy to create a new.

conan profile new myowndefault --detect
Found gcc 10.2
Found clang 11.0
gcc>=5, using the major as version

************************* WARNING: GCC OLD ABI COMPATIBILITY ***********************

Conan detected a GCC version > 5 but has adjusted the 'compiler.libcxx' setting to
'libstdc++' for backwards compatibility.
Your compiler is likely using the new CXX11 ABI by default (libstdc++11).

If you want Conan to use the new ABI for the myowndefault profile, run:

    $ conan profile update settings.compiler.libcxx=libstdc++11 myowndefault

Or edit '/home/calle/.conan/profiles/myowndefault' and set compiler.libcxx=libstdc++11

************************************************************************************



Profile created with detected settings: /home/calle/.conan/profiles/myowndefault

Ohh, and it gave us a hint that it used libstdc++11 which is a newer ABI. So lets change that.

sed -i 's/libstdc++/libstdc++11/' myowndefault

Nice and smooth.

Lets get our hands a bit dirtier. Lets create a new profile which uses clang compiler instead. Its basically the same , but switching out the compiler and version.

conan profile new /tmp/profiles/clang --detect
sed -i 's/libstdc++/libstdc++11/' clang
sed -i 's/gcc/clang/' clang

CLANG_VERSION=$(clang --version  | gawk 'match($0,/clang\s+version\s+([0-9]+).(.*)/,a ) { print a[1]}')
sed -i "s/compiler.version=10/compiler.version=${CLANG_VERSION}/" clang

sed -i '/\[env\]/a CXX=/usr/bin/clang++' clang
sed -i '/\[env\]/a CC=/usr/bin/clang' clang
[settings]
os=Linux
os_build=Linux
arch=x86_64
arch_build=x86_64
compiler=clang
compiler.version=11
compiler.libcxx=libstdc++11
build_type=Release
[options]
[build_requires]
[env]
CC=/usr/bin/clang
CXX=/usr/bin/clang++

The profiles don't need to be placed in the default directory, but if one wants to use it it needs to be specified with path.

So lets use the ev3dev package that we created before, but this time we the clang profile.

conan create . cool/clang --profile=/tmp/profiles/clang
Exporting package recipe
ev3dev/latest@cool/clang: A new conanfile.py version was exported
ev3dev/latest@cool/clang: Folder: /home/calle/.conan/data/ev3dev/latest/cool/clang/export
ev3dev/latest@cool/clang: Exported revision: bca5ddeaed80faa66f4bc5f97258b11f
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=clang
compiler.libcxx=libstdc++11
compiler.version=11
os=Linux
os_build=Linux
[options]
[build_requires]
[env]
CC=/usr/bin/clang
CXX=/usr/bin/clang++
ev3dev/latest@cool/clang: Forced build from source
Installing package: ev3dev/latest@cool/clang
Requirements
    ev3dev/latest@cool/clang from local cache - Cache
Packages
    ev3dev/latest@cool/clang:0ac8c9952b8651eefe078adcd928017cea3e0340 - Build

Installing (downloading, building) binaries...
ev3dev/latest@cool/clang: Configuring sources in /home/calle/.conan/data/ev3dev/latest/cool/clang/source
ev3dev/latest@cool/clang: Copying sources to build folder
ev3dev/latest@cool/clang: Building your package in /home/calle/.conan/data/ev3dev/latest/cool/clang/build/0ac8c9952b8651eefe078adcd928017cea3e0340
ev3dev/latest@cool/clang: Generator txt created conanbuildinfo.txt
ev3dev/latest@cool/clang: Calling build()
-- The C compiler identification is Clang 11.0.0
-- The CXX compiler identification is Clang 11.0.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/clang++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/calle/.conan/data/ev3dev/latest/cool/clang/build/0ac8c9952b8651eefe078adcd928017cea3e0340
Scanning dependencies of target ev3dev
Scanning dependencies of target api_tests
[  5%] Building CXX object tests/CMakeFiles/api_tests.dir/api_tests.cpp.o
[ 10%] Building CXX object CMakeFiles/ev3dev.dir/ev3dev.cpp.o
[ 15%] Building CXX object tests/CMakeFiles/api_tests.dir/__/ev3dev.cpp.o
[ 21%] Linking CXX static library libev3dev.a
[ 21%] Built target ev3dev
Scanning dependencies of target sound-test
Scanning dependencies of target ev3dev-lang-demo
Scanning dependencies of target ev3dev-lang-test
Scanning dependencies of target button-test
Scanning dependencies of target drive-test
Scanning dependencies of target remote_control-test
Scanning dependencies of target remote-control
[ 26%] Building CXX object demos/CMakeFiles/sound-test.dir/sound-test.cpp.o
[ 31%] Building CXX object demos/CMakeFiles/ev3dev-lang-demo.dir/ev3dev-lang-demo.cpp.o
[ 42%] Building CXX object demos/CMakeFiles/ev3dev-lang-test.dir/ev3dev-lang-test.cpp.o
[ 42%] Building CXX object demos/CMakeFiles/button-test.dir/button-test.cpp.o
[ 47%] Building CXX object demos/CMakeFiles/remote-control.dir/remote-control.cpp.o
[ 57%] Building CXX object demos/CMakeFiles/remote_control-test.dir/remote_control-test.cpp.o
[ 57%] Building CXX object demos/CMakeFiles/drive-test.dir/drive-test.cpp.o
[ 63%] Linking CXX executable button-test
[ 68%] Linking CXX executable remote_control-test
[ 68%] Built target button-test
[ 68%] Built target remote_control-test
[ 73%] Linking CXX executable sound-test
[ 78%] Linking CXX executable remote-control
[ 78%] Built target sound-test
[ 78%] Built target remote-control
[ 84%] Linking CXX executable drive-test
[ 84%] Built target drive-test
[ 89%] Linking CXX executable ev3dev-lang-test
[ 89%] Built target ev3dev-lang-test
[ 94%] Linking CXX executable ev3dev-lang-demo
[ 94%] Built target ev3dev-lang-demo
[100%] Linking CXX executable api_tests
[100%] Built target api_tests
ev3dev/latest@cool/clang: Package '0ac8c9952b8651eefe078adcd928017cea3e0340' built
ev3dev/latest@cool/clang: Build folder /home/calle/.conan/data/ev3dev/latest/cool/clang/build/0ac8c9952b8651eefe078adcd928017cea3e0340
ev3dev/latest@cool/clang: Generated conaninfo.txt
ev3dev/latest@cool/clang: Generated conanbuildinfo.txt
ev3dev/latest@cool/clang: Generating the package
ev3dev/latest@cool/clang: Package folder /home/calle/.conan/data/ev3dev/latest/cool/clang/package/0ac8c9952b8651eefe078adcd928017cea3e0340
ev3dev/latest@cool/clang: Calling package()
ev3dev/latest@cool/clang package(): Packaged 1 '.h' file: ev3dev.h
ev3dev/latest@cool/clang package(): Packaged 1 '.a' file: libev3dev.a
ev3dev/latest@cool/clang: Package '0ac8c9952b8651eefe078adcd928017cea3e0340' created
ev3dev/latest@cool/clang: Created package revision 9cf0c58ac490a016464a9067481d81d8

Voilá! We have two packages built from the same recipe but built with different compilers.

conan search "ev3dev*cool"
Existing package recipes:

ev3dev/latest@cool/beta
ev3dev/latest@cool/clang

profiles can be used in many of the conan commands.

  • create
  • new
  • install

. .

profiles also supports package settings and enviroment variables There is even possibility to make profiles that compiles only some libs with clang and others with gcc. I leave that to some other time.

This is an important ingredients when we start cross-compiling. Lets not get ahead of ourself, we still need to compile using conan on our host machine before fiddling with cross-compiling.

Lets put it all together.

Finally we got to a point where we can put everything together. Just to make sure that the concept works. Lets first create the cmake file and a main.cpp.

 1: cat <<'EOF' > CMakeLists.txt
 2: cmake_minimum_required(VERSION 3.17)
 3: project ( first_test_prj )
 4: 
 5: set(CMAKE_CXX_STANDARD 17)
 6: set(CMAKE_CXX_STANDARD_REQUIRED ON)
 7: set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic")
 8: 
 9: ###########
10: # Include generated conan cmake file
11: ###########
12: include(${CMAKE_SOURCE_DIR}/conan/conanbuildinfo.cmake)
13: conan_basic_setup()
14: 
15: 
16: ###########
17: # Add exectutable
18: ###########
19: add_executable( ${PROJECT_NAME} main.cpp )
20: 
21: 
22: target_include_directories(
23:   ${PROJECT_NAME} PRIVATE
24:   ${CONAN_INCLUDE_DIRS_FMT}
25:   )
26: 
27: ###########
28: # Sources that compiles
29: ###########
30: set( SRC main.cpp)
31: 
32: 
33: ###########
34: # Library linking
35: ###########
36: target_link_libraries( ${PROJECT_NAME}
37: PRIVATE
38:  ${CONAN_LIBS}
39: )
40: EOF
41: 
42: cat <<EOF > main.cpp
43: #include <string>
44: #include <iostream>
45: #include <fmt/format.h>
46: 
47: int main(int argc, char *argv[])
48: {
49:   fmt::print(" Fmt is online \n");
50:   std::cout << "Hello World" << '\n';
51:   return 0;
52: }
53: EOF

The cmake file includes a generated conan file (see line 12) in the ./conan, where the conan generated file will be located. It also needs to call the conan_basic_setup() (see line 13) which will set the variable which are used to include right directory and get the linking right.

cat<<'EOF' > conanfile.txt
[requires]
 fmt/[>6.0.0 <7.0.0]

[build_requires]
 cmake/[>3.17]

[options]
 fmt:shared=True

[generators]
 cmake
 compiler_args
 virtualenv

EOF

To generate the file we need to invoke the conan install (see Conan install). We do that from the directory which we used to include the generated cmake file.

mkdir -p conan ; cd conan
conan install ..
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=10
os=Linux
os_build=Linux
[options]
[build_requires]
[env]

Version ranges solved
    Version range '>6.0.0 <7.0.0' required by 'conanfile.txt' resolved to 'fmt/6.2.1' in local cache
    Version range '>3.17' required by 'conanfile.txt' resolved to 'cmake/3.19.2' in local cache

conanfile.txt: Installing package
Requirements
    fmt/6.2.1 from 'conan-center' - Cache
Packages
    fmt/6.2.1:79f200efe18f84353bb1264b3eeee74874c3dd69 - Download
Build requirements
    cmake/3.19.2 from 'conan-center' - Cache
    openssl/1.1.1i from 'conan-center' - Cache
Build requirements packages
    cmake/3.19.2:5c09c752508b674ca5cb1f2d327b5a2d582866c8 - Cache
    openssl/1.1.1i:19729b9559f3ae196cad45cb2b97468ccb75dcd1 - Cache

Installing (downloading, building) binaries...
fmt/6.2.1: Retrieving package 79f200efe18f84353bb1264b3eeee74874c3dd69 from remote 'conan-center'
Downloading conanmanifest.txt
Downloading conaninfo.txt
Downloading conan_package.tgz
fmt/6.2.1: Package installed 79f200efe18f84353bb1264b3eeee74874c3dd69
fmt/6.2.1: Downloaded package revision 0
openssl/1.1.1i: Already installed!
cmake/3.19.2: Already installed!
cmake/3.19.2: Appending PATH environment variable: /home/calle/.conan/data/cmake/3.19.2/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin
conanfile.txt: Applying build-requirement: cmake/3.19.2
conanfile.txt: Applying build-requirement: openssl/1.1.1i
conanfile.txt: Generator cmake created conanbuildinfo.cmake
conanfile.txt: Generator compiler_args created conanbuildinfo.args
conanfile.txt: Generator virtualenv created activate.ps1
conanfile.txt: Generator virtualenv created deactivate.ps1
conanfile.txt: Generator virtualenv created environment.ps1.env
conanfile.txt: Generator virtualenv created activate.sh
conanfile.txt: Generator virtualenv created deactivate.sh
conanfile.txt: Generator virtualenv created environment.sh.env
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo

It all seems great at this point. Now lets see if it compiles, but before we compile it we need to activate the virtualenv that was generated in order to be able to use the cmake binary in our local cache.

source conan/activate.sh
which cmake
mkdir build ; cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
/home/calle/.conan/data/cmake/3.19.2/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake
-- The C compiler identification is GNU 10.2.0
-- The CXX compiler identification is GNU 10.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Conan: Adjusting output directories
-- Conan: Using cmake global configuration
-- Conan: Adjusting default RPATHs Conan policies
-- Conan: Adjusting language standard
-- Current conanbuildinfo.cmake directory: /tmp/proj/conan
-- Conan: Compiler GCC>=5, checking major version 10
-- Conan: Checking correct version: 10
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/proj/build

And finally, build…

cd build
make -j8 VERBOSE=1
/home/calle/.conan/data/cmake/3.19.2/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -S/tmp/proj -B/tmp/proj/build --check-build-system CMakeFiles/Makefile.cmake 0
/home/calle/.conan/data/cmake/3.19.2/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_progress_start /tmp/proj/build/CMakeFiles /tmp/proj/build//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/tmp/proj/build'
make  -f CMakeFiles/first_test_prj.dir/build.make CMakeFiles/first_test_prj.dir/depend
make[2]: Entering directory '/tmp/proj/build'
cd /tmp/proj/build && /home/calle/.conan/data/cmake/3.19.2/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_depends "Unix Makefiles" /tmp/proj /tmp/proj /tmp/proj/build /tmp/proj/build /tmp/proj/build/CMakeFiles/first_test_prj.dir/DependInfo.cmake --color=
Dependee "/tmp/proj/build/CMakeFiles/first_test_prj.dir/DependInfo.cmake" is newer than depender "/tmp/proj/build/CMakeFiles/first_test_prj.dir/depend.internal".
Dependee "/tmp/proj/build/CMakeFiles/CMakeDirectoryInformation.cmake" is newer than depender "/tmp/proj/build/CMakeFiles/first_test_prj.dir/depend.internal".
Scanning dependencies of target first_test_prj
make[2]: Leaving directory '/tmp/proj/build'
make  -f CMakeFiles/first_test_prj.dir/build.make CMakeFiles/first_test_prj.dir/build
make[2]: Entering directory '/tmp/proj/build'
[ 50%] Building CXX object CMakeFiles/first_test_prj.dir/main.cpp.o
/usr/bin/c++  -I/home/calle/.conan/data/fmt/6.2.1/_/_/package/79f200efe18f84353bb1264b3eeee74874c3dd69/include -I/home/calle/.conan/data/openssl/1.1.1i/_/_/package/19729b9559f3ae196cad45cb2b97468ccb75dcd1/include -Wall -Wextra -pedantic   -g  -DFMT_SHARED -std=gnu++17 -o CMakeFiles/first_test_prj.dir/main.cpp.o -c /tmp/proj/main.cpp
[100%] Linking CXX executable bin/first_test_prj
/home/calle/.conan/data/cmake/3.19.2/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_link_script CMakeFiles/first_test_prj.dir/link.txt --verbose=1
/usr/bin/c++ -Wall -Wextra -pedantic   -g  CMakeFiles/first_test_prj.dir/main.cpp.o -o bin/first_test_prj   -L/home/calle/.conan/data/fmt/6.2.1/_/_/package/79f200efe18f84353bb1264b3eeee74874c3dd69/lib  -L/home/calle/.conan/data/openssl/1.1.1i/_/_/package/19729b9559f3ae196cad45cb2b97468ccb75dcd1/lib  -Wl,-rpath,/home/calle/.conan/data/fmt/6.2.1/_/_/package/79f200efe18f84353bb1264b3eeee74874c3dd69/lib:/home/calle/.conan/data/openssl/1.1.1i/_/_/package/19729b9559f3ae196cad45cb2b97468ccb75dcd1/lib -lfmt -lssl -lcrypto -ldl -lpthread
make[2]: Leaving directory '/tmp/proj/build'
[100%] Built target first_test_prj
make[1]: Leaving directory '/tmp/proj/build'
/home/calle/.conan/data/cmake/3.19.2/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_progress_start /tmp/proj/build/CMakeFiles 0

Voilá!! It worked! Only one thing left, run it..

./build/bin/first_test_prj
 Fmt is online
Hello World

Success!!

Using clang profile

One last final thing. We want to be able to build with clang. We have all the structure already, so its just a matter of direcories and maybe a small change in the CMakeLists.txt

Lets first create a new conan directory in which the generators can add their files, and lets generate with the profile that we created in conan profile section.

mkdir -p conan_clang ; cd conan_clang
conan install .. --profile=/tmp/profiles/clang
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=clang
compiler.libcxx=libstdc++11
compiler.version=10
os=Linux
os_build=Linux
[options]
[build_requires]
[env]
CC=/usr/bin/clang
CXX=/usr/bin/clang++
Version ranges solved
    Version range '>6.0.0 <7.0.0' required by 'conanfile.txt' resolved to 'fmt/6.2.1' in local cache
    Version range '>3.17' required by 'conanfile.txt' resolved to 'cmake/3.19.2' in local cache

conanfile.txt: Installing package
Requirements
    fmt/6.2.1 from 'conan-center' - Cache
Packages
    fmt/6.2.1:96a938e29ffc183f7ba57a5ffbd23312a4caffdf - Cache
Build requirements
    cmake/3.19.2 from 'conan-center' - Cache
    openssl/1.1.1i from 'conan-center' - Cache
Build requirements packages
    cmake/3.19.2:5c09c752508b674ca5cb1f2d327b5a2d582866c8 - Cache
    openssl/1.1.1i:2b1e5ff9df96aaf5924c273e1368c632fcb32dd2 - Cache

Installing (downloading, building) binaries...
fmt/6.2.1: Already installed!
openssl/1.1.1i: Already installed!
cmake/3.19.2: Already installed!
cmake/3.19.2: Appending PATH environment variable: /home/s0001195/.conan/data/cmake/3.19.2/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin
conanfile.txt: Applying build-requirement: cmake/3.19.2
conanfile.txt: Applying build-requirement: openssl/1.1.1i
conanfile.txt: Generator cmake created conanbuildinfo.cmake
conanfile.txt: Generator virtualenv created activate.ps1
conanfile.txt: Generator virtualenv created deactivate.ps1
conanfile.txt: Generator virtualenv created environment.ps1.env
conanfile.txt: Generator virtualenv created activate.sh
conanfile.txt: Generator virtualenv created deactivate.sh
conanfile.txt: Generator virtualenv created environment.sh.env
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generator compiler_args created conanbuildinfo.args
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo

This time we built fmt using our clang profile. Let use the produced files and build the project. Though we actually need to do a small change the ./conan directory which is used by the CMakeLists.txt (see line 12 ). Lets do it real simple, and just move the conan directory to conan_gcc and then we can use a link to what ever we like to use.

mv conan conan_gcc
ln -s conan_clang conan
source conan/activate.sh
mkdir -p build_clang ; cd build_clang
export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
cmake .. -DCMAKE_BUILD_TYPE=Debug
-- The C compiler identification is Clang 10.0.0
-- The CXX compiler identification is Clang 10.0.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/clang++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Conan: Adjusting output directories
-- Conan: Using cmake global configuration
-- Conan: Adjusting default RPATHs Conan policies
-- Conan: Adjusting language standard
-- Current conanbuildinfo.cmake directory: /tmp/proj/conan
-- Conan: Compiler Clang>=8, checking major version 10
-- Conan: Checking correct version: 10
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/proj/build_clang

And finally we can build the project.

make -j8
./bin/first_test_prj
Scanning dependencies of target first_test_prj
[ 50%] Building CXX object CMakeFiles/first_test_prj.dir/main.cpp.o
[100%] Linking CXX executable bin/first_test_prj
[100%] Built target first_test_prj
 Fmt is online
Hello World

Voilá! Success!! Again, this time with clang.

Lets just compare them

I will not dive into a deep discussion on this. I'll just compare the sizes and leave it at that.

ldd build_gcc/bin/first_test_prj > /tmp/gcc.ldd
ldd build_clang/bin/first_test_prj > /tmp/clang.ldd
diff -dwy /tmp/gcc.ldd /tmp/clang.ldd
echo
linux-vdso.so.1 (0x00007fffbd7e9000)                  |		linux-vdso.so.1 (0x00007ffeec7c0000)
libfmt.so.6 => /home/s0001195/.conan/data/fmt/6.2.1/_ |		libfmt.so.6 => /home/s0001195/.conan/data/fmt/6.2.1/_
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.s |		libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x000
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so. |		libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.s
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007 |		libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1  |		libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007
/lib64/ld-linux-x86-64.so.2 (0x00007f239ea7f000)      |		libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007 |		libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007
                                                      >		/lib64/ld-linux-x86-64.so.2 (0x00007feb2eab5000)

There is one more dependency using clang ld.

regarding size:

size file
181K build_gcc/bin/first_test_prj
50K build_clang/bin/first_test_prj

Probably need to strip the binary to get a more conclusive view.

size file
15K build_gcc/bin/first_test_prj
15K build_clang/bin/first_test_prj

Yep, pretty much the same. I'll just leave it at that, I trust both of the compilers to do their work, and basically have the same output.

Cross-compiling

Now we are getting close. The docker is installed, we know how to use conan (at least good enough) we have Gorby installed ready for action, and for that we have ev3dev-cpp recipe.

In the docker there are three different compiler toolchains setup. The first and native compiler which is the gcc, we want be using that since we already have newer gcc on our host. On the other hand the docker contains two other toolchains arm-linux-gnueabi-* and arm-linux-gnueabi, arm-linux-gnueabihf. The difference seems to be that the floating point, which we know should be soft on Gorby.

gcc-arm-linux-gnueabi
is the cross-toolchain package for the armel

architecture. This toolchain implies the EABI generated by gcc's -mfloat-abi=soft or -mfloat-abi=softfp options.

gcc-arm-linux-gnueabihf
is the cross-toolchain package for the armhf

architecture. This toolchain implies the EABI generated by the gcc -mfloat-abi=hard option.

There is also another toolchain, though not installed which is called gcc-arm-linux-eabi . There is a small but significant difference. The former (gnuabi) assumes that glibc which is part of linux is installed while the eabi is bare metal is using different c library or not even that.. Anyhow, in this case we will only use gnuabi since we are programming linux and as described earlier section gorby does not have an fpu which means arm-linux-gnueabi will be used.

lets see what kind of armel packages that are installed

 docker exec -t GorbyBuild /bin/bash -c "
    dpkg -l | awk '/armel/ {print \$2,\$3}'
"
cpp-arm-linux-gnueabi 4:6.3.0-4
crossbuild-essential-armel 12.3
g++-arm-linux-gnueabi 4:6.3.0-4
gcc-arm-linux-gnueabi 4:6.3.0-4
libasan3-armel-cross 6.3.0-18cross1
libatomic1-armel-cross 6.3.0-18cross1
libc6-armel-cross 2.24-10cross1
libc6-dev-armel-cross 2.24-10cross1
libgcc-6-dev-armel-cross 6.3.0-18cross1
libgcc1-armel-cross 1:6.3.0-18cross1
libgomp1-armel-cross 6.3.0-18cross1
libstdc++-6-dev-armel-cross 6.3.0-18cross1
libstdc++6-armel-cross 6.3.0-18cross1
libubsan0-armel-cross 6.3.0-18cross1
linux-libc-dev-armel-cross 4.9.25-1cross1

a closer examination of one of the packages (g++-arm-linux-gnueabi) reveals that the compiler is installed in:

docker exec -t GorbyBuild /bin/bash -c "
    dpkg -L g++-arm-linux-gnueabi | grep -v "/usr/share"
"
/.
/usr
/usr/bin
/usr/bin/arm-linux-gnueabi-g++

And the libraries are installed in:

docker exec -t GorbyBuild /bin/bash -c "
dirname \$(dpkg -L libc6-armel-cross | grep -v "/usr/share") | \
awk '{dirs[\$0]++} END{ for(i in dirs){print i} }'
"

/usr/arm-linux-gnueabi/lib
/usr
/
/usr/arm-linux-gnueabi

So lets check out the compiler a bit more..

docker exec -t GorbyBuild /bin/bash -c "
 arm-linux-gnueabi-g++ -v -std=c++14 -xc++ -fsyntax-only /dev/null
 "

Using built-in specs.
COLLECT_GCC=arm-linux-gnueabi-g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc-cross/arm-linux-gnueabi/6/lto-wrapper
Target: arm-linux-gnueabi
Configured with: ../src/configure -v --with-pkgversion='Debian 6.3.0-18' --with-bugurl=file:///usr/share/doc/gcc-6/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-6 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libitm --disable-libquadmath --enable-plugin --enable-default-pie --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-6-armel-cross/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-6-armel-cross --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-6-armel-cross --with-arch-directory=arm --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libgcj --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-sjlj-exceptions --with-arch=armv4t --with-float=soft --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=arm-linux-gnueabi --program-prefix=arm-linux-gnueabi- --includedir=/usr/arm-linux-gnueabi/include
Thread model: posix
gcc version 6.3.0 20170516 (Debian 6.3.0-18) 
COLLECT_GCC_OPTIONS='-v' '-std=c++14' '-fsyntax-only' '-shared-libgcc' '-march=armv4t' '-mfloat-abi=soft' '-mtls-dialect=gnu'
 /usr/lib/gcc-cross/arm-linux-gnueabi/6/cc1plus -quiet -v -imultilib . -imultiarch arm-linux-gnueabi -D_GNU_SOURCE /dev/null -quiet -dumpbase null -march=armv4t -mfloat-abi=soft -mtls-dialect=gnu -auxbase null -std=c++14 -version -fsyntax-only -o /dev/null
GNU C++14 (Debian 6.3.0-18) version 6.3.0 20170516 (arm-linux-gnueabi)
        compiled by GNU C version 6.3.0 20170516, GMP version 6.1.2, MPFR version 3.1.5, MPC version 1.0.3, isl version 0.15
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/arm-linux-gnueabi"
ignoring nonexistent directory "/usr/include/arm-linux-gnueabi"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc-cross/arm-linux-gnueabi/6/../../../../arm-linux-gnueabi/include/c++/6
 /usr/lib/gcc-cross/arm-linux-gnueabi/6/../../../../arm-linux-gnueabi/include/c++/6/arm-linux-gnueabi/.
 /usr/lib/gcc-cross/arm-linux-gnueabi/6/../../../../arm-linux-gnueabi/include/c++/6/backward
 /usr/lib/gcc-cross/arm-linux-gnueabi/6/include
 /usr/lib/gcc-cross/arm-linux-gnueabi/6/include-fixed
 /usr/lib/gcc-cross/arm-linux-gnueabi/6/../../../../arm-linux-gnueabi/include
 /usr/include
End of search list.
GNU C++14 (Debian 6.3.0-18) version 6.3.0 20170516 (arm-linux-gnueabi)
        compiled by GNU C version 6.3.0 20170516, GMP version 6.1.2, MPFR version 3.1.5, MPC version 1.0.3, isl version 0.15
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: a5e48cccdfbd9b08b43e5f380dbad667
COMPILER_PATH=/usr/lib/gcc-cross/arm-linux-gnueabi/6/:/usr/lib/gcc-cross/arm-linux-gnueabi/6/:/usr/lib/gcc-cross/arm-linux-gnueabi/:/usr/lib/gcc-cross/arm-linux-gnueabi/6/:/usr/lib/gcc-cross/arm-linux-gnueabi/:/usr/lib/gcc-cross/arm-linux-gnueabi/6/../../../../arm-linux-gnueabi/bin/
LIBRARY_PATH=/usr/lib/gcc-cross/arm-linux-gnueabi/6/:/usr/lib/gcc-cross/arm-linux-gnueabi/6/../../../../arm-linux-gnueabi/lib/:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-std=c++14' '-fsyntax-only' '-shared-libgcc' '-march=armv4t' '-mfloat-abi=soft' '-mtls-dialect=gnu'

This tells us where we can find the std libraries and what include paths are used. We also note that see the we are using -mfloat-abi=soft by default so we don't need to worry about that flag. Another important point is the -march=armv4t, that seems abit strange to us since we are using armv5tej and according to arm @ wiki this should be armv5 and looking at the gcc options it should be armv5te. So we'll keep that in mind and add that to the flags later on.

Lets also checkout the difference on the arm-linux-gnueabihf toolchain.

docker exec -t GorbyBuild /bin/bash -c "
 arm-linux-gnueabihf-g++ -v -std=c++14 -xc++ -fsyntax-only /dev/null 2>&1 | grep "COLLECT"
 "
COLLECT_GCC=arm-linux-gnueabihf-g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc-cross/arm-linux-gnueabihf/6/lto-wrapper
COLLECT_GCC_OPTIONS='-v' '-std=c++14' '-fsyntax-only' '-shared-libgcc' '-march=armv7-a' '-mfloat-abi=hard' '-mfpu=vfpv3-d16' '-mthumb' '-mtls-dialect=gnu'
COLLECT_GCC_OPTIONS='-v' '-std=c++14' '-fsyntax-only' '-shared-libgcc' '-march=armv7-a' '-mfloat-abi=hard' '-mfpu=vfpv3-d16' '-mthumb' '-mtls-dialect=gnu'

This is using a -march=armv7-a -mfloat-abi=hard -mfpu=vfpv3-d16 that doesn't fit our purpose. So We'll leave it for now.

Here is something more interesting

docker exec -t GorbyBuild /bin/bash -c "
arm-linux-gnueabihf-g++ -marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s -Q --help=target
"
The following options are target specific:
  -mabi=                      		aapcs-linux
  -mabort-on-noreturn         		[disabled]
  -mandroid                   		[disabled]
  -mapcs                      		[disabled]
  -mapcs-float                		[disabled]
  -mapcs-frame                		[disabled]
  -mapcs-reentrant            		[disabled]
  -mapcs-stack-check          		[disabled]
  -march=                     		armv5te
  -marm                       		[enabled]
  -masm-syntax-unified        		[disabled]
  -mbig-endian                		[disabled]
  -mbionic                    		[disabled]
  -mcallee-super-interworking 		[disabled]
  -mcaller-super-interworking 		[disabled]
  -mcpu=                      		arm926ej-s
  -mfix-cortex-m3-ldrd        		[enabled]
  -mflip-thumb                		[disabled]
  -mfloat-abi=                		hard
  -mfp16-format=              		none
  -mfpu=                      		vfpv3-d16
  -mglibc                     		[enabled]
  -mhard-float                		
  -mlittle-endian             		[enabled]
  -mlong-calls                		[disabled]
  -mmusl                      		[disabled]
  -mneon-for-64bits           		[disabled]
  -mnew-generic-costs         		[disabled]
  -mold-rtx-costs             		[disabled]
  -mpic-data-is-text-relative 		[enabled]
  -mpic-register=             		
  -mpoke-function-name        		[disabled]
  -mprint-tune-info           		[disabled]
  -mrestrict-it               		[enabled]
  -msched-prolog              		[enabled]
  -msingle-pic-base           		[disabled]
  -mslow-flash-data           		[disabled]
  -msoft-float                		
  -mstructure-size-boundary=  		0x20
  -mthumb                     		[disabled]
  -mthumb-interwork           		[enabled]
  -mtls-dialect=              		gnu
  -mtp=                       		auto
  -mtpcs-frame                		[disabled]
  -mtpcs-leaf-frame           		[disabled]
  -mtune=                     		arm926ej-s
  -muclibc                    		[disabled]
  -munaligned-access          		[enabled]
  -mvectorize-with-neon-double 		[disabled]
  -mvectorize-with-neon-quad  		[enabled]
  -mword-relocations          		[disabled]

  Known ARM ABIs (for use with the -mabi= option):
    aapcs aapcs-linux apcs-gnu atpcs iwmmxt

  Known ARM architectures (for use with the -march= option):
    armv2 armv2a armv3 armv3m armv4 armv4t armv5 armv5e armv5t armv5te armv6
    armv6-m armv6j armv6k armv6kz armv6s-m armv6t2 armv6z armv6zk armv7 armv7-a
    armv7-m armv7-r armv7e-m armv7ve armv8-a armv8-a+crc armv8.1-a armv8.1-a+crc
    iwmmxt iwmmxt2 native

  Known __fp16 formats (for use with the -mfp16-format= option):
    alternative ieee none

  Known ARM FPUs (for use with the -mfpu= option):
    crypto-neon-fp-armv8 fp-armv8 fpv4-sp-d16 fpv5-d16 fpv5-sp-d16 neon
    neon-fp-armv8 neon-fp16 neon-vfpv4 vfp vfp3 vfpv3 vfpv3-d16 vfpv3-d16-fp16
    vfpv3-fp16 vfpv3xd vfpv3xd-fp16 vfpv4 vfpv4-d16

  Valid arguments to -mtp=:
    auto cp15 soft

  Known floating-point ABIs (for use with the -mfloat-abi= option):
    hard soft softfp

  Known ARM CPUs (for use with the -mcpu= and -mtune= options):
    arm1020e arm1020t arm1022e arm1026ej-s arm10e arm10tdmi arm1136j-s
    arm1136jf-s arm1156t2-s arm1156t2f-s arm1176jz-s arm1176jzf-s arm2 arm250
    arm3 arm6 arm60 arm600 arm610 arm620 arm7 arm70 arm700 arm700i arm710
    arm7100 arm710c arm710t arm720 arm720t arm740t arm7500 arm7500fe arm7d
    arm7di arm7dm arm7dmi arm7m arm7tdmi arm7tdmi-s arm8 arm810 arm9 arm920
    arm920t arm922t arm926ej-s arm940t arm946e-s arm966e-s arm968e-s arm9e
    arm9tdmi cortex-a12 cortex-a15 cortex-a15.cortex-a7 cortex-a17
    cortex-a17.cortex-a7 cortex-a32 cortex-a35 cortex-a5 cortex-a53 cortex-a57
    cortex-a57.cortex-a53 cortex-a7 cortex-a72 cortex-a72.cortex-a53 cortex-a8
    cortex-a9 cortex-m0 cortex-m0.small-multiply cortex-m0plus
    cortex-m0plus.small-multiply cortex-m1 cortex-m1.small-multiply cortex-m3
    cortex-m4 cortex-m7 cortex-r4 cortex-r4f cortex-r5 cortex-r7 cortex-r8
    ep9312 exynos-m1 fa526 fa606te fa626 fa626te fa726te fmp626 generic-armv7-a
    iwmmxt iwmmxt2 marvell-pj4 mpcore mpcorenovfp native qdf24xx strongarm
    strongarm110 strongarm1100 strongarm1110 xgene1 xscale

  TLS dialect to use:
    gnu gnu2

This stuff is quite interesting, this shows what different options and CPU's that the compiler and can handle.

Creating a armel conan profile

We already touched on profiles , but this time we'll need to create a profile using the arm-linux-gnueabi toolchain. I will not dwelve into profiles to much , there is however more information on the conan website. This profiles will be used solely in the docker, there is at least three ways we can do this.

  1. create the profile inside the docker, using the docker users ~/.conan/profiles directory. Then we can use the profile profile as usual conan search "....." --profile=armel
  2. Create the profile on the host, and since the home directory is mounted in /src/ its possible to use the absolute path ie. /src/.conan/profiles directory. i.e conan search "....." --profile=/src/.conan/profiles/armel
  3. The third alternative is to install it using conan config install. Conan config install can install configuration files from different sources to the local configuration directory.

But before we do that we need to create an armel profile. There is a possibility to use multiple profiles in the command line.

You can specify multiple profiles in the command line. The applied configuration will be the composition of all the profiles applied in the order they are specified.

This gives us the opportunity to use ie [build_require] instead of injecting something that is going to be used in all builds.

Lets create another profile where we set the Gorby compiler flags.

cat <<EOF > Gorby_Flags
[env]
CXXFLAGS="-marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s"
EOF

Lets continue with the armel profile. In a profile its possible to declare variables that will be replaced by conan before the profile is applied. The variables have to be declared at the top of the file.

armel profile:

cat ~/.conan/profiles/armel
target_host=arm-linux-gnueabi
os_target=Linux
cc_compiler=gcc
cxx_compiler=g++

[settings]
os=Linux
os_build=$os_target
compiler=gcc
compiler.version=6
arch=armv5el
compiler.libcxx=libstdc++11

[env]
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
CHOST=$target_host
CXX=$target_host-$cxx_compiler

Host/build context

As we discussed before there is a distiction between host and build. Host is whats running on your host machine, thats probably your PC or laptop. The build context in this case is Gorby. So why is this important? The reason is that we want to make use of the conan to fetch us the build tools that we need to build binaries for the target system. In this case we want cmake installed in the docker (it already is, but version 3.7.2 so we want a newer) see this link for further information. I will keep it simple, first I will create a tools directory in which I will install the conanfile.txt which only has

[requires]
cmake/3.19.3

[generators]
virtualenv

Using that together with a profile with native gcc that is installed in the docker container. This will create a generated virtualenv which can activated and consequently give access to a newer cmake.

docker exec -t  GorbyBuild /bin/bash -c '
mkdir -p /tmp/tools
cat <<EOF > /tmp/tools/conanfile.txt
[requires]
cmake/3.19.3

[generators]
virtualenv
EOF
'
docker exec -t  GorbyBuild /bin/bash -c "
mkdir -p /tmp/tools/gen; cd /tmp/tools/gen 
/home/compiler/.local/bin/conan install .. --profile=default
echo
"
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++
compiler.version=6
os=Linux
os_build=Linux
[options]
[build_requires]
[env]

conanfile.txt: Installing package
Requirements
    cmake/3.19.3 from 'conan-center' - Cache
    openssl/1.1.1i from 'conan-center' - Cache
Packages
    cmake/3.19.3:5c09c752508b674ca5cb1f2d327b5a2d582866c8 - Cache
    openssl/1.1.1i:f7e573cb501ccfc49e9e4d84de886bc1ef2e6ebb - Cache

Installing (downloading, building) binaries...
openssl/1.1.1i: Already installed!
cmake/3.19.3: Already installed!
cmake/3.19.3: Appending PATH environment variable: /home/compiler/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin
conanfile.txt: Generator virtualenv created activate.sh
conanfile.txt: Generator virtualenv created environment.sh.env
conanfile.txt: Generator virtualenv created environment.ps1.env
conanfile.txt: Generator virtualenv created deactivate.sh
conanfile.txt: Generator virtualenv created activate.ps1
conanfile.txt: Generator virtualenv created deactivate.ps1
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo


Ev3dev-cpp cross compile

There is one more thing that needs to be done before we can start working on our project. The ev3dev library is not part of the conan remote repository, so we need to build it in the docker with our cross-compiler toolchain so it is available in the docker conan cache.

We already created a conanfile.py when we Conan new/create section. All we need to do is to use the same concept, but using a different profile. This time we need to do it in the docker with the my_arm and arm_flags

docker exec -t GorbyBuild /bin/bash -c "
cd /src/tmp/conan/ev3dev-latest
/home/compiler/.local/bin/conan create /src/tmp/conan/ev3dev-latest --profile=my_arm --profile=arm_flags
echo
"
Exporting package recipe
ev3dev/latest: A new conanfile.py version was exported
ev3dev/latest: Folder: /home/compiler/.conan/data/ev3dev/latest/_/_/export
ev3dev/latest: Exported revision: ae45f41e2bd73871616d806fada3e8ff
Configuration:
[settings]
arch=armv5el
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=6
os=Linux
os_build=Linux
[options]
[build_requires]
[env]
AR=arm-linux-gnueabi-ar
AS=arm-linux-gnueabi-as
CC=arm-linux-gnueabi-gcc
CHOST=arm-linux-gnueabi
CPPFLAGS=-marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s
CXX=arm-linux-gnueabi-g++
CXXFLAGS=-marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s
LD=arm-linux-gnueabi-ld
RANLIB=arm-linux-gnueabi-ranlib
STRIP=arm-linux-gnueabi-strip
ev3dev/latest: Forced build from source
Installing package: ev3dev/latest
Requirements
    ev3dev/latest from local cache - Cache
Packages
    ev3dev/latest:1545952387bac72781ac7816c6257544b5c273bc - Build

Cross-build from 'Linux:x86_64' to 'Linux:armv5el'
Installing (downloading, building) binaries...
ev3dev/latest: Configuring sources in /home/compiler/.conan/data/ev3dev/latest/_/_/source
Cloning into 'ev3dev-lang-cpp'...
remote: Enumerating objects: 800, done.
.
.
.
ev3dev/latest: Copying sources to build folder
ev3dev/latest: Building your package in /home/compiler/.conan/data/ev3dev/latest/_/_/build/1545952387bac72781ac7816c6257544b5c273bc
ev3dev/latest: Generator txt created conanbuildinfo.txt
ev3dev/latest: Calling build()
-- No build type selected, default to RelWithDebInfo
-- The C compiler identification is GNU 6.3.0
-- The CXX compiler identification is GNU 6.3.0
-- Check for working C compiler: /usr/bin/arm-linux-gnueabi-gcc
-- Check for working C compiler: /usr/bin/arm-linux-gnueabi-gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/arm-linux-gnueabi-g++
-- Check for working CXX compiler: /usr/bin/arm-linux-gnueabi-g++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
.
.
.
-- Build files have been written to: /home/compiler/.conan/data/ev3dev/latest/_/_/build/1545952387bac72781ac7816c6257544b5c273bc
ev3dev/latest: WARN: build_type setting should be defined.
Scanning dependencies of target ev3dev
Scanning dependencies of target api_tests
[  5%] Building CXX object CMakeFiles/ev3dev.dir/ev3dev.cpp.o
[ 10%] Building CXX object tests/CMakeFiles/api_tests.dir/api_tests.cpp.o
[ 15%] Building CXX object tests/CMakeFiles/api_tests.dir/__/ev3dev.cpp.o
[ 21%] Linking CXX static library libev3dev.a
[ 21%] Built target ev3dev
Scanning dependencies of target button-test
Scanning dependencies of target ev3dev-lang-demo
Scanning dependencies of target remote_control-test
Scanning dependencies of target sound-test
Scanning dependencies of target ev3dev-lang-test
Scanning dependencies of target drive-test
Scanning dependencies of target remote-control
[ 26%] Building CXX object demos/CMakeFiles/remote_control-test.dir/remote_control-test.cpp.o
.
.
.
[ 94%] Built target ev3dev-lang-demo
[100%] Linking CXX executable api_tests
[100%] Built target api_tests
ev3dev/latest: Package '1545952387bac72781ac7816c6257544b5c273bc' built
ev3dev/latest: Build folder /home/compiler/.conan/data/ev3dev/latest/_/_/build/1545952387bac72781ac7816c6257544b5c273bc
ev3dev/latest: Generated conaninfo.txt
ev3dev/latest: Generated conanbuildinfo.txt
ev3dev/latest: Generating the package
ev3dev/latest: Package folder /home/compiler/.conan/data/ev3dev/latest/_/_/package/1545952387bac72781ac7816c6257544b5c273bc
ev3dev/latest: Calling package()
ev3dev/latest package(): Packaged 1 '.h' file: ev3dev.h
ev3dev/latest package(): Packaged 1 '.a' file: libev3dev.a
ev3dev/latest: Package '1545952387bac72781ac7816c6257544b5c273bc' created
ev3dev/latest: Created package revision a4bc1dac9e75d06acae869ff5d027dbf

Voilá! Nice and easy… Lets verify that infact all the things that we want are there.

docker exec -t GorbyBuild /bin/bash -c '
/home/compiler/.local/bin/conan search ev3dev/latest@
echo
'
Existing packages for recipe ev3dev/latest:

    Package_ID: 1545952387bac72781ac7816c6257544b5c273bc
        [options]
            fPIC: True
            shared: False
        [settings]
            arch: armv5el
            compiler: gcc
            compiler.libcxx: libstdc++11
            compiler.version: 6
            os: Linux
        Outdated from recipe: False

It might be possible to bind the conan cache from our host system to share it inside the docker. In that case it would be possible to see the arm version of ev3dev outside the docker. But I leave that to someone else to see if it works, and for the time being I see no benefit of sharing the arm binary. A better solution would be to set up a remote cache for all the different builds of ev3dev but then again, I leave that to someone else to figure out how to do it.

Makeing a project

Finally, lets create a project. Nothing fancy, just to check that everything is working as expected.

Here is the tree structure.

.
├── arm_build
├── CMakeLists.txt
├── conan
│   ├── arm_gcc
│   ├── conanfile.txt
│   ├── env
│   │   ├── conanfile.txt
│   │   └── virtualenv
│   ├── x86_clang
│   └── x86_gcc
├── src
│   ├── CMakeLists.txt
│   └── main.cpp
├── x86_build
└── x86_clang

10 directories, 5 files

The concept is to be able to build the project in two different flavors.

X86_64
The X86_64 version is mainly used for testing, the host machine does not have any sensors or motors connected to it. But some of sensors can be simulated, and output signals can be read which means its possible to test the sofware in a controlled enviroment before sending it out on exploration with untested code.
ARM
The arm version is what Gorby brain will consists of. After testing and evaluating the x86_64 code , the arm code is the final product that will be added to Gorby, a controlled test environment is one thing. But sending out Gorby on a real mission will give us the complete picture on how Gorby will handle it self in different situations, many which probably haven't been thought of …..

Lets start working our way up. We have three build directories out of source tree build directories x86_build, x86_clang and arm_build These are build directories for cmake, its the directory where cmake will store all its objects,binaries and caches. The final product for the different platform will be found here in the bin directory

The src directory contains the source to the final product. The last directory conan contains the conanfile.txt which looks like

[requires]
fmt/7.1.3
asio/1.18.1
ev3dev/latest

[generators]
cmake
compiler_args

These are the required 3rd part libraries this project needs that are dependent on what compiler and arhitecture we want to build. It also includes the generators (cmake, compiler_args), though the compilers_args are not really necessary I like to include it just to be able to look what compiler args are provided. There is one more directory which I haven't mentioned yet and thats the conan/env. It has one requirement file, and that is for the tools I want to include , this will generate a virtualenv which can be activated to be able to use the tool. Lets first take a look at the conan/env/conanfile.txt/

[requires]
cmake/3.19.3

[generators]
virtualenv

When we run conan install on this we will get a activate script for the host which will setup the paths to make sure we are using cmake 3.19.3

Lets try it out.

cd conan/env/virtualenv
conan install .. --profile=default > /dev/null
source conan/env/virtualenv/activate
cmake --version
cmake version 3.19.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).

I intentionally removed some of the output from install command to keep this abit shorter.

X86_64 build

To build for x86_64 system only the host system and a native compiler is used, but before we build anything we need to activate the virtualenv so that it uses the tools that we want (in this case cmake)

The first thing that needs to be done is to generate the conan cmake files using the default profile. We do that as follows

source conan/env/virtualenv/activate.sh 
cd conan/x86_gcc 
conan install .. --profile=default 
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=10
os=Linux
os_build=Linux
[options]
[build_requires]
[env]

conanfile.txt: Installing package
Requirements
    asio/1.18.1 from 'conan-center' - Cache
    ev3dev/latest from local cache - Cache
    fmt/7.1.3 from 'conan-center' - Cache
Packages
    asio/1.18.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Cache
    ev3dev/latest:b173bbda18164d49a449ffadc1c9e817f49e819d - Cache
    fmt/7.1.3:b173bbda18164d49a449ffadc1c9e817f49e819d - Cache

Installing (downloading, building) binaries...
asio/1.18.1: Already installed!
ev3dev/latest: Already installed!
fmt/7.1.3: Already installed!
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generator compiler_args created conanbuildinfo.args
conanfile.txt: Generator cmake created conanbuildinfo.cmake
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo

The generated output includes a conanbuildinfo.cmake which can be included from the CMakeLists.txt, now its time to build the project we make sure that the virtualenv is activated.

source conan/env/virtualenv/activate.sh
cmake --version
cmake version 3.19.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).

Et Voilá! We can use cmake 3.19.

With that fixed, its time to fix the CmakeLists.txt to include the generated cmake. There are some different strategies that can be made here, though I haven't figured out which one is the best.

Approach - using variable in cmake

This approach basically checks a variable inside the cmake and based on that it chooses a conan directory.

cmake_minimum_required(VERSION 3.7)

SET(PRJ_BUILD_T "x86" CACHE STRING "Build type to use")

if( ${PRJ_BUILD_T} STREQUAL "x86_gcc")
  set(CMAKE_C_COMPILER gcc)
  set(CMAKE_CXX_COMPILER g++)
  set(PRJ_CONAN_DIR "${CMAKE_SOURCE_DIR}/conan/x86_gcc")
elseif(${PRJ_BUILD_T} STREQUAL "x86_clang")
  message(STATUS "Hello clang")
  set(CMAKE_C_COMPILER clang)
  set(CMAKE_CXX_COMPILER clang++)
  set(PRJ_CONAN_DIR "${CMAKE_SOURCE_DIR}/conan/x86_clang")
elseif(${PRJ_BUILD_T} STREQUAL "arm_gcc")
  set(CMAKE_C_COMPILER arm-linux-gnueabi-gcc)
  set(CMAKE_CXX_COMPILER arm-linux-gnueabi-g++)
  set(PRJ_CONAN_DIR "${CMAKE_SOURCE_DIR}/conan/arm_gcc")
endif()

The variable PRJ_BUILD_T has three options. The two first is focused on building on the host, the last one arm_gcc

x86_gcc
sets the compiler to gcc, and sets the variable PRJ_CONAN_DIR=${CMAKE_SOURCE_DIR}/conan/x86_gcc
x86_clang
sets the compiler to clang, and sets the variable PRJ_CONAN_DIR=${CMAKE_SOURCE_DIR}/conan/x86_clang
arm_gcc
set the x-compiler arm-linux-gnueabi-g++ and sets the variable PRJ_CONAN_DIR=${CMAKE_SOURCE_DIR}/conan/arm_gcc

This works, but im not particularly fond of doing it this way. But it works, and for this purpose im satisfied. The pros with this approach is that when setting the PRJ_BUILD_TYPE it automatically uses the right path and conan directory, on the other hand it easily becomes an explosion of configuration if for example we start mixing the conan and compilers. I.e if we want to build the project with conan binaries from clang and the actual project with gcc and so on.

Approach - using linked conan build

Another approach is to use a soft links to a conan directory. This will leave out the CMakeLists.txt, but then we always need to make sure we have the right link. This approach has the benefit of being more versatile, on the other hand it needs manual configuration, and easy to forget to change the links when using another configuration.

Final build (x86 gcc)

The final build is to use "out-of-source" build in the directory x86_build We will use the approach where we modify the cmake with variable PRJ_BUILD_TYPE=...

  1. First we activate so we get all the tools
    1. since we setting the gcc inside cmake there is no need to set the environment variables
  2. Change the directory to the out-of source tree build x86_build
source conan/env/virtualenv/activate.sh
cmake --version
cd x86_build
cmake -DPRJ_BUILD_TYPE=x86_gcc ..


cmake version 3.19.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).
-- The C compiler identification is GNU 10.2.0
-- The CXX compiler identification is GNU 10.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Conan: Adjusting output directories
-- Conan: Using cmake global configuration
-- Conan: Adjusting default RPATHs Conan policies
-- Conan: Adjusting language standard
-- Current conanbuildinfo.cmake directory: /home/calle/git/my_prj/conan/x86_gcc
-- Conan: Compiler GCC>=5, checking major version 10
-- Conan: Checking correct version: 10
-- Configuring done
-- Generating done
-- Build files have been written to: /home/calle/git/my_prj/x86_build

Now finally we build it using make.

source conan/env/virtualenv/activate.sh
cmake --version
cd x86_build
make -j8 VERBOSE=1
cmake version 3.19.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).
/home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -S/home/calle/git/my_prj -B/home/calle/git/my_prj/x86_build --check-build-system CMakeFiles/Makefile.cmake 0
/home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_progress_start /home/calle/git/my_prj/x86_build/CMakeFiles /home/calle/git/my_prj/x86_build//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/calle/git/my_prj/x86_build'
make  -f src/CMakeFiles/third.dir/build.make src/CMakeFiles/third.dir/depend
make[2]: Entering directory '/home/calle/git/my_prj/x86_build'
cd /home/calle/git/my_prj/x86_build && /home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_depends "Unix Makefiles" /home/calle/git/my_prj /home/calle/git/my_prj/src /home/calle/git/my_prj/x86_build /home/calle/git/my_prj/x86_build/src /home/calle/git/my_prj/x86_build/src/CMakeFiles/third.dir/DependInfo.cmake --color=
Dependee "/home/calle/git/my_prj/x86_build/src/CMakeFiles/third.dir/DependInfo.cmake" is newer than depender "/home/calle/git/my_prj/x86_build/src/CMakeFiles/third.dir/depend.internal".
Dependee "/home/calle/git/my_prj/x86_build/src/CMakeFiles/CMakeDirectoryInformation.cmake" is newer than depender "/home/calle/git/my_prj/x86_build/src/CMakeFiles/third.dir/depend.internal".
Scanning dependencies of target third
make[2]: Leaving directory '/home/calle/git/my_prj/x86_build'
make  -f src/CMakeFiles/third.dir/build.make src/CMakeFiles/third.dir/build
make[2]: Entering directory '/home/calle/git/my_prj/x86_build'
[ 50%] Building CXX object src/CMakeFiles/third.dir/main.cpp.o
cd /home/calle/git/my_prj/x86_build/src && /usr/bin/g++  -I/home/calle/.conan/data/fmt/7.1.3/_/_/package/b173bbda18164d49a449ffadc1c9e817f49e819d/include -I/home/calle/.conan/data/asio/1.18.1/_/_/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include -I/home/calle/.conan/data/ev3dev/latest/_/_/package/b173bbda18164d49a449ffadc1c9e817f49e819d/include -I/home/calle/git/my_prj/include -I/home/calle/git/my_prj/src/include -Wall -Wextra -pedantic -DASIO_STANDALONE -std=gnu++14 -o CMakeFiles/third.dir/main.cpp.o -c /home/calle/git/my_prj/src/main.cpp
[100%] Linking CXX executable ../bin/third
cd /home/calle/git/my_prj/x86_build/src && /home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_link_script CMakeFiles/third.dir/link.txt --verbose=1
/usr/bin/g++ -Wall -Wextra -pedantic CMakeFiles/third.dir/main.cpp.o -o ../bin/third   -L/home/calle/.conan/data/fmt/7.1.3/_/_/package/b173bbda18164d49a449ffadc1c9e817f49e819d/lib  -L/home/calle/.conan/data/ev3dev/latest/_/_/package/b173bbda18164d49a449ffadc1c9e817f49e819d/lib  -Wl,-rpath,/home/calle/.conan/data/fmt/7.1.3/_/_/package/b173bbda18164d49a449ffadc1c9e817f49e819d/lib:/home/calle/.conan/data/ev3dev/latest/_/_/package/b173bbda18164d49a449ffadc1c9e817f49e819d/lib -lfmt -lev3dev -lpthread 
make[2]: Leaving directory '/home/calle/git/my_prj/x86_build'
[100%] Built target third
make[1]: Leaving directory '/home/calle/git/my_prj/x86_build'
/home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_progress_start /home/calle/git/my_prj/x86_build/CMakeFiles 0

Final build (x86 clang)

This is done basically the same way. The difference is that we need to use the clang profile to generate the output from conan

source conan/env/virtualenv/activate.sh
cmake --version
cd conan/x86_clang
conan install .. --profile=clang

cmake version 3.19.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=clang
compiler.libcxx=libstdc++11
compiler.version=11
os=Linux
os_build=Linux
[options]
[build_requires]
[env]
CC=/usr/bin/clang
CXX=/usr/bin/clang++
conanfile.txt: Installing package
Requirements
    asio/1.18.1 from 'conan-center' - Cache
    ev3dev/latest from local cache - Cache
    fmt/7.1.3 from 'conan-center' - Cache
Packages
    asio/1.18.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Cache
    ev3dev/latest:0ac8c9952b8651eefe078adcd928017cea3e0340 - Cache
    fmt/7.1.3:0ac8c9952b8651eefe078adcd928017cea3e0340 - Cache

Installing (downloading, building) binaries...
asio/1.18.1: Already installed!
ev3dev/latest: Already installed!
fmt/7.1.3: Already installed!
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generator cmake created conanbuildinfo.cmake
conanfile.txt: Generator compiler_args created conanbuildinfo.args
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo

Next we activate the build environment and executes cmake

source conan/env/virtualenv/activate.sh
cmake --version
cd x86_clang
cmake -DPRJ_BUILD_TYPE=x86_clang ..

cmake version 3.19.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).
-- Hello clang
-- The C compiler identification is Clang 11.0.1
-- The CXX compiler identification is Clang 11.0.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/clang++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Conan: Adjusting output directories
-- Conan: Using cmake global configuration
-- Conan: Adjusting default RPATHs Conan policies
-- Conan: Adjusting language standard
-- Current conanbuildinfo.cmake directory: /home/calle/git/my_prj/conan/x86_clang
-- Conan: Compiler Clang>=8, checking major version 11
-- Conan: Checking correct version: 11
-- Configuring done
-- Generating done
-- Build files have been written to: /home/calle/git/my_prj/x86_clang

And finally we build the project

cd x86_clang
make -j8 VERBOSE=1
/home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -S/home/calle/git/my_prj -B/home/calle/git/my_prj/x86_clang --check-build-system CMakeFiles/Makefile.cmake 0
/home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_progress_start /home/calle/git/my_prj/x86_clang/CMakeFiles /home/calle/git/my_prj/x86_clang//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/home/calle/git/my_prj/x86_clang'
make  -f src/CMakeFiles/third.dir/build.make src/CMakeFiles/third.dir/depend
make[2]: Entering directory '/home/calle/git/my_prj/x86_clang'
cd /home/calle/git/my_prj/x86_clang && /home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_depends "Unix Makefiles" /home/calle/git/my_prj /home/calle/git/my_prj/src /home/calle/git/my_prj/x86_clang /home/calle/git/my_prj/x86_clang/src /home/calle/git/my_prj/x86_clang/src/CMakeFiles/third.dir/DependInfo.cmake --color=
make[2]: Leaving directory '/home/calle/git/my_prj/x86_clang'
make  -f src/CMakeFiles/third.dir/build.make src/CMakeFiles/third.dir/build
make[2]: Entering directory '/home/calle/git/my_prj/x86_clang'
make[2]: Nothing to be done for 'src/CMakeFiles/third.dir/build'.
make[2]: Leaving directory '/home/calle/git/my_prj/x86_clang'
[100%] Built target third
make[1]: Leaving directory '/home/calle/git/my_prj/x86_clang'
/home/calle/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_progress_start /home/calle/git/my_prj/x86_clang/CMakeFiles 0

Et Voila! It worked..

Arm build

So far all we done has been to make sure that our project is able to build on out host system, but the final goal is to have it running in Gorby, which means we need to compile it as a arm binary. The final part is to build the whole project for the target system which is using an arm processor. But since we don't have the cross-compiler locally installed this needs to be done inside the docker that we setup in previous section.

Lets first start the docker

docker start GorbyBuild
GorbyBuild

As with the host machine, we need to make sure that the virtualenv is installed. Since I don't want to mess with the virtual env that was installed on the host I create a new directory

docker exec GorbyBuild /bin/bash -c "
       cd ${docker_dir}/conan/env
       mkdir docker_virt
       cd docker_virt
       ~/.local/bin/conan install .. --profile=default
       "
Configuration:
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.libcxx=libstdc++
compiler.version=6
os=Linux
os_build=Linux
[options]
[build_requires]
[env]

conanfile.txt: Installing package
Requirements
    cmake/3.19.3 from 'conan-center' - Cache
    openssl/1.1.1i from 'conan-center' - Cache
Packages
    cmake/3.19.3:5c09c752508b674ca5cb1f2d327b5a2d582866c8 - Cache
    openssl/1.1.1i:f7e573cb501ccfc49e9e4d84de886bc1ef2e6ebb - Cache

Installing (downloading, building) binaries...
openssl/1.1.1i: Already installed!
cmake/3.19.3: Already installed!
cmake/3.19.3: Appending PATH environment variable: /home/compiler/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin
conanfile.txt: Generator virtualenv created activate.ps1
conanfile.txt: Generator virtualenv created environment.sh.env
conanfile.txt: Generator virtualenv created environment.ps1.env
conanfile.txt: Generator virtualenv created deactivate.ps1
conanfile.txt: Generator virtualenv created deactivate.sh
conanfile.txt: Generator virtualenv created activate.sh
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo

Now its time to install the 3rd-part dependecies as we did with x86 builds but we need to use the profiles my_arm and arm_flags

docker exec GorbyBuild /bin/bash -c "
source ${docker_dir}/conan/env/docker_virt/activate.sh
cd ${docker_dir}/conan/arm_gcc
~/.local/bin/conan install .. --profile=my_arm --profile=arm_flags 
"
Configuration:
[settings]
arch=armv5el
compiler=gcc
compiler.libcxx=libstdc++11
compiler.version=6
os=Linux
os_build=Linux
[options]
[build_requires]
[env]
AR=arm-linux-gnueabi-ar
AS=arm-linux-gnueabi-as
CC=arm-linux-gnueabi-gcc
CHOST=arm-linux-gnueabi
CPPFLAGS=-marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s
CXX=arm-linux-gnueabi-g++
CXXFLAGS=-marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s
LD=arm-linux-gnueabi-ld
RANLIB=arm-linux-gnueabi-ranlib
STRIP=arm-linux-gnueabi-strip
conanfile.txt: Installing package
Requirements
    asio/1.18.1 from 'conan-center' - Cache
    ev3dev/latest from local cache - Cache
    fmt/7.1.3 from 'conan-center' - Cache
Packages
    asio/1.18.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Cache
    ev3dev/latest:1545952387bac72781ac7816c6257544b5c273bc - Cache
    fmt/7.1.3:1545952387bac72781ac7816c6257544b5c273bc - Cache

Cross-build from 'Linux:x86_64' to 'Linux:armv5el'
Installing (downloading, building) binaries...
asio/1.18.1: Already installed!
ev3dev/latest: Already installed!
fmt/7.1.3: Already installed!
conanfile.txt: Generator compiler_args created conanbuildinfo.args
conanfile.txt: Generator cmake created conanbuildinfo.cmake
conanfile.txt: Generator txt created conanbuildinfo.txt
conanfile.txt: Generated conaninfo.txt
conanfile.txt: Generated graphinfo

We have now make sure that the third party libraries are installed. Its time for the finalé , this is where we build the project for Gorby.

docker exec GorbyBuild /bin/bash -c "
source ${docker_dir}/conan/env/docker_virt/activate.sh
cd ${docker_dir}/arm_build
cmake -DPRJ_BUILD_TYPE=arm_gcc ..
"
-- The C compiler identification is GNU 6.3.0
-- The CXX compiler identification is GNU 6.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/arm-linux-gnueabi-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/arm-linux-gnueabi-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- CMake FLAGS  -marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s
-- Conan: Adjusting output directories
-- Conan: Using cmake global configuration
-- Conan: Adjusting default RPATHs Conan policies
-- Conan: Adjusting language standard
-- Current conanbuildinfo.cmake directory: /src/git/my_prj/conan/arm_gcc
-- Conan: Compiler GCC>=5, checking major version 6
-- Conan: Checking correct version: 6
-- Configuring done
-- Generating done
-- Build files have been written to: /src/git/my_prj/arm_build

And now to the grand-finalé!

docker exec GorbyBuild /bin/bash -c "
       source ${docker_dir}/conan/env/docker_virt/activate.sh
       cd ${docker_dir}/arm_build
       make -j8 VERBOSE=1
"
/home/compiler/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -S/src/git/my_prj -B/src/git/my_prj/arm_build --check-build-system CMakeFiles/Makefile.cmake 0
/home/compiler/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_progress_start /src/git/my_prj/arm_build/CMakeFiles /src/git/my_prj/arm_build//CMakeFiles/progress.marks
make  -f CMakeFiles/Makefile2 all
make[1]: Entering directory '/src/git/my_prj/arm_build'
make  -f src/CMakeFiles/third.dir/build.make src/CMakeFiles/third.dir/depend
make[2]: Entering directory '/src/git/my_prj/arm_build'
cd /src/git/my_prj/arm_build && /home/compiler/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_depends "Unix Makefiles" /src/git/my_prj /src/git/my_prj/src /src/git/my_prj/arm_build /src/git/my_prj/arm_build/src /src/git/my_prj/arm_build/src/CMakeFiles/third.dir/DependInfo.cmake --color=
Dependee "/src/git/my_prj/arm_build/src/CMakeFiles/third.dir/DependInfo.cmake" is newer than depender "/src/git/my_prj/arm_build/src/CMakeFiles/third.dir/depend.internal".
Dependee "/src/git/my_prj/arm_build/src/CMakeFiles/CMakeDirectoryInformation.cmake" is newer than depender "/src/git/my_prj/arm_build/src/CMakeFiles/third.dir/depend.internal".
Scanning dependencies of target third
make[2]: Leaving directory '/src/git/my_prj/arm_build'
make  -f src/CMakeFiles/third.dir/build.make src/CMakeFiles/third.dir/build
make[2]: Entering directory '/src/git/my_prj/arm_build'
[ 50%] Building CXX object src/CMakeFiles/third.dir/main.cpp.o
cd /src/git/my_prj/arm_build/src && /usr/bin/arm-linux-gnueabi-g++  -I/home/compiler/.conan/data/fmt/7.1.3/_/_/package/1545952387bac72781ac7816c6257544b5c273bc/include -I/home/compiler/.conan/data/asio/1.18.1/_/_/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include -I/home/compiler/.conan/data/ev3dev/latest/_/_/package/1545952387bac72781ac7816c6257544b5c273bc/include -I/src/git/my_prj/include -I/src/git/my_prj/src/include -marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s   -Wall -Wextra  -DASIO_STANDALONE -std=gnu++14 -o CMakeFiles/third.dir/main.cpp.o -c /src/git/my_prj/src/main.cpp
[100%] Linking CXX executable ../bin/third
cd /src/git/my_prj/arm_build/src && /home/compiler/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_link_script CMakeFiles/third.dir/link.txt --verbose=1
/usr/bin/arm-linux-gnueabi-g++  -marm -march=armv5te -mcpu=arm926ej-s -mtune=arm926ej-s   -Wall -Wextra  CMakeFiles/third.dir/main.cpp.o -o ../bin/third   -L/home/compiler/.conan/data/fmt/7.1.3/_/_/package/1545952387bac72781ac7816c6257544b5c273bc/lib  -L/home/compiler/.conan/data/ev3dev/latest/_/_/package/1545952387bac72781ac7816c6257544b5c273bc/lib  -Wl,-rpath,/home/compiler/.conan/data/fmt/7.1.3/_/_/package/1545952387bac72781ac7816c6257544b5c273bc/lib:/home/compiler/.conan/data/ev3dev/latest/_/_/package/1545952387bac72781ac7816c6257544b5c273bc/lib -lfmt -lev3dev -lpthread 
make[2]: Leaving directory '/src/git/my_prj/arm_build'
[100%] Built target third
make[1]: Leaving directory '/src/git/my_prj/arm_build'
/home/compiler/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin/cmake -E cmake_progress_start /src/git/my_prj/arm_build/CMakeFiles 0

Its just a matter of transferring the binary to Gorby and run it!

Disclaimer

What I explained worked, but there might be better or easier way to achieve the same thing. I've taken shortcuts especially when it comes to cmake, this was not attended to be a tutorial on cmake therefor I just created the project quick and dirty. I will not state that this is the best way of doing things, I just states that this is a way of doing it.

Links

Footnotes:

1

The optimal block size for dd depends on disk and its not crucial it just takes longer..

2

tramp mode in emacs a short introduction.

Author: Calle Olsen

Created: 2021-01-31 Sun 15:46

Validate