備忘録: WSL2 Ubuntu上で Nervesをカスタマイズしてみる

これまで、WSL2 Ubuntuに Nervesの開発環境をセットアップし、そこで作ったアプリと WSL2が LAN通信できるかどうかを確かめてきた。結果は良好で、WSL2 Ubuntuは Nervesの開発プラットフォームとして、Linux Mintに対し遜色なく使えると分かった。

それでは最後に、Nervesシステムをカスタマイズするプラットフォームとして利用できるかどうかを確かめておこう。

#1.準備:WSL2のコンフィグ
いきなり WSL2環境の手直し(^^;)
実は、WSL2のデフォルトの設定では、Nervesシステムのコンフィグレーター(make linux-config, make busybox-config)がエラーしてまうのだ。原因は、WSL2 Ubuntuの PATH環境変数に空白文字混じりの PATH名が含まれているためだ。そう、WSL2のデフォルトの設定では、Windows 10の PATH環境変数を引き継ぐようになっているのだ。これを解除するために、下記のファイル"wsl.config"を /etcの下に作成し、WSL2 Ubuntuを再起動する。

```ini:/etc/wsl.config
[interop]
appendWindowsPath = false
```

これで PATH環境変数から、 Windows 10の PATHがきれいさっぱり消えた。ん?、"mix burn"が動かなくなったではないか。そうか、Windows 10の fwupコマンドの PATHも消えたためか。Nervesビルド・システムが必要とする PATHだけは復活させよう -- *コマンドプロンプト、PowerShell、Chocolatey*。

```bash:bash
echo 'PATH=$PATH:/mnt/c/Windows/System32'                        >> ~/.profile
echo 'PATH=$PATH:/mnt/c/Windows/System32/WindowsPowerShell/v1.0' >> ~/.profile
echo 'PATH=$PATH:/mnt/c/ProgramData/chocolatey/bin'              >> ~/.profile
source ~/.profile
```

