Bitbake の Hello World サンプルを試す

Bitbake というのは Yocto Project で使われるビルド用のツールです。Make のように依存関係を調べて必要なタスクを行う仕組みです。文法が妙なのと、読み込まれるファイルが暗黙のうちに決まってとっつきにくいので、Bitbake のマニュアルの Hello World サンプルを元に、Bitbake の基本機能を調べました。

http://www.yoctoproject.org/docs/2.3/bitbake-user-manual/bitbake-user-manual.html#hello-world-example

## 作るもの

* サンプルレポジトリ https://github.com/propella/hello-bitbake に以下があります。
    * プロジェクトフォルダ ~/bbtest/myproject: プロジェクトの全体的な設定をここに書く。
        * conf/bitbake.conf: 設定ファイル
        * conf/bblayers.conf: レイヤを読むディレクトリを指定する。
        * classes/base.bbclass 自動的に読み込まれるクラスファイル。全てのレシピがこれを読み込む。
    * サンプルレイヤ ~/bbtest/mylayer: プロジェクトに含めるアプリのビルド手順(レシピ)をここに書く。実用的な Bitbake プロジェクトは複数のレシピで出来ている。
        * conf/layer.conf: レシピの位置を指定する。
        * *.bb: 色々なレシピ

## 作業

準備

`git clone git://git.yoctoproject.org/poky` などで bitbake を入手。bitbake コマンドにパスを通しておく

```sh
PATH=~/poky/bitbake/bin:$PATH
```

まず、エラーが出なくなる最低限の設定を作る。conf や classes の位置は BBPATH で決まる。設定ファイルに相対パスを含めない。もしも相対パスを含めると、BBPATH と PWD が異なる時にうまく動かない。また、シェル展開されないのでチルダ等も使えない。BBPATH には、ビルド環境のディレクトリを指定する。

* $BBPATH/conf/bitbake.conf : Bitbake 設定ファイル。使うディレクトリ等を指定。
* $BBPATH/classes/base.bbclass : 自動的に読み込まれるクラスファイル
    * addtask とは?
        * addtask で関数をタスクにする。例えば `addtask task1` と指定すると `do_task1` 関数がタスクになる。
        * addtask でタスクの依存を定義する。例えば `addtask task1 after do_task2` で、`do_task2` の後に do_task1 が実行される。

```sh
$ mkdir -p ~/bbtest/myproject/conf

$ cat << 'EOF' > ~/bbtest/myproject/conf/bitbake.conf
TMPDIR  = "${TOPDIR}/tmp" ### TOPDIR は自動設定される。bitbake を起動した時の BBPATH と同じ?
CACHE   = "${TMPDIR}/cache" ### CACHE は metadata のパース済キャッシュ
STAMP   = "${TMPDIR}/stamps" ### レシピのタイムスタンプ
T       = "${TMPDIR}/work" ### 一時ファイル
B       = "${TMPDIR}" ### ビルド中関数を実行するディレクトリ
EOF

$ mkdir -p ~/bbtest/myproject/classes
$ echo 'addtask build' > ~/bbtest/myproject/classes/base.bbclass

$ cd ~/bbtest/myproject/

$ BBPATH=~/bbtest/myproject bitbake
Nothing to do.  Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.
```

確認のため実行時に BBPATH を指定した。まだレシピが無いので何もしないで終わった。エラーが出ないのを確認したら、 レイヤ mylayer と、レシピ printhello.bb を作る

```sh
$ mkdir -p ~/bbtest/mylayer/conf

$ cat << 'EOF' > ~/bbtest/mylayer/conf/layer.conf
BBPATH .= ":${LAYERDIR}" ### BBPATH の後ろに空白無しで追加。LAYERDIR は自動設定される。レイヤのあるディレクトリ
BBFILES += "${LAYERDIR}/*.bb" ### BBFILES の後ろに空白ありで追加。BBFILES はレシピのリスト
BBFILE_COLLECTIONS += "mylayer" ### BBFILE_COLLECTIONS はレイヤの名前 BBFILE_* のように参照出来るようになる。
BBFILE_PATTERN_mylayer := "^${LAYERDIR_RE}/" ### BBFILE_PATTERN_レイヤ名 よくわからなかった。
EOF

$ cat << 'EOF' > ~/bbtest/mylayer/printhello.bb
DESCRIPTION = "Prints Hello World"
PN = 'printhello'
PV = '1'

python do_build() {
   bb.plain("=====  Hello, World!   =====");
   bb.plain("TOPDIR:" + d.getVar("TOPDIR", False));
   bb.plain("BBPATH:" + d.getVar("BBPATH", False));
}
EOF
```

作ったレイヤを myproject に登録する。

```sh
$ echo "BBLAYERS ?= '$HOME/bbtest/mylayer'" > ~/bbtest/myproject/conf/bblayers.conf
$ BBPATH=~/bbtest/myproject bitbake printhello
Parsing recipes: 100% |##################################################################| Time: 0:00:00
Parsing of 1 .bb files complete (0 cached, 1 parsed). 1 targets, 0 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies
Initialising tasks: 100% |###############################################################| Time: 0:00:00
NOTE: Executing RunQueue Tasks
None do_build: *  Hello, World!   *
NOTE: Tasks Summary: Attempted 1 tasks of which 0 didn't need to be rerun and all succeeded.
```

一旦成功したタスクは実行済みとマークされてしまうので、もう一度やる時は tmp を削除するか、bitbake -f で強制実行する。

