みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

YoctoProject Toaster使ってみる

はじめに

YoctoProjectはToasterというWebインターフェースを提供している。

Toasterを使うとコマンドラインに慣れていないユーザーでもYoctoProjectを使用して ビルドを行うことができ、ビルドされたイメージをブラウザでダウンロードすることもできる。

筆者はコマンドラインのほうが色々ラクなのでなのでこれまで使ってこなかったがせっかくなので調べてみる。

Toasterの機能

マニュアルの説明

Toaster ManualによるとToaster機能は下記の通り。

  • Browse layers listed in the various layer sources that are available in your project (e.g. the OpenEmbedded Layer Index at https://layers.openembedded.org/).
  • Browse images, recipes, and machines provided by those layers.
  • Import your own layers for building.
  • Add and remove layers from your configuration.
  • Set configuration variables.
  • Select a target or multiple targets to build.
  • Start your builds.

あと

  • See what was built (recipes and packages) and what packages were installed into your final image.
  • Browse the directory structure of your image.
  • See the value of all variables in your build configuration, and which files set each value.
  • Examine error, warning, and trace messages to aid in debugging.
  • See information about the BitBake tasks executed and reused during your build, including those that used shared state.
  • See dependency relationships between recipes, packages, and tasks.
  • See performance information such as build time, task time, CPU usage, and disk I/O.

ざっくりと要約

ブラウザで以下のことができる。

  • LayerIndex上のレイヤ、レシピの検索ができる
  • 見つかったレイヤ、レシピを自分のプロジェクトに追加、削除ができる
  • local.conf(実際にはtoaster.conf)に変数の設定ができる
  • ビルドができ、ビルド結果をダウンロードできる
  • ビルド時の情報(実行されたタスクや収録されたレシピ、パッケージの一覧、rootfsのディレクトリツリーなど)が閲覧できる
  • ビルド時のリソースのログ(ビルド時間、CPU使用率、ディスクIOなど)が閲覧できる

その他

個人的にキーとなるポイントは下記。

  1. LayerIndexと連携している
  2. ビルド結果などのデータをsqliteのデータベースに保存している

Toasterの実行

とりあえずToasterを一度使ってみる

環境構築

Pokyの取得

下記を実行しPokyを取得する。

$ mkdir -p ~/yocto/toaster
$ cd ~/yocto/toaster
$ git clone git://git.yoctoproject.org/poky.git -b kirkstone

依存するpythonパッケージのインストール

ToasterはPython3のDjangoを使用しているため下記のように必要なパッケージをインストールする。

$ sudo apt install -y python3-pip
$ pip3 install --user -r poky/bitbake/toaster-requirements.txt

Toasterの起動

いつもどおりbitbakeの環境変数を読み込む

$ source poky/oe-init-build-env

下記でToasterを起動する。

$ source toaster start webport=0.0.0.0:8400

筆者の環境ではbitbakeを実行するPCとブラウザなどで作業するPCが異なっているため リモートアクセスできるように「webport=0.0.0.0:8400」を指定している。 これによって下記のようにアクセスすることができる。

http://”IPアドレス”:8400

core-image-minimalをビルド

とりあえず、qemuarm64向けのcore-image-minimalをビルドしてみる。

ブラウザ起動後の手順は下記のようになる。

  1. プロジェクトの作成
  2. MACHINEの設定
  3. イメージレシピのビルド

プロジェクトの作成

ブラウザでToasterにアクセスすると下記のようになる。

「Create your first Toaster project to run manage builds」か、画面右上の「New project」をクリックし、プロジェクト作成画面に遷移する。

下記を設定する

  1. 「Project Name」にToasterTestと入力
  2. 「Release」でYocto Project 4.0 "Kirkstone"を選択
  3. 「Create project」をクリック

プロジェクトが作成され、Configuration画面に遷移する。

MACHINEの設定

初期設定ではqemux86になっているので、qemuarm64に変更する。

Machineのボックスのリンクをクリックし、MACHINE変更に遷移。

エディットにqemuarm64を入力し、「Save」をクリック。

MACHINEが変更された。

イメージレシピのビルド

イメージレシピを選択する。

画面左側の「Image recipes」をクリックしImage recipes画面に遷移する。

下記を行う。

  1. エディットにcore-image-minimalと入力
  2. 「Search」をクリック

検索結果に「core-image-minimal」が出てくるので、画面右側の「Build recipe」をクリック。

ビルドの進捗が表示される。

ビルドが完了すると、ビルド履歴の画面に遷移する。

この時点でビルドしたものの履歴や、ビルド完了時刻、ビルドにかかった時間などの情報が得られる。 エラーや警告なども見ることができる。

完了したビルドのリンクをクリックするとビルドの詳細な情報を見ることができる。

ビルド結果のファイルもここからダウンロードできる。

ビルドホストの状態

Toasterが実際に動作しているビルドホストはどのようになっているか確認する。

$ tree . -d  -L 1
.
├── _toaster_clones
├── build
├── build-toaster-2
├── downloads
├── poky
└── sstate-cache

それぞれについては下記のようになっている。

ディレクト 概要
_toaster_clones Toasterがダウンロードしたレイヤ
build 最初のbuildディレクト
build-toaster-2 Toasterが作成したbuildディレクト
downloads ダウンロードキャッシュ
poky Poky
sstate-cache SStateキャッシュ

ToasterはLayerIndexと連携して自動的にレイヤをダウンロードしたりする都合上、 自分が使用するレイヤは_toaster_clonesに格納するようになっている。

またビルドディレクトリは「build-toaster-n」の名前で管理される。 nの部分はToaster内のプロジェクト番号に紐づく。 今回作成したプロジェクト「ToasterTest」のプロジェクトのURLは「http://"IPアドレス":8400/toastergui/project/2/ 」になっていることから プロジェクト番号は2であることがわかる。なのでビルドディレクトリも「build-toaster-2」となっている。 つまりToasterのプロジェクトとビルドディレクトリは1:1で紐付いていることになる。

Toasterで作成したプロジェクトのlocal.conf(toaster.conf)では、DL_DIRとSSTATE_DIRがあえてビルドディレクトリの1個上に設定されている。 これはプロジェクト間でダウンロードキャッシュとSStateキャッシュを共有しストレージや実行時間を短縮する狙いがあると推測できる。

ビルドディレクトリの内容

ビルドディレクトリは下記のようになっている。

./build-toaster-2
├── buildhistory
├── cache
├── conf
└── tmp

一見普通のビルドディレクトリと変わらない。

confディレクトリを見ると違いがあることがわかる。

./build-toaster-2/conf/
├── bblayers.conf
├── local.conf
├── templateconf.cfg
├── toaster-bblayers.conf
└── toaster.conf

通常のlocal.confやbblayers.confの他にtoaster.conftoaster-bblayers.confが存在している。

local.confのMACHINE設定を確認するとqemux86-64になっている。

$ grep 'MACHINE' ./build-toaster-2/conf/local.conf
#MACHINE ?= "qemuarm"
#MACHINE ?= "qemuarm64"
#MACHINE ?= "qemumips"
#MACHINE ?= "qemumips64"
#MACHINE ?= "qemuppc"
#MACHINE ?= "qemux86"
#MACHINE ?= "qemux86-64"
#MACHINE ?= "beaglebone-yocto"
#MACHINE ?= "genericx86"
#MACHINE ?= "genericx86-64"
#MACHINE ?= "edgerouter"
MACHINE ??= "qemux86-64"
#SDKMACHINE ?= "i686"

toaster.confのMACHINE設定を確認するとqemuarm64になっている。

$ grep 'MACHINE' ./build-toaster-2/conf/toaster.conf
MACHINE="qemuarm64"

このことからlocal.confは使用されずtoaster.confが使用されていることがわかる。

bblayers.confの内容は下記。

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
POKY_BBLAYERS_CONF_VERSION = "2"

BBPATH = "${TOPDIR}"
BBFILES ?= ""

BBLAYERS ?= " \
  /home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta \
  /home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta-poky \
  /home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta-yocto-bsp \
  "

toaster-bblayers.confの内容は下記。

# line added by toaster build control
BBLAYERS = "/home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta /home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta-poky /home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta-yocto-bsp"

現状ではToasterでレイヤの追加は行っていないのでどちらが使われているか判断できない。(がおそらくtoaster-bblayers.conf)

LayerIndexとの連携を試す

meta-oeなどのレイヤに含まれるレシピをビルドしてみる。

emacsレシピのビルド

今回は適当にemacsを試す。

画面左側の「Software recipes」をクリック。

エディットにemacsと入力し、「Search」をクリック。

検索結果にemacsが現れるが、meta-oeはプロジェクトのビルド対象外なので「Add layer」が表示されている。 とりあえず「Add layer」をクリックする。

meta-oeがプロジェクトに追加され、「Build recipe」が表示された。 「Build recipe」をクリックしてビルドしてみる。

ビルドの進捗が表示される。

emacsのビルドが完了した。

LayerIndexに登録されているレシピをToasterの中だけで簡単に検索でき、プロジェクトへ追加できるのは便利かもしれない。

レイヤ追加後のビルドホストの状態

bblayers.confとtoaster-bblayers.confのどちらにmeta-oeが追加されているか確認する。

$ grep 'meta-oe'  build-toaster-2/conf/bblayers.conf
$ grep 'meta-oe'  build-toaster-2/conf/toaster-bblayers.conf
BBLAYERS = "/home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta /home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta-poky /home/mickey/yocto/toaster/_toaster_clones/_git___git.yoctoproject.org_poky_kirkstone/meta-yocto-bsp /home/mickey/yocto/toaster/_toaster_clones/_git___git.openembedded.org_meta-openembedded_kirkstone/meta-oe"

Toasterからはtoaster-bblayers.confが使用されていることがわかった。

Command line buildsについて

ビルドホストでは、Toasterを起動するために読み込んだoe-init-build-envで作成されたビルドディレクトリであるbuildでbitbake可能な状態となっている。

Toasterを起動した状態でコマンドラインでビルドすると、ToasterからはCommand line buildsという特殊なプロジェクトとして扱われることになっている。

つまり、Toasterを起動した端末でbitbakeを行うこともできるようになっており、そのビルド時の情報もToasterでモニタリングできる。 試しに下記を実行してみる。

その前にToasterのダウンロードキャッシュとSStateキャッシュを参照するようにbuild/conf/local.confに下記を追加する。

DL_DIR = "${TOPDIR}/../downloads"
SSTATE_DIR = "${TOPDIR}/../sstate-cache"

bitbakeを実行する。

$ bitbake core-image-minimal

するとこのようにToaster上の「All builds」のタブで進捗が表示される。

その代わり、端末上の表示はいつものbitbakeの表示にはならない。

NOTE: Logfile for task /home/mickey/yocto/toaster/build/tmp/work/x86_64-linux/e2fsprogs-native/1.46.5-r0/temp/log.do_unpack.3681697
NOTE: Logfile for task /home/mickey/yocto/toaster/build/tmp/work/core2-64-poky-linux/sqlite3/3_3.38.5-r0/temp/log.do_patch.3684828
NOTE: Logfile for task /home/mickey/yocto/toaster/build/tmp/work/core2-64-poky-linux/util-macros/1_1.19.3-r0/temp/log.do_patch.3686898
NOTE: Logfile for task /home/mickey/yocto/toaster/build/tmp/work/core2-64-poky-linux/libnsl2/2.0.0-r0/temp/log.do_deploy_source_date_epoch.3687362
NOTE: Logfile for task /home/mickey/yocto/toaster/build/tmp/work/core2-64-poky-linux/xz/5.2.6-r0/temp/log.do_deploy_source_date_epoch.3686944
NOTE: Logfile for task /home/mickey/yocto/toaster/build/tmp/work/core2-64-poky-linux/readline/8.1.2-r0/temp/log.do_deploy_source_date_epoch.3686976
NOTE: Logfile for task /home/mickey/yocto/toaster/build/tmp/work/core2-64-poky-linux/sqlite3/3_3.38.5-r0/temp/log.do_deploy_source_date_epoch.3687798

bitbakeをCtrl+Cで中断してもToasterはきちんと検出する。

再度実行し今度は最後まで走らせる。

Projectが「Command line builds」になっている。

通常のToasterプロジェクトと同様にビルド結果を見ることができる。

既存のbitbake環境について

これまでToasterを使用せずに運用してきたbitbakeのビルドディレクトリについてもこの機能は使用することができる。 Toasterが起動した状態で1つでもタスクが実行されればToasterは情報収集を行うようだ。 なので、下記のようにすると既存の環境でもToasterの解析情報を使用することができる。

すでにcore-image-minimalがビルドされていると仮定して。

$ bitbake core-image-minimal -c clean
$ source toaster start nobuild webport=0.0.0.0:8400
$ bitbake core-image-minimal

nobuildオプションではToasterのプロジェクト作成およびビルド機能を無効にした状態で起動することができる。 Command line buildsの情報収集をするためだけならプロジェクト作成もビルド機能も不要なのでこのようにするとブラウザ上の操作もシンプルになる。

Toasterの終了

下記のコマンドでToasterを終了する。

$ source toaster stop

Toasterの気になる点

Toasterの3.10.2 Additional Information About the Local Yocto Project Releaseによると

Toaster will fetch the tip of your selected release from the upstream Yocto Project repository every time you run a build.

どういうことかというと、プロジェクト作成時に選んだYoctoProjectのRelease(バージョン)が対応するブランチが更新されると、Toasterでビルドを実行するたびにビルド環境にも反映されるということ。 これはビルド環境を更新していないけど、アップストリームのブランチに更新があるとToasterが自動的にそれを取り込むということになる。

組み込み開発環境では再現性を重視することが多いためバージョンやリビジョンを固定することが多い。 このような部分が自動的に行われるということはUncontrollableであるということにほかならない。

上記のリンクではこれを回避するためにはプロジェクト作成時に「Local Yocto Project」を選択とよいとなっているが、 その代わり「Local Yocto Project」ではLayerIndexとの連携は行わないとなっている。 それではあまりToasterを使うメリットもないように思う。

まとめ

  • YoctoProjectはToasterというWebインターフェースを提供している
  • 主な目的はコマンドラインに不慣れなユーザー向けらしい(がオフィシャルなマニュアルにはToasterのメリットや目的の記載が見つからない)
  • ToasterはLayerIndex(主にOpenEmbedded Layer Index)と連携している
  • ビルド時の情報をsqliteのDBに保存している
  • コマンドラインのbitbakeのビルド情報を収集することができる
  • ビルドごとにレイヤをフェッチし直すらしい

結局色々とやってみたが、筆者の感想としてはコマンドラインのbitbakeの情報収集以外に便利そうな要素が無い。 ポーティングやBSPの開発ではToasterを使用するにはオーバーヘッドが大きすぎる。

毎回レイヤをフェッチし直すというリスクもある。 例えばアップストリームでカーネルのレシピが更新されれば運が悪ければ変更中のドライバのパッチが急に当たらなくなる可能性もある。 (もちろんBug Fix、Security Fixがメインなので確率は低いと思うが。。。)

Local Yocto Projectを使用すればフェッチのリスクは回避できるが、LayerIndexとの連携がなくなるのでそもそも使う理由がなくなる気がする。

機能紹介やチュートリアルはよく見かけるのだが、想定するUseCaseがいまいちわからない。Toasterのマニュアルにもそういう記載は見つけられなかった。特にCI的に使用もしくは連携できるわけでもなさそうだし。

想定するユーザー層をもう少し明確にしてもらえると嬉しいかもしれない。

emacsのlanguage serverをEglotに乗り換える

はじめに

lsp-modeがあまり使いやすくないのでemacs 29から標準に入ったEglotに乗り換える。

環境設定

emacs

Ubuntu 22.04のemacsは古いのでsnapを使用する。 aptなどでインストール済みのものがある場合は削除しておく。

$ sudo snap install emacs --classic

Node.js

いくつかのlanguage serverはNode.jsで実装されているのでsnapでインストールする。

$ sudo snap install node --classic

pip3

いくつかのlanguage serverはPython3で実装されているのでpip3をインストールする。

$ sudo apt install python3-pip

language serverのインストール

下記をインストールする

  • bash-language-server
  • bitbake-language-server
  • cmake-language-server
  • html-languageservice
  • clangd
$ npm install bash-language-server
$ npm install --save vscode-html-languageservice
$ pip3 install --user bitbake-language-server
$ pip3 install --user cmake-language-server
$ pip3 install --user python-lsp-server
$ sudo apt install clangd

init.elの設定

;; eglot(LSP)
(use-package eglot
  :ensure t
  :hook
  (c++-mode . eglot-ensure)
  (sh-mode . eglot-ensure)
  (python-mode . eglot-ensure)
  (html-mode . eglot-ensure)
  (cmake-mode . eglot-ensure)
  (bitbake-mode . eglot-ensure)
  :config
  (add-to-list 'eglot-server-programs '((bitbake-mode) "bitbake-language-server"))
  :bind (("M-t" . xref-find-definitions)
     ("M-r" . xref-find-references)
     ("C-t" . xref-go-back)))

まとめ

lsp-modeはサポートしていないLSPサーバを登録するのにいろいろ書かないといけないし プロジェクトルートを登録するか毎回聞いてきてウザいなと色々不満があった。 設定が汚れてくるとまともにサーバと接続できなくて~/.emacs.d/.lsp-session-v1を削除することもしばしば。

eglotはサーバの登録も簡単だし毎回プロジェクトルートを聞いてくることもない。 今の所の使用感はかなり良い。

sshとtmuxを組み合わせる

はじめに

ssh先でもローカルでもbash起動時にtmuxが起動した状態にしたい。

設定

bashrc

bashを起動した時点でdefaultというセッションでtmuxを起動したい。 すでにdefaultセッションが存在する場合にはアタッチする。

~/.bashrcに下記を追加する

if [ -z ${TMUX} ]; then
    tmux new-session -A -s default
fi

sshへの細工

上記の内容をローカルとssh先の両方に書いた場合、tmuxセッションがネストしてしまう。 これを避けるために下記のようなスクリプト~/bin/ssh.shとして作成する。

#!/bin/sh

if [ -n "${TMUX}" ]; then
    tmux detach -E "ssh $*"
else
    ssh "$@"
fi

これをalias設定する。

alias ssh='ssh.sh'

これによりssh実行時にローカルのtmuxセッションがデタッチされる。

tmuxセッションの中から新規セッションを作成

Ctrl+bしてから:new -s <session-name>する。

tmuxセッションを切り替える

Ctrl+bしてからsを押す。

端末のキャプション

~/.config/tmux/tmux.confに下記を追加

# makes tmux send out the proper escape codes for setting the terminal title
set-option -g set-titles on
# sets the content of the title: user@host: /current/working/directory
set-option -g set-titles-string "#(echo $USER)@#H: #{pane_current_path}"

参考

まとめ

若干使い勝手が悪い部分もあるワークアラウンドだが自分がやりたいことはできているので良しとする。

YoctoProject bitbake-hashservを使ってみる(TCP/IP編)

はじめに

前回unix domain socketでローカルPC上でsstate-cacheを共有した。

今回はリモートPCでsstate-cacheを共有してみる。

bitbake-hashservの実行

サーバのIPアドレス

リモートPCから接続するためにIPアドレスを決めておく必要がある。

今回は192.168.1.100とする。

サーバ側の設定

サーバとなる初回ビルドを行う環境についてはlocal.confにこのような設定があれば良い。

BB_HASHSERVE = "auto"
BB_SIGNATURE_HANDLER = "OEEquivHash"

このままbitbakeを実行して「hashserv.db」と「sstate-cache」が生成されれば必要なデータは揃う。 もちろん初回実行時なので数時間はかかる。

サーバ側環境の構築

実際にbitbake-hashservを動かす環境を作成してみる。

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone
$ git clone git://git.yoctoproject.org/poky.git -b kirkstone
$ source poky/oe-init-build-env build-hashserv
$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confに下記を追加する。

MACHINE = "raspberrypi4-64"

# setup for hashserv database
BB_SIGNATURE_HANDLER = "OEEquivHash"

# enable uart
ENABLE_UART = "1"

# systemd
INIT_MANAGER = "systemd"

これでbitbakeを実行する。

$  bitbake core-image-base

ここまでは前回と同じ。

SSTATE_DIRの公開

bitbake-hashservでリモートからの接続を受け付ける前に、SSTATE_DIRに保存されているssatete-cacheをリモートからアクセスできるようにする必要がある。

httpでアクセスできるようにpythonhttp.serverを使用する。

$ cd ~/yocto/rpi-kirkstone/build-hashserv/sstate-cache
$ python3 -m http.server 9000

bitbake-hashserv

別の端末でbitbake-hashservを起動する。

$ cd ~/yocto/rpi-kirkstone
$ source poky/oe-init-build-env build-hashserv
$ bitbake-hashserv -r -l debug -d ./cache/hashserv.db -b "0.0.0.0:8687"

bitbake-hashservへの接続

クライアント環境の構築

クライアントとなる別のPCでYoctoProjectの環境を作成する。

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone
$ git clone git://git.yoctoproject.org/poky.git -b kirkstone
$ source poky/oe-init-build-env
$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confの設定

local.confに下記の内容を追加する。

MACHINE = "raspberrypi4-64"

# setup for connecting to bitbake-hashserv
BB_SIGNATURE_HANDLER = "OEEquivHash"
BB_HASHSERVE = "192.168.1.100:8687"
SSTATE_MIRRORS = "file://.* http://192.168.1.100:9000/PATH"

# enable uart
ENABLE_UART = "1"

# systemd
INIT_MANAGER = "systemd"

bitbake-hashservへの接続

接続のための設定をlocal.confに行ったため、bitbakeを実行してみる。

$ time bitbake core-image-base
... (snip) ...
real    7m56.090s
user    0m15.131s
sys     0m2.858s

今回使用したクライアント用のPCはビルドPCのスペックからみるとコア数が1/4のノートPCなので、通信の遅延を考慮しても 約8分でbitbakeが完了するのはやはりすごい。

応用編 kasをつかう

リモートでsstate-cacheを共有する時に最大限に効果を得るためには下記のようなことをサーバ側と揃える必要がある。

  1. 使用するレイヤ
  2. レイヤのリビジョン
  3. bitbake-hashservへの接続情報

それらを手動で揃えるのもそこまで手間ではないがkasを使用するともっと確実に環境を整えることができる。

kasのインストール

kasはpipでインストールできる

$ pip3 install --user kas

設定ファイルの作成

kasはYAML形式の設定ファイルを使用してbitbake環境を整えてくれる。bblayers.confやlocal.confまで自動生成してくれるので、 YAMLファイルを共有しておけば確実に同じ環境を作成することができる。

この内容と適当な名前で保存する。今回はtest.yamlとする。

header:
  version: 14

machine: raspberrypi4-64
distro: poky
target:
  - core-image-base

repos:
  poky:
    url: https://git.yoctoproject.org/git/poky
    path: poky
    refspec: 6cb27ba5379e0132d7ec46f1633c24855a3fa298
    layers:
      meta:
      meta-poky:
      meta-yocto-bsp:

  meta-raspberry:
    url: https://git.yoctoproject.org/meta-raspberrypi
    path: poky/meta-raspberrypi
    refspec: 9dc6673d41044f1174551120ce63501421dbcd85

bblayers_conf_header:
  standard: |
    POKY_BBLAYERS_CONF_VERSION = "2"
    BBPATH = "${TOPDIR}"
    BBFILES ?= ""

local_conf_header:
  standard: |
    CONF_VERSION = "2"
    PACKAGE_CLASSES = "package_rpm"
    SDKMACHINE = "x86_64"
    USER_CLASSES ?= "buildstats"
    PATCHRESOLVE = "noop"
  debug-tweaks: |
    EXTRA_IMAGE_FEATURES ?= "debug-tweaks"
  diskmon: |
    BB_DISKMON_DIRS ??= "\
      STOPTASKS,${TMPDIR},1G,100K \
      STOPTASKS,${DL_DIR},1G,100K \
      STOPTASKS,${SSTATE_DIR},1G,100K \
      STOPTASKS,/tmp,100M,100K \
      HALT,${TMPDIR},100M,1K \
      HALT,${DL_DIR},100M,1K \
      HALT,${SSTATE_DIR},100M,1K \
      HALT,/tmp,10M,1K"
  qemu_configuration: |
    PACKAGECONFIG:append:pn-qemu-system-native = " sdl"
  hashserv: |
    BB_SIGNATURE_HANDLER = "OEEquivHash"
    BB_HASHSERVE = "192.168.1.100:8687"
    SSTATE_MIRRORS = "file://.* http://192.168.1.100:9000/PATH"
  enable_uart: |
    ENABLE_UART = "1"
  systemd: |
    INIT_MANAGER = "systemd"

kasの実行

今回はビルド時間を計測したいので下記を実行して予めビルド環境を取得しておく。

$ kas checkout test.yaml

ビルドを実行する。書きを実行することでkasの中からbitbakeが呼び出される。

$ time kas build tests.yaml
... (snip) ...
real    7m39.846s
user    0m15.850s
sys     0m2.914s

手動で作成した環境のビルド時間とほぼ同じとなる。

「kas checkout」を実行せずに「kas build」を実行した場合でも自動的にcheckout相当の処理が実行される。 また、一度checkoutして環境を作成してしまえばkasを使用せずに通常通りbitbakeを使ってビルドすることもできる。

BSPによってはrepoツールで複数のレイヤを管理するケースもあるがrepoはリポジトリの管理しかしないため、 local.confやbblayers.confなどは自分で生成する必要がある。 サーバ設定などの反映は手動で行うか、自動化したければ自分でスクリプトなどを作成する必要がある。

まとめ

リモートで他のPCのsstate-cacheを共有する方法についてまとめた。 クライアント側の設定を解説する情報はわりとよく見かけるが、サーバ側の設定については見かけないので、調べるのに少し苦労した。 クライアント側でサーバの共通設定を共有するためにはkasは便利だが、それを使用しない場合でも共通設定だけsite.confに記述して共有する方法もある。

自前でsstate-cacheのサーバを構築できれば、社内などでYoctoProjectを使用する際の作業効率は劇的に向上するのではなかろうか。

YoctoProject bitbake-hashservを使ってみる(unix domain socket編)

はじめに

bitbakeではShared State Cache(sstate-cache)という仕組みを持っていて、今回ビルドしたいものが前回ビルドしたものと変更がなければ、成果物をそのまま再利用するようになっている。

手っ取り早い方法は複数のビルドディレクトリのlocal.confで設定されるSSTATE_DIRを同じディレクトリに設定すること。

この仕組みをリモート間でも共有できるようにbitbake-hashservというツールが提供されている。

今回はbitbake-hashservをunix domain socketで使用してみる。

Shared State Cacheの仕組み

ざっくりとsstate-cacheの仕組みを説明すると下記のようになる。

初回実行時

  1. bitbakeで実行されるタスクのインプットデータについてハッシュを取っておく
  2. そのハッシュとタスクのアウトプットデータのマッピング情報を保存しておく
  3. アウトプットデータの情報は捨てずに取っておく

再実行時

  1. 今回実行されるタスクのインプットデータのハッシュとsstate-cache内のハッシュが一致するか確認する
  2. 一致した場合そのタスクは処理しない(一致しない場合はタスクを実行して3のプロセスはスキップ)
  3. 処理しないタスクのアウトプットデータをクライアントに供給する

bitbake-hashservについて

unix domain socketでの接続

bitbake-hashservはunix domain socketでも通信できるようになっていて、ローカルのPC内でもShared Stateを共有できるようになっている。 単にSSTATE_DIRを共有することとの違いは後から参照するbitbakeによってもともとあるsstate-cacheが破壊されづらいという利点がある。 bitbake-hashservは起動時にリードオンリーモードを指定できるため、更に安全に運用することができる。

短所としてはデータがコピーされるため同一PC内ではsstate-cacheに使用されるストレージ容量が増加してしまうところ。

BB_SIGNATURE_HANDLERに「OEEquivHash」を設定してbitbakeを実行するとcache/hashserv.dbが作成される。 これはSQLiteのデータベースとなっており、タスクのインプットデータのハッシュとアウトプットデータのマッピング情報を持っている。

また、OEEquivHashの「Equiv」はEquivalenceの略で、インプットデータ中の空白など実体に関係ない差分は「同一とみなす」という処理を行っている。これによりsstate-cacheの適用可能性が高くなるようになっている。

bitbake-hashservの実行

サーバ側の設定

サーバとなる初回ビルドを行う環境についてはlocal.confにこのような設定があれば良い。

BB_HASHSERVE = "auto"
BB_SIGNATURE_HANDLER = "OEEquivHash"

このままbitbakeを実行して「hashserv.db」と「sstate-cache」が生成されれば必要なデータは揃う。 もちろん初回実行時なので数時間はかかる。

サーバ側環境の構築

実際にbitbake-hashservを動かす環境を作成してみる。

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone
$ git clone git://git.yoctoproject.org/poky.git -b kirkstone
$ source poky/oe-init-build-env build-hashserv
$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confに下記を追加する。

MACHINE = "raspberrypi4-64"

# setup for hashserv database
BB_SIGNATURE_HANDLER = "OEEquivHash"

# enable uart
ENABLE_UART = "1"

# systemd
INIT_MANAGER = "systemd"

試しにcore-image-baseをビルドする。 今回はtimeコマンドで時間を計測する。

$ time bitbake core-image-base
... (snip) ...
real    84m43.468s
user    0m29.526s
sys     0m9.020s

まっさらな状態でビルドした結果、約85分かかった。

bitbake-hashservの起動

この状態でcacheディレクトリを覗くと下記のようにhashserv.dbが生成されていることがわかる。

~/yocto/rpi-kirkstone/build-hashserv$ ls ./cache/
bb_codeparser.dat        bb_unihashes.dat  local_file_checksum_cache.dat
bb_persist_data.sqlite3  hashserv.db       sanity_info

これを使用してbitbake-hashserveを起動する。接続状況を把握しやすいようにログレベルをdebugに指定している。

$ bitbake-hashserv -r -l debug -d ./cache/hashserv.db -b "unix://./hashserv.sock"

今回は「unix://./hashserv.sock」で待ち受けしているため、${HOME}/yocto/rpi-kirkstone/build-hashserv/hashserv.sockに接続することでサーバと通信できる。

bitbake-hashservへの接続

クライアント側の設定

実行中のbitbake-hashservに接続するにはlocal.confに次のような設定を行う。

BB_SIGNATURE_HANDLER = "OEEquivHash"
BB_HASHSERVE = "unix://${HOME}/yocto/rpi-kirkstone/build-hashserv/hashserv.sock"
SSTATE_MIRRORS = "file://.* file://${HOME}/yocto/rpi-kirkstone/build-hashserv/sstate-cache/PATH"

BB_HASHSERVEにはbitbake-hashserv実行時に作成されたunix domain socketのファイルを指定する。

SSTATE_MIRRORSにはサーバ側の初回ビルド時に作成されたsstate-cacheの保存場所を指定する。 つまり、サーバが提供するsstate-cacheの在処となる。 PATHと指定することでsstate-cacheディレクトリ以下の構造をbitbakeが展開できるようになる。

クライアント側環境の構築

実際にbitbake-hashservに接続する環境を作成してみる。

サーバ側とは別の端末を開き下記を実行する。

$ cd ~/yocto/rpi-kirkstone
$ source poky/oe-init-build-env build-client
$ bitbake-layers add-layer ../poky/meta-raspberrypi

local.confに下記を記載する。

MACHINE = "raspberrypi4-64"

# setup for connecting to bitbake-hashserv
BB_SIGNATURE_HANDLER = "OEEquivHash"
BB_HASHSERVE = "unix://${HOME}/yocto/rpi-kirkstone/build-hashserv/hashserv.sock"
SSTATE_MIRRORS = "file://.* file://${HOME}/yocto/rpi-kirkstone/build-hashserv/sstate-cache/PATH"

# enable uart
ENABLE_UART = "1"

# systemd
INIT_MANAGER = "systemd"

bitbake-hashservへの接続

接続のための設定をlocal.confに行ったため、bitbakeを実行してみる。

$ time bitbake core-image-base
... (snip) ...
real    1m43.522s
user    0m1.438s
sys     0m0.472s

bitbake-hashservの提供するsstate-cacheを参照することでビルド時間が劇的に短くなることがわかる。

サーバ側のログ

この時のサーバ側のログの表示は下記のようになっており、外部から接続されデータを供給したことが伺える。

$ bitbake-hashserv -r -l debug -d ./cache/hashserv.db -b "unix://./hashserv.sock"
Listening on './hashserv.sock'
Client '' connected
Handling get-stream
Client disconnected

まとめ

bitbakeではもともと実装されていたsstate-cacheを他のビルド環境から参照することでビルド時間が劇的に改善することがわかった。 もちろん別のMACHINE向けのビルドだったり、参照するレシピが更新されていたりして保存されているsstate-cacheと入出力が異なる場合はタスクが実行されるためこの効果は薄いもしくは無い。

bitbake-hashservを使用することで直接SSTATE_DIRを共有しなくても、既存のsstate-cacheを参照できることがわかった。 今回はunix domain socketで同一ホストのデータを参照したが、リモートで提供されているsstate-cacheを参照できそうなこともわかった。

機会があれば実際にリモートのsstate-cacheを提供/参照する方法も試してみたい。

YoctoProject devtoolを使ってみる(modify編)

はじめに

下記ではdevtoolについて解説してきた。

devtoolでは以下のことができる。

  • レシピの新規作成
  • レシピの編集
  • レシピのアップグレード

今回はレシピの編集に注目してみる。

環境構築

作業用レイヤ

作業用レイヤとしてmeta-modifyを作成する。

$ bitbake-layers create-layer ../poky/meta-modify
$ bitbake-layers add-layer ../poky/meta-modify

サンプルレシピ

YoctoProject devtoolを使ってみる(add編)で作成したhello-autotoolsのレシピであるhello_0.0.1.bb使用する。

devtoolによるレシピの編集

ワークスペースへの登録

$ devtool modify hello

ワークスペースを確認する。

workspace
├── README
├── appends
│   └── hello_0.0.1.bbappend
├── conf
│   └── layer.conf
└── sources
    └── hello
        ├── LICENSE
        ├── Makefile.am
        ├── README.md
        ├── configure.ac
        └── src
            ├── Makefile.am
            ├── hello.cpp
            ├── hello.h
            └── main.cpp

ちなみにdevtool modifyの場合はYoctoProject devtool addでローカルのソースをレシピ化することを考えるで扱ったhelloworldの様にローカルのソースを持ったレシピでもワークスペースのsourcesディレクトリにソースを展開してくれる。

initial-revの確認

hello_0.0.1.bbappendでinitial-revを確認する。

$ grep 'initial_rev' workspace/appends/hello_0.0.1.bbappend
# initial_rev: 36ef62fd0cd05f72f589a296e39044d71371a46b

このリビジョンが修正差分の基点となる。

ソースを修正する

ソースを修正してみる。 もともと大した処理はしていないのでループにしてみる。

diff --git a/src/main.cpp b/src/main.cpp
index ae9fcb0..3ae5264 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,6 +1,11 @@
 #include "hello.h"
+#include <iostream>

 int main() {
-    Hello("world").say();
+    Hello hello("world");
+    for (int i = 0; i < 10; ++i) {
+        std::cout << "i = " << i << " : ";
+        hello.say();
+    }
     return 0;
 }

コミットする。

$ git commit -m "Modify to loop"

ついでにREADME.mdも修正してみる。

diff --git a/README.md b/README.md
index 9a047d9..fe1b69e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
 # hello-autotools

 This repository contains a sample project using the Autotools.
+
+The sample program says "hello" to the world.

こちらもコミットする。

$ git commit -m "Update README"

コミット履歴を見る。

$ git log --oneline
5344599 (HEAD -> devtool) Update README
0bc1094 Modify to loop
36ef62f (tag: devtool-patched, tag: devtool-base, origin/main, origin/HEAD, main) Add the program
5b8d63b Initial commit

2つコミットが積まれている。

meta-modifyに修正内容を登録

devtool finishを行いmeta-modifyにレシピの修正差分を登録する。

$ devtool finish -r hello ../poky/meta-modify

meta-modifyの内容を確認する。

../poky/meta-modify/
├── COPYING.MIT
├── README
├── conf
│   └── layer.conf
├── recipes-app
│   └── hello
│       ├── hello
│       │   ├── 0001-Modify-to-loop.patch
│       │   └── 0002-Update-README.patch
│       └── hello_%.bbappend
├── recipes-example
│   └── example
│       └── example_0.1.bb
└── recipes-recipetool

bbappendが作成されinitial-rev以降の2つのコミットがパッチとしてレシピに登録されている。 このパッチはgit format-patchによって生成されている。

この時点のworkspaceを確認する。

workspace/
├── README
├── appends
├── conf
│   └── layer.conf
└── sources

初期状態に戻っている。

まとめ

devtool modifyは文句なく便利。

YoctoProject devtool addでローカルのソースをレシピ化することを考える

はじめに

devtool addはレシピ作成の大半の処理をrecipetool createで行っているが、 どちらのツールもネットワーク越しに公開されているソースコードからレシピを作成することを前提としている。

つまり、ローカルストレージ上にあってネットワーク上で公開されていないソースファイルをこれらのツールで レシピ化するにはハードルがあるということになる。

このようなソースコードからdevtool addを使用してレシピを作成するにはどうすればよいのか考えてみる。

ソースコードの準備

前回使用したサンプルとは別のものを使用する。

$ cd 
$ git clone https://github.com/mickey-happygolucky/helloworld.git
$ cd helloworld
$ rm -rf .git

.gitを削除することでgitリポジトリではなくしている。

devtool

ワークスペースへの追加

$ devtool add ~/helloworld

ワークスペースの内容を確認してみる。

./workspace/
├── README
├── appends
│   └── helloworld.bbappend
├── conf
│   └── layer.conf
├── recipes
│   └── helloworld
│       └── helloworld.bb
└── sources

リモートのプロジェクトを指定したときと異なり、ソースがワークスペース内のsourcesディレクトリに配置されていない。

これはrecipetool createにローカルのソースツリーを指定した場合ダウンロード処理が行われず、かつ、--extract-toオプションを指定できないため、ワークスペースのへのソースツリーのダウンロードが行われないためこのような動作になっている。

helloworld.bbappendを確認するとEXTERNAL_SRCにソースツリーのパスが直接指定されるようになっている。

inherit externalsrc
EXTERNALSRC = "/home/mickey/helloworld"

このため、devtool buildなどを行っても見かけ上はワークスペース内のsourcesディレクトリにあるときと同じような動作になる。

helloworld.bbを確認するとSRC_URIが空になっていることがわかる。

# Recipe created by recipetool
# This is the basis of a recipe and may need further editing in order to be fully functional.
# (Feel free to remove these comments when editing.)

# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is
# your responsibility to verify that the values are complete and correct.
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=134b970c55f7388271efa7b17c06d072"

# No information for SRC_URI yet (only an external source tree was specified)
SRC_URI = ""

inherit cmake

# Specify any options you want to pass to cmake using EXTRA_OECMAKE:
EXTRA_OECMAKE = ""

レシピのレイヤへの登録

devtool finishを実行してみる。

$ devtool finish helloworld ../poky/meta-work

下記のようなエラーになる。

NOTE: Starting bitbake server...
Traceback (most recent call last):
  File "/home/mickey/yocto/rpi-kirkstone/sources/poky/scripts/devtool", line 338, in <module>
    ret = main()
  File "/home/mickey/yocto/rpi-kirkstone/sources/poky/scripts/devtool", line 325, in main
    ret = args.func(args, config, basepath, workspace)
  File "/home/mickey/yocto/rpi-kirkstone/sources/poky/scripts/lib/devtool/standard.py", line 2060, in finish
    check_git_repo_op(srctree, [corebasedir])
  File "/home/mickey/yocto/rpi-kirkstone/sources/poky/scripts/lib/devtool/__init__.py", line 371, in check_git_repo_op
    stdout, _ = bb.process.run('git rev-parse --show-toplevel', cwd=srctree)
  File "/home/mickey/yocto/rpi-kirkstone/sources/poky/bitbake/lib/bb/process.py", line 189, in run
    raise ExecutionError(cmd, pipe.returncode, stdout, stderr)
bb.process.ExecutionError: Execution of 'git rev-parse --show-toplevel' failed with exit code 128:
fatal: not a git repository (or any of the parent directories): .git

devtool finishでは、レイヤにレシピを登録する際にワークスペース内で行った修正の差分からパッチを作成し、レシピに登録するという処理を行うため、ワークスペース登録中のレシピのソースツリーはgitリポジトリである必要がある。

前回説明した通り、devtool addでネットワーク越しのソースを指定した場合は対象がgitリポジトリじゃない場合は自動的にgitリポジトリに変換される。

ソースツリーをgitリポジトリにしてみる。

$ cd ~/helloworld
$ git init
$ git add .
$ git commit -m "Initial commit"

この状態で再度devtool finishを実行する。

$ devtool finish helloworld ../poky/meta-work

エラーの内容は変化するがfinishは失敗する。

NOTE: Starting bitbake server...
NOTE: Reconnecting to bitbake server...
NOTE: Retrying server connection (#1)...
Loading cache: 100% |##################################################################| Time: 0:00:00
Loaded 1679 entries from dependency cache.
Parsing recipes: 100% |################################################################| Time: 0:00:00
Parsing of 919 .bb files complete (917 cached, 2 parsed). 1680 targets, 67 skipped, 0 masked, 0 errors.
ERROR: Unable to find initial revision - please specify it with --initial-rev

devtoolは通常ワークスペースに登録された時点のコミットIDをinitial-revとして覚えておき、finishする際に差分を取るための基点としている。

devtool finishにはオプションでinitial-revが指定できるようになっているため実行時に指定する。

initiale-revに指定するためのコミットIDは下記のように取得する。

$ git rev-parse HEAD
1af1253de73276bc60689af176d69c99df26064a

initial-revを指定してdevtool finishを実行する。

$ devtool finish --initial-rev 1af1253de73276bc60689af176d69c99df26064a helloworld ../poky/meta-work

コマンドは成功する。

レイヤに登録されたレシピ

meta-workを確認する。

../poky/meta-work/
├── COPYING.MIT
├── README
├── conf
│   └── layer.conf
(... snip ...)
└── recipes-helloworld
    └── helloworld
        └── helloworld.bb

レイヤにはレシピは存在するが、ソースコードは存在していない。

helloworld.bbは下記のようにSRC_URIが設定されていない不完全な状態となっている。

# Recipe created by recipetool
# This is the basis of a recipe and may need further editing in order to be fully functional.
# (Feel free to remove these comments when editing.)

# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is
# your responsibility to verify that the values are complete and correct.
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=134b970c55f7388271efa7b17c06d072"

# No information for SRC_URI yet (only an external source tree was specified)
SRC_URI = ""

inherit cmake

# Specify any options you want to pass to cmake using EXTRA_OECMAKE:
EXTRA_OECMAKE = ""

完全な状態にする

ソースツリーをmeta-workに配置し、SRC_URIを設定する。

$ mkdir ../poky/meta-work/recipes-helloworld/helloworld/files
$ cp -ra ~/helloworld ../poky/meta-work/recipes-helloworld/helloworld/files
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=134b970c55f7388271efa7b17c06d072"

# No information for SRC_URI yet (only an external source tree was specified)
SRC_URI = "file://helloworld"

S = "${WORKDIR}/${BPN}"

inherit cmake

# Specify any options you want to pass to cmake using EXTRA_OECMAKE:
EXTRA_OECMAKE = ""

結局の所、肝心な部分は自動化できていない。

recipetoolでレシピを作成する

一旦devtoolで作成したレシピを削除する。

$ rm -rf ../poky/meta-work/recipes-helloworld

筆者がよくやる手順は下記のような感じになる。

  1. レイヤに格納先ディレクトリを作成する
  2. レイヤにソースコードを配置する
  3. recipetool createでレシピを作成する
  4. レシピの修正

具体的には下記のようになる。

$ mkdir -p ../poky/meta-work/recipes-app/helloworld/files
$ cp -ra ~/helloworld ../poky/meta-work/recipes-app/helloworld/files
$ recipetool create ../poky/meta-work/recipes-app/helloworld/files/helloworld -o ../poky/meta-work/recipes-app/helloworld/helloworld_0.1.bbt
$ recipetool edit helloworld

先ほどとレシピの内容は変わらず。

LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=134b970c55f7388271efa7b17c06d072"

# No information for SRC_URI yet (only an external source tree was specified)
SRC_URI = "file://helloworld"

S = "${WORKDIR}/${BPN}"

inherit cmake

# Specify any options you want to pass to cmake using EXTRA_OECMAKE:
EXTRA_OECMAKE = ""

まとめ

ローカルストレージ上のソースからレシピを作成するためにdevtool addを使用してもあまり便利ではない。 recipetool createのほうがgitリポジトリ化の必要がない分結果的に手順が少なくなる。

ローカルにしろネットワーク越しにしろ、レシピの新規作成にはdevtool addを使用するメリットはあまり感じられなかった。