Yocto：rootfsへのパッケージ追加・削除方法

# 1. はじめに
本ページでは、Yoctoでビルドしたイメージに含まれるパッケージの追加/削除方法について紹介します。
BSPなどで作成されるrootfsイメージに対して必要なミドルウェアを追加する場合、パッケージを追加する必要があります。また、パッケージ削除は、組込み用途向けなどストレージ容量が限られているケースで必要とされる他、使用しないパッケージをイメージから取り除くことで、OSSライセンス違反（例：ソース コード開示義務があるのに、公開していなかった、など）のリスクを減らすことができます。

この記事は、上記のような理由でrootfsに変更を加えたいけど、rootfsへのパッケージ追加/削除の方法が良く分からない！という、Yocto初心者向けの内容になっています。
また、以降の説明は、BSP等のビルド環境が既に存在していることを前提に説明します。ビルド環境がない場合、以下ページを参考にビルド環境を構築することができます。
https://docs.yoctoproject.org/brief-yoctoprojectqs/index.html

# 2. 事前準備：イメージファイル内のパッケージを確認する方法について
パッケージの追加/削除を行う前に、rootfsに何が入っていて、何が不要なのかはっきりさせる必要があります。これを確認するために、イメージファイル(Yoctoでビルドされるrootfsイメージ)に含まれるパッケージを確認します。
イメージビルドを実行すると、ビルドディレクトリにmanifestファイルが作成され、このファイルにrootfsに含まれているパッケージが記載されます。また、以降の手順でbuildhistoryを確認する必要があるので、イメージビルドを実行する際は、buildhistoryを有効にしておきます。buildhistoryは、local.conf(<ビルドディレクトリ>/conf/local.conf)に以下の記述を追加することで有効にすることができます。
`
INHERIT += "buildhistory"
`
manifestファイルは、以下に作成されます。ビルドディレクトリ、マシン名、イメージ名については、各自のビルド環境依存で変わる部分のため、適宜読み代えてください。
・<ビルドディレクトリ>/tmp/deploy/images/<マシン名>/<イメージ名>.manifest
このファイルには、スペース区切りで以下が記載されています。
・<パッケージ名> <パッケージアーキ> <パッケージバージョン>

また、buildhistoryを有効にしている場合、以下ファイルにて、どのパッケージがインストールされているのか、ipkやrpmの単位で確認することができます。
・<ビルドディレクトリ>/buildhistory/images/<マシン名>/glibc/<イメージ名>/installed-packages.txt

# 3. パッケージ追加について
### 3-1. パッケージ単体の追加方法 ###
rootfsへのパッケージ追加は、イメージレシピ(イメージファイルを作成時にbitbakeの引数として指定するレシピ：e.g. core-image-minimal)に、追加したいパッケージ名を追記することで実現できます。
また、Yoctoは既存のレシピを直接変更しないことが一般的なため、イメージレシピ自体を直接変更するのではなく、<イメージレシピ名>.bbappendファイル(以降、イメージアペンドレシピと呼称)を作成し、そちらに変更内容を記載します。イメージアペンドレシピは、bitbake実行時にイメージレシピと合わせて読み込まれ、両方の記載内容を反映した状態で、ビルドが行われます。
以下は、カスタマイズ用のレイヤ(meta-customize)内で、変更用レシピを作成する際の例です。
~~~
$ cat meta-customize/recipes-core/images/<イメージレシピ名>.bbappend
IMAGE_INSTALL_append = " busybox"
# IMAGE_INSTALL += " busybox" は、IMAGE_INSTALL_appendと同じ動作となる
# IMAGE_INSTALL = " busybox" はIMAGE_INSTALLが上書きされてしまうのでNG
~~~
例に記載した内容について説明します。
例に示した<イメージレシピ名>のファイル名にて、作成したイメージアペンドレシピが、どのイメージレシピへの変更用レシピであるか示しています。
イメージレシピに含まれるパッケージは、IMAGE_INSTALL変数にまとめて設定されています。これに記述を追加するために、追加しようとしているパッケージをIMAGE_INSTALL_appendで指定しています。また、"IMAGE_INSTALL +="で指定しても、上記同様の動作となります。
なお、"IMAGE_INSTALL ="で指定した場合、元のイメージレシピのIMAGE_INSTALL変数を上書きすることになってしまうため、注意が必要です。
### 3-2. パッケージグループを利用した追加方法 ###
また、rootfsへのパッケージ追加のもう一つの一般的な方法として、パッケージグループに追加する方法があります。
パッケージグループは、特定の機能を持っていません。また、パッケージグループ用のレシピでは、任意のパッケージを指定しています。このパッケージグループをrootfsに追加することで、パッケージグループ用レシピで指定している全てのパッケージが、rootfsに追加されます。
そのため、パッケージグループは、機能カテゴリのグルーピングや、debug用パッケージの管理に利用されます。また、パッケージグループ自体は、3-1. と同様の方法で、イメージレシピに追加することができます。
以下は、パッケージグループの記載例です。ここでは、packagegroup-debugという名前のパッケージグループ用のレシピを作成し、busybox、coreutils、tcpdumpの3パッケージを、パッケージグループに含めるパッケージとして指定しています。
~~~
$ cat meta-customize/recipes-core/packagegroups/packagegroup-debug.bb
DESCRIPTION = "Packagegroup for debug packages"
LICENSE = "MIT"
inherit packagegroup
ALLOW_EMPTY_${PN} = "1"

