😺

Docker開発環境(5): キャッシュの活用とYoctoビルド環境の派生

2021/08/10に公開

前回の続きです。

https://zenn.dev/anyakichi/articles/398f27e5b431bf

前回は docker-buildenv を用いて Yocto のビルド環境を作成しました。Yocto を素の状態からビルドをするとソースコードのダウンロードやアプリケーションのコンパイルでかなり時間がかかるのですが、Yocto 自身にダウンロードしたソースツリーを保存しておく機能や、ビルド結果を保持しておく機能があります。まずはこれらを活用してビルド時間を短縮する方法を確認しましょう。

キャッシュの活用

Yocto に限らずですが、何かキャッシュデータを持たせておくことでビルドが高速化できることがあります。ただし、Docker コンテナは毎回新しいものが作り直されてしまうので、何も考えずにビルドをするとカレントディレクトリ以外まっさらな状態から始まってしまいます。

もちろん「前回と同じディレクトリ」を使ってビルドする場合にはそのディレクトリにキャッシュデータが残っていればキャッシュの恩恵を受けることができますが、新しく作ったビルド環境と別の場所で作ったビルド環境でキャッシュを活用したいと思うと何かしらデータの共有スペースが必要になります。

docker-buildenv では、din スクリプトで ~/.cache/buildenv が存在する場合に、この場所を /cache にマップするようになっています。つまり、コンテナから /cache 以下に行った書き込みの結果は、他のコンテナからも参照可能な形で保持されます(ビルド環境によっては /cache に他の場所をマウントしたいかもしれないので ~/.cache/buildenv を自動では作りません)。

ということで、前回の Yocto のビルドをこのキャッシュ領域を使って共有してみます。と言っても前回作成した setup.40.md にキャッシュを利用するための仕組みが実は組み込まれていました。

setup.40.md
Skip setup if setup is already done.

    $ [[ "\${BBPATH}" ]] && return 0

Setup yocto build environment.

    $ source poky/oe-init-build-env build || return 1

Create auto.conf for our build environment.

    $ rm -rf conf/auto.conf
    $ [[ "${YOCTO_MACHINE}" ]] && echo "MACHINE = \"${YOCTO_MACHINE}\"" >> conf/auto.conf
    $ [[ "${YOCTO_DL_DIR}" ]] && echo 'DL_DIR = "${YOCTO_DL_DIR}"' >> conf/auto.conf
    $ [[ "${YOCTO_SSTATE_DIR}" ]] && echo 'SSTATE_DIR = "${YOCTO_SSTATE_DIR}"' >> conf/auto.conf

最後の 2 行のところですね。もしも $YOCTO_DL_DIR が設定されていれば、Yocto の設定ファイルの DL_DIR にこの値を設定します。こうすることで、ダウンロードした tarball などが $YOCTO_DL_DIR の位置に保存されます。同様に Yocto の SSTATE_DIR には $YOCTO_SSTATE_DIR の値が設定されます。こうすることで、生成したバイナリのキャッシュファイルが $YOCTO_SSTATE_DIR 以下に置かれるようになります。

例えば

$ din \
    -e YOCTO_DL_DIR=/cache/yocto/downloads \
    -e YOCTO_SSTATE_DIR=/cache/yocto/sstate \
    anyakichi/yocto-builder:dunfell

ように起動すれば、~/.cache/buildenv/yocto/{downloads,sstate} 以下に共有データを置ける、という要領ですね。ccache を使いたければ -e CCACHE_DIR=/cache/ccache を追加していくようなイメージです。

普段から使うオプションは alias で登録してあった方が便利です。

$ alias din="din -e YOCTO_DL_DIR=/cache/yocto/downloads -e YOCTO_SSTATE_DIR=/cache/yocto/sstate"

ここでこの alias された状態の din で改めて 1 回目のビルドをし、

$ mkdir -p ~/.cache/buildenv
$ mkdir ~/yocto-1 && cd $_
$ din anyakichi/yocto-builder:dunfell extract -y
$ din anyakichi/yocto-builder:dunfell build -y

次に別のディレクトリを作ってビルドをしてみます。

$ mkdir ~/yocto-2 && cd $_
$ din anyakichi/yocto-builder:dunfell extract -y
$ din anyakichi/yocto-builder:dunfell build -y

yocto-2 で実施した 2 回目のビルドはかなり早く終わるのではないかと思います。

buildenv でのビルド時には /cache を活用してビルドを高速化するのが、コンテナ使用時のベストプラクティスです。

Raspberry Pi の Yocto ビルド環境を作成する

次に docker-yocto-builder からビルド環境を派生させていくことを考えていきます。ここでは Raspberry Pi 用の Yocto ファームウェアを作る環境を考えます。

通常 Yocto は本家の出しているものがそのまま直接ファームウェアに使われることは少なく、各ベンダーが BSP などの形で追加のレイヤ・レシピを提供しているのが普通です。そのようなバリエーションごとに個別に Docker 環境を作る必要があるかというとそういうことはなく、簡単な拡張で異なる派生物に対応可能です。

