みつきんのメモ

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

recipetoolでバイナリパッケージを作る

はじめに

recipetoolでバイナリパッケージを作ってみる。

ちなみに今回は作ることが目的のため、作ったものの動作確認はしていない。

reciptool

オプションの確認

$ recipetool create --help
NOTE: Starting bitbake server...
usage: recipetool create [-h] [-o OUTFILE] [-p PROVIDES] [-m] [-x EXTRACTPATH]
                         [-N NAME] [-V VERSION] [-b] [--also-native]
                         [--src-subdir SUBDIR] [-a | -S SRCREV] [-B SRCBRANCH]
                         [--keep-temp] [--npm-dev] [--mirrors]
                         source

Creates a new recipe from a source tree

arguments:
  source                Path or URL to source

options:
  -h, --help            show this help message and exit
  -o OUTFILE, --outfile OUTFILE
                        Specify filename for recipe to create
  -p PROVIDES, --provides PROVIDES
                        Specify an alias for the item provided by the recipe
  -m, --machine         Make recipe machine-specific as opposed to
                        architecture-specific
  -x EXTRACTPATH, --extract-to EXTRACTPATH
                        Assuming source is a URL, fetch it and extract it to
                        the directory specified as EXTRACTPATH
  -N NAME, --name NAME  Name to use within recipe (PN)
  -V VERSION, --version VERSION
                        Version to use within recipe (PV)
  -b, --binary          Treat the source tree as something that should be
                        installed verbatim (no compilation, same directory
                        structure)
  --also-native         Also add native variant (i.e. support building recipe
                        for the build host as well as the target machine)
  --src-subdir SUBDIR   Specify subdirectory within source tree to use
  -a, --autorev         When fetching from a git repository, set SRCREV in the
                        recipe to a floating revision instead of fixed
  -S SRCREV, --srcrev SRCREV
                        Source revision to fetch if fetching from an SCM such
                        as git (default latest)
  -B SRCBRANCH, --srcbranch SRCBRANCH
                        Branch in source repository if fetching from an SCM
                        such as git (default master)
  --keep-temp           Keep temporary directory (for debugging)
  --npm-dev             For npm, also fetch devDependencies
  --mirrors             Enable PREMIRRORS and MIRRORS for source tree fetching
                        (disabled by default).

バイナリオプション-bがある。

これを指定してレシピを作成すると、自動的にbin_packageクラスを継承してくれる。

レシピの作成

今回は例としてラズベリーパイ4向けのtensorflowを作成する。

$ recipetool create -b https://github.com/Qengineering/Tensorflow-Raspberry-Pi/raw/master/libtensorflow_2_1_0.tar.gz

レイヤの作成

ビルドするために適当なレイヤを作成し、レシピを移動する。

$ bitbake-layers create-layer meta-local
$ bitbake-layers add-layer meta-local
$ mkdir ./meta-local/recipes-example/libtensorflow
$ mv ./libtensorflow-2-1_0.bb ./meta-local/recipes-example/libtensorflow

ビルド

とりあえずビルドしてみる。

$ bitbake libtensorflow-2-1
NOTE: Tasks Summary: Attempted 551 tasks of which 158 didn't need to be rerun and all succeeded.

問題なくビルドできた。

tmp/work/cortexa7t2hf-neon-vfpv4-poky-linux-gnueabi/libtensorflow-2-1/0-r0/packages-split/libtensorflow-2-1でパッケージ内のツリー構成がどうなっているか確認する。

.
├── LICENSE
├── THIRD_PARTY_TF_C_LICENSES
├── include
│   └── tensorflow
│       └── c
│           ├── c_api.h
│           ├── c_api_experimental.h
│           ├── eager
│           │   └── c_api.h
│           ├── tf_attrtype.h
│           ├── tf_datatype.h
│           ├── tf_file_statistics.h
│           ├── tf_status.h
│           └── tf_tensor.h
└── lib
    ├── libtensorflow.so.2 -> libtensorflow.so.2.1.0
    ├── libtensorflow.so.2.1.0
    ├── libtensorflow_framework.so.2 -> libtensorflow_framework.so.2.1.0
    └── libtensorflow_framework.so.2.1.0

tarの内容を/に展開しているようだ。

bin_package.bbclass

bin_package.bbclassの実装を確認してみると下記のようになっている。

do_configure[noexec] = "1"
do_compile[noexec] = "1"

# Install the files to ${D}
bin_package_do_install () {
    # Do it carefully
    [ -d "${S}" ] || exit 1
    if [ -z "$(ls -A ${S})" ]; then
        bbfatal bin_package has nothing to install. Be sure the SRC_URI unpacks into S.
    fi
    cd ${S}
    tar --no-same-owner --exclude='./patches' --exclude='./.pc' -cpf - . \
        | tar --no-same-owner -xpf - -C ${D}
}

FILES_${PN} = "/"

EXPORT_FUNCTIONS do_install

FILES_${PN} = "/"となっている。?=などではなく=で設定されているため、レシピやlocal.confでは設定を上書きできない。

bin_package.bbclassを継承しているの場合、どこにインストールされるかは元のtarの構成に依存するということになりそうだ。

