| # How to cross-compile Clang/LLVM using Clang/LLVM |
| |
| ## Introduction |
| |
| This document contains information about building LLVM and |
| Clang on a host machine, targeting another platform. |
| |
| For more information on how to use Clang as a cross-compiler, |
| please check <https://clang.llvm.org/docs/CrossCompilation.html>. |
| |
| This document describes cross-building a compiler in a single stage, using an |
| existing `clang` install as the host compiler. |
| |
| ```{note} |
| These instructions have been tested for targeting 32-bit ARM, AArch64, or |
| 64-bit RISC-V from an x86_64 Linux host. But should be equally applicable to |
| any other target. |
| ``` |
| |
| ## Setting up a sysroot |
| |
| You will need a sysroot that contains essential build dependencies compiled |
| for the target architecture. In this case, we will be using CMake and Ninja on |
| a Linux host and compiling against a Debian sysroot. Detailed instructions on |
| producing sysroots are outside of the scope of this documentation, but the |
| following instructions should work on any Linux distribution with these |
| pre-requisites: |
| |
| - `binfmt_misc` configured to execute `qemu-user` for binaries of the |
| target architecture. This is done by installing the `qemu-user-static` |
| and `binfmt-support` packages on Debian-derived distributions. |
| - Root access (setups involving `proot` or other tools to avoid this |
| requirement may be possible, but aren't described here). |
| - The `debootstrap` tool. This is available in most distributions. |
| |
| The following snippet will initialise sysroots for 32-bit Arm, AArch64, and |
| 64-bit RISC-V (just pick the target(s) you are interested in): |
| |
| ```bash |
| sudo debootstrap --arch=armhf --variant=minbase --include=build-essential,symlinks stable sysroot-deb-armhf-stable |
| sudo debootstrap --arch=arm64 --variant=minbase --include=build-essential,symlinks stable sysroot-deb-arm64-stable |
| sudo debootstrap --arch=riscv64 --variant=minbase --include=build-essential,symlinks unstable sysroot-deb-riscv64-unstable |
| ``` |
| |
| The created sysroot may contain absolute symlinks, which will resolve to a |
| location within the host when accessed during compilation, so we must convert |
| any absolute symlinks to relative ones: |
| |
| ```bash |
| sudo chroot sysroot-of-your-choice symlinks -cr . |
| ``` |
| |
| ## Configuring CMake and building |
| |
| For more information on how to configure CMake for LLVM/Clang, |
| see {doc}`CMake`. Following CMake's recommended practice, we will create a |
| [toolchain file](https://cmake.org/cmake/help/book/mastering-cmake/chapter/Cross%20Compiling%20With%20CMake.html#toolchain-files). |
| |
| The following assumes you have a system install of `clang` and `lld` that |
| will be used for cross compiling and that the listed commands are executed |
| from within the root of a checkout of the `llvm-project` git repository. |
| |
| First, set variables in your shell session that will be used throughout the |
| build instructions: |
| |
| ```bash |
| SYSROOT=$HOME/sysroot-deb-arm64-stable |
| TARGET=aarch64-linux-gnu |
| CFLAGS="" |
| ``` |
| |
| To customise details of the compilation target or choose a different |
| architecture altogether, change the `SYSROOT`, |
| `TARGET`, and `CFLAGS` variables to something matching your target. For |
| example, for 64-bit RISC-V you might set |
| `SYSROOT=$HOME/sysroot-deb-riscv64-unstable`, `TARGET=riscv64-linux-gnu` |
| and `CFLAGS="-march=rva20u64"`. Refer to documentation such as your target's |
| compiler documentation or processor manual for guidance on which `CFLAGS` |
| settings may be appropriate. The specified `TARGET` should match the triple |
| used within the sysroot (i.e. `$SYSROOT/usr/lib/$TARGET` should exist). |
| |
| Then execute the following snippet to create a toolchain file: |
| |
| ```bash |
| cat - <<EOF > $TARGET-clang.cmake |
| set(CMAKE_SYSTEM_NAME Linux) |
| set(CMAKE_SYSROOT "$SYSROOT") |
| set(CMAKE_C_COMPILER_TARGET $TARGET) |
| set(CMAKE_CXX_COMPILER_TARGET $TARGET) |
| set(CMAKE_C_FLAGS_INIT "$CFLAGS") |
| set(CMAKE_CXX_FLAGS_INIT "$CFLAGS") |
| set(CMAKE_LINKER_TYPE LLD) |
| set(CMAKE_C_COMPILER clang) |
| set(CMAKE_CXX_COMPILER clang++) |
| 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) |
| EOF |
| ``` |
| |
| Then configure and build by invoking `cmake`: |
| |
| ```bash |
| cmake -G Ninja \ |
| -DCMAKE_BUILD_TYPE=Release \ |
| -DLLVM_ENABLE_PROJECTS="lld;clang" \ |
| -DCMAKE_TOOLCHAIN_FILE=$(pwd)/$TARGET-clang.cmake \ |
| -DLLVM_HOST_TRIPLE=$TARGET \ |
| -DCMAKE_INSTALL_PREFIX=$HOME/clang-$TARGET \ |
| -S llvm \ |
| -B build/$TARGET |
| cmake --build build/$TARGET |
| ``` |
| |
| These options from the toolchain file and `cmake` invocation above are |
| important: |
| |
| - `CMAKE_SYSTEM_NAME`: Perhaps surprisingly, explicitly setting this |
| variable [causes CMake to set |
| CMAKE_CROSSCOMPIILING](https://cmake.org/cmake/help/latest/variable/CMAKE_CROSSCOMPILING.html#variable:CMAKE_CROSSCOMPILING). |
| - `CMAKE_{C,CXX}_COMPILER_TARGET`: This will be used to set the |
| `--target` argument to `clang`. The triple should match the triple used |
| within the sysroot (i.e. `$SYSROOT/usr/lib/$TARGET` should exist). |
| - `CMAKE_FIND_ROOT_PATH_MODE_*`: These [control the search behaviour for |
| finding libraries, includes or binaries](https://cmake.org/cmake/help/book/mastering-cmake/chapter/Cross%20Compiling%20With%20CMake.html#finding-external-libraries-programs-and-other-files). |
| Setting these prevents files for the host being used in the build. |
| - `LLVM_HOST_TRIPLE`: Specifies the target triple of the system the built |
| LLVM will run on, which also implicitly sets other defaults such as |
| `LLVM_DEFAULT_TARGET_TRIPLE`. For example, if you are using an x86_64 |
| host to compile for RISC-V, this will be a RISC-V triple. |
| - `CMAKE_SYSROOT`: The path to the sysroot containing libraries and headers |
| for the target. |
| - `CMAKE_INSTALL_PREFIX`: Setting this avoids installing binaries compiled |
| for the target system into system directories for the host system. It is |
| not required unless you are going to use the `install` target. |
| |
| See [LLVM's build documentation](https://llvm.org/docs/CMake.html#frequently-used-cmake-variables) for more |
| guidance on CMake variables (e.g. `LLVM_TARGETS_TO_BUILD` may be useful if |
| your cross-compiled binaries only need to support compiling for one target). |
| |
| ## Working around a ninja dependency issue |
| |
| If you followed the instructions above to create a sysroot, you may run into a |
| [longstanding problem related to path canonicalization in ninja](https://github.com/ninja-build/ninja/issues/1330). GCC canonicalizes system |
| headers in dependency files, so when ninja reads them it does not need to do |
| so. Clang does not do this, and unfortunately ninja does not implement the |
| canonicalization logic at all, meaning for some system headers with symlinks |
| in the paths, it can incorrectly compute a non-existing path and consider it |
| as always modified. |
| |
| If you are suffering from this issue, you will find any attempt at an |
| incremental build (including the suggested command to build the `install` |
| target in the next section) results in recompiling everything. `ninja -C |
| build/$TARGET -t deps` shows files in `$SYSROOT/include/*` that |
| do not exist (as the `$SYSROOT/include` folder does not exist) and you can |
| further confirm these files are causing `ninja` to determine a rebuild is |
| necessary with `ninja -C build/$TARGET -d deps`. |
| |
| A workaround is to create a symlink so that the incorrect |
| `$SYSROOT/include/*` dependencies resolve to files within |
| `$SYSROOT/usr/include/*`. This works in practice for the simple |
| cross-compilation use case described here, but is not a general solution. |
| |
| ```bash |
| sudo ln -s usr/include $SYSROOT/include |
| ``` |
| |
| ## Testing the just-built compiler |
| |
| Confirm the `clang` binary was built for the expected target architecture: |
| |
| ```bash |
| $ file -L ./build/aarch64-linux-gnu/bin/clang |
| ./build/aarch64-linux-gnu/bin/clang: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=516b8b366a790fcd3563bee4aec0cdfcb90bb1c7, not stripped |
| ``` |
| |
| If you have `qemu-user` installed you can test the produced target binary |
| either by invoking `qemu-{target}-static` directly: |
| |
| ```bash |
| $ qemu-aarch64-static -L $SYSROOT ./build/aarch64-linux-gnu/bin/clang --version |
| clang version 21.0.0git (https://github.com/llvm/llvm-project cedfdc6e889c5c614a953ed1f44bcb45a405f8da) |
| Target: aarch64-unknown-linux-gnu |
| Thread model: posix |
| InstalledDir: /home/asb/llvm-project/build/aarch64-linux-gnu/bin |
| ``` |
| |
| Or, if binfmt_misc is configured (as was necessary for debootstrap): |
| |
| ```bash |
| $ export QEMU_LD_PREFIX=$SYSROOT; ./build/aarch64-linux-gnu/bin/clang --version |
| clang version 21.0.0git (https://github.com/llvm/llvm-project cedfdc6e889c5c614a953ed1f44bcb45a405f8da) |
| Target: aarch64-unknown-linux-gnu |
| Thread model: posix |
| InstalledDir: /home/asb/llvm-project/build/aarch64-linux-gnu/bin |
| ``` |
| |
| ## Installing and using |
| |
| ```{note} |
| Use of the `install` target requires that you have set |
| `CMAKE_INSTALL_PREFIX` otherwise it will attempt to install in |
| directories under `/` on your host. |
| ``` |
| |
| If you want to transfer a copy of the built compiler to another machine, you |
| can first install it to a location on the host via: |
| |
| ```bash |
| cmake --build build/$TARGET --target=install |
| ``` |
| |
| This will install the LLVM/Clang headers, binaries, libraries, and other files |
| to paths within `CMAKE_INSTALL_PREFIX`. Then tar that directory for transfer |
| to a device that runs the target architecture natively: |
| |
| ```bash |
| tar -czvf clang-$TARGET.tar.gz -C $HOME clang-$TARGET |
| ``` |
| |
| The generated toolchain is portable, but requires compatible versions of any |
| shared libraries it links against. This means using a sysroot that is as |
| similar to your target operating system as possible is desirable. Other [CMake |
| variables](https://llvm.org/docs/CMake.html#frequently-used-cmake-variables) |
| may be helpful, for instance `LLVM_STATIC_LINK_CXX_STDLIB`. |
| |