みつきんのメモ

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

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を使用するメリットはあまり感じられなかった。

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

はじめに

YoctoProjecではdevtoolというツールを提供している。 このツールはbitbake環境か拡張SDKの環境で使用することができる。

devtoolには様々なサブコマンドが用意されており、次のようなことができるようになっている。

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

今回はレシピの新規作成に注目してみる。

先日唐突にものすごく久しぶりにAutotoolsを使ってみたのは、 これのためのサンプルを作るため。

作業用レイヤの作成

bitbake-layersコマンドで作業用レイヤを作成する。

$ bitbake-layers create-layer ../poky/meta-work
$ bitbake-layers add-layer ../poky/meta-work
$ bitbake-layers show-layers

devtool

今回はbitbake環境で作業する。

$ source poky/oe-init-build-env

ワークスペースの作成

devtoolの初回実行時には下記のことが実行される。

  • workspaceの作成
  • workspaceのbblayers.confへの追加

これによりbitbake実行時にもworkspaceの作業内容が反映される様になる。

ワークスペースへのレシピの作成

hello-autotoolsのサンプルでレシピを作成する。

githubのデフォルトブランチはmainだが、devtoolはブランチを指定しないとmasterとみなすので注意が必要。

$ devtool add https://github.com/mickey-happygolucky/hello-autotools.git -B main

このコマンドによって下記のことが実行される。

レシピの雛形

workspace/recipes/hello_git.bbが下記の内容で作成される。

# 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=7d3d185c8b095f61b1f86140b6a49a93"

SRC_URI = "git://github.com/mickey-happygolucky/hello-autotools.git;protocol=https;branch=master"

# Modify these as desired
PV = "0.0.1+git${SRCPV}"
SRCREV = "36ef62fd0cd05f72f589a296e39044d71371a46b"

S = "${WORKDIR}/git"

# NOTE: if this software is not capable of being built in a separate build directory
# from the source, you should replace autotools with autotools-brokensep in the
# inherit line
inherit autotools

# Specify any options you want to pass to the configure script using EXTRA_OECONF:
EXTRA_OECONF = ""

1行目のコメントからこのレシピの雛形はrecipetoolで作成されたことがわかる。 これは、devtoolの中からreciptool createが呼び出されるようになっているため。

recipetoolはソースツリーを解析し使用されているビルドシステムを判別するようになっている。 hello-autotoolsはautotoolsを使用しているため、inherit autotoolsしている。

SRC_URIにはgithubのURLが設定されている。

ワークスペースへのソースコード配置

workspace/sources/helloソースコードが配置される。 こちらはrecipetool createの--extractオプションによって行われている。

ソースコードがgitリポジトリじゃない場合はdevtoolによって自動的にgit initが実行され、 gitリポジトリになるようになっている。

External Sourceの設定

devtoolやbitbakeがワークスペースに配置されたソースを参照する様にExternal Sourceの仕組みが使用されている。 workspace/appends/hello_git.bbappendが下記のように作成されている。

inherit externalsrc
EXTERNALSRC = "/home/mickey/yocto/rpi-kirkstone/build/workspace/sources/hello"

# initial_rev: 36ef62fd0cd05f72f589a296e39044d71371a46b

inherit externalsrcしてワークスペース上のソースのパスをを設定している。

initial_revはワークスペースにgitリポジトリが配置された最新のコミットを指しており、 ワークスペース内で修正された差分を作成する基点として扱われる。

レシピのレイヤへの登録

ワークスペースに作成したレシピをレイヤに配置し正式にレシピとして登録する。

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

-rをつけることでsourceディレクトリに配置されたソースコードが削除される。 逆に言えばこのオプションをつけないとソースコードが残ったままとなる。

レシピを登録したmeta-workの内容を確認してみる。

../poky/meta-work
├── COPYING.MIT
├── README
├── conf
│   └── layer.conf
├── recipes-example
│   └── example
│       └── example_0.1.bb
└── recipes-hello
    └── hello
        └── hello_git.bb

