macOS上のRaspbianクロス環境「決定版」 [Raspberry Pi]
Raspberry Pi用のクロスコンパイル環境にこの人の作ってくれたツールチェインとクロスコンパイラを使ってEclipseでRaspbian用のIDEにしてきた。Linaroのクロスコンパイラを使ってツールチェイン全体を構築している。
しかしもうすでに4年前のタイムスタンプで、glibcとかのバージョンがRaspberry Piにインストールされてるのに比べると齟齬が大きくなってきた。
ツールチェインにない新しいライブラリを追加しようと思っても僕がlinuxのライブラリの構造をよく知らないせいで、リンクできない、シンボルが見つからない、バージョンが違う、ということになってしまう。
ということで代わりのものを探していた。macOS用となると非常に限られるけど、素晴らしいものを教えてくれてる人がいた。 この人やこの人に従ってやってみるとちゃんとできた。これをEclipseで動かせるようにした。これこそmacOS上のRaspbianクロスコンパイルIDE環境の決定版である。その特徴は
ありがとうzw3rktechさんとSungjin Hanさん、そしてwelzelsさん!
今回は長くなるけど具体的なインストールの仕方なんかの話をするので、macOSでRaspberry Pi向けの開発をしている人はぜひ試してほしい。そしておそらくWindowsでも同じことをすれば完全に同じネイティブなクロス環境ができるはずである。僕自身は関係ないけど.....
まずインストールから順番に示す。
ということでLLVM+clangをダウンロードする(教えてくれた人たちはwgetを使ってるけどmacOSに標準では入っていない。代わりにcurlを使う。僕はcurlの方が使い慣れてるし)。現在での最新版は6.0.0。
300MB近くあるのでかなり時間がかかる。これをさっき作った作業ディレクトリの中に展開する。
これでコンパイラとリンカその他が揃った。
教えてくれた人も指摘してるが、ターゲットであるRaspbianには実行用のパッケージさえあればいいけど、クロス環境にはヘッダなどを含んだパッケージが必要になる。具体的に言えば例えばRaspbianにはlibxml2パッケージがあればいいが、クロス環境側にはlibxml2-devパッケージが必要になるので、これもインストールしてコピーする必要がある。容量が厳しいならコピー後はRaspbian側は消してしまって構わない。
Homebrewのrsyncは複数ソースからを1行に書けるらしいけど、macOS標準のrsyncはダメなので、こんなスクリプトを書く。
コピーを受け取るだけなのでroot権限はいらないはずだけど、ログイン認証が必要になるのでsshの公開鍵認証を設定しておいた方が楽になる(macOSのrsyncはrshからではなくまずsshで接続を試すらしい。manページ参照。Raspbianが相手ではsshで接続が確立する)。
すっぴんのstretchでも1GBを超えるので、不要とはっきり分かっているものがあるなら、除いておいた方がいいけど、それもめんどくさい。
僕の場合
これをセーブして実行可能にマーク
教えてくれた人たちはこのまま使ってるけど、僕はLinaroのパターンと同じにして
僕が使っているEclipseは
Cross GCCのproject(gccじゃないけど)を作って、Cross GCC commandに
パスはclangをインストールした場所を指定する。
ProjectのPropertyパネルを開いてCross Settingsを見れば となっているはず。
そして「Cross GCC Compiler」のCommandを
そして「Cross GCC Linker」のCommandを 同じく
デフォルトのインクルード先やライブラリの場所はスクリプトに埋めたsysrootにあるので、基本的な部分はこれでOK。hello.cでも書いてコンパイルしてみれば確認できる。
あとは他の必要な設定、/usr/localにパスを通したり、pigpioやpthreadライブラリを追加したり、とかをすればいい。
僕自身はこのあと、Eclipseで書いたソースファイルをXcodeに読み込んだXcodeのプロジェクトを作っている。僕はEclipseよりXcodeのほうが使い慣れてるのと、Raspbian単独で動作するのではなくmacOSをクライアントにして動くようなのばかり作っているので、RaspbianとmacOSとをひとつのプロジェクトにまとめてしまっている。Xcodeで編集とmacOS側のコンパイル/デバグとRaspbian側の構文チェックまでやって(pigpioのAPIスタブとかも作ってある)、Eclipse上ではクロスコンパイルとリモートデバグだけをやってる。
ほんとはEclipseでやってる部分も含めて全部Xcode上でできるのが僕にとっては便利なんだけど、それはAppleが許してくれないようである。まあ、しょうがない。
ローカルでarm-linux-gnueabihf-gdb -> /usr/bin/lldb
リモートで/usr/bin/gdbserver -> /usr/bin/lldb-server
に入れ替えてみたけど、動かなかった(Raspbian側でlldb-serverは起動するけどそこから何もしない)。Eclipse上での設定が必要らしい。さすがに甘すぎた。
gdbはとりあえずLinaroのやつをコピーすれば問題ないので当面は困ることはなさそう。具体的にはクライアント側の
いずれはこのバージョンのgdbも使えなくなるだろうし、lldbのほうはあとで確認しよう。
最初Raspbianのファイルをそのままコピーしてきて、リンクするときのパスに指定したが動かない。ソースからクロスコンパイルしようとしたんだけど、glibcのバージョンなんかが古いし、それ以外にも依存するライブラリがたくさんあって、なかなかうまくいかない。
そもそも動的ライブラリってどうやって名前解決するのか知らなかったので、調べてみてわかった。ああ、これじゃダメだわ。
ちょっとライブラリの勉強をしたおかげで、libxml2を動かせそうにはなったんだけど、他のライブラリを追加したいときはまたこの面倒を繰り返さないといけない。sysrootのメカニズムがわかったので、全部そのデンでできないか、と考えていて行き当たった。
僕がunixを使い始めた当時はライブラリは「.a」のスタティックしかなかった。スタティックライブラリのリンクは簡単で、コンパイルの後リンクに成功すればあとは何も考える必要はなかった。「.so」のダイナミックリンクライブラリがいつから使われるようになったのかよく覚えてないけど、ユーザが簡単に移行できるようになっていたんだろう。僕は全く意識することがなかった。
ところがいまではライブラリのバージョン管理の問題や互換性の問題からとんでもなく複雑になっているらしい。ライブラリが別のライブラリを必要とする場合、実行時にリンクローダにシンボルを解決してもらうんではなく、呼び出し側のライブラリが必要なライブラリの絶対パスを指定することがあるらしい。このためそのパスに必要なライブラリがなければ実行時ではなくリンクの時点で失敗する。
ただライブラリをツールチェインにコピーしてきてLD_LIBRARY_PATHとかを指定するだけではダメなのはこのせいらしい。
さらにクロスコンパイル環境では致命的になる。絶対パスで指定してしまうと開発環境側のパスをたどることになる。それでsysrootを作ったんだろう。sysrootはgccのオプションで絶対パス指定されたディレクトリのルートを他のディレクトリで置き換えるような機能をはたす。ただしこれは優先順位は低いらしくて、例えば-I指定したパスには影響しない、とか非常にややこしい。
前のLinaroを使ったクロス環境ではsysrootを使っていなかったようで、あとから追加したいときにだけ使うようになっていたらしいけど、今度のは全部がsysrootにぶら下がることになっている。
その分シンプルで、追加ライブラリをクロスコンパイルする必要はなくただターゲット環境からコピーしてくるだけで済む。組み込みやArduinoなんかではメリットはまったくないけどRaspberry Piでは非常に有効である。
実はこれに出会う前、libxml2が依存しているglibcや他のライブラリのバージョンをなんとかして上げるか、Crosstool-NGでなんとかして自分でツールチェインを作るか、と悩んでいた。特にglibcの依存は大きくて、いろんなところに影響が出る。Crosstool-NGは僕から見ると非常に癖のあるシステムのように思えて、動かすまでめちゃ苦労しそうで実際に手を出すところまでもいかなかった。最悪はRaspbian側に開発環境を全部移動しようか、と考えていた。
すでにmacOS+Raspbianでもうソコソコの数を仕事用の装置に使ってしまっていて、そんなわけで頭を抱えていた、というかこのままではけつかっちんになってマジでヤバいんではないかい、とヒヤヒヤしていた。
しかしほんとにLLVMを使うなんてまったく思いもしなかった。
ほんとにありがたい。感謝感謝。
しかしもうすでに4年前のタイムスタンプで、glibcとかのバージョンがRaspberry Piにインストールされてるのに比べると齟齬が大きくなってきた。
ツールチェインにない新しいライブラリを追加しようと思っても僕がlinuxのライブラリの構造をよく知らないせいで、リンクできない、シンボルが見つからない、バージョンが違う、ということになってしまう。
ということで代わりのものを探していた。macOS用となると非常に限られるけど、素晴らしいものを教えてくれてる人がいた。 この人やこの人に従ってやってみるとちゃんとできた。これをEclipseで動かせるようにした。これこそmacOS上のRaspbianクロスコンパイルIDE環境の決定版である。その特徴は
- コード生成にLLVM+clangとgnuのbinutils
- Raspbianのライブラリ群がそのままツールチェインに使える
- EclipseをIDEとして使用
- すべてが現在アクティブなプロジェクトなので環境を最新に維持できる
ありがとうzw3rktechさんとSungjin Hanさん、そしてwelzelsさん!
今回は長くなるけど具体的なインストールの仕方なんかの話をするので、macOSでRaspberry Pi向けの開発をしている人はぜひ試してほしい。そしておそらくWindowsでも同じことをすれば完全に同じネイティブなクロス環境ができるはずである。僕自身は関係ないけど.....
1 ツールチェインの作成
今回のツールチェインのキモは- gccの代わりにLLVM+clangを選択
- sysrootメカニズム
まずインストールから順番に示す。
1.1 作業ディレクトリ
とりあえずどこでもいいので作業ディレクトリを作る。教えてくれた人に従ってmkdir -p raspbian-sdk/{prebuilt,sysroot}とする(僕は作業途中のゴミがTimeMachineにアップロードされてしまわないように~/Downloadsの中でやった)。最終的にはこれをこのまま/usr/localなどにインストールすことになる。それはまあ最後に。
1.2 homebrewの回避
zw3rktechさんSungjin Hanさんはhomebrewを使っているけど、必須ではない。ほんのちょっと手間を省くためだけに使っていて、僕は自分で使わない(使えない)ものをインストールするのは好きではないので、そのほんのちょっとの手間をかける方を選んだ。1.3 ダウンロード
1.3.1 LLVM+clang
Xcodeにはそのバンドルの中のDeveloper/Toolchainsにllvmとclangは入ってるけど、arm-linux-gnueabihfのコードとフォーマットで出力できるようなオプションを持っていないし、入れても無視される。AppleはどうやらXcodeがApple向け以外に使われることを頑として拒絶しているようである。ということでLLVM+clangをダウンロードする(教えてくれた人たちはwgetを使ってるけどmacOSに標準では入っていない。代わりにcurlを使う。僕はcurlの方が使い慣れてるし)。現在での最新版は6.0.0。
$ curl -L -O http://releases.llvm.org/6.0.0/clang+llvm-6.0.0-x86_64-apple-darwin.tar.xz「$ 」はシェルのプロンプト。以下同じ。行がラップして表示されてしまってるけど、1行に書く。
300MB近くあるのでかなり時間がかかる。これをさっき作った作業ディレクトリの中に展開する。
$ tar -xzvf clang+llvm-6.0.0-x86_64-apple-darwin.tar.xz \ -C raspbian-sdk/prebuilt --strip-components=1展開すると1.4GBぐらいに増えてわかめちゃんになる。これはもうすでにmacOS用のバイナリになってるのでことあと何もすることはない。
1.3.2 binutils
この次にbinutilsを取ってくる。binutilsはgnuのバイナリ用ツール群で、今回の環境にはリンカなどをここから得ることになる。現在の最新版は2.30(gitでは2.31が見えてる)。$ curl -L -O http://ftp.gnu.org/gnu/binutils/binutils-2.30.tar.xz $ tar -xzvf binutils-2.30.tar.xzこっちはおとなしくて20MBほどで展開すると300MB弱。ソースが落ちてくるのでいつもの通り
$ cd binutils-2.30 $ ./configure --prefix=/[absolutePathTo]/raspbian-sdk/prebuilt \ --target=arm-linux-gnueabihf \ --enable-gold=yes \ --enable-ld=yes \ --enable-targets=arm-linux-gnueabihf \ --enable-multilib \ --enable-interwork \ --disable-werror $ make $ make install $ cd ..ここでインストール先をさっき作った作業ディレクトリのprebuiltにする。[absolutePathTo]はそこへの絶対パス。configureに渡す引数でターゲットを指定するようである。makeすると僕の環境ではgettextのWarningがいっぱい出るけど、無視するとエラーはなくてビルドできてしまう。
これでコンパイラとリンカその他が揃った。
1.3.3 Raspbianからのコピー
あとはツールチェインの残り、ヘッダやライブラリをRaspbianからコピーする。ターゲットになるRaspbianに(apt updateしてから)必要なものをインストールしておく。コピーにはrsyncが便利である。教えてくれた人も指摘してるが、ターゲットであるRaspbianには実行用のパッケージさえあればいいけど、クロス環境にはヘッダなどを含んだパッケージが必要になる。具体的に言えば例えばRaspbianにはlibxml2パッケージがあればいいが、クロス環境側にはlibxml2-devパッケージが必要になるので、これもインストールしてコピーする必要がある。容量が厳しいならコピー後はRaspbian側は消してしまって構わない。
Homebrewのrsyncは複数ソースからを1行に書けるらしいけど、macOS標準のrsyncはダメなので、こんなスクリプトを書く。
#!/bin/sh rsync -rzvLR --safe-links $1:/usr/lib/arm-linux-gnueabihf raspbian-sdk/sysroot rsync -rzvLR --safe-links $1:/usr/lib/gcc/arm-linux-gnueabihf raspbian-sdk/sysroot rsync -rzvLR --safe-links $1:/usr/include raspbian-sdk/sysroot rsync -rzvLR --safe-links $1:/lib/arm-linux-gnueabihf raspbian-sdk/sysroot rsync -rzvLR --safe-links $1:/usr/local/lib raspbian-sdk/sysroot rsync -rzvLR --safe-links $1:/usr/local/include raspbian-sdk/sysrootこのスクリプトの引数にRaspbianのユーザとホストを渡す。例えばこのスクリプトの名前をmrsyncとでもしてchmod +xしたあと
$ ./mrsync pi@raspberrypi.localとすると、ホスト名「raspberrypi.local」の「pi」ユーザとしてログインして必要なディレクトリのファイルをコピーすることになる。
コピーを受け取るだけなのでroot権限はいらないはずだけど、ログイン認証が必要になるのでsshの公開鍵認証を設定しておいた方が楽になる(macOSのrsyncはrshからではなくまずsshで接続を試すらしい。manページ参照。Raspbianが相手ではsshで接続が確立する)。
すっぴんのstretchでも1GBを超えるので、不要とはっきり分かっているものがあるなら、除いておいた方がいいけど、それもめんどくさい。
僕の場合
$ du -d 2 -h sysroot/ 44M sysroot//usr/include 37M sysroot//usr/local 1.3G sysroot//usr/lib 1.4G sysroot//usr 30M sysroot//lib/arm-linux-gnueabihf 30M sysroot//lib 1.4G sysroot/だった。/usr/libだけが極端にでかい。まあ、これでライブラリなどがRaspbianのルートディレクトリ以下と同じ構造でsysroot以下にコピーされたことになる。
1.4 ラッパスクリプトの作成
clangへのラッパスクリプトを書く。$ vi raspbian-sdk/prebuilt/bin/arm-linux-gnueabihf-clangでエディタを開いて
#!/bin/bash BASE=$(dirname $0) SYSROOT="${BASE}/../../sysroot" TARGET=arm-linux-gnueabihf COMPILER_PATH="${SYSROOT}/usr/lib/gcc/${TARGET}/6.3.0" exec env COMPILER_PATH="${COMPILER_PATH}" \ "${BASE}/clang" --target=${TARGET} \ --sysroot="${SYSROOT}" \ -isysroot "${SYSROOT}" \ -L"${COMPILER_PATH}" \ --gcc-toolchain="${BASE}" \ "$@"sysrootとバイナリターゲットの設定をしてclang本体を呼んでいることがわかる。こんなんでいいんだ。いかにも書き慣れた人のスクリプトに見える。
これをセーブして実行可能にマーク
$ chmod +x raspbian-sdk/prebuilt/bin/arm-linux-gnueabihf-clangしておく。
教えてくれた人たちはこのまま使ってるけど、僕はLinaroのパターンと同じにして
$ sudo mv raspbian-sdk /usr/local/ $ sudo chown -R root:wheel /usr/local/raspbian-sdkとしておく。
2 Eclipseで使えるようにする
このままでもコマンドラインで使えるけど、名前は長いしパスを通さないとフルパス書かないといけない。これでは使いづらいのでEclipseで使えるような設定をする。僕が使っているEclipseは
Eclipse IDE for C/C++ Developers Version: Oxygen.3a Release (4.7.3a) Eclipse C/C++ Development Tools Version: 9.4.3.201802261533である。
Cross GCCのproject(gccじゃないけど)を作って、Cross GCC commandに
Cross compiler prefix : arm-linux-gnueabihf- Cross complier path : /usr/local/raspbian-sdk/prebuilt/binとする。
パスはclangをインストールした場所を指定する。
ProjectのPropertyパネルを開いてCross Settingsを見れば となっているはず。
そして「Cross GCC Compiler」のCommandを
Command : clangとする(上図の右上)。これはつまりさっき書いたラッパスクリプトである。
そして「Cross GCC Linker」のCommandを 同じく
Command : clangとする(上図の右上のほう)。
デフォルトのインクルード先やライブラリの場所はスクリプトに埋めたsysrootにあるので、基本的な部分はこれでOK。hello.cでも書いてコンパイルしてみれば確認できる。
あとは他の必要な設定、/usr/localにパスを通したり、pigpioやpthreadライブラリを追加したり、とかをすればいい。
僕自身はこのあと、Eclipseで書いたソースファイルをXcodeに読み込んだXcodeのプロジェクトを作っている。僕はEclipseよりXcodeのほうが使い慣れてるのと、Raspbian単独で動作するのではなくmacOSをクライアントにして動くようなのばかり作っているので、RaspbianとmacOSとをひとつのプロジェクトにまとめてしまっている。Xcodeで編集とmacOS側のコンパイル/デバグとRaspbian側の構文チェックまでやって(pigpioのAPIスタブとかも作ってある)、Eclipse上ではクロスコンパイルとリモートデバグだけをやってる。
ほんとはEclipseでやってる部分も含めて全部Xcode上でできるのが僕にとっては便利なんだけど、それはAppleが許してくれないようである。まあ、しょうがない。
2.1 リモートデバッガ
LLVMのプロジェクトにはデバッガもあってlldbという。これもインストールしてgdbの代わりに使ってみようと思ったんだけど、うまくいかなかった。lldbはローカルもリモートも区別がないとあったので単にローカルでarm-linux-gnueabihf-gdb -> /usr/bin/lldb
リモートで/usr/bin/gdbserver -> /usr/bin/lldb-server
に入れ替えてみたけど、動かなかった(Raspbian側でlldb-serverは起動するけどそこから何もしない)。Eclipse上での設定が必要らしい。さすがに甘すぎた。
gdbはとりあえずLinaroのやつをコピーすれば問題ないので当面は困ることはなさそう。具体的にはクライアント側の
- arm-linux-gnueabihf-gdb
- gdbが依存しているgettextのlibintl.8.dylib
- /usr/bin/gdbserver
いずれはこのバージョンのgdbも使えなくなるだろうし、lldbのほうはあとで確認しよう。
3 そもそもなぜ
そもそもなんでこんなことを始めたかというと、libxml2を使いたくなったのがきっかけ。Raspbianにはaptで簡単にインストールできるんだけど、ツールチェインに含まれていないので追加しようとした。最初Raspbianのファイルをそのままコピーしてきて、リンクするときのパスに指定したが動かない。ソースからクロスコンパイルしようとしたんだけど、glibcのバージョンなんかが古いし、それ以外にも依存するライブラリがたくさんあって、なかなかうまくいかない。
そもそも動的ライブラリってどうやって名前解決するのか知らなかったので、調べてみてわかった。ああ、これじゃダメだわ。
ちょっとライブラリの勉強をしたおかげで、libxml2を動かせそうにはなったんだけど、他のライブラリを追加したいときはまたこの面倒を繰り返さないといけない。sysrootのメカニズムがわかったので、全部そのデンでできないか、と考えていて行き当たった。
僕がunixを使い始めた当時はライブラリは「.a」のスタティックしかなかった。スタティックライブラリのリンクは簡単で、コンパイルの後リンクに成功すればあとは何も考える必要はなかった。「.so」のダイナミックリンクライブラリがいつから使われるようになったのかよく覚えてないけど、ユーザが簡単に移行できるようになっていたんだろう。僕は全く意識することがなかった。
ところがいまではライブラリのバージョン管理の問題や互換性の問題からとんでもなく複雑になっているらしい。ライブラリが別のライブラリを必要とする場合、実行時にリンクローダにシンボルを解決してもらうんではなく、呼び出し側のライブラリが必要なライブラリの絶対パスを指定することがあるらしい。このためそのパスに必要なライブラリがなければ実行時ではなくリンクの時点で失敗する。
ただライブラリをツールチェインにコピーしてきてLD_LIBRARY_PATHとかを指定するだけではダメなのはこのせいらしい。
さらにクロスコンパイル環境では致命的になる。絶対パスで指定してしまうと開発環境側のパスをたどることになる。それでsysrootを作ったんだろう。sysrootはgccのオプションで絶対パス指定されたディレクトリのルートを他のディレクトリで置き換えるような機能をはたす。ただしこれは優先順位は低いらしくて、例えば-I指定したパスには影響しない、とか非常にややこしい。
前のLinaroを使ったクロス環境ではsysrootを使っていなかったようで、あとから追加したいときにだけ使うようになっていたらしいけど、今度のは全部がsysrootにぶら下がることになっている。
その分シンプルで、追加ライブラリをクロスコンパイルする必要はなくただターゲット環境からコピーしてくるだけで済む。組み込みやArduinoなんかではメリットはまったくないけどRaspberry Piでは非常に有効である。
実はこれに出会う前、libxml2が依存しているglibcや他のライブラリのバージョンをなんとかして上げるか、Crosstool-NGでなんとかして自分でツールチェインを作るか、と悩んでいた。特にglibcの依存は大きくて、いろんなところに影響が出る。Crosstool-NGは僕から見ると非常に癖のあるシステムのように思えて、動かすまでめちゃ苦労しそうで実際に手を出すところまでもいかなかった。最悪はRaspbian側に開発環境を全部移動しようか、と考えていた。
すでにmacOS+Raspbianでもうソコソコの数を仕事用の装置に使ってしまっていて、そんなわけで頭を抱えていた、というかこのままではけつかっちんになってマジでヤバいんではないかい、とヒヤヒヤしていた。
しかしほんとにLLVMを使うなんてまったく思いもしなかった。
ほんとにありがたい。感謝感謝。
2018-07-02 21:34
nice!(0)
コメント(0)
コメント 0