Building the C++ example project
Pieter PTable of Contents list
Downloading the example project
The example is just a command line program that asks the user for his/her
name using Boost.program_options
and prints a hello world message.
Download it from GitHub:
mkdir -p ~/GitHub
cd ~/GitHub
git clone https://github.com/tttapa/RPi-Cross-Cpp-Development.git
cd RPi-Cross-Cpp-Development
Customize the paths
Edit the CMake toolchain file to point to the correct root filesystem path
(the value of the CMAKE_SYSROOT
variable):
sed -i 's/schroot-name/rpizero-buster/' cmake/armv6-rpi-linux-gnueabihf.cmake
You might want to change the toolchain prefix or the architecture-specific flags
as well.
If you're using a 64-bit toolchain, edit the
aarch64-rpi3-linux-gnu.cmake
file instead.
Installing the dependencies
Thanks to the sbuild
development tools, managing dependencies is
really easy, you can just install them to the Raspberry Pi OS root filesystem
using the familiar apt-get install
command.
We use the sbuild-apt
tool,
and specify the name of the root filesystem we created on the previous page.
sudo sbuild-apt rpizero-buster-armhf apt-get install libboost-all-dev
Now Boost is installed on our build computer, but not yet on the Raspberry Pi itself.
Let's do that now, using the standard apt install
command over SSH:
ssh RPi0 sudo apt install -y libboost-all-dev
Strictly speaking, we don't need all development libraries on the Pi, so to save some time and space, you could install just the libraries you need, e.g.
ssh RPi0 sudo apt install -y libboost-program-options1.67.0
Configuring and building the project
Open the ~/GitHub/RPi-Cross-Cpp-Development
folder in Visual Studio Code
(using the Ctrl+K+O shortcut or “Open Folder”).
armv6-rpi-linux-gnueabihf
toolchain
we installed, as well as the Boost libraries in the Raspberry Pi OS root filesystem.
If CMake raises an error, see the Troubleshooting section below.
You can verify that everything worked correctly using the file
command:
file build/hello
build/hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped
As you can see, hello
is a 32-bit ARM executable, so the cross-compilation was successful.
Running the example program on the Raspberry Pi
All we have to do now is copy the hello
file to the Raspberry Pi and run it.
We'll copy it over SSH using the scp
command, and then run it over SSH as well:
scp build/hello RPi0:~
ssh RPi0 '~/hello' --help
That's it, you have successfully executed your cross-compiled C++ program on the Raspberry Pi!
Using a staging directory
For larger projects, you usually don't want to have to dig around in the
build
directory to gather all the files you need to copy to the
Raspberry Pi. Instead, you'll use cmake --install
to let
CMake install all the necessary files into a so-called staging directory on
your computer, which you can then copy to the Pi.
After building the project as explained in the previous section, press
Ctrl+Shift+P in VSCode and execute the CMake: Install
command. You can then find the hello
program in the bin
subdirectory of
~/RPi-dev/staging-armv6-rpi
.
You'll usually compress the staging folder using tar
and then
deploy it to the Pi by extracting it to the ~/.local
or
/usr/local
folder.
A closer look at the build process
There's only one source file, main.cpp
, and we'll focus on the
build process, so we won't go into the code in detail here.
The main CMakeLists.txt
file is really basic:
it just defines the project, looks for the Boost.program_options library,
compiles main.cpp
into an executable with the name hello
,
and then links this executable with the Boost library. Finally, an install
rule is added to install the hello
program to the bin
folder of the staging directory.
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(hello VERSION 0.1.0 LANGUAGES C CXX Fortran)
find_package(Boost REQUIRED COMPONENTS program_options)
add_executable(hello main.cpp)
target_link_libraries(hello PRIVATE Boost::program_options)
include(GNUInstallDirs)
install(TARGETS hello
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
The way we tell CMake to cross-compile this project for the Raspberry Pi is using a so-called toolchain file. You can find more information on https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling.
cmake/armv6-rpi-linux-gnueabihf.cmake
# https://cmake.org/cmake/help/book/mastering-cmake/chapter/Cross%20Compiling%20With%20CMake.html
# Cross-compilation system information
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# The sysroot contains all the libraries we might need to link against and
# possibly headers we need for compilation
set(CMAKE_SYSROOT /var/lib/schroot/chroots/rpizero-buster-armhf)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
set(CMAKE_LIBRARY_ARCHITECTURE arm-linux-gnueabihf)
set(CMAKE_STAGING_PREFIX $ENV{HOME}/RPi-dev/staging-armv6-rpi)
# Set the compilers for C, C++ and Fortran
set(RPI_GCC_TRIPLE "armv6-rpi-linux-gnueabihf")
set(CMAKE_C_COMPILER ${RPI_GCC_TRIPLE}-gcc CACHE FILEPATH "C compiler")
set(CMAKE_CXX_COMPILER ${RPI_GCC_TRIPLE}-g++ CACHE FILEPATH "C++ compiler")
set(CMAKE_Fortran_COMPILER ${RPI_GCC_TRIPLE}-gfortran CACHE FILEPATH "Fortran compiler")
# Set the architecture-specific compiler flags
set(ARCH_FLAGS "-mcpu=arm1176jzf-s")
set(CMAKE_C_FLAGS_INIT ${ARCH_FLAGS})
set(CMAKE_CXX_FLAGS_INIT ${ARCH_FLAGS})
set(CMAKE_Fortran_FLAGS_INIT ${ARCH_FLAGS})
# Don't look for programs in the sysroot (these are ARM programs, they won't run
# on the build machine)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Only look for libraries, headers and packages in the sysroot, don't look on
# the build machine
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE armhf)
First we set the system name and architecture, then we set some paths to
the sysroot, so that CMake will be able to find the Boost library.
If you gave your chroot a different name, you have to change these
paths.
The CMAKE_STAGING_PREFIX
variable is useful for installing your
project to a “staging” directory for deploying to the Pi.
The CMAKE_LIBRARY_ARCHITECTURE
variable
helps CMake find libraries in Debian's Multiarch directory structure,
e.g. in /usr/lib/arm-linux-gnueabihf/
.
Next, we tell CMake to use the armv6-rpi-linux-gnueabihf
cross-compilers, and we set some compiler flags. The -mcpu
flag
is a bit redundant here, since the toolchain is already configured to
generate code for that specific CPU, but it serves as an example, you might
want to add more specific flags.
Finally, we tell CMake never to run any programs it finds in the sysroot (because they are ARM binaries, you cannot run them on your computer without extra steps), and it should only look for libraries, headers and packages in the sysroot. We don't want CMake to find and use packages installed on our computer, because these are x86_64 libraries, not ARM.
The CMake Tools VSCode extension picks up these toolchain files using the following configuration file:
.vscode/cmake-kits.json
[
{
"name": "Raspberry Pi 3 (aarch64)",
"toolchainFile": "${workspaceFolder}/cmake/aarch64-rpi3-linux-gnu.cmake"
},
{
"name": "Raspberry Pi 3 Clang (aarch64)",
"toolchainFile": "${workspaceFolder}/cmake/aarch64-rpi3-linux-gnu-clang.cmake"
},
{
"name": "Raspberry Pi (armv6)",
"toolchainFile": "${workspaceFolder}/cmake/armv6-rpi-linux-gnueabihf.cmake",
"environmentVariables": {
"PATH": "${env:HOME}/opt/x-tools/armv6-rpi-linux-gnueabihf/bin:${env:PATH}"
}
}
]
You can see that there's a second toolchain file for newer 64-bit boards. If you need different configurations for different Pi models, you can add them here and easily switch between them by clicking the CMake Kit button in VSCode.
Manual build
If you don't want to use VSCode as your editor, you can also build the project from the command line:
cd ~/GitHub/RPi-Cross-Cpp-Development
rm -rf build
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=cmake/armv6-rpi-linux-gnueabihf.cmake
cmake --build build -j
cmake --install build
Troubleshooting
This section covers some common problems that you might run into. After
fixing them, you can re-run CMake by pressing Ctrl+Shift+P
in VSCode and executing the CMake: Delete Cache and Reconfigure
command.
Compiler not found
The CMAKE_C_COMPILER:
armv6-rpi-linux-gnueabihf-gcc
is not a full path and was not found in the PATH.
This indicates that you either didn't download and install the toolchain correctly, or that it has not been added to the PATH. See the previous page for more details.
Note that changing the
PATH in ~/.profile
only has effect after logging out and back
in again.
If you used export PATH="..."
in a terminal, this only affects
the current terminal, so unless you start VSCode from that terminal, it
won't see the updated PATH.
Compiler is not able to compile a simple test program
-- Check for working C compiler: ~/opt/x-tools/armv6-rpi-linux-gnueabihf/bin/armv6-rpi-linux-gnueabihf-gcc - broken
CMake Error at ~/.local/share/cmake-3.22/Modules/CMakeTestCCompiler.cmake:69 (message):
The C compiler
"~/opt/x-tools/armv6-rpi-linux-gnueabihf/bin/armv6-rpi-linux-gnueabihf-gcc"
is not able to compile a simple test program.
It fails with the following output:
Change Dir: ~/GitHub/RPi-Cross-Cpp-Development/build/CMakeFiles/CMakeTmp
Run Build Command(s):/usr/bin/ninja cmTC_f7b02 && [1/2] Building C object CMakeFiles/cmTC_f7b02.dir/testCCompiler.c.o
[2/2] Linking C executable cmTC_f7b02
FAILED: cmTC_f7b02
: && ~/opt/x-tools/armv6-rpi-linux-gnueabihf/bin/armv6-rpi-linux-gnueabihf-gcc --sysroot=/var/lib/schroot/chroots/wrong-name-armhf -mcpu=arm1176jzf-s -mcpu=arm1176jzf-s CMakeFiles/cmTC_f7b02.dir/testCCompiler.c.o -o cmTC_f7b02 && :
~/opt/x-tools/armv6-rpi-linux-gnueabihf/bin/../lib/gcc/armv6-rpi-linux-gnueabihf/11.2.0/../../../../armv6-rpi-linux-gnueabihf/bin/ld.bfd: cannot find crt1.o: No such file or directory
~/opt/x-tools/armv6-rpi-linux-gnueabihf/bin/../lib/gcc/armv6-rpi-linux-gnueabihf/11.2.0/../../../../armv6-rpi-linux-gnueabihf/bin/ld.bfd: cannot find crti.o: No such file or directory
collect2: error: ld returned 1 exit status
This error indicates that the path to the root filesystem is not correct.
Make sure that the CMAKE_SYSROOT
variable is set to the correct
schroot you created on the previous
page,
double check the name (with the architecture as suffix), and check that the
folder exists.
Boost not found
CMake Error at ~/.local/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
Could NOT find Boost (missing: Boost_INCLUDE_DIR program_options)
Call Stack (most recent call first):
~/.local/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:594 (_FPHSA_FAILURE_MESSAGE)
~/.local/share/cmake-3.22/Modules/FindBoost.cmake:2375 (find_package_handle_standard_args)
CMakeLists.txt:4 (find_package)
This error occurs when the Boost development package is not installed in your root filesystem. Follow the Installing the dependencies section to install it.