recipes-hello/hello/hello_git.bbにレシピが配置されている。ディレクトリ名はdevtoolによって自動的に決められてしまう。

recipetoolによるレシピの作成

devtool addの処理の大半がrecipetool createだったので、recipetoolを使用してレシピを作成してみる。

$ mkdir -p ../poky/meta-work/recipes-app/hello
$ recipetool create https://github.com/mickey-happygolucky/hello-autotools.git -B main -o ../poky/meta-work/recipes-app/hello/hello_0.0.1.bb

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

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

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

../poky/meta-work
├── COPYING.MIT
├── README
├── conf
│   └── layer.conf
├── recipes-app
│   └── hello
│       └── hello_0.0.1.bb
└── recipes-example
    └── example
        └── example_0.1.bb

まとめ

devtoolは強力なツールだ。しかしレシピの新規作成に関してはレイヤに登録する際のディレクトリ名を自由に設定できなかったり、 一旦ワークスペースソースコードを展開したりしてもあまりメリットを感じない。

ワークスペース内で行うことができるdevtoolの機能については新規作成時にも使用可能だがそれをする意味もない気がする。 個人的にはdevtoolの真価はレシピの編集とレシピのアップグレードにあると思っている。

新規作成に関してはrecipetoolを使用したほうが自由がきいてよいのではないだろうか。

ものすごく久しぶりにAutotoolsを使ってみた

はじめに

表題のとおり。

プロジェクト作成

Autotoolsを使用したプロジェクトを作成する。

ディレクトリ構造

ディレクトリ構造は以下のようになる。

hello
├── Makefile.am
├── configure.ac
└── src
    ├── Makefile.am
    ├── hello.cpp
    ├── hello.h
    └── main.cpp

ソースコード

ベースとなるソースコードを作成する。

  • hello.h
  • hello.cpp
  • main.cpp

hello.h

とりあえずヘッダファイルがある状態を試したいので適当にクラスを定義する。

#ifndef HELLO_H
#define HELLO_H

#include <string>

class Hello {
    std::string to_say_;
public:
    explicit Hello(const std::string& to_say);
    void say() const;
};

#endif //HELLO_H

hello.cpp

クラスを定義したので実装する。

#include "hello.h"
#include <iostream>

Hello::Hello(const std::string& to_say) : to_say_(to_say) {
}

void Hello::say() const {
    std::cout << "Hello " << to_say_ << std::endl;
}

main.cpp

プログラムの本体。

#include "hello.h"

int main() {
    Hello("world").say();
    return 0;
}

src/Makefile.am

srcディレクトリ内のMakefile.am

bin_PROGRAMS = hello
hello_SOURCES = hello.cpp main.cpp

Makefile.am

トップディレクトリのMakefile.am サブディレクトリの定義のみ.

SUBDIRS = src

configure.ac

configure.acの雛形を作成する。

$ autoscan
$ mv ./configure.scan ./configure.ac

その後適宜修正を行う。

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.71])
AC_INIT([hello], [0.0.1], [mickey.happygolucky@gmail.com])
AM_INIT_AUTOMAKE([foreign])
AC_CONFIG_SRCDIR([src/hello.h])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CXX
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile
                 src/Makefile])
AC_OUTPUT

プロジェクトの生成

ケルトンが作成できたのでconfigure.acからconfiugreを生成する。

$ autoreconf -i

ビルド

ビルドを行う。

$ ./configure
$ make -j

動作確認

実行してみる。

$ src/hello
Hello world

まとめ

Autotoolsはかなり忘れていた。 C++もしばらく遠ざかっているので古臭いコードなのは勘弁。

YoctoProject イメージの初回起動時に実行される処理を実装する

はじめに

YoctoProjectでストレージに書き込んだイメージの初回起動時にのみ実行される処理を記述する。

実際にはイメージ作成処理のPostProcessを起動時まで遅延させているだけなのだが、 以前に実装した「YoctoProject 最後のパーティションを目一杯まで広げる」を応用できそうなので試してみる。