```sh
rm -r ~/bbtest/myproject/tmp
```

## 代入の文法

* VARIABLE = "value" 遅延代入。
* VARIABLE := "value" 即時代入。
* \ で改行を繋げる \n 等は使えない。
* " と ' は同じ意味。
* ${VAR} で参照。= で代入した変数はここで展開する。
* A ?= "aval" デフォルト値の定義。変数が未定義の時だけ即時代入される。
* A ??= "someothervalue" さらに弱い定義。変数が未定義の時パースが終わった後で代入される。
* A += "append" 後に追加。間に空白がつく。
* A =+ "prepend" 前に追加。間に空白がつく。
* A .= "append" 後に追加。間に空白がつかない。
* A =. "prepend" 前に追加。間に空白がつかない。
* A_append = "aaa" 後に追加。間に空白がつかない。パースが終わった後で処理される。
* A_prepend = "aaa" 前に追加。間に空白がつかない。パースが終わった後で処理される。
* A_remove = "aaa" A から aaa を削除。前後の空白も削除される。パースが終わった後で処理される。
* _append や _prepend は class 内で inherit したレシビの変数に値を追加したい時に便利。
   * _append だと a.bb が b.bbclass を inherit した時、a.bb で定義した変数を b.bbclass が上書き出来る。
* VAR[PROP] = "aaa" VAR が定義されて無くてもいきなりプロパティ PROP を定義出来る。
* DATE = "${@time.strftime('%Y%m%d',time.gmtime())}" ${@なんとか} で Python の文が書ける。
* export VAR 環境変数定義。指定位置に関わらず VAR が環境変数になる。

## 関数の文法

.bbclass .bb .inc には関数が書ける。この関数の文法ははシェル式と Python 式が選べる変態的な物となっている。

お薦めは、出来るだけ普通の Python 関数を使うべきらしい。

### シェル関数

```sh
some_function () {
    echo "Hello World"
}
```

some_function_append のように定義すると、既存の定義を拡張出来る。

### BitBake 流 Python 関数。

幾つかのオブジェクトが定義されている。タスクになれる。以下のオブジェクトが import されている。

* d: グローバル変数のアクセッサ
* bb: 色々な関数 bitbake/lib/bb/__init__.py 参照
* os: 

```python
python some_python_function () {
    d.setVar("TEXT", "Hello World")
    print d.getVar("TEXT")
}
```

some_function_function_append のように定義すると、既存の定義を拡張出来る。

### 普通の Python 関数

task になれない。

```python
def get_depends(d):
    if d.getVar('SOMECONDITION'):
        return "dependencywithcond"
    else:
        return "dependency"
SOMECONDITION = "1"
DEPENDS = "${@get_depends(d)}"
```

### 匿名 Python 関数

パースが終わるとすぐ実行されるので、地の文で Python を使える。

```python
python () {
    if d.getVar('SOMEVAR') == 'value':
        d.setVar('ANOTHERVAR', 'value2')
}
```

## もうちょっと実践的なサンプル

https://wiki.yoctoproject.org/wiki/Building_your_own_recipes_from_first_principles に実際に Yocto に組み込むサンプルがあります。このサンプルに実際にリモートからソースコードを取得してビルドする方法が書かれてあります。

### おすすめしない方法: レシピを既存のレイヤに組み込む。

一般的なレイヤのレシピの定義は以下のようになっている。

```
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
            ${LAYERDIR}/recipes-*/*/*.bbappend"
```

なので、[bbexample_1.0.bb](https://raw.githubusercontent.com/DynamicDevices/meta-example/master/recipes-example/bbexample/bbexample_1.0.bb)
を既存のレイヤ/recipes-bbexample/bbexample/bbexample_1.0.bb のように配置すると meta-agl-demo の一部になる。bitbake でビルド出来る。

    bitbake bbexample

### お薦めの方法、レイヤを作る

例えば ~/bbtest/meta-example にサンプルのレイヤを作ります。

```
mkdir ~/bbtest
cd ~/bbtest
git clone https://github.com/DynamicDevices/meta-example
```

既存のプロジェクトの conf/bblayers.conf の BBLAYERS にレイヤを追加すると、bitbake の対象になる。

    BBLAYERS =+ "/home/tyamamiya/bbtest/meta-example/"

以下のコマンドで設定を確認出来る。

* `bitbake-layers show-layers` 読み込むレイヤの表示
* `bitbake-layers show-recipes` 読み込むレシピの表示
* `bitbake bbexample` でビルド

この時点でビルドしたものは rpm ファイルになっていてるだけで(何故 rpm を作るのかは謎)、まだ SD image には含まれない。SD image に含むためには conf/local.conf の IMAGE_INSTALL にレシピを追加する。通常 conf/bitbake.conf が conf/local.conf を読み込む。

    IMAGE_INSTALL_append = " bbexample "

このサンプルには三つの使い方の例が含まれている。

* ソースが git レポジトリにある: https://github.com/DynamicDevices/meta-example/blob/master/recipes-example/bbexample/bbexample_1.0.bb
* ソースがリモートアーカイブにある: https://github.com/DynamicDevices/meta-example/blob/master/recipes-example/bbexample/bbexample-rt_1.0.bb
* ソースがローカルアーカイブにある: https://github.com/DynamicDevices/meta-example/blob/master/recipes-example/bbexample/bbexample-lt_1.0.bb

do_install は [autotools](http://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/meta/classes/autotools.bbclass) から inherit されるため自分で書かなくても良い。