Using Conan and cross-compile to EV3
Table of Contents
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.
- It will take up alot of disk space to install a complete development enviroment
- 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.
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$
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: 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. Forfmt
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 aconanbuildinfo.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. Thecompiler_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 pythonvirtualenv
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.
- create the profile inside the docker, using the docker users
~/.conan/profiles
directory. Then we can use the profile profile as usualconan search "....." --profile=armel
- 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.econan search "....." --profile=/src/.conan/profiles/armel
- 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 "
[1m[36mConfiguration:[0m [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] [1m[35mconanfile.txt: [0m[1m[37mInstalling package[0m [1m[33mRequirements[0m [1m[36m cmake/3.19.3 from 'conan-center' - Cache[0m [1m[36m openssl/1.1.1i from 'conan-center' - Cache[0m [1m[33mPackages[0m [1m[36m cmake/3.19.3:5c09c752508b674ca5cb1f2d327b5a2d582866c8 - Cache[0m [1m[36m openssl/1.1.1i:f7e573cb501ccfc49e9e4d84de886bc1ef2e6ebb - Cache[0m [1m[36mInstalling (downloading, building) binaries...[0m [1m[32mopenssl/1.1.1i: [0m[1m[37mAlready installed![0m [1m[32mcmake/3.19.3: [0m[1m[37mAlready installed![0m [1m[36mcmake/3.19.3: [0m[1m[37mAppending PATH environment variable: /home/compiler/.conan/data/cmake/3.19.3/_/_/package/5c09c752508b674ca5cb1f2d327b5a2d582866c8/bin[0m [1m[36mconanfile.txt: [0m[1m[37mGenerator virtualenv created activate.sh[0m [1m[36mconanfile.txt: [0m[1m[37mGenerator virtualenv created environment.sh.env[0m [1m[36mconanfile.txt: [0m[1m[37mGenerator virtualenv created environment.ps1.env[0m [1m[36mconanfile.txt: [0m[1m[37mGenerator virtualenv created deactivate.sh[0m [1m[36mconanfile.txt: [0m[1m[37mGenerator virtualenv created activate.ps1[0m [1m[36mconanfile.txt: [0m[1m[37mGenerator virtualenv created deactivate.ps1[0m [1m[36mconanfile.txt: [0m[1m[37mGenerator txt created conanbuildinfo.txt[0m [1m[36mconanfile.txt: [0m[1m[37mGenerated conaninfo.txt[0m [1m[36mconanfile.txt: [0m[1m[37mGenerated graphinfo[0m [0m
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=...
- First we activate so we get all the tools
- since we setting the
gcc
inside cmake there is no need to set the environment variables
- since we setting the
- 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
- docker library
- Snapshots
- fpu on arm
- compiler options for arm processors
- gcc arm options
- arm models
- docker cli documentation
- Conan documentation
- conan center
- Bincrafters remote conan repository
- Conan Generators
- Build requries article
- Cmake build system
Footnotes:
The optimal block size for dd
depends on disk and its not
crucial it just takes longer..
tramp mode in emacs a short introduction.