Yocto build 時間を10時間から10分に高速化した話

# 時間無い人向け結論

Yocto 便利ですね！
しかしビルド時間が長いのがネックですね。。
とある OS を私の手元で以下のように手を加えていったところ、
デフォルトで10時間掛かっていましたが、
最終的には10分まで短縮できました！
＃5分単位で丸めてます。

| 内容                               | パッケージ数 | 時間[H:M] | 簡単さ | 効果 | 注                 |
|:-----------------------------------|:-------------|:----------|:-------|:-----|:-------------------|
| デフォルト                         |        5257  | 10:00     | -      |  -   | 10時間             |
| ソースダウンロードディレクトリ固定 |        5257  |  2:40     | ◎     |  ◎  | 逆に言うと10時間のうちダウンロード時間が7時間20分。2回目以降から有効。|
| 更に不要な package をビルド対象外に|        5004  |  2:35     | ○     |  △  |                    |
| 更に ICECC 使用                    |        5004  |  1:30     | △     |  ○  | 要ビルド用PC複数台 |
| 更に sstate cache 使用             |        5004  |  0:10     | ◎     |  ◎  | 2回目以降から有効。 |

また高速化出来たら随時更新します。
他にもこういうのあるよ、という方は是非教えてください！

では本題です。

# Yocto Project って何？

組込 Linux 作成ツールです。
ツール名であり、団体名でもあります。2010年結成。
OS 一式(u-boot, kernel, rootfs あたり)を作っちゃおう、というもの。
公式サイト: https://www.yoctoproject.org/
Document: http://www.yoctoproject.org/docs/2.3/mega-manual/mega-manual.html