#2.Nervesシステムのリビルド
さあ、準備が整ったので Nervesシステムのリビルド/カスタマイズをやってみよう。大丈夫、何も難しいことはない。Nerves Project HPにある["Customizing Your Own Nerves System"](https://hexdocs.pm/nerves/customizing-systems.html#content)ドキュメントに沿って手を動かせば良いだけだ(^_-)

まず最初に、作業用ディレクトリを作る。上のドキュメントに倣って名前は "projects"で良いかな。このディレクトリ下に、Nervesシステムのベース・コンフィグ[^1]を git cloneする。ここでは、無印の Raspberry Pi Bをターゲット・ボードとするので、Nerves Projectの [nerves_system_rpi](https://github.com/nerves-project/nerves_system_rpi)のリポジトリからベース・コンフィグを cloneする。ローカル名は custom_rpiとするかな。おっと、バージョンの指定を忘れないよーにΦ(._.)

それから、ローカルの変更を間違って Nerves Projectのリポジトリに git pushしてしまわない様に、リモート名を originから upstreamに変えておこう。

次に、適当な Nervesアプリのプロジェクトを作業ディレクトリ直下に用意する。Nervesシステムのビルドは、このプロジェクトを介して行う様だ。新しいプロジェクトを作っても良いが、ここでは前回にもお世話になった blinkyプロジェクトをコピーして使うことにする。

[^1]: 組み込みLinuxをターゲット・ボード向けにビルドするためのコンフィグ等を集めたものの様だ

```bash:bash
mkdir projects
cd projects
git clone https://github.com/nerves-project/nerves_system_rpi.git custom_rpi -b v1.12.1
pushd custom_rpi
git remote rename origin upstream
git checkout -b master

popd
cp -r ~/blinky .
```

作業用ディレクトリprojectsの構造は次のとおり。

```
projects
  ├─ custom_rpi
  └─ blinky
```

ふむ、Nervesシステムをビルドするためには、もう少し作業が必要な様だ。
次の章では、Nervesシステムに少し改造を加えて、マイNervesシステムを作りたいと思うので、ターゲット・ボード名を既存の名前と被らない custom_rpiとしておこう。それに伴いまず custom_rpi/mix.exsに次の修正が必要になる。

1. モジュール名を "NervesSystemRpi"から "CustomRpi"に変更する
2. @app定数を ":nerves_system_rpi"から ":custom_rpi"に変更する
3. 当面はローカルで実験/動作確認の予定なので、@github_organization定数とそれを参照しているブロックはコメントアウトする

```diff:custom_rpi/mix.exs
diff --git a/mix.exs b/mix.exs
index 943340b..41fed31 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,8 +1,9 @@
-defmodule NervesSystemRpi.MixProject do
+defmodule CustomRpi.MixProject do
   use Mix.Project

-  @github_organization "nerves-project"
-  @app :nerves_system_rpi
+#  @github_organization "nerves-project"
+  @app :custom_rpi
   @version Path.join(__DIR__, "VERSION")
            |> File.read!()
            |> String.trim()

@@ -35,9 +36,9 @@ defmodule NervesSystemRpi.MixProject do
   defp nerves_package do
     [
       type: :system,
-      artifact_sites: [
-        {:github_releases, "#{@github_organization}/#{@app}"}
-      ],
+#     artifact_sites: [
+#       {:github_releases, "#{@github_organization}/#{@app}"}
+#     ],
       build_runner_opts: build_runner_opts(),
       platform: Nerves.System.BR,
       platform_config: [

@@ -66,8 +67,8 @@ defmodule NervesSystemRpi.MixProject do
   defp package do
     [
       files: package_files(),
-      licenses: ["Apache 2.0"],
-      links: %{"GitHub" => "https://github.com/#{@github_organization}/#{@app}"}
+      licenses: ["Apache 2.0"]
+#      links: %{"GitHub" => "https://github.com/#{@github_organization}/#{@app}"}
     ]
   end
```

システム側の修正は以上だ。次に、blinkyアプリがこの custom_rpiシステムを利用できるように blinky/mix.exs他に修正を加える。

1. @all_targetsリストに :custom_rpiを加える
2. 依存リスト depsに、custom_rpiシステムの PATH他を加える
3. blinky/config/rpi.exsをコピーし、blinky/config/custom_rpi.exsを用意する

```diff:blinky/mix.exs
diff --git a/mix.exs b/mix.exs
index ce6d996..c7337dd 100644
--- a/mix.exs
+++ b/mix.exs
@@ -3,7 +3,7 @@ defmodule Blinky.MixProject do

   @app :blinky
   @version "0.1.0"
-  @all_targets [:rpi, :rpi0, :rpi2, :rpi3, :rpi3a, :rpi4, :bbb, :x86_64]
+  @all_targets [:rpi, :rpi0, :rpi2, :rpi3, :rpi3a, :rpi4, :bbb, :x86_64, :custom_rpi]

   def project do
     [
@@ -57,7 +57,8 @@ defmodule Blinky.MixProject do
       {:nerves_system_rpi3a, "~> 1.11", runtime: false, targets: :rpi3a},
       {:nerves_system_rpi4, "~> 1.11", runtime: false, targets: :rpi4},
       {:nerves_system_bbb, "~> 2.6", runtime: false, targets: :bbb},
-      {:nerves_system_x86_64, "~> 1.11", runtime: false, targets: :x86_64}
+      {:nerves_system_x86_64, "~> 1.11", runtime: false, targets: :x86_64},
+      {:custom_rpi, path: "../custom_rpi", runtime: false, targets: :custom_rpi, nerves: [compile: true]}
     ]
   end
```

ええっと、忘れものはないかな…
では、早速 Nervesシステム(custom_rpi)をリビルドしてみよう。手順は、普通にblinkyアプリをビルドする手順と何ら変わらない。MIX_TARGETに custom_rpiを指定して、いつものように mixするだけだ。
…まぁ、ツールチェインや Linuxカーネルなどをコンパイルするので、ビルド時間はいつもより長くなるが

```bash:bash
pushd blinky
export MIX_TARGET=custom_rpi
mix deps.get
mix firmware
mix burn
```
からくりは、mixの依存関係解決タスクが、Nervesシステム(custom_rpi)をビルドする為に必要なファイル(ツールチェインやLinuxカーネル他)をインターネットから掻き集めコンパイルしてくれるのだ。

実際のビルド作業は "custom_rpi/.nerves/artifacts/custom_rpi-portable-1.12.1"隠しディレクトリで行われるよーだ。サブディレクトリの用途は↓こんな感じかな?

- host ---- ツールチェイン等のホスト環境
- target -- ターゲット・ボード上のシステム
- images -- targetをパックしたイメージファイル
- build --- コンパイル結果

```bash:custom_rpi/.nerves
custom_rpi/.nerves/
└── artifacts
    └── custom_rpi-portable-1.12.1
        ├── build
        ├── host
        │   ├── arm-buildroot-linux-gnueabi
        │   ├── bin
        │   ├── etc
        │   ├── include
        │   ├── lib
        │   ├── lib64 -> lib
        │   ├── libexec
        │   ├── opt
        │   ├── sbin
        │   ├── share
        │   └── usr -> .
        ├── images
        ├── scripts -> /home/shoz/projects/blinky/deps/nerves_system_br/scripts
        ├── staging -> /home/shoz/projects/custom_rpi/.nerves/artifacts/custom_rpi-portable-1.12.1/host/arm-buildroot-linux-gnueabi/sysroot
        └── target
            ├── bin
            ├── boot
            ├── dev
            ├── etc
            ├── lib
            ├── lib32 -> lib
            ├── media
            ├── mnt
            ├── opt
            ├── proc
            ├── root
            ├── run
            ├── sbin
            ├── sys
            ├── tmp
            ├── usr
            └── var
```

また、インターネットからダウンロードしたファイルは "~/.nerves/dl"に置かれ、以後キャッシュとして利用されるよーだ。"~/.nerves/artifacts"には、リビルドした Nervesシステム(custom_rpi)の artifactを指すシンボリック・リンクが追加されている。

```bash:
/home/shoz/.nerves/
├── artifacts
│   ├── custom_rpi-portable-1.12.1 -> /home/shoz/projects/custom_rpi/.nerves/artifacts/custom_rpi-portable-1.12.1
│   ├── nerves_system_rpi-portable-1.12.1
│   └── nerves_toolchain_armv6_rpi_linux_gnueabi-linux_x86_64-1.3.2
└── dl
    ├── acl
    │   └── acl-2.2.53.tar.gz
    ├── alsa-lib
    │   └── alsa-lib-1.2.2.tar.bz2
    ├── alsa-utils
    │   └── alsa-utils-1.2.2.tar.bz2
    ├── attr
    │   └── attr-2.4.48.tar.gz
    ├── autoconf
    │   └── autoconf-2.69.tar.xz
    ├── automake
    │   └── automake-1.15.1.tar.xz
    ├── bison
    │   └── bison-3.4.2.tar.xz
    ├── boardid
    │   └── boardid-v1.8.0.tar.gz
    ├── buildroot-2020.05.tar.gz
    ├── busybox
    │   └── busybox-1.31.1.tar.bz2
    ├── e2fsprogs
    │   └── e2fsprogs-1.45.6.tar.xz
    ├── erlang
    │   └── OTP-23.0.2.tar.gz
    ├── erlinit
    │   └── erlinit-v1.7.0.tar.gz
    ├── espeak
    │   └── espeak-1.48.04-source.zip
    ├── fakeroot
    │   └── fakeroot_1.20.2.orig.tar.bz2
    ├── flex
    │   └── flex-2.6.4.tar.gz
    ├── fwup
    │   └── fwup-1.8.0.tar.gz
    ├── gawk
    │   └── gawk-5.1.0.tar.xz
    ├── gettext-tiny
    │   ├── gettext-0.20.1.tar.xz
    │   └── gettext-tiny-adaa9c64921e80f2b8dd3610ffb508618b9204f3.tar.gz
    ├── glibc
    │   └── glibc-2.30-67-g4748829f86a458b76642f3e98b1d80f7b868e427.tar.gz
    ├── kmod
    │   └── kmod-27.tar.xz
    ├── libarchive
    │   └── libarchive-3.4.2.tar.gz
    ├── libconfuse
    │   └── confuse-3.2.2.tar.xz
    ├── libmnl
    │   └── libmnl-1.0.4.tar.bz2
    ├── libnl
    │   └── libnl-3.5.0.tar.gz
    ├── libopenssl
    │   └── openssl-1.1.1g.tar.gz
    ├── libp11
    │   └── libp11-0.4.10.tar.gz
    ├── libsysfs
    │   └── sysfsutils-2.1.0.tar.gz
    ├── libtool
    │   └── libtool-2.4.6.tar.xz
    ├── libzlib
    │   └── zlib-1.2.11.tar.xz
    ├── linux
    │   └── linux-raspberrypi-kernel_1.20200601-1.tar.gz
    ├── linux-firmware
    │   ├── git
    │   ├── git.readme
    │   └── linux-firmware-20200122.tar.gz
    ├── lz4
    │   └── lz4-1.9.2.tar.gz
    ├── lzo
    │   └── lzo-2.10.tar.gz
    ├── m4
    │   └── m4-1.4.18.tar.xz
    ├── nbtty
    │   └── nbtty-v0.4.1.tar.gz
    ├── ncurses
    │   ├── ncurses-6.1-20190609-patch.sh.bz2
    │   ├── ncurses-6.1-20190615.patch.gz
    │   ├── ncurses-6.1-20190623.patch.gz
  <<中略>>
    │   ├── ncurses-6.1-20200111.patch.gz
    │   ├── ncurses-6.1-20200118.patch.gz
    │   └── ncurses-6.1.tar.gz
    ├── nerves_heart
    │   └── nerves_heart-v0.2.0.tar.gz
    ├── nerves_system_rpi-portable-1.12.1-0319303.tar.gz
    ├── nerves_toolchain_armv6_rpi_linux_gnueabi-linux_x86_64-1.3.2-CDA7B05.tar.xz
    ├── patchelf
    │   └── patchelf-0.9.tar.bz2
    ├── pigpio
    │   └── pigpio-75.tar.gz
    ├── pkgconf
    │   └── pkgconf-1.6.1.tar.xz
    ├── rng-tools
    │   └── rng-tools-6.10.tar.gz
    ├── rpi-firmware
    │   └── rpi-firmware-1.20200601.tar.gz
    ├── rpi-userland
    │   └── rpi-userland-f97b1af1b3e653f9da2c1a3643479bfd469e3b74.tar.gz
    ├── rpi-wifi-firmware
    │   └── rpi-wifi-firmware-d4f7087ecbc8eff9cb64a4650765697157821d64.tar.gz
    ├── squashfs
    │   └── squashfs-4.4.tar.gz
    ├── tar
    │   └── tar-1.29.cpio.gz
    ├── toolchain-external-custom
    │   └── nerves_toolchain_armv6_rpi_linux_gnueabi-linux_x86_64-1.3.2-CDA7B05.tar.xz
    ├── uboot-tools
    │   └── u-boot-2020.04.tar.bz2
    ├── unixodbc
    │   └── unixODBC-2.3.7.tar.gz
    ├── util-linux
    │   └── util-linux-2.35.1.tar.xz
    ├── wireless-regdb
    │   └── wireless-regdb-2019.06.03.tar.xz
    ├── wpa_supplicant
    │   └── wpa_supplicant-2.9.tar.gz
    ├── xz
    │   └── xz-5.2.4.tar.bz2
    └── zstd
        └── zstd-1.4.3.tar.gz
```

#3.Nervesシステムのカスタマイズ
前章では、無印 Raspberry Pi B向けに Nervesシステム(custom_rpi)をリビルドしてみた。システムのコンフィグには一切手を加えていないので、理屈上は [Nerves Projectのリポジトリ](https://github.com/nerves-project/nerves_system_rpi/releases)にある zImage, rootfs.squashfsと寸分違わないものが出来る……筈だったのだが、微妙にバイナリサイズが違っているなぁ(^^;) 動作上は問題なさそうなので、まあいいや[^2]。次に進もう。

[^2]: 仕事ではこーいう妥協をしてはいけない。なぜ違いが発生したのかを詳細に調査し、課題であるのかないのかの白黒をつけるべし。

それでは、本丸の Nervesシステムのカスタマイズに取り掛かろう。とは言っても、Bootローダや Linuxカーネルを弄ったりする大掛かりなカスタマイズは荷が重い。ちょこっと Linuxコマンドを追加する程度のことをやってみる。仕様は以下の通り。

- マイNervesシステムのカスタム仕様:<br>
Nerves Projectリリースには含まれていない"hostname"コマンドを追加する

---

具体的な作業に入る前におさらいをしておこう。Nervesは、ビルドシステムとして "Buildroot"を採用している。前章で Neresシステム(custom_rpi)をリビルドした際、"mix firmware"の裏では Buildrootの仕組みが寡黙に働いていたのだ。Buildrootでは、ツールチェインやカーネルなどの基本的なコンフィグレーションを行うツールと共に、Linuxカーネルなどを個別にコンフィグレーションするツールを援用するよーだ。Nervesでは、次の3つが用意されている。

1. make menuconfig<br>
ツールチェインや Linuxカーネルなどの基本的なカスタマイズ
2. make linux-menuconfig<br>
Linuxカーネルの詳細なカスタマイズ
3. make busybox-menuconfig<br>
Busyboxの詳細なカスタマイズ

マイNervesシステムのカスタム仕様だと、三つ目の "busybox-menuconfig"だけを弄ればよいのだが、折角の機会なので上から順に起動してどんなカスタマイズ項目が並んでいるのか眺めておこう。

おっと! 各コンフィグ・ツールの起動は Linuxネイティブの bashからではなく、"mix nerves.system.shell"からね。 

一番バッターの "menuconfig"は、ターゲットCPUの選択、ツールチェイン、ブートローダー、カーネル、ルート・ファイル etc.と、組み込みLinuxの構築に必要な一通りの項目をコンフィグするモノのようだ。新たなボード・コンピューターに Nervesをポーティングする際には、まずココを攻略せねばならない。コンフィグ項目が多岐に渡るので、軟弱な小生は途中でギブアップしそうだが(- -;)

```bash:
mix nerves.system.shell
make menuconfig
```
![buildroot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14158/cff7f524-ab81-7880-a02f-d22d3e0ac70a.jpeg)

次の "linux-menuconfig"は、組み込みLinuxのカーネル周りを事細かにコンフィグするモノのよーだ。特にペリフェラルの追加でドライバを組み込む必要がある場合は、ここでゴソゴソすることになる。

そー言えば、一昔前の「無印の Raspberry Pi」Nervesシステムには、手持ちのUSB無線LAN(Ralink rt2800チップ)のドライバがインストールされていなかったので[^4]、意味不明のテクニカル・タームだらけのメニューの中を彷徨い、何とか使えるようにしたっけな。

[^4]: 近頃のリリースには Ralinkドライバはインストールされています…チッ (- -ﾒ)

```bash:
make linux-menuconfig
```
![linux.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14158/5cffef86-6b25-d63f-7011-382f3bea7755.jpeg)

さて、今回主役の "busybox-menuconfig"の番である。Busyboxとは、組み込みLinux向けに un*xの標準的なコマンド群をコンパクトに実装したアーミーナイフ的なコンポーネントだそうだ。昨今では、ほぼどの組み込み Linuxでも利用されているデファクトスタンダードらしい。

```bash:
make busybox-menuconfig
```
![busybox.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14158/92451d1f-49d9-5b0f-52c1-dbe50cb6d3ec.jpeg)
ええっと、マイNervesシステムのカスタム仕様に基づいて、"Networking Utilities"の下にある "hostname"を選択して Exitする。コンフィグの結果は、"build/busybox-1.31.1/.config"に保存される。
![hostname.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14158/8b5f2282-d72a-2505-fabb-1063e6f8e7af.jpeg)

"menuconfig"と "linux-menuconfig"には、コンフィグ結果の".config"を所定のディレクトリにセットアップする makeターゲットが用意されているのだが、生憎 "busybox-menuconfig"にはそう言うモノはない。下記のように手作業でセットアップする必要がある。

```bash
cp build/busybox-1.31.1/.config ~/projects/custom_rpi/busybox_defconfig
make menuconfig
```

![busy_defconfig.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14158/b36c2aee-6673-b71f-48f8-d1ea9fd325b0.jpeg)

```bash
make savedefconfig
```

---
さあ、時は満ちた。nerves.system.shellを exitして、マイNervesシステムをコンパイルしよう(^^♪

```
exit
mix firmware
```
![build.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14158/54afb55a-7dd9-9558-1bd4-ebfb1d72af72.jpeg)

出来上がったマイNervesシステムを無印 Raspberry Pi Bにインストールして、果たして "hostname"コマンドが追加されているかどうかを確かめる …… OKだ(^_-)v
![run_hostname.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14158/879c970e-d7d1-d968-b7f9-f62b86768f81.jpeg)

#4.大団円
ふぅ、終わった。「備忘録: WSL2 Ubuntu上でNerves…」シリーズは、これでフィナーレだ。
Nervesアプリの開発、LAN通信、システムのカスタマイズの何れも、WSL2 Ubuntu上でそつ無くこなせることが分かった。諸般の事情から、普段の開発環境が Windows派の方にとって、WSL2は一つの選択肢と考えても良いのではないだろうか。

最後に、ひとつ残念なことをお伝えする。WSL2が利用できる Windows10 2004 (ビルド19041.329)は、まだ完成度が十分ではなく、PCのハードウェア構成によってはインストールを拒否される場合があるよーだ。まぁ、裏技があるには在るのだが…

(完)

#5.参考文献
1. 組み込みLINUXシステム構築(第2版), KARIM YAGHMOUR他, オライリー・ジャパン
2. Mastering Embedded Linux Programming (2d Ed), Chris Simmonds, Packt Publishing
3. The Buildroot user manual (https://buildroot.org/downloads/manual/manual.html)