Nimクロスコンパイルのやり方
はじめに
この記事はNim Advent Calender 2017 20日目の記事です。
Nim歴一週間未満のhatchetです。Advent Calender初参加です。先週の金曜日にPythonに似たコンパイル言語ないかなと思っていたら、Nimに出会ってしまい、そのまま基本的な文法を覚えてしまいました。
書きやすく、読みやすく、C言語ソースも生成してくれ、実行速度も早いと、ポテンシャルの高い言語だと思います。
特に、メンバ関数的に使用できる第一引数の省略ルール(proc foo(this , arg: int) bool =
みたいな関数をbar.foo(5)
とすると第一引数this
にbar
が入る)はわかりやすく使いやすいので好みです。
まだまだ発展途上ではありますが、次世代の組み込み開発言語、IoT言語として大いに期待の持てる(そうなってほしい)言語ですね。
しかし、IoTに使うとすればクロスコンパイルは必須でしょう。
そこで、今日はNimでのクロスコンパイルの方法をご紹介したいと思います。
Nimのバージョンはv0.17.2です。
プロシージャのことを関数と読んでいます。ご了承ください。
バージョン切り替えのためにホームディレクトリに環境を置いている方も多いでしょうが、普通にインストールした体で話します。
ホスト環境はelementaryOS Lokiです。
そもそもコンパイラの設定ってどうなってるの?
ご存知の通り、Nimは Nimソースファイル → Cソースファイル → (コンパイル) → バイナリという手順でコンパイルを行います。
Nimコンパイラ(usr/bin/nim)はこの過程のうち、Nimソースファイル → Cソースファイル を行います。
NimコンパイルによってCソースファイルが生成された後にお馴染みのgccなどでコンパイルを行うことになります。
Nimでは、 .cfg ファイルでコンパイラやアーキテクチャ、OSなどを予め定義しておき、コンパイル時のオプションでそれらの定義から何を使用するのかを決定します。
デフォルトでは以下のファイルが読み込まれます(ユーザ設定使用時も読み込まれます)。
# You may set environment variables with
# @putenv "key" "val"
# Environment variables can be accessed like so:
# gcc.path %= "$CC_PATH"
cc = gcc
# additional options always passed to the compiler:
--parallel_build: "0" # 0 to auto-detect number of processors
<snip>
# example of how to setup a cross-compiler:
arm.linux.gcc.exe = "arm-linux-gcc"
arm.linux.gcc.linkerexe = "arm-linux-gcc"
<snip>
@if unix:
# -fopenmp
gcc.options.linker = "-ldl"
gcc.cpp.options.linker = "-ldl"
clang.options.linker = "-ldl"
clang.cpp.options.linker = "-ldl"
tcc.options.linker = "-ldl"
@if bsd or haiku:
# BSD got posix_spawn only recently, so we deactivate it for osproc:
define:useFork
# at least NetBSD has problems with thread local storage:
tlsEmulation:on
@end
@end
<snip>
gcc.options.speed = "-O3 -fno-strict-aliasing"
gcc.options.size = "-Os"
gcc.options.debug = "-g3 -O0"
<snip>
見ての通り、コンパイラの指定(cc = gcc
)、特定のアーキテクチャ、OS向けにコンパイルする際に使用するコンパイラの設定(arm.linux.gcc.exe = "arm-linux-gcc"
, @if unix:
以下)、デフォルトで使用されるオプション(gcc.options.speed = "-O3 -fno-strict-aliasing"
)などがこのファイルで定義されています。
こういった _.cfg_ファイルで定義された設定を使用するにはコンパイル時にオプションでOS等を指定します。
例えば、MacOS向けにコンパイルするには以下のようにします。
$ nim c --os:macosx source.nim
その他のオプションに関してはCompiler-Usageをどうぞ
aarch64向けにクロスコンパイルする
さて、お待ちかねの本題です。
まずはaarch64クロスコンパイラをインストールしましょう。
$ sudo apt-get install gcc-aarc:h64-linux-gnu
$ which aarch64-linux-gnu-gcc
/usr/bin/aarch64-linux-gnu-gcc
次に適当なNimソースファイル(hello.nim)とその設定ファイル hello.nim.cfg を作成します。
proc HelloNim(): int {.discardable.} =
echo "What yout name?"
let MyName = readLine(stdin)
if MyName == "":
echo "HAHA! You've forgotten your own name?"
elif MyName == "Name" or MyName == "name":
echo "HAHA! Your name is name? It's funny."
else:
echo "Hello", MyName, "Welcome Nim's World"
HelloNim()
# for aarch64
cpu:aarch64
os:linux
aarch64.linux.gcc.path = "/usr/bin/"
aarch64.linux.gcc.exe = "aarch64-linux-gnu-gcc"
aarch64.linux.gcc.linkerexe = "aarch64-linux-gnu-gcc"
作成したら、nim c hello.nim
でコンパイルします。失敗します。
command line(1, 2) Error: unknown CPU: 'aarch64'
はい、実はNimではARM 64bitはaarch64という名前で扱われていません。
どういう意図なんでしょう? 最初ここで大いにハマりました、勘弁してほしいところです。
ARM 64bitはarm64という名前になっています。ちなみに32bitはarmです。_hello.nim.cfg_を以下のように修正します。
# for arm64
cpu:arm64
os:linux
arm64.linux.gcc.path = "/usr/bin/"
arm64.linux.gcc.exe = "aarch64-linux-gnu-gcc"
arm64.linux.gcc.linkerexe = "aarch64-linux-gnu-gcc"
修正したら、コンパイルしましょう。以下のようにします。
$ nim c hello.nim
Hint: used config file '/etc/nim.cfg' [Conf]
Hint: used config file '/path/to/hello.nim.cfg' [Conf]
Hint: system [Processing]
Hint: hello [Processing]
Hint: [Link]
Hint: operation successful (10956 lines compiled; 0.215 sec total; 17.938MiB peakmem; Debug Build) [SuccessX]
上では--cpu:
、--os:
オプションを使用していませんが、これは hello.nim.cfg の以下の部分でcpu
,、os
を指定しているからです。
cpu:arm64
os:linux
コンパイルに成功すれば、hello
という名前のバイナリができているはずです。file
コマンドでちゃんとクロスコンパイルできているか確認してみましょう。
$ file hello
ello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=e62b806a649e6625532973d1ccb9aa8f6f7ec282, not stripped
これでクロスコンパイルは成功です。
ちなみに hello.nim.cfg がある状態でx86_64向けバイナリを作成するには以下のように hello.nim.cfg を修正します。
arm64.linux.gcc.path = "/usr/bin/"
arm64.linux.gcc.exe = "aarch64-linux-gnu-gcc"
arm64.linux.gcc.linkerexe = "aarch64-linux-gnu-gcc"
この状態でオプションを付けずにnim c hello.nim
とすれば、x86_64向けバイナリが、オプション付きでnim c --cpu:arm64 --os:linux hello.nim
とすればaarch64向けバイナリが作成されます。
$ nim c hello.nim
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3debb2320a0db45a81599ccdd611352ad4d18844, not stripped
$ nim c --cpu:arm64 --os:linux hello.nim
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=e62b806a649e6625532973d1ccb9aa8f6f7ec282, not stripped
参考資料
-
Nim Compiler User Guide - Cross Compilation
Nim公式サイトのコンパイラに関するドキュメント -
[Debian]: build.sh error: unknown processor: aarch64
Github上のイシュー -
Cross compile to OS X
Nim フォーラムサイトで紹介されていたamb64 MacOSクロスコンパイルの方法 -
Nim on Android
同じくフォーラムサイトよりAndroid向けクロスコンパイルの方法