RDEPENDS_${PN} = " \
  busybox \
  coreutils \
  tcpdump \
"
~~~
各項の概要は以下の通りです。
| 項目 | 概要 |
| ---- | ---- |
| DESCRIPTION | このパッケージグループを表す説明 |
| LICENSE | このパッケージのライセンス<br> 自身の運用方針に沿ったライセンスを設定する|
| inherit packagegroup | パッケージグループ用のクラスファイル設定を読み込む<br>クラスファイルは、特定の処理をするための便利なライブラリ、程度の認識でOK |
| ALLOW_EMPTY_${PN} | このパッケージグループが空であることを許容するための記述<br>パッケージにファイルが含まれていないとエラーするため、それに対する対処 |
| RDEPENDS_${PN} | このパッケージグループに含めるパッケージ名を記載する |

また、上記の例では新規にパッケージグループを作成していますが、既存のパッケージグループに任意のパッケージを追加することもできます。
以下は、packagegroup-debugというパッケージグループに、busyboxを追加する際の例です。
~~~
$ cat meta-customize/recipes-core/images/packagegroup-debug.bbappend
RDEPENDS_${PN}_append = " busybox"
~~~

# 4. パッケージ削除について
パッケージを削除するためには、まずどうやって削除対象のパッケージがrootfsに追加されているのか理解する必要があります。
rootfsにパッケージを追加する方法には、大きく分けて以下2通りのケースがあります。
| ケース | 概要 |
| ---- | ---- |
| 明示的にrootfsに追加しているケース | イメージレシピ、もしくはイメージアペンドレシピで、IMAGE_INSTALL or IMAGE_INSTALL_appendで指定しているパッケージが該当 |
| rootfsに含まれる別のパッケージの依存で追加されているケース | rootfsに含まれるパッケージのレシピで、RDEPENDS_\\${PN} or RDEPENDS_\\${PN}_appendに指定されたパッケージが該当|

後者のケースの説明を少々補足します。例えば、パッケージAに含まれているコマンドで、パッケージBに含まれているライブラリが必要な場合、パッケージAのレシピでは、RDEPENDS_\\${PN}にパッケージBを指定しています。
このとき、パッケージAはパッケージBに依存している(パッケージBはパッケージAに依存されている)と言えます。また、パッケージAがrootfsに含まれる場合、パッケージBも自動的にrootfsに追加されます。

どちらの方法で削除対象パッケージが追加されているかによって、削除するための手順が異なります。また、両方のケースを満たすこともあるので、パッケージ削除時には両方とも確認する必要があります。

### 4-1. 明示的にrootfsに追加しているケースへの対処
明示的に追加している場合、buildhistoryの以下ファイルの"IMAGE_INSTALL“に、パッケージ名が記載されています。
・<ビルドディレクトリ>/buildhistory/images/<マシン名>/glibc/<イメージ名>/image-info.txt

この場合、例えば、busyboxをrootfsから削除したいのであれば、以下のように"IMAGE_INSTALL_remove“の処理を追加することで、rootfsからbusyboxを削除できます。
~~~
$ cat meta-customize/recipes-core/images/<イメージレシピ名>.bbappend
IMAGE_INSTALL_remove = " busybox"
~~~
###  4-2. rootfsに含まれる別のパッケージの依存で追加されているケースへの対処
パッケージ間の依存関係は、以下のファイルで確認することができます。このファイルには、graphvizの形式でyoctoパッケージの依存関係が記載されています。
・<ビルドディレクトリ>/buildhistory/images/<マシン名>/glibc/<イメージ名>/depends.dot

以下を例にdepends.dotファイルの見方について説明します。
`
"busybox" -> "libc6"
`
この場合、busyboxはlibc6に依存している(libc6はbusyboxに依存されている)と言えます。また、libc6はbusyboxによってrootfsに追加されている状態であると言えます。

libc6をrootfsから削除したいのであれば、busyboxもrootfsから削除する必要があります。また、libc6を削除するためには、他にもlibc6に依存しているパッケージがないか確認し、全てのlibc6に依存しているパッケージを削除することで、rootfsからlibc6を削除することができます。

上記は、通常のパッケージが依存しているケースでしたが、パッケージグループが依存しているケースでは、少し考え方が変わります。
packagegroup-debugがbusyboxに依存している場合を元に説明します。
`
"packagegroup-debug" -> "busybox"
`
busybox&libc6の例と同様に、packagegroup-debug自体をrootfsから削除することができれば、問題なくbusyboxを削除することができます。ただ、影響範囲の都合などで、パッケージグループ自体を削除することが難しいケースがあります。この場合、以下のように、パッケージグループ側の依存関係を変更することで、packagegroup-debug/busybox間の依存関係を無くすことができ、packagegroup-debugをrootfsから削除することなく、busyboxだけをrootfsから削除することができます。
~~~
$ cat meta-customize/recipes-core/packagegroups/packagegroup-debug.bbappend
RDEPENDS_${PN}_remove = " \
  busybox \
"
~~~
パッケージグループ自体はグルーピングのためのレシピなので、特定のパッケージが無くなってもビルドエラーを起こすことはありません。しかし、グループ内の別レシピが、削除したパッケージを必要としているケースもあるので、本当に削除して問題ないのか確認してから削除する必要があります。

# 5. まとめ
今回の記事は、あまりYoctoに習熟していない方向けに書かせていただきました。
記事を読んでいただければ分かる通り、パッケージ追加自体はそんなに難しい話ではないと思います。パッケージ削除は、どうやって削除対象パッケージが追加されているのか理解した上で、それを打ち消すようなレシピを作成する必要があるため、若干難易度が上がると思いますが、多くの場合は記事を例に各自のビルド環境を調べていただければ対処可能だと思います。
ただ、全てのケースは網羅できていないため、実際のビルド環境を弄っていると、ここで紹介している以外の方法でパッケージを追加しているケースも出てくると思います。しかし、その場合でも基本的な考え方は変わらないです。その時は、パッケージがどうやって追加されているのか理解し、それを打ち消すレシピをどうやって書いたらいいのか考えてみてください。