Raspberry Pi のクロスビルド環境を Clang で作る

## 動機

[Crow](https://github.com/ipkn/crow) とか [uvw](https://github.com/skypjack/uvw) を使ったアプリを拙稿「[Raspberry Pi のセルフビルド環境を QEMU で作る](https://qiita.com/autch/items/c8c9cdc7b8e5821e81a4)」のビルド環境で書いていたら、300 行程度のソースのビルドに 3 分とかかかるようになってしまったので、もうちょっとマシな方法を考える。

### 前提

- 実機ビルドや QEMU ビルドは遅すぎるのでクロスコンパイルする
- ホストマシンは Debian buster x86_64
- Crow や uvw のようなモダン C++ がコンパイルできること
- `pkg-config` で実機用の `brcmGLESv2` などが検索できること
- CLion から使えること。CLion のカスタムツールチェイン機能は役に立たないので、自分で `toolchain.cmake` を書くのは構わない。

## https://github.com/raspberrypi/tools/arm-bcm2708

真っ先にこれを試すわけだけど、gcc が 4.8 とか 4.9 なので Crow がコンパイルできず。
一応 C や古い C++ しか使わないという人の助けのためにも toolchain.cmake ファイルを貼っておきます。

この toolchain ファイルでは pkg-config が正しく動くようになっています。

```cmake:raspberrypi-cross.cmake
# this one is important
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_VERSION 1)
SET(CMAKE_SYSTEM_PROCESSOR armhf)

# 記事参照
set(CMAKE_FIND_ROOT_PATH "$ENV{HOME}/arm/raspbian/")
# コンパイラをおいた場所。
set(MY_TOOLCHAIN_BIN "$ENV{HOME}/src/raspberrypi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin")

set(ENV{PKG_CONFIG_SYSROOT_DIR} "${CMAKE_FIND_ROOT_PATH}")
set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_FIND_ROOT_PATH}/usr/lib/pkgconfig:${CMAKE_FIND_ROOT_PATH}/usr/lib/arm-linux-gnueabihf/pkgconfig")
set(ENV{PKG_CONFIG_PATH} "${CMAKE_FIND_ROOT_PATH}/opt/vc/lib/pkgconfig")
set(ENV{PKG_CONFIG_ALLOW_SYSTEM_CFLAGS} "1")
set(ENV{PKG_CONFIG_ALLOW_SYSTEM_LIBS} "1")

# cross compiler settings
#set(CMAKE_CROSSCOMPILING TRUE)
set(CMAKE_SYSROOT "${CMAKE_FIND_ROOT_PATH}")
set(CMAKE_C_COMPILER "${MY_TOOLCHAIN_BIN}/arm-linux-gnueabihf-gcc")
set(CMAKE_CXX_COMPILER "${MY_TOOLCHAIN_BIN}/arm-linux-gnueabihf-g++")
set(CMAKE_LINKER "${MY_TOOLCHAIN_BIN}/arm-linux-gnueabihf-ld")
#set(CMAKE_MAKE_PROGRAM "/usr/bin/make")

# root path settings
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)    # use host system root for program
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)     # use CMAKE_FIND_ROOT_PATH for library
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)     # use CMAKE_FIND_ROOT_PATH for include
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
```

`CMAKE_FIND_ROOT_PATH` は「[Raspberry Pi のセルフビルド環境を QEMU で作る](https://qiita.com/autch/items/c8c9cdc7b8e5821e81a4)」で作った `~/arm/raspbian` を指しています。

```shell-session
$ cmake -DCMAKE_TOOLCHAIN_FILE=../raspberrypi-cross.cmake ../ -GNinja
```

とかやって使います。

raspberrypi/tools にある設定を使って、Crosstool-ng でもっと新しい gcc をビルドする試みは [うまくいってないらしい](https://github.com/raspberrypi/tools/issues/81) です。とはいえ彼らはカーネルからユーザランドまでの互換性と、歴代 Pi との互換性を確保して進めなければならないので、ユーザランドだけビルドできればいい今回のケースには関係ないのかもしれません。

## 仕方ないので Clang

`raspberrypi cross clang` でググると出てくる [Making a Raspbian Cross Compilation SDK](https://medium.com/@zw3rk/making-a-raspbian-cross-compilation-sdk-830fe56d75ba) という記事を参考にします。

### SDK 置き場を決める

上記記事中で `raspbian-sdk/` と呼んでいる SDK パスは、`$HOME/local/rpi-cross-clang` にしました。

### コンパイラの入手

[LLVM Download Page](http://releases.llvm.org/download.html) → LLVM 7.0.1 → Pre-built binaries から Ubuntu 18.04 の tarball をダウンロード。

```shell-session
$ wget http://releases.llvm.org/7.0.1/clang+llvm-7.0.1-x86_64-linux-gnu-ubuntu-18.04.tar.xz
$ tar xaf clang+llvm-7.0.1-x86_64-linux-gnu-ubuntu-18.04.tar.xz -C "~/local/rpi-cross-clang/prebuilt" --strip-components=1
$ cd ~/local/rpi-cross-clang/prebuilt/bin
$ ln -s clang arm-linux-gnueabihf-clang
$ ln -s clang++ arm-linux-gnueabihf-clang++
```
これは展開してシンボリックリンクを貼るだけ。

### binutils のビルド

[Binutils 公式](https://www.gnu.org/software/binutils/) から https://ftpmirror.gnu.org/binutils でミラーに飛ぶ。そこから binutils-2.32 を入手。

```shell-session
$ wget https://mirrors.tripadvisor.com/gnu/binutils/binutils-2.32.tar.xz
$ tar xaf binutils-2.32.tar.xz
$ cd binutils-2.32
$ ./configure --prefix=$HOME/local/rpi-cross-clang/prebuilt/ --target=arm-linux-gnueabihf --enable-gold=yes --enable-ld=yes --enable-targets=arm-linux-gnueabihf --enable-multilib --enable-interwork --disable-werror
$ make -j8
$ make install
```

### ヘッダとライブラリの入手

例によって例のごとく「[Raspberry Pi のセルフビルド環境を QEMU で作る](https://qiita.com/autch/items/c8c9cdc7b8e5821e81a4)」で展開した `~/arm/raspbian` をそのまま使うことにします。 chroot QEMU 環境があれば `apt-get upgrade` するのも簡単だし。

### toolchain.cmake ファイル

```cmake:raspberrypi-cross-clang.cmake
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_VERSION 1)
SET(CMAKE_SYSTEM_PROCESSOR armhf)

#set(CMAKE_SYSROOT "/path/to/your/raspbian")
set(CMAKE_FIND_ROOT_PATH "${CMAKE_SYSROOT}")
#set(MY_TOOLCHAIN_BIN "$ENV{HOME}/local/rpi-cross-clang/prebuilt/bin")
set(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN "${MY_TOOLCHAIN_BIN}")
set(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN "${MY_TOOLCHAIN_BIN}")
set(CMAKE_C_COMPILER_TARGET "arm-linux-gnueabihf")
set(CMAKE_CXX_COMPILER_TARGET "arm-linux-gnueabihf")
set(TARGET "arm-linux-gnueabihf")

set(ENV{PKG_CONFIG_SYSROOT_DIR} "${CMAKE_FIND_ROOT_PATH}")
set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_FIND_ROOT_PATH}/usr/lib/pkgconfig:${CMAKE_FIND_ROOT_PATH}/usr/lib/${TARGET}/pkgconfig")
set(ENV{PKG_CONFIG_PATH} "${CMAKE_FIND_ROOT_PATH}/opt/vc/lib/pkgconfig:${CMAKE_FIND_ROOT_PATH}/usr/local/lib/pkgconfig:${CMAKE_FIND_ROOT_PATH}/usr/local/lib/${TARGET}/pkgconfig")
set(ENV{PKG_CONFIG_ALLOW_SYSTEM_CFLAGS} "1")
set(ENV{PKG_CONFIG_ALLOW_SYSTEM_LIBS} "1")

set(CMAKE_EXE_LINKER_FLAGS "-B${CMAKE_FIND_ROOT_PATH}/usr/lib/gcc/${TARGET}/6 -L${CMAKE_FIND_ROOT_PATH}/usr/lib/gcc/${TARGET}/6 -Wl,-rpath-link,${CMAKE_FIND_ROOT_PATH}/opt/vc/lib:${CMAKE_FIND_ROOT_PATH}/usr/lib/${TARGET}:${CMAKE_FIND_ROOT_PATH}/lib/${TARGET}")
set(CMAKE_CXX_FLAGS "-isystem ${CMAKE_FIND_ROOT_PATH}/usr/include/c++/6 -isystem ${CMAKE_FIND_ROOT_PATH}/usr/include/${TARGET}/c++/6")

set(CMAKE_CROSSCOMPILING TRUE)
set(CMAKE_C_COMPILER "${MY_TOOLCHAIN_BIN}/${TARGET}-clang")
set(CMAKE_CXX_COMPILER "${MY_TOOLCHAIN_BIN}/${TARGET}-clang++")
set(CMAKE_LINKER "${MY_TOOLCHAIN_BIN}/${TARGET}-ld")
#set(CMAKE_MAKE_PROGRAM "/usr/bin/make")

# root path settings
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)    # use host system root for program
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)     # use CMAKE_FIND_ROOT_PATH for library
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)     # use CMAKE_FIND_ROOT_PATH for include
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
```

```shell-session
$ cmake -DCMAKE_TOOLCHAIN_FILE=../raspberrypi-cross-clang.cmake \
        -DCMAKE_SYSROOT=$HOME/arm/raspbian \
        -DMY_TOOLCHAIN_BIN=$HOME/local/rpi-cross-clang/prebuilt/bin \
        ../ -GNinja
```

## 比較

9 ファイル、300 行程度、ccache は実行前にキャッシュをクリア
ビルドはすべて [Ninja](https://ninja-build.org/) を使用、`ccache -C && ninja clean && time ninja`

### 実機

Raspberry Pi 3B+, [GPIO 給電](http://akizukidenshi.com/catalog/g/gP-11073/) [5V 4A](http://akizukidenshi.com/catalog/g/gM-06238/), [ヒートシンク](https://raspberry-pi.ksyic.com/main/index/pdp.id/36/pdp.open/36)装着済み、[USB 接続の 64GB SSD から起動](https://dev.classmethod.jp/hardware/raspberry-pi-3-model-b-plus-usb-boot/)
[Cotire](https://github.com/sakra/cotire) 有効

```
$ time ninja
real	18m3.033s
user	2m55.337s
sys	0m47.376s
```

もう少し規模が増えるとメモリが足りないかもしれない。`ninja -j1` すれば回避できるだろうが、そうするとさらにコンパイル時間が伸びることになる。

### QEMU in chroot

ThinkPad A285 Ryzen 7 PRO 2700U 4C8T
Debian buster x86_64
Cotire 有効

```
$ time ninja
real	3m26.746s
user	4m59.894s
sys	0m3.895s
```

### 今回のクロス Clang 環境

マシンスペックは上記 QEMU in chroot と同じ
Cotire 無効

```
$ time ninja
real	0m10.166s
user	0m30.007s
sys	0m1.308s
```

一応 Clang ビルドでもアプリの正常動作は確認できたので、リリースビルド的なものでなければこれで十分でしょう。

## ちなみにこんな CMakeLists を書いている

Raspberry Pi で CMake で `pkg-config` で `brcmGLESv2` とか `bcm_host` とかを使う例として。

```cmake:CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(foo-project C CXX)

set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake")
include(cotire)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
set(THREADS_PREFER_PTHREAD_FLAG ON)

find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)

set(VC_ROOT "/opt/vc")
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${VC_ROOT}/lib/pkgconfig")
set(OPT_VC_SRC "${VC_ROOT}/src/hello_pi")

find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
endif(CCACHE_FOUND)

find_package(Boost 1.52 COMPONENTS system thread REQUIRED)
include_directories(${Boost_INCLUDE_DIR})

pkg_check_modules(BCM_HOST REQUIRED bcm_host)
pkg_check_modules(BRCMGLESV2 REQUIRED brcmglesv2)
pkg_check_modules(BRCMEGL REQUIRED brcmegl)
pkg_check_modules(AVFORMAT REQUIRED libavformat)
pkg_check_modules(JPEG REQUIRED libjpeg)
pkg_check_modules(PNG REQUIRED libpng)
pkg_check_modules(UV REQUIRED libuv)
pkg_check_modules(MMAL REQUIRED mmal)
pkg_check_modules(CURL REQUIRED libcurl)

set(OPT_VC_INCLUDE "${VC_ROOT}/include")

include_directories(${OPT_VC_INCLUDE})
include_directories(${BCM_HOST_INCLUDE_DIRS})
include_directories(${BRCMGLESV2_INCLUDE_DIRS})
include_directories(${BRCMEGL_INCLUDE_DIRS})
include_directories(${AVFORMAT_INCLUDE_DIRS})
include_directories(${JPEG_INCLUDE_DIRS})
include_directories(${PNG_INCLUDE_DIRS})
include_directories(${UV_INCLUDE_DIRS})
include_directories(${MMAL_INCLUDE_DIRS})
include_directories(${CURL_INCLUDE_DIRS})

include_directories(${PROJECT_SOURCE_DIR}/uvw)
include_directories(${PROJECT_SOURCE_DIR}/crow)

link_directories(${BCM_HOST_LIBRARY_DIRS})
link_directories(${BRCMGLESV2_LIBRARY_DIRS})
link_directories(${BRCMEGL_LIBRARY_DIRS})
link_directories(${AVFORMAT_LIBRARY_DIRS})
link_directories(${JPEG_LIBRARY_DIRS})
link_directories(${PNG_LIBRARY_DIRS})
link_directories(${UV_LIBRARY_DIRS})
link_directories(${MMAL_LIBRARY_DIRS})
link_directories(${CURL_LIBRARY_DIRS})

add_definitions(
        -DSTANDALONE -D__STDC_CONSTANT_MACROS
        -D__STDC_LIMIT_MACROS -DTARGET_POSIX
        -DPIC -D_REENTRANT -D_LARGEFILE64_SOURCE
        -D_FILE_OFFSET_BITS=64 -U_FORTIFY_SOURCE
        -DHAVE_LIBOPENMAX=2 -DOMX -DOMX_SKIP64BIT
        -DUSE_EXTERNAL_OMX -DHAVE_LIBBCM_HOST -DUSE_EXTERNAL_LIBBCM_HOST -DUSE_VCHIQ_ARM
)
add_compile_options(
        -fPIC
        -ftree-vectorize
        -Wno-psabi
        -g
        -Wall
        -Weffc++
        -O3
)

SET(COMPILE_DEFINITIONS -Werror -Wall)

add_executable(foo-program
    main.cpp
)
target_link_libraries(foo-program
    ${BCM_HOST_LIBRARIES}
    ${BRCMGLESV2_LIBRARIES}
    ${BRCMEGL_LIBRARIES}
    ${AVFORMAT_LIBRARIES}
    ${JPEG_LIBRARIES}
    ${PNG_LIBRARIES}
    ${UV_LIBRARIES}
    ${MMAL_LIBRARIES}
    ${Boost_LIBRARIES}
    ${CURL_LIBRARIES}

    Threads::Threads
)
if(NOT DONT_USE_COTIRE) # defined in toolchain file
cotire(foo-program)
endif()
```