# 環境構築

作業環境

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone

pokyの取得

$ git clone git://git.yoctoproject.org/poky.git -b kirkstone 

環境変数の設定

$ source poky/oe-init-build-env

meta-raspberrypiの取得

$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confを編集

MACHINE = "raspberrypi4-64"
DL_DIR = "${TOPDIR}/../downloads"

# systemd
INIT_MANAGER = "systemd"

# enable uart
ENABLE_UART = "1"

レイヤを作成

meta-workを作成

$ bitbake-layers create-layer meta-work
$ rm -rf ./meta-work/recipes-example
$ mkdir -p ./meta-work/recipes-support/repart/files ./meta-work/wic
$ bitbake-layers add-layer ./meta-work

レシピ作成

repart.bb

meta-work/recipes-support/repart/repart.bbを以下の内容で作成する。

SUMMARY = "resize partition for data"
DESCRIPTION = "Recipe for resizin the partition and filesystem"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

REPART_DEV ??= ""
REPART_NO ??= ""
REPART_MOUNTPOINT ??= ""

ALLOW_EMPTY:${PN} = "1"

RDEPENDS:${PN} = "parted"

inherit features_check
REQUIRED_DISTRO_FEATURES += "systemd"

S = "${WORKDIR}"

do_compile[noexec] = "1"

do_install() {
    if [ -z "${REPART_DEV}" ]; then
        bbfatal "REPART_DEV should be set."
    fi
    if [ -z "${REPART_NO}" ]; then
        bbfatal "REPART_NO should be set."
    fi
    if [ -z "${REPART_LABEL}" ]; then
        bbfatal "REPART_LABEL should be set."
    fi
    if [ -z "${REPART_MOUNTPOINT}" ]; then
        bbfatal "REPART_MOUNTPOINT should be set."
    fi
}

pkg_postinst_ontarget:${PN}() {
    if [ ! -d "${REPART_MOUNTPOINT}" ]; then
        install -d ${REPART_MOUNTPOINT}
        echo "${REPART_MOUNTPOINT} is created"
    fi
    echo "unmounting ..."
    umount ${REPART_MOUNTPOINT}

    echo "resizing ..."
    /usr/sbin/parted --script ${REPART_DEV} resizepart ${REPART_NO} 100%

    echo "re-mounting ..."
    mount -L ${REPART_LABEL} -o "x-systemd.growfs" ${REPART_MOUNTPOINT}
}

下記の3つの変数は、必要に応じてlocal.confなどで設定する。

REPART_DEV ??= ""
REPART_NO ??= ""
REPART_LABEL ??= ""
REPART_MOUNTPOINT ??= ""

wksファイルを作成

meta-work/wic/sdimage-raspberrypi-growfs.wks.inを下記の内容で作成し、/homeにx-systemd.growfsを指定することにより、システム起動時にパーティションジいっぱいに拡張されるようにする。

part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 20
part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4096
part ${REPART_MOUNTPOINT} --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/home --ondisk mmcblk0 --fstype=ext4 --label ${REPART_LABEL} --align 1024 --size 500 --fsoptions "x-systemd.growfs"

拡張子をwksではなくwks.inとすることで、wksファイルの中でbitbakeの変数を使用することができる。

local.confの修正

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

# re-partitioning
IMAGE_INSTALL:append = " repart"
REPART_DEV ?= "/dev/mmcblk0"
REPART_NO ?= "3"
REPART_LABEL ?= "homefs"
REPART_MOUNTPOINT ?= "/home"
WKS_FILE = "sdimage-raspberrypi-growfs.wks.in"

今回は下記の4つの変数を設定する様に設計した。REPART_MOUNTPOINTに関しては、wksファイルと連動するようにしたため、 既存のディレクトリであってもこの変数を設定する必要がある。

  • REPART_DEV
  • REPART_NO
  • REPART_LABEL
  • REPART_MOUNTPOINT

動作確認

