動機
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 が正しく動くようになっています。
# 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 ファイル
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-config
で brcmGLESv2
とか bcm_host
とかを使う例として。
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()