インストールディレクトリを変更したい

先述の通り、bin_packageクラスを継承する場合は、パッケージのディレクトリツリーを単純には変更できない。

今回のように。/include/libのように、本来は使用するべきではないディレクトリにインストールされてしまうのは避けたい。

そのような場合は次のようにする。

do_install() {
    install -d ${D}/opt
    cp -ra "${S}/" "${D}/opt/"
    chown -R root:root "${D}/opt/"
}


FILES_${PN}_remove = "/"
FILES_${PN}-dev = "/opt/libtensorflow-2-1-0/lib/*.so \
           /opt/libtensorflow-2-1-0/lib/*.so.2 \
"
FILES_${PN} = "/opt/libtensorflow-2-1-0/"

do_installを上書きして、インストール先を変更する。この時ファイルの所有権をroot:rootに変更することを忘れると下記のエラーに苦しむ。

ERROR: libtensorflow-2-1-0-r0 do_package: Error executing a python function in exec_python_func() autogenerated:

The stack trace of python calls that resulted in this exception/failure was:
File: 'exec_python_func() autogenerated', lineno: 2, function: <module>
     0001:
 *** 0002:sstate_report_unihash(d)
     0003:
File: '/home/mickey/work/yocto/rpi-dunfell/layers/poky/meta/classes/sstate.bbclass', lineno: 840, function: sstate_report_unihash
     0836:    report_unihash = getattr(bb.parse.siggen, 'report_unihash', None)
     0837:
     0838:    if report_unihash:
     0839:        ss = sstate_state_fromvars(d)
 *** 0840:        report_unihash(os.getcwd(), ss['task'], d)
     0841:}
     0842:
     0843:#
     0844:# Shell function to decompress and prepare a package for installation
File: '/home/mickey/work/yocto/rpi-dunfell/layers/poky/bitbake/lib/bb/siggen.py', lineno: 555, function: report_unihash
     0551:
     0552:            if "." in self.method:
     0553:                (module, method) = self.method.rsplit('.', 1)
     0554:                locs['method'] = getattr(importlib.import_module(module), method)
 *** 0555:                outhash = bb.utils.better_eval('method(path, sigfile, task, d)', locs)
     0556:            else:
     0557:                outhash = bb.utils.better_eval(self.method + '(path, sigfile, task, d)', locs)
     0558:
     0559:            try:
File: '/home/mickey/work/yocto/rpi-dunfell/layers/poky/bitbake/lib/bb/utils.py', lineno: 420, function: better_eval
     0416:    if extraglobals:
     0417:        ctx = copy.copy(ctx)
     0418:        for g in extraglobals:
     0419:            ctx[g] = extraglobals[g]
 *** 0420:    return eval(source, ctx, locals)
     0421:
     0422:@contextmanager
     0423:def fileslocked(files):
     0424:    """Context manager for locking and unlocking file locks."""
File: '<string>', lineno: 1, function: <module>
  File "<string>", line 1, in <module>

File: '/home/mickey/work/yocto/rpi-dunfell/layers/poky/meta/lib/oe/sstatesig.py', lineno: 585, function: OEOuthashBasic
     0581:
     0582:                update_hash("\n")
     0583:
     0584:            # Process this directory and all its child files
 *** 0585:            process(root)
     0586:            for f in files:
     0587:                if f == 'fixmepath':
     0588:                    continue
     0589:                process(os.path.join(root, f))
File: '/home/mickey/work/yocto/rpi-dunfell/layers/poky/meta/lib/oe/sstatesig.py', lineno: 548, function: process
     0544:                    add_perm(stat.S_IXOTH, 'x')
     0545:
     0546:                if include_owners:
     0547:                    try:
 *** 0548:                        update_hash(" %10s" % pwd.getpwuid(s.st_uid).pw_name)
     0549:                        update_hash(" %10s" % grp.getgrgid(s.st_gid).gr_name)
     0550:                    except KeyError:
     0551:                        bb.warn("KeyError in %s" % path)
     0552:                        raise
Exception: KeyError: 'getpwuid(): uid not found: 1000'

ERROR: Logfile of failure stored in: /home/mickey/work/yocto/rpi-dunfell/build/tmp/work/cortexa7t2hf-neon-vfpv4-poky-linux-gnueabi/libtensorflow-2-1/0-r0/temp/log.do_package.8254
ERROR: Task (/home/mickey/work/yocto/rpi-dunfell/build/meta-local/recipes-example/libtensorflow/libtensorflow-2-1_0.bb:do_package) failed with exit code '1'

これはhashを生成する際にファイルの所有権をキーにするらしいのだが、この時bitbakeはfakerootで動いており、この動作環境内に一般ユーザーが存在しないためException: KeyError: 'getpwuid(): uid not found: 1000'が発生するということらしい。

まとめ

  • バイナリパッケージを作成するにはbin_packageクラスを継承する。
  • 作成されるパッケージのツリー構成は/からに固定されている。
  • 自分の好きなようにファイルを配置するには少し手間がかかるが不可能ではない。
  • do_install時に所有権の変更を忘れずに。