root@raspberrypi4-64:~# fdisk -l
Disk /dev/mmcblk0: 59 GB, 63831015424 bytes, 124669952 sectors
1947968 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk0p1 *  64,0,1      1023,3,32         8192     149679     141488 69.0M  c Win95 FAT32 (LBA)
/dev/mmcblk0p2    1023,3,32   1023,3,32       155648     624229     468582  228M 83 Linux
/dev/mmcblk0p3    1023,3,32   1023,7,32       624640  124669951  124045312 59.1G 83 Linux

初回起動時のdmesg

root@raspberrypi4-64:~# dmesg | grep -i filesystem
[    2.808849] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none.
[    2.819970] VFS: Mounted root (ext4 filesystem) readonly on device 179:2.
[    6.271483] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none.
[    6.384369] EXT4-fs (mmcblk0p3): resizing filesystem from 166400 to 166400 blocks
[    7.207984] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none.
[    7.239674] EXT4-fs (mmcblk0p3): resizing filesystem from 166400 to 15505664 blocks
[    7.860898] EXT4-fs (mmcblk0p3): resized filesystem to 15505664

2回目起動時のdmesg

root@raspberrypi4-64:~# dmesg | grep -i filesystem
[    2.738405] EXT4-fs (mmcblk0p2): INFO: recovery required on readonly filesystem
[    2.951263] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none.
[    2.961224] VFS: Mounted root (ext4 filesystem) readonly on device 179:2.
[    6.408181] EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none.
[    6.454720] EXT4-fs (mmcblk0p3): resizing filesystem from 15505664 to 15505664 blocks

まとめ

イメージ書き込み後初回起動時にのみ実行される処理を実装してみた。 濫用は避けるべきだと思うが、この機能の存在を認識しておくことで、 設計がきれいになることもあると思う。

YoctoProject RaspberryPi4でnanbieldを試す

はじめに

YoctoProject 4.3(nanbield)がリリースされていた。 実機で動かしたほうが楽しいのでRaspberryPi4 Model Bで試してみる。

作業ディレクト

作業ディレクトリは~/yocto/rpi-nanbieldとする。

$ mkdir -p ~/yocto/rpi-nanbield
$ cd ~/yocto/rpi-nanbield

ソースツリーの構築

pokyの取得

いつもは~/yocto/rpi-nanbield/pokyのようにして、pokyディレクトリ配下に外部のレイヤを配置していたが、 新し目のbitbake-layers layerindex-fetchでは自由にディレクトリ構成を決められる様になっているので、 あえて下記のような構成にする。

.
└── sources
    ├── meta-raspberrypi
    └── poky

本来はpokyとmeta-raspberrypiは別のgitリポジトリで管理されているので、 このように並列な階層になっている方が管理しやすい。

imx-yocto-bspを使用している人にはおなじみのディレクトリ構成と言える。

下記のコマンドでpokyをダウンロードする。

$ mkdir -p ~/yocto/rpi-nanbield/sources
$ cd ~/yocto/rpi-nanbield/sources
$ git clone git://git.yoctoproject.org/poky.git -b nanbield

meta-raspberrypiの取得

下記のコマンドでmeta-raspberrypiを取得する。

$ cd ~/yocto/rpi-nanbield
$ source sources/poky/oe-init-build-env
$ bitbake-layers layerindex-fetch -f ../sources meta-raspberrypi

ビルド

local.confの修正

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

だいたいいつもの設定。

MACHINE = "raspberrypi4-64"
DL_DIR = "${TOPDIR}/../downloads"

# enable uart
ENABLE_UART = "1"

# systemd
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

エラー発生(systemd)

下記のようなエラーが発生した。

ERROR: Nothing PROVIDES 'systemd' (but /home/mickey/yocto/rpi-nanbield/sources/poky/meta/recipes-core/psplash/psplash_git.bb DEPENDS on or otherwise requires it)
systemd was skipped: missing required distro feature 'usrmerge' (not in DISTRO_FEATURES)
NOTE: Runtime target 'psplash' is unbuildable, removing...
Missing or unbuildable dependency chain was: ['psplash', 'systemd']
ERROR: Required build target 'core-image-base' has no buildable providers.
Missing or unbuildable dependency chain was: ['core-image-base', 'psplash', 'systemd']

