クロスプラットフォーム
IoT
Nim
クロスコンパイル

Nimクロスコンパイルのやり方

はじめに

この記事はNim Advent Calender 2017 20日目の記事です。

Nim歴一週間未満のhatchetです。Advent Calender初参加です。先週の金曜日にPythonに似たコンパイル言語ないかなと思っていたら、Nimに出会ってしまい、そのまま基本的な文法を覚えてしまいました。

書きやすく、読みやすく、C言語ソースも生成してくれ、実行速度も早いと、ポテンシャルの高い言語だと思います。
特に、メンバ関数的に使用できる第一引数の省略ルール(proc foo(this , arg: int) bool =みたいな関数をbar.foo(5)とすると第一引数thisbarが入る)はわかりやすく使いやすいので好みです。
まだまだ発展途上ではありますが、次世代の組み込み開発言語、IoT言語として大いに期待の持てる(そうなってほしい)言語ですね。

しかし、IoTに使うとすればクロスコンパイルは必須でしょう。
そこで、今日はNimでのクロスコンパイルの方法をご紹介したいと思います。

Nimのバージョンはv0.17.2です。
プロシージャのことを関数と読んでいます。ご了承ください。
バージョン切り替えのためにホームディレクトリに環境を置いている方も多いでしょうが、普通にインストールした体で話します。
ホスト環境はelementaryOS Lokiです。


そもそもコンパイラの設定ってどうなってるの?

ご存知の通り、Nimは Nimソースファイル → Cソースファイル → (コンパイル) → バイナリという手順でコンパイルを行います。

Nimコンパイラ(usr/bin/nim)はこの過程のうち、Nimソースファイル → Cソースファイル を行います。
NimコンパイルによってCソースファイルが生成された後にお馴染みのgccなどでコンパイルを行うことになります。
Nimでは、 .cfg ファイルでコンパイラやアーキテクチャ、OSなどを予め定義しておき、コンパイル時のオプションでそれらの定義から何を使用するのかを決定します。

デフォルトでは以下のファイルが読み込まれます(ユーザ設定使用時も読み込まれます)。

/etc/nim.cfg
  # 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 を作成します。

hello.nim
  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()
hello.nim.cfg
  # 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を以下のように修正します。

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を指定しているからです。

hello.nim.cfg
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 を修正します。

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

参考資料