まずは Raspberry Pi 用の Yocto の meta レイヤを探します。以下が見つかりますね。

https://git.yoctoproject.org/cgit/cgit.cgi/meta-raspberrypi/about/

poky の他に meta-openembedded が必要と記載されていますが、他はほとんどオリジナルの Yocto と同様でビルドができそうです。

docker-yocto-builder には extract.40.md, setup.40.md などが含まれているので、今回必要な追加部分を extract.60.md, setup.60.md などで入れていくことにしましょう。

まず extract では meta-opemembedded と meta-raspberrypi を追加でダウンロードするようにします。YOCTO_BRANCH は docker-yocto-builder で定義されている環境変数ですが、そのまま流用します。

buildenv.d/extract.60.md
Clone meta-raspberrypi and dependencies.

    $ git clone -b ${YOCTO_BRANCH} \
        https://git.openembedded.org/meta-openembedded
    $ git clone -b ${YOCTO_BRANCH} \
        https://git.yoctoproject.org/git/meta-raspberrypi

次に、追加したリポジトリの中から必要な meta レイヤを追加していきます。bitbake-layers -F add-layer とすることで、既にレイヤが登録されている場合でもエラーになることなく処理が継続します。

buildenv.d/setup.60.md
Add meta layers.

    $ bitbake-layers -F add-layer \$(pwd)/../meta-openembedded/meta-oe
    $ bitbake-layers -F add-layer \$(pwd)/../meta-openembedded/meta-python
    $ bitbake-layers -F add-layer \$(pwd)/../meta-openembedded/meta-multimedia
    $ bitbake-layers -F add-layer \$(pwd)/../meta-openembedded/meta-networking
    $ bitbake-layers -F add-layer \$(pwd)/../meta-raspberrypi

最後に Dockerfile です。docker-yocto-builder のターゲットとする Yocto のブランチ (dunfell など)を base として渡すことで派生物を一気に作ります。上記ファイルを追加するとともに、YOCTO_MACHINE 環境変数でビルドしたいボードのバリエーション(raspberrypi4-64 など)を設定できるようにします。

Dockerfile
ARG base
FROM ghcr.io/anyakichi/yocto-builder:${base}