systemdがPROVIDESされていない。

ただしこの行に注目するとヒントがある。

systemd was skipped: missing required distro feature 'usrmerge' (not in DISTRO_FEATURES)

DISTRO_FEATURESにusrmergeが含まれていないからスキップしたとある。

DISTRO_FEATURESにusrmergeを追加してみるとエラーは発生しなくなる。

エラー発生(linux-firmware-rpidistro-bcm43456)

systemdのエラーを回避したあと下記のようなエラーが発生した。

ERROR: Nothing RPROVIDES 'linux-firmware-rpidistro-bcm43456' (but /home/mickey/yocto/rpi-nanbield/sources/poky/meta/recipes-core/packagegroups/packagegroup-base.bb RDEPENDS on or otherwise requires it)
linux-firmware-rpidistro RPROVIDES linux-firmware-rpidistro-bcm43456 but was skipped: Has a restricted license 'synaptics-killswitch' which is not listed in your LICENSE_FLAGS_ACCEPTED.
NOTE: Runtime target 'linux-firmware-rpidistro-bcm43456' is unbuildable, removing...
Missing or unbuildable dependency chain was: ['linux-firmware-rpidistro-bcm43456']
NOTE: Runtime target 'packagegroup-base-extended' is unbuildable, removing...
Missing or unbuildable dependency chain was: ['packagegroup-base-extended', 'linux-firmware-rpidistro-bcm43456']
ERROR: Required build target 'core-image-base' has no buildable providers.
Missing or unbuildable dependency chain was: ['core-image-base', 'packagegroup-base-extended', 'linux-firmware-rpidistro-bcm43456']

今度はlinux-firmware-rpidistro-bcm43456がPROVIDESされていない。

こちらもヒントがある。

linux-firmware-rpidistro RPROVIDES linux-firmware-rpidistro-bcm43456 but was skipped: Has a restricted license 'synaptics-killswitch' which is not listed in your LICENSE_FLAGS_ACCEPTED.

LICENSE_FLAGS_ACCEPTEDにsynaptics-killswitch含めろと書いてある。

local.conf(修正版)

エラーを回避するためのlocal.confは下記のようになる。

MACHINE = "raspberrypi4-64"
DL_DIR = "${TOPDIR}/../downloads"

# enable uart
ENABLE_UART = "1"

# systemd
DISTRO_FEATURES:append = " systemd usrmerge"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# Accept for the synaptics license
LICENSE_FLAGS_ACCEPTED = "synaptics-killswitch"

ビルド

core-image-baseをビルドする。

$ bitbake core-image-base

これでイメージが作成できる。

書き込み

bmaptoolで書き込む。

$ sudo bmaptool copy core-image-base-raspberrypi4.wic.bz2 /dev/sdX

/dev/sdXは環境に応じて適宜読み替える。

起動確認

動いた。

Poky (Yocto Project Reference Distro) 4.3.1 raspberrypi4-64 ttyS0

raspberrypi4-64 login: root
root@raspberrypi4-64:~# cat /etc/os-release
ID=poky
NAME="Poky (Yocto Project Reference Distro)"
VERSION="4.3.1 (nanbield)"
VERSION_ID=4.3.1
VERSION_CODENAME="nanbield"
PRETTY_NAME="Poky (Yocto Project Reference Distro) 4.3.1 (nanbield)"
CPE_NAME="cpe:/o:openembedded:poky:4.3.1"
root@raspberrypi4-64:~# uname -r
6.1.61-v8

meta-raspberrypiのカーネルは6.1系。

まとめ

久々にkirkstoneよりも新しいバージョンのYoctoProjectに触れてみた。 細かいところが変わっていた。

YoctoProject やってはダメな実装の例

はじめに

実際にgithubで公開されているレイヤで見逃せない悪い実装例があったので紹介する。 特定の個人や団体を批判するものではなく、実装、および設計思想に問題があるというための例示であることを強調しておく。

