5-Minute Read

Cross Compile to PinePhone with Clang

Click Here for GCC Part one

Click Here for GCC Part three

Advantages of Clang

If you have read the previous posts about cross-compiling with gcc, you know that we need to install the cross-compiler first. This is not that difficult, but it has a few pitfalls. In particular, the one provided by ArchLinux by default does not work in our use case.

The good thing about clang is that clang is inherently a cross-compiler. It lowers C/C++ source code into LLVM IR, which is platform independent. And LLVM then lower the IR further into platform-dependent machine code. One less tool to install, one less tool to maintain.

Toolchain and SDK

Toolchain

However, We still need to install Toolchain. Install clang, and install lld if you haven’t already. And like GCC post, install qemu-user-static for Arm emulation. It will be used during SDK generation and later when calling the target platform’s moc, qmake etc. To ensure the correctness our cross compile result. After installing qemu-user-static, don’t forget to restart bin-fmt service: systemctl restart systemd-binfmt.service. That is all for the toolchain.

SDK

The SDK contains the libraries for linking and the build tools for code generation. Since the compiled binary runs on target platform, these libraries and build tools should also be from the target platform. I have already written about how to make your own SDK for PinePhone on https://www.hanyoung.uk/blog/cross-compile-to-pinephone-part-one, but for the sake of completeness I will repeat it again.

Download the image and mount it to system

Get the Manjaro daily dev image from https://github.com/manjaro-arm/rootfs/releases If you want to test on other distros, use their image. Decompress the file and mount the actual system root partition into a directory. To find out where the partition starts, use fdisk:

$ fdisk -l Manjaro-ARM-plasma-mobile-pinephone-20210919.img
Disk Manjaro-ARM-plasma-mobile-pinephone-20210919.img: 5.87 GiB, 6307184640 bytes, 12318720 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x132613bb

Device                                            Boot  Start      End  Sectors   Size Id Type
Manjaro-ARM-plasma-mobile-pinephone-20210919.img1       62500   500000   437501 213.6M  c W95 FAT32 (LBA)
Manjaro-ARM-plasma-mobile-pinephone-20210919.img2      500001 12318719 11818719   5.6G 83 Linux

Here we see that it contains two partitions, the first partition is obviously the UEFI boot partition. The second partition is the system root. Now we know that the block size is 512 bytes and the second partition starts at block 500001, we mount it with offset 512 * 500001 = 256000512:

$ mkdir pp-mnt
$ sudo mount -o loop,offset=256000512 Manjaro-ARM-plasma-mobile-pinephone-20210919.img pp-mnt

Now the system root has been mounted on pp-mnt, but its content still in the image file Manjaro-ARM-plasma-mobile-pinephone-20210919.img. We copy it to another location in the hard driver of the host system. And unmount the image.

$ mkdir pinephone-manjaro-rootfs
$ cp -r pp-mnt pinephone-manjaro-rootfs
$ sudo umount pp-mnt

We can use arch-chroot to chroot into pinephone-manjaro-rootfs, but if we do that, pacman will complain about “can’t find mount point” and refuse to install packages. To satisfy pacman, we have to bind mount it first:

$ sudo mount --bind pinephone-manjaro-rootfs pinephone-manjaro-rootfs

Then chroot into it:

$ sudo arch-chroot pinephone-manjaro-rootfs

Once you are in, first init pacman keyring. Because we need to install some build dependencies later. This may take some time.

# pacman-key --init
# pacman-key --populate

Install gcc, extra-cmake-modules, we will not use gcc directly. Instead, we will link our binary against gcc c/c++ runtime (compiler-rt). Of course we can use LLVM’s compiler-rt, but no other programs on the Manjaro development image do, and neither do we.

# pacman -Syu gcc

Install dependencies for projects. We use a daily development image that includes the latest KDE libraries from git, so we have nothing more to install here.

All done, exit the chroot.

CMake toolchain file

We need to tell cmake that we are cross-compiling with clang by specifying a cmake toolchian file on the commandline. The toolchain contains the information about target platform, the compiler to use and where to find libraries.

I have named my toolchain file armv8-toolchain.cmake

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(triple aarch64-unknown-linux-gnu)

set(CMAKE_C_COMPILER clang)
set(CMAKE_C_COMPILER_TARGET ${triple})
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_CXX_COMPILER_TARGET ${triple})

set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")

set(CMAKE_SYSROOT /home/hany/Develop/CrossCompile/pinephone-manjaro-rootfs)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

I have explained some of the options above at https://www.hanyoung.uk/blog/cross-compile-to-pinephone-part-two, others I will explain here.

set(triple aarch64-unknown-linux-gnu)

The aarch64-unknown-linux-gnu tells clang the target platform. It is important to use the same name as that of gcc. You can find the name of gcc in SDK.

ls /home/hany/Develop/CrossCompile/pinephone-manjaro-rootfs/usr/lib/gcc/

Because otherwise cmake/clang will not find the gcc runtime libraries we link against.

set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")

Above lines tell cmake that we want to use LLVM’s linker, lld. CMake uses GCC’s ld by default.

I put armv8-toolchain.cmake in the root directory of the project.

Buiding & Packaging

Today we will compile kclock.

Always use out of source build, today’s build directory is called kclock-build, the treeview is like:

├── kclock │   ├── armv8-toolchain.cmake │   ├── CMakeLists.txt │   └── src ├── kclock-build

cd kclock-build

Before we call cmake, we first make sure we have set the QEMU prefix; I mentioned the reason for this in https://www.hanyoung.uk/blog/cross-compile-to-pinephone-part-two.

export QEMU_LD_PREFIX=/home/hany/Develop/CrossCompile/pinephone-manjaro-rootfs

and we have to set the CMAKE INSTALL PREFIX explicitly. Due to the toolchain file, cmake installs to the SDK directory by default.

cmake -DCMAKE_INSTALL_PREFIX=/usr ../kclock --toolchain ../kclock/armv8-toolchain.cmake

build the project

make -j `nproc`

We don’t stop here, what we really want is ArchLinux package. Grab the example PKGBUILD file.

cp /usr/share/pacman/PKGBUILD.proto PKGBUILD

And edit it to suit our needs

pkgname=kclock
pkgver=999_custom
pkgrel=1
epoch=
pkgdesc=""
arch=('any')
url=""
license=('GPL')
groups=()
depends=()
makedepends=()
checkdepends=()
optdepends=()
provides=()
conflicts=()
replaces=()
backup=()
options=()
install=
changelog=
noextract=()
md5sums=()
validpgpkeys=()

package() {
        cd "/home/hany/Develop/C++/Qt/KDE/Plamsa Mobile/kclock-build"
        DESTDIR="$pkgdir/" cmake --install .
}

Feel free to change pkgver, but the arch should be any. Notice the colon at the end of DESTDIR="$pkgdir/" cmake --install .

To build the package, run makepkg. scp kclock-999_custom-1-any.pkg.tar.zst to your pinephone and install it with pacman -U. Voilà!

Recent Posts

Categories

About

A young developer who loves Linux.