Nervesでも動くNIFに仕上げる

# はじめに

記事[Elixir NIFを使ってみる](https://qiita.com/GeekMasahiro/items/a95d9051bd6659143108)でNIFを使ってみました。
この記事では、NIFの動作を確認できたんですが、ちょっと心配な事があります。

Nervesの場合、ファームウエアの作成（＝compile）はhostで行い、実行はtargetのマシーンで行われます。
hostでcompileするときに、targetのアーキテクチャでクロスコンパイルされる必要があるんですが、その考慮を何もしていません。

適切にクロスcompileされるか、確かめてみます

# やってみる

プロジェクトを作成

```bash
mix nerves.new test001
```

mixのdepsにhello_nifを加える

```
      {:hello_nif, git: "https://github.com/masahiro-999/hello_nif", branch: "main"}
```

```mix deps.get```と```mix firmware```を実行

hello_nifがコンパイルされています。
```
==> hello_nif
gcc -o multiply.so -shared -fPIC -I/home/masa/.asdf/installs/erlang/27.0.1/erts-15.0.1/include multiply.c
Compiling 1 file (.ex)
Generated hello_nif app
```

# 実行結果１

案の定、エラーが発生して、再起動を繰り返しました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/76620f25-951c-c0e1-2795-8e5cba561b26.png)

multiply.soが含まれていない様子です。
クロスコンパイル以前に、パッケージとしての問題がありました。


# 修正

load_nifでカレントディレクトリのファイルを取得するようにしていたことが問題
multiply.soを prev ディレクトリに置く様に修正。
```:code.priv_dir(:hello_nif)```を使って、場所を取得するように修正

```Elixir
  def load_nif() do
    :erlang.load_nif(:code.priv_dir(:hello_nif) ++ ~c"/multiply", 0)
  end
```

# 実行結果２

```mix firmware```を実行すると次のエラーが発生

```
Updating base firmware image with Erlang release...
scrub-otp-release.sh: ERROR: Unexpected executable format for '/home/masa/nerves/test001/_build/m5stack_core_mp135_dev/_nerves-tmp/rootfs_overlay/srv/erlang/lib/hello_nif-0.1.0/priv/multiply.so'

Got:
 readelf:Advanced Micro Devices X86-64;0x0

Expecting:
 readelf:ARM;0x5000400, Version5 EABI, hard-float ABI

This file was compiled for the host or a different target and probably
will not work.

Check the following:

1. If this file comes from a library, that library may be compiling to
/home/masa/.nerves/artifacts/nerves_system_m5stack_core_mp135-portable-0.0.7/scripts/scrub-otp-release.sh: line 153: deps: command not found
   its source directory under . Manually clean up the source
   directory and try building again.

2. Are you using a path dependency in your mix deps? If so, run
   'mix clean' in that directory to avoid pulling in any of its
   build products.

3. Did you recently upgrade or change your Nerves system? If so,
   try cleaning and rebuilding this project and its deps.

4. Are you building outside of Nerves' mix integration? If so,
   make sure that you've sourced 'nerves-env.sh'.

If you are very sure you know what you are doing, you may place an empty
file in the same directory as the offending file(s) called '.noscrub'.
This will explicitly disable scrubbing for that directory.

If you're still having trouble, please file an issue on Github
at https://github.com/nerves-project/nerves_system_br/issues.
```

ああ～～。やっぱりクロスコンパイルできてない事がわかりました。

# Nervesで使えているライブラリーを真似する

正しい方法がわかりません。Nerves projectの方のパッケージを参考にすることにしました。

[Circuits i2cのMakefile](https://github.com/elixir-circuits/circuits_i2c/blob/main/Makefile)のMakefileを見てみます。

コンパイルするコマンドが$(CC)となっていました。

この変更で、ターゲットに適したコンパイラーが使われるようになり、エラーが解消されました。

```Makefile:Makefile
PREFIX = $(MIX_APP_PATH)/priv
BUILD  = $(MIX_APP_PATH)/obj

NIF = $(PREFIX)/multiply.so

SRC = c_src/multiply.c

ERLANG_INCLUDE_DIR = $(shell erl -eval 'io:format("~s", [code:lib_dir(erl_interface, include)])' -s init stop -noshell)
ERTS_INCLUDE_DIR = $(shell erl -eval 'io:format("~s", [code:root_dir() ++ "/erts-" ++ erlang:system_info(version) ++ "/include"])' -s init stop -noshell)

all: compile

compile:
	mkdir -p $(PREFIX)
	$(CC) -o $(NIF) -shared -fPIC -I$(ERTS_INCLUDE_DIR) $(SRC)

clean:
	rm -f $(NIF)
 ```

# 実行結果

Core MP135（ARM CPU）で実行して、実行できました

```
iex(2)> HelloNif.multiply(2,3)
6
iex(3)> 
```