今回取り上げるのはradxaのmeta-rockchip。 「radxaの」と書いたのは、meta-rockchipが複数あり一つはYoctoProjectの管理下にあるから。

決して実装のクォリティが低いとかそういうことではなく、YoctoProjectを使用する上で間違った設計手法を取り入れているということの指摘だと受け取っていただきたい。

YoctoProjectの前提

YoctoProjectを使用する上で最初に知っておいてほしい前提が「What I wish I’d known about Yocto Project」に記載されている。

その中でも重要なのが下記になる。

Never modify the POKY layer. Never. Ever. When you update to the next release, you’ll lose all of your work. ALL OF IT.

これはpokyレイヤを直接編集してはいけない。ということを示している。なぜならpokyは継続的にメンテナンスされていてここを直接編集すると必ずマージ作業が発生するから。「you’ll lose all of your work. ALL OF IT.」は、あなたの修正は全て無かったことになるよ!という表現だが、結局はそういうことを意図していると思う。

転じて、既存のレイヤをいじってはいけない。既存のレイヤの振る舞いを変えたい時はYoctoProject/OpenEmbeddedが提供している、bbappendなどの仕組みを使って必要な部分を上書きしなさいという前提となる。 それはつまり、自分がレイヤを作成するときに他の人が上書きすることを前提に設計しなくては行けないということになる。自分の書いたレイヤが他の人が書いたレイヤに悪影響を及ぼしてはいけない。作法に則った書き方をしたレイヤならその作法から推測できる振る舞いをしなくてはならないということにもつながる。

radxaのmeta-rockchipの例

ここで例示するのはカーネルのレシピが参照しているlinux-rockchip.inc

linux-rockchip.inc

linux-rockchip.incの内容は下記の通り。

# Copyright (C) 2019, Fuzhou Rockchip Electronics Co., Ltd
# Released under the MIT license (see COPYING.MIT for the terms)

inherit python3-dir

DEPENDS:append = " openssl-native lz4-native ${PYTHON_PN}-native"

LINUX_VERSION_EXTENSION ?= "-rockchip-${LINUX_KERNEL_TYPE}"

PATCHPATH = "${THISDIR}/${BPN}_${LINUX_VERSION}"
inherit auto-patch

KCONFIG_MODE ?= "--alldefconfig"

# Make sure we use /usr/bin/env ${PYTHON_PN} for scripts
do_patch:append() {
    for s in `grep -rIl python ${S}/scripts`; do
        sed -i -e '1s|^#!.*python[23]*|#!/usr/bin/env ${PYTHON_PN}|' $s
    done
}

do_compile:prepend() {
    export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${STAGING_LIBDIR_NATIVE}
}

do_compile_kernelmodules:prepend() {
        export PKG_CONFIG_DIR="${STAGING_DIR_NATIVE}${libdir_native}/pkgconfig"
        export PKG_CONFIG_PATH="$PKG_CONFIG_DIR:${STAGING_DATADIR_NATIVE}/pkgconfig"
        export PKG_CONFIG_LIBDIR="$PKG_CONFIG_DIR"
        export PKG_CONFIG_SYSROOT_DIR=""
    export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${STAGING_LIBDIR_NATIVE}
}

# Hack for rockchip style images
ROCKCHIP_KERNEL_IMAGES = "boot.img zboot.img"
KERNEL_IMAGETYPES:append = " ${ROCKCHIP_KERNEL_IMAGES}"
python () {
    if not d.getVar('KERNEL_DEVICETREE'):
        raise bb.parse.SkipPackage('KERNEL_DEVICETREE is not specified!')

    # Use rockchip stype target, which is '<dts(w/o suffix)>.img'
    d.setVar('KERNEL_IMAGETYPE_FOR_MAKE', ' ' + d.getVar('KERNEL_DEVICETREE').replace('rockchip/', '').replace('.dtb', '.img'));
}