公式サイトから全体図・構成図のみ引用して貼っておきます。
![Development Environment](http://www.yoctoproject.org/docs/2.3/mega-manual/figures/yocto-environment.png)

Yocto build の大きな流れは、

1. 各種ソース(u-boot, kernel, rootfs(ライブラリ、パッケージ))ダウンロード
2. 各種ソースを1つ1つクロスビルド
3. 最後にビルドした結果(各種ライブラリ等々)を rootfs として tar で1つのファイルに固める

です。

## Yocto の利点

一番は自分の手元で OS を作れることでしょう。
カスタマイズも好きに出来ます（慣れるまでは大変ですが）。

## Yocto の欠点

PC にマシンパワーが必要です。
OS 1つ作るだけでも、
ストレージ: 100GB オーダー(GUI 有無、ビルドするライブラリの多寡、で変わります)
ビルド時間: 数時間オーダー(CPU の性能によります)
程度は必要です。デフォルトでは。

# 今回の環境

## 作るもの

GDP(Genivi Development Platform) と呼ばれる車載向けの OS を作ります。
今回の主旨ではないので GDP の説明は割愛します。
Genivi Development Platform
https://at.projects.genivi.org/wiki/pages/viewpage.action?pageId=11567210

実機は Raspberry Pi3 です。
https://www.raspberrypi.org/products/raspberry-pi-3-model-b/
![Raspberry Pi3](https://www.raspberrypi.org/app/uploads/2016/02/Raspberry-Pi-3-top-down-web.jpg)

GDP on Raspberry Pi3 画面イメージ
![alt](https://at.projects.genivi.org/wiki/download/attachments/11569744/GDP-11-HMI.png?version=1&modificationDate=1476314176000&api=v2)

## 開発環境

OS: Ubuntu 14.04
CPU: Intel Core i7-4770 CPU @ 3.40GHz × 8 
RAM: 15.6 GB
Disk: SSD

簡単にビルドコマンド例をまとめます。

```
// 新規に terminal を開く。
/opt/Proj/Auto/GDP/RPi3/$ mkdir GDP
$ cd GDP/
$ git clone https://github.com/GENIVI/genivi-dev-platform.git -b gdp-11
$ cd genivi-dev-platform
$ vi gdp-src-build/conf/templates/local.inc  // ごにょごにょ設定変更。詳細は後述。
$ source init.sh raspberrypi3
$ bitbake genivi-dev-platform  // ビルド開始。数時間放置
$ ls tmp/deploy/images/raspberrypi3/ | grep sdimg
genivi-dev-platform-raspberrypi3.rpi-sdimg   // これがrootfs
```

# 高速化詳細

## デフォルトでビルドした場合

ほぼ、 10時間ちょうどかかりました。。。Orz


## 高速化1: ソースダウンロードディレクトリ固定

各種ソースのダウンロード先ディレクトリを固定化します。

Yocto の設定ファイル local.conf を編集し、
各種ソースのダウンロードディレクトリをどこか好きな場所に固定に設定しましょう。
＃ デフォルトはビルドディレクトリトップの ./download です。
＃ 相対パスではビルドディレクトリが変わる毎に変わり毎回ソースを全てダウンロード
＃ することになり重複がもったいないので、どこか固定に設定しましょう。
2回目以降のダウンロード時間が短縮されます
（1回はダウンロードを行わないといけないので初回のビルドは効果なし）。


```
$ vi local.conf

- DL_DIR ?= "${TOPDIR}/downloads"
+ DL_DIR ?= "/opt/Yocto/downloads"
```

※上記 PATH は例です。上記でなくてもどこでも構いません。
※本当は gdp は local.inc が設定ファイルなのですが、 一般的には local.conf が設定ファイルだと思いますのでそのように書いています。

手元の環境だとデフォルト10時間(ダウンロード時間＋ビルド時間)が、
これで2回目以降のビルドは2時間40分(ビルド時間)になりました！
逆に言うとダウンロード時間が7時間20分掛っていたことになります。

## 高速化2: 不要な package をビルド対象外にする

設定ファイル local.conf の中を見てみましょう。

```
PACKAGE_CLASSES ?= "package_rpm package_ipk"
```

などと記載があるはずです。
GDP としては rpm が使用可能で、その他の package システムは不要なので、
ipk はビルド対象外にしてしまいましょう。

```
$ vi gdp-src-build/conf/templates/local.inc
- PACKAGE_CLASSES ?= "package_rpm package_ipk" 
+ PACKAGE_CLASSES ?= "package_rpm" 
```

等と編集し rpm のみビルド対象にします。

これで、
「ビルドパッケージ数5257、ビルド時間2時間40分」が
「ビルドパッケージ数5004、ビルド時間2時間35分」になりました。

どのパッケージマネージャーが使用可能か調べる方法は、
簡単に知るには一度実機を起動できる状態にして、実機ログイン後、

```
# rpm --version   // rpm が使用可能か確認
# opkg --version  // ipk が使用可能か確認
```

が一番簡単かと思います。

rpm ipk 以外に他にも不要なパッケージやレシピがあったら削りましょう。
＃ここは不要なものが何か精査しないといけないという意味で、若干手間です。

## 高速化3: ICECC 使用

ICECC とは分散ビルドツールです。
事前準備として、 PC を複数用意し、 ICECC の設定をします。
ICECC 設定済の PC のマシンリソースに空きがあったら、
空きの PC にビルドするファイルを送ってビルドしてもらうという仕組みです。

環境構築は ICECC 本家の https://github.com/icecc/icecream を見ていただくとして、
ここでは yocto 的設定のポイントを記載します。

同じように、 local.conf に以下のような設定をしていきます。

```
+ ICECC_PATH = "/usr/bin/icecc"   // ICECC の実行ファイルのパス
+ INHERIT += "icecc"              // おまじない
+ BB_NUMBER_THREADS ?= "12"       // BitBakeが一度に並列に実行するタスクの最大数。要チューニング。
+ ICECC_USER_PACKAGE_BL ?= "gpsd" // ICECC しない(ホストビルドする)パッケージを指定。 BL = black list
```

参考
http://www.yoctoproject.org/docs/1.8/ref-manual/ref-manual.html

> ```
BB_NUMBER_THREADS
The maximum number of tasks BitBake should run in parallel at any one time. The OpenEmbedded build system automatically configures this variable to be equal to the number of cores on the build system. For example, a system with a dual core processor that also uses hyper-threading causes the BB_NUMBER_THREADS variable to default to "4".
For single socket systems (i.e. one CPU), you should not have to override this variable to gain optimal parallelism during builds. However, if you have very large systems that employ multiple physical CPUs, you might want to make sure the BB_NUMBER_THREADS variable is not set higher than "20".
```

参考
https://www.openembedded.org/wiki/Using_IceCC

> Local Configuration
> A sample local.conf entry for icecc that does not distribute compiles jobs for native packages looks like this. Change the paths to match your setup

> ```
PARALLEL_MAKE = "-j 10"
ICECC_PATH = "/usr/bin/icecc"
#ICECC_ENV_EXEC = "/proj/oplinux-0.2/op-linux/branches/oplinux-0.2/tmp/icecc-create-env"
ICECC_USER_CLASS_BL = " native"
INHERIT += "icecc"
```



## 高速化4: sstate-cache

sstate-cache はビルドのキャッシュ化、「前回と同じビルドするなら再度ビルドせず、
前回のビルド結果を流用しよう」という仕組みです。
当然初回ビルドは高速化の効果は無く、2回目以降のビルドで効果が出ます。
こちらも local.conf で一行指定するのみです。

### 指定方法

```local.conf
SSTATE_DIR ?= "/opt/Yocto/sstate/gdp"
```

※具体的なパスはお好みで設定してください。

## 最終的な実行結果

```
$ time bitbake genivi-dev-platform
Parsing recipes: 100% |##########################################################################################| Time: 00:00:32
Parsing of 1745 .bb files complete (0 cached, 1745 parsed). 2329 targets, 324 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies

Build Configuration:
BB_VERSION        = "1.30.0"
BUILD_SYS         = "x86_64-linux"
NATIVELSBSTRING   = "Ubuntu-14.04"
TARGET_SYS        = "arm-poky-linux-gnueabi"
MACHINE           = "raspberrypi3"
DISTRO            = "poky-ivi-systemd"
DISTRO_VERSION    = "11.0.0"
TUNE_FEATURES     = "arm armv7ve vfp thumb neon vfpv4 callconvention-hard cortexa7"
TARGET_FPU        = "hard"
meta              
meta-yocto        
meta-yocto-bsp    = "HEAD:12eb72ee3b02f826a156ff4e396c770f2b93571e"
meta-ivi          
meta-ivi-bsp      = "HEAD:1f172234b04e1439f8d66593c4326657a2428d83"
meta-oe           
meta-filesystems  
meta-ruby         = "HEAD:247b1267bbe95719cd4877d2d3cfbaf2a2f4865a"
meta-qt5          = "HEAD:d715f2c1d340fa38f8a9860acc73de5e14a38b75"
meta-genivi-dev   = "HEAD:e757709e60667de3ca6aba30477a522578ededd8"
meta-rust         = "HEAD:d0663639a08ed60bb83fd6eb99e3e2045b21b53c"
meta-oic          = "HEAD:4d215eb63a27b5fe14216a25a760092002d13a33"
meta-erlang       = "HEAD:4d7eacc8e6593934ed5b0c8abc3d3e9dc339d849"
meta-rvi          = "HEAD:de9d548fe35e2cee8688faaae910b4f6f7fea17e"
meta-nodejs       = "master:eec531e97a17bfd406f3bf76dee4057dcf5286a4"
meta-raspberrypi-gdp = "HEAD:e757709e60667de3ca6aba30477a522578ededd8"
meta-raspberrypi  = "HEAD:a5f9b07a820d50ae5fb62e07306cd4e72d8638a9"

NOTE: Preparing RunQueue
NOTE: Executing SetScene Tasks
NOTE: Executing RunQueue Tasks
NOTE: Tasks Summary: Attempted 5013 tasks of which 4149 didn't need to be rerun and all succeeded.

real	10m14.927s
user	43m1.578s
sys	7m0.703s
$ 
```

爆速！10分！


参考
https://wiki.yoctoproject.org/wiki/Enable_sstate_cache
> Use local sstate cache
You can make all the builds pointing to the same SSTATE_DIR to share sstate files between them, like this:
```
SSTATE_DIR ?= "/share/sstate-cache"
```

## 最終的なビルドコマンドまとめ

```
// 新規にシェルを開く。
$ mkdir GDP
$ cd GDP/
$ git clone https://github.com/GENIVI/genivi-dev-platform.git -b gdp-11
$ cd genivi-dev-platform
$ vi gdp-src-build/conf/templates/local.inc   // 以下追加変更
// 不要な package をビルド対象外に
- PACKAGE_CLASSES ?= "package_rpm package_ipk" 
+ PACKAGE_CLASSES ?= "package_rpm" 

// ICECC 使用
+ ICECC_PATH = "/usr/bin/icecc" 
+ INHERIT += "icecc" 
+ BB_NUMBER_THREADS ?= "12" 
+ ICECC_USER_PACKAGE_BL ?= "gpsd" 

// ソースダウンロードディレクトリ固定
+ DL_DIR = "/opt/Yocto/downloads/gdp" 

// sstate cache 使用
+ SSTATE_DIR ?= "/opt/Yocto/sstate/gdp"

$ source init.sh raspberrypi3
$ bitbake genivi-dev-platform
```

# まとめ

結果を再度まとめます。

| 内容                               | パッケージ数 | 時間[H:M] | 簡単さ | 効果 | 注                 |
|:-----------------------------------|:-------------|:----------|:-------|:-----|:-------------------|
| デフォルト                         |        5257  | 10:00     | -      |  -   | 10時間             |
| ソースダウンロードディレクトリ固定 |        5257  |  2:40     | ◎     |  ◎  | 逆に言うと10時間のうちダウンロード時間が7時間20分。2回目以降から有効。|
| 更に不要な package をビルド対象外に|        5004  |  2:35     | ○     |  △  |                    |
| 更に ICECC 使用                    |        5004  |  1:30     | △     |  ○  | 要ビルド用PC複数台 |
| 更に sstate cache 使用             |        5004  |  0:10     | ◎     |  ◎  | 2回目以降から有効。 |


Yocto ビルドの高速化方法は世の中に数あれど、
具体的な高速化具合を定量的に記載している記事が少ない気がしたので数字入りで記載しました。

`DL_DIR` , `SSTATE_DIR` の指定は対応量の割に効果絶大(1行指定するだけで数時間短縮)
なのでこれは絶対実施しましょう！

あとはリビルド頻度と対応の手間のバランスを見ながら実施するのが良いでしょう。
何回もリビルドするなら不要な package をビルド対象外にした方が良いですし、
ICECC は環境構築大変だったり PC いくつも用意しないといけませんが、
一度環境作れば、初回ビルドから高速化されます。


# あとがき・謝辞
DL_DIR 指定のやり方、 [Yocto Project Japan 勉強会 #1](https://atnd.org/events/47348) で岩松先生 [@iwamatsu](https://twitter.com/iwamatsu) に教えて頂いたものです。
ビルド早くなって感謝感謝。