Edited at

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


動機

Crow とか uvw を使ったアプリを拙稿「Raspberry Pi のセルフビルド環境を QEMU で作る」のビルド環境で書いていたら、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 が正しく動くようになっています。


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 で作る」で作った ~/arm/raspbian を指しています。

$ cmake -DCMAKE_TOOLCHAIN_FILE=../raspberrypi-cross.cmake ../ -GNinja

とかやって使います。

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


仕方ないので Clang

raspberrypi cross clang でググると出てくる Making a Raspbian Cross Compilation SDK という記事を参考にします。


SDK 置き場を決める

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


コンパイラの入手

LLVM Download Page → LLVM 7.0.1 → Pre-built binaries から Ubuntu 18.04 の tarball をダウンロード。

$ 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://ftpmirror.gnu.org/binutils でミラーに飛ぶ。そこから binutils-2.32 を入手。

$ 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 で作る」で展開した ~/arm/raspbian をそのまま使うことにします。 chroot QEMU 環境があれば apt-get upgrade するのも簡単だし。


toolchain.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)


$ 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 を使用、ccache -C && ninja clean && time ninja


実機

Raspberry Pi 3B+, GPIO 給電 5V 4A, ヒートシンク装着済み、USB 接続の 64GB SSD から起動

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-configbrcmGLESv2 とか bcm_host とかを使う例として。


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()