# Force regenerating defconfig
do_kernel_metadata:prepend() {
    rm -f ${WORKDIR}/defconfig
}

# Link rockchip style images
do_install:prepend() {
    for image in ${ROCKCHIP_KERNEL_IMAGES};do
        ln -sf ${B}/${image} ${B}/arch/${ARCH}/boot/
    done

    if [ "x${RK_USE_COMPRESSED_KERNEL}" = "x1" ]; then
        ln -sf zboot.img ${B}/arch/${ARCH}/boot/boot.img
    fi
}

しっかりとした実装に見えるし、問題なさそうだがこの分が問題。

inherit auto-patch

これは自前で実装したクラスである。

auto-patch.bbclass

auto-patch.bbclassの実装はここにある。

実装に問題があるわけではないので省略するが、設計思想がまず大問題である。

このクラスは「特定のディレクトリにパッチがあったらSRC_URIを修正して自動的に適用されるようにする」というものである。 つまり、パッチがあった場合レシピを改ざんするするものになる。 これは「パッチはどうせ増えていくんだからいちいちレシピ修正しなくてもいいよね!」という考え方で設計されたものになる。 一見、問題ないし便利そうに思えるかもしれない。

しかし!!!

このレイヤが提供するパッチに問題があり、このパッチをリバートするパッチを追加した別のレイヤがあった時に、 auto-patch.bbclassがどのタイミングどの順序でパッチをあてるのか確定できないためうまく動かない。 普通にSRC_URIにパッチのパスが書いてあって、レイヤのプライオリティで調整することができれば。 ただ単にそのパッチを取り消す修正は簡単なのに。

いちいちこの実装を解析して、どう動くかを考えてデバッグして。。それでも状況によっては動作が変わる可能性が孕む。 なので、リバートしたいパッチをmeta-rockchipから直接削除するのが一番確実ということになる。

今は修正されているかもしれないが、0001-net-rkwifi-Fix-include-path-error.patchの実装に問題があり、 このパッチをリバートすることで動作するドライバあるというケースがあった。

私はこのパッチをリバートするために、当たり前のように優先度を高くしてリバートパッチをSRC_URIに追加するだけのbbappendを含んだレイヤを作成した。 しかしパッチをあてるタイミングがauto-patch.bbclassの振る舞いによってdo_patchでは「そんなコミットはないからリバートできない」というエラーになってしまうのだ。

まとめ

原文

ちゃんとレシピ書け。横着すんな。 あと、横着するのは構わないけどルールは守れ。 変なbbclass自作すんな。ルールを理解してからにしろ。 他のレイヤがお作法守っているのに、ちゃんと動かないようなレイヤを作るな。

あまりにもひどいので要約。

  • レシピの修正をめんどくさがらない
  • めんどくさいからとなにか工夫する場合でもルールは守ったほうが良い
  • bbclassを自作するのは、ある程度習熟してから。また、ルールは守ってほしい。
  • 他のレイヤがお作法を守っているのにうまく動かなくなるような機能は追加してはいけない

tmuxを使えるようにする

はじめに

Linuxでコンソールの画面を分割したり、切れたセッションに再アタッチしたり便利そうなtmux。 しかし何も設定しないで使用するとマウスホイールでスクロールしなかったり、スクロールバックて文字列をコピペする方法がわからなかったり、 Ubuntuの「端末」に慣れていると結構不便なイメージがあった。

今回、[02] tmux 2.6 で emacs 風のキーバインドにする ... Emacs風キー割り当て を参考に設定を作って使いやすそうにしてみた。

xclipのインストール

設定ファイルでxclipを使用するようにしたのでインストール

$ sudo apt udpate 
$ sudo apt install -y xclip

設定ファイル

~/.config/tmux/tmux.confを使用する。

$ mkdir -p ~/.config/tmux 
$ pushd ~/.config/tmux
$ cat << 'EOF' > tmux.conf
# ======================
# Setting For tmux
# based on https://qiita.com/robozushi10/items/810c1047c2e9e088cca6
# ======================
# プレフィックスキー C-x
unbind C-b
set-option -g prefix C-x

# 256色
set-option -g default-terminal screen-256color
set -g terminal-overrides 'xterm:colors=256'

#全てのベルを無視
set-option -g bell-action none
# ウィンドウ番号基準値
set-option -g base-index 1
# ウィンドウの自動リネームoff
set-window-option -g automatic-rename off
#ウィンドウで動作があるとステータスラインでハイライト
set-window-option -g monitor-activity on

# コピー、選択モードのキーバインドemacs
set -g mode-keys emacs

# 設定ファイル再読み込み r
bind r source-file ~/.tmux.conf \; display-message "Reloaded config !!"

# Sync
bind S setw synchronize-panes on
bind E setw synchronize-panes off

# 直前のウィンドウ t
bind C-t last-window

# デタッチ d
bind d detach

# タイトル変更 A
bind A command-prompt "rename-window %%"

# ウィンドウ選択画面
bind b choose-window

# 新ウィンドウ作成
bind 5 new-window

# 分割していたペインそれぞれをWindowに
# bind 0 break-pane

# ペイン終了
bind k kill-pane
bind 0 kill-pane

# ウィンドウ終了
bind C-c kill-window

# ペイン番号表示
#bind i display-panes

# ペインの縦分割
bind 2 split-window -v
# ペインの横分割
bind 3 split-window -h

# resize panes like vim
# feel free to change the "1" to however many lines you want to resize by, only
# one at a time can be slow
bind < resize-pane -L 1
bind > resize-pane -R 1
bind - resize-pane -D 1
bind + resize-pane -U 1

# history size
set-option -g history-limit 100000
set-option -g mouse on

bind -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'select-pane -t=; copy-mode -e; send-keys -M'"
bind -n WheelDownPane select-pane -t= \; send-keys -M

#bind -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e'"

# ステータスラインの設定 {{{
# powerline setting
set-option -g status on
set -g status-interval 1
# window list alignment
set-option -g status-justify "left"
set-option -g status-left-length 150
set-option -g status-right-length 150
set-option -g status-left "#(~/.tmux/tmux-powerline/powerline.sh left)"
set-option -g status-right "#(~/.tmux/tmux-powerline/powerline.sh right)"
# # }}}

# window-status を中央揃えで配置する
set-option -g status-justify "centre"
# # status line の背景色を指定する。
set-option -g status-fg white
set-option -g status-bg black

# Copy Mode Start
# To copy:
#ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
# tmux 2.0 の場合
#ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
# bind-key -n -t emacs-copy M-w copy-pipe "xclip -i -sel p -f | xclip -i -sel c "
# bind-key -n -t emacs-copy C-w copy-pipe "xclip -i -sel p -f | xclip -i -sel c "

#ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
# tmux 2.6 の場合
#ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
unbind -T copy-mode MouseDragEnd1Pane
bind-key -n -T copy-mode M-w send-keys -X copy-pipe-and-cancel "xclip -i -sel p -f | xclip -i -sel c "
bind-key -n -T copy-mode C-w send-keys -X copy-pipe-and-cancel "xclip -i -sel p -f | xclip -i -sel c "

# To paste:
bind-key -n C-y run "xclip -o | tmux load-buffer - ; tmux paste-buffer"

set-option -g status-left '#[fg=cyan,bg=#303030]#{?client_prefix,#[reverse],} #H[#S] #[default]'

# Clear screen and scroll-back
bind-key -n C-l send-keys C-l \; send-keys -R \; clear-history

EOF

セッション

セッションの確認

$ tmux ls

アタッチ

$ tmux a

セッション名指定

$ tmux a -t <session>

セッションの削除

$ tmux kill-session -t <session>

全部削除

$ tmux kill-server

paneを一時的に全画面

参考

<prefix> z

戻す場合はもう一度

まとめ

emacsキーバインドにしてマウスもある程度使えるようにしたら思いの外捗るのでびっくり。

sshした先でtmuxを使うとsshが切れたときに再接続ができるのは、やはり非常に便利。