COPY buildenv.d/* /etc/buildenv.d/

ARG yocto_machine
ENV \
  YOCTO_MACHINE=${yocto_machine}

ちなみに、前回は Docker Hub の anyakichi/yocto-builder を使っていましたが、GitHub の方にも同じものを push してあるので ghcr.io/anyakichi/yocto-builder からも同じものが取得できます。GitHub Actions でビルドする際に有利なのかなということで ghcr.io を使っていますが、実際にはどちらでも構いません。

build については変更しなくても問題ないので、build.60.md は追加しません。デフォルトのビルドターゲットを変えたければ YOCTO_BITBAKE_TARGET=core-image-base のように指定しておくこともできます。

ちなみに、Raspberry Pi 用のビルド環境も既に存在しており、以下のものを使うことができます。

https://github.com/anyakichi/docker-yocto-rpi-builder

使い方はほとんど変わりません。

$ mkdir rpi-1 && cd $_
$ din anyakichi/yocto-rpi-builder:hardknott-raspberrypi4-64 extract -y
$ din anyakichi/yocto-rpi-builder:hardknott-raspberrypi4-64 build -y

Intel Atom の Yocto ビルド環境を作成する

次に、Intel が公式にリリースしている Atom E3900 シリーズ向けのファームウェアの構築環境を作ることを考えます。

https://github.com/intel/iotg-yocto-bsp-public

この BSP では、poky を直接 clone するのではなく、上記の iotg-yocto-bsp-public に含まれるスクリプトを使うと、poky 他必要なリポジトリを clone してくるような作りになっています。そのため、docker-yocto-builder の extract, setup がそのままでは使用できません。

このような場合はシンプルに docker-yocto-builder の extract.40.md, setup.40.md を上書きして置き換えてしまいます。

buildenv.d/extract.40.md
Get iotg-yocto-bsp-public.

    $ git clone -b ${IOTG_YOCTO_BRANCH:-master} \
        https://github.com/01org/iotg-yocto-bsp-public.git

Execute setup.sh to obtain requied repositories.

    $ (cd iotg-yocto-bsp-public && printf "1\\n4\\n" | ./setup.sh)

setup.sh というスクリプトを実行すると依存するリポジトリ類が clone されるようになっているのですが、インタラクティブに選択肢に答える必要があるので、printf で回答を先打ちしています。

このようなインタラクティブ回避はビルド自動化においては必要になることはあるのですが、i.MX のビルド環境などではライセンスに同意するかどうかをインタラクティブに聞かれることがあり、こういうものは(少なくともパブリックに公開するような Docker イメージでは)自動的に回答するべきではないでしょう。

setup.40.md は oe-init-build-env の存在するパスが異なる以外は同じですので、掲載は省略します。あとは iotg-yocto-bsp-public として必要な依存パッケージを追加する Dockerfile を用意すれば完成です。

Intel Atom 用ビルド環境は以下に用意されています。

https://github.com/anyakichi/docker-yocto-iotg-builder

ビルド手順は以下のとおりです(いい加減飽きてきていれば、慣れてきた証拠です)。

$ mkdir iotg-1 && cd $_
$ din anyakichi/yocto-iotg-builder:mr5b04 extract -y
$ din anyakichi/yocto-iotg-builder:mr5b04 build -y

RZ/G2 + Gecko の Yocto ビルド環境を作成する

次にルネサス RZ/G2 シリーズの Yocto 環境に、Gecko ビルド環境を追加するという 3 層構造の場合を考えます。Gecko というのは要するに Firefox なのですが、ソースコードを改変すると Firefox は名乗れない問題があるためここでは Gecko と表記します。

RZ/G2 用ファームウェアのための meta-rzg2 は以下で公開されています。

https://github.com/renesas-rz/meta-rzg2

meta レイヤについては Raspberry Pi の場合と同様で、poky の他に必要なものを別途 clone すれば良いという標準的なモデルなので、追加の extract.60.md を用意します。

buildenv.d/extract.60.md
Checkout the specific revision of poky.

    $ git -C poky checkout 7e7ee662f5dea4d090293045f7498093322802cc

Clone the repositories required to build the RZ/G2 series firmware.

    $ git clone https://git.linaro.org/openembedded/meta-linaro.git
    $ git -C meta-linaro checkout 75dfb67bbb14a70cd47afda9726e2e1c76731885

    $ git clone https://git.openembedded.org/meta-openembedded
    $ git -C meta-openembedded checkout 352531015014d1957d6444d114f4451e241c4d23

    $ git clone http://git.yoctoproject.org/cgit.cgi/meta-gplv2
    $ git -C meta-gplv2 checkout f875c60ecd6f30793b80a431a2423c4b98e51548

    $ git clone https://github.com/meta-qt5/meta-qt5.git
    $ git -C meta-qt5 checkout c1b0c9f546289b1592d7a895640de103723a0305

    $ git clone -b ${META_RZG2_BRANCH} https://github.com/renesas-rz/meta-rzg2.git
    $ [[ ${META_RZG2_BRANCH} != BSP-1.0.0 ]] || git -C meta-rzg2 cherry-pick 79dbcd79ed4e1192c408a60a17c70b14c6d0715e

poky の checkout すべきリビジョンがピンポイントで指定されていたりするのがちょっと珍しい感じですが、このあたりは黙って公式に従います。

次に setup ですが、meta-rzg2 では bbyalers.conf と local.conf をテンプレートからコピーしろ、というビルド手順になっています。docker-buildenv の思想としては setup は何度やっても無害であるべき、なのですが、毎回 bblayers.conf と local.conf をコピーしているようでは、ユーザーが書き換えた(かもしれない)bblayers.conf や local.conf を上書きしてしまします。

これではちょっと困るので、local.conf にコピーする情報は auto.conf へ、bblayers.conf の中身については oe-init-build-env を実行する前の段階でファイルが存在しなければコピー、という手順に変えています。

例によって完成物はこちらです。

https://github.com/anyakichi/docker-yocto-rzg2-builder

EK874 向けに BSP-1.0.8 をビルドする場合は以下のようにビルドできます(ただし、一応メッセージも出てくるのですが、プロプライエタリのドライバをルネサスからユーザー登録して組み込まないとビルドが完走しません)。

$ mkdir rzg2-1 && cd $_
$ din anyakichi/yocto-rzg2-builder:ek874-108 extract -y
$ din anyakichi/yocto-rzg2-builder:ek874-108 build -y

ここからさらに Gecko のビルド手順を加えます。yocto-rzg2-builder で extract.60.md など 60 まで使っちゃったので、次は 80 かな、みたいな感じで拡張していくことになります。

完成品はこちら。

https://github.com/anyakichi/docker-gecko-embedded-builder

ビルド手順は同様です。Gecko 78 を組み込んだファームウェアを作ってみます。

$ mkdir ek874-esr78 && cd $_
$ din anyakichi/gecko-embedded-builder:ek874-108-esr78 extract -y
$ din anyakichi/gecko-embedded-builder:ek874-108-esr78 build -y

その他の組み込み向けビルド環境

ここまで Yocto のビルド環境とその派生物を見てきましたが、最後にその他の組み込み向けビルド環境も紹介しておきます。

まずは UEFI アプリケーションを作成するための EDK-II アプリケーションのビルド環境。

https://github.com/anyakichi/docker-edk2-builder

次に Android の OS などの基礎部分 (Android Open Source Project) のビルド環境。

https://github.com/anyakichi/docker-aosp-builder

私のような組み込み技術者は手順のややこしい様々なファームウェアを作らなければならないことが多いのですが、Docker でパッケージ化してあれば何はともあれ extract, build とすればビルドまではいけるのでとても重宝しています。

次回は組み込みからはちょっと離れて、docker-buildenv のアプリケーションへの応用を考えていきます。

Discussion