10
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-10

動機

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()
10
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?