essay on programming languages, computer science, information techonlogies and all.

Wednesday, July 6, 2022

Cross-compile Armv7 from x86_64 using docker

Cross compiling sounds like a big water difficult to cross. Of course it asks a lot of knowledge but at the deep down, it is just another way of compiling and linking. And in here I am trying to show it by cross-compiling raspberrypi target from ubuntu x86_64 machine.

First, you need toolchain - arm gnu toolchain. I used 9.2 as it happended to be the version I used on work.

Second, part of target file system - /usr and /lib. For raspberry pi 32bit, download raspberry pi OS lite at here. Then extract /usr and /lib only.

With these two, we can make docker image for the build. Build needs x86_64 tools. And cross-compiler tool-chain - gcc-arm-xxx-x86_64-arm-xxx. And target root of raspbian os.

# Dockerfile
FROM  ubuntu:20.04

RUN useradd -s /bin/bash -u 1000 docker_user && \
    apt-get update && \
    apt-get install -y \
    gcc g++ cmake git ninja-build lcov bison flex curl python3 fakeroot libpcre3-dev libcurl4-openssl-dev libxml2-dev \
    cppcheck clang-tools clang-format clang-tidy
RUN apt-get install -y python3-pip 

# https://developer.arm.com/downloads/-/gnu-a
# AArch32 target with hard float (arm-none-linux-gnueabih, )
ADD gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf.tar.xz /opt

# SYSROOT from rapsbian buster armhf - usr, lib only
ADD raspios-buster-armhf-lite.usrlib.tar.gz /opt/raspios-buster-armhf-lite
RUN ln -s /opt/raspios-buster-armhf-lite/lib/arm-linux-gnueabihf/ /lib/arm-linux-gnueabihf

... snip ...


Third, we needs cmake cross-compiling toolchain file. Below cmake defines the cross-compiling by specifying compiler - gcc-arm-xxx - and include and lib path at /opt/raspios-xxx. You may wonders why is there no CMAKE_SYSROOT, CMAKE_FIND_ROOT_PATH. I found those generate number of error that ask other changes which seems to be too obscure to pursue. And I think it is succinct to tell the tool-chain where to look at to compile and link instead of using those magic words.

# toolchain_raspberrypi_gcc9.cmake

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION arm)
set(CMAKE_SYSTEM_PROCESSOR arm-linux-gnueabihf)
set(CMAKE_C_COMPILER /opt/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /opt/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-g++)

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/opt/raspios-buster-armhf-lite/usr")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/opt/raspios-buster-armhf-lite/usr/lib")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/opt/raspios-buster-armhf-lite/usr/lib/arm-linux-gnueabihf")

include_directories(BEFORE 
    /opt/raspios-buster-armhf-lite/usr/include
    /opt/raspios-buster-armhf-lite/usr/include/arm-linux-gnueabihf


With above cmake toolchain file, we can define remainging part of docker that prepares google unittest and google benchmark.

# Dockerfile
...
ADD toolchain_raspberrypi_gcc9.cmake .

RUN git clone https://github.com/google/googletest.git --depth 1 --branch release-1.11.0 &&\
    cd googletest &&\
    cmake -S . -B ./build_x86_64 &&\
    cmake --build ./build_x86_64 &&\
    cmake --install ./build_x86_64 &&\
    cmake -S . -B ./build_arm -DCMAKE_TOOLCHAIN_FILE=../toolchain_raspberrypi_gcc9.cmake &&\
    cmake --build ./build_arm &&\
    cmake --install ./build_arm --prefix /opt/raspios-buster-armhf-lite/usr/ &&\
    cd ../ &&\
    rm -rf googletest

ADD CMakeLists.benchmark.txt .

RUN git clone https://github.com/google/benchmark.git --depth 1 --branch v1.6.1 && \ 
    cd benchmark &&\
    mv ../CMakeLists.benchmark.txt ./CMakeLists.txt &&\
    cmake -S . -B ./build_x86_64 -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0&&\
    cmake --build ./build_x86_64 --config Release &&\
    cmake --install ./build_x86_64 &&\
    cmake -S . -B ./build_arm -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0 -DCMAKE_TOOLCHAIN_FILE=../toolchain_raspberrypi_gcc9.cmake &&\
    cmake --build ./build_arm &&\
    cmake --install ./build_arm --prefix /opt/raspios-buster-armhf-lite/usr/ &&\
    cd ../ &&\
    rm -rf benchmark


After you generate docker image, you can go in the image, and make the host build and cross-compile build.

$ docker run --rm -it -v "$PWD":/du -w /du -u 0 samplecpp_build:0.1

/du# rm -rf ./build_x86_64
/du# cmake -S . -B ./build_x86_64
/du# cmake --build ./build_x86_64

/du# rm -rf ./build_arm
/du# cmake -S . -B ./build_arm -DCMAKE_TOOLCHAIN_FILE=../toolchain_raspberrypi_gcc9.cmake
/du# cmake --build ./build_arm


Cross-compiling means you specify compiler and include and lib path. And that is all.

You can find the code at my gitlab project samplecpp