Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
13
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated 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()
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
13
Help us understand the problem. What are the problem?