Alpine Linuxの自前パッケージをビルドする方法

この記事はRecruit Advent Calendar 2021 - Adventarの24日目(12/24)のエントリーです。 adventar.org

1. Alpine Linuxとは?

Alpine LinuxはDockerイメージ作成でも良く使われるLinuxディストリビューションの一つで、組み込み用途で使われていたbusyboxを標準で利用し、豪華な構文を持ったbashではなくシンプルなash、機能の多いglibcではなく簡素なmusl-libcを採用していて、トータルのバイナリサイズがとても小さいという特徴があります。Alpine Linuxでは、apkコマンド(Debian系だとapt、RedHat系だとyumに相当)を利用してパッケージインストールができるのですが、この記事では自前のapkパッケージをビルドする方法について解説します。

2. ユーザの作成

Alpine Linux上でパッケージをビルドするユーザtakesakoを作成し(このユーザ名は自分の名前に変えてください)、abuildグループに所属させます。この作業はrootで行います。

adduser takesako
addgroup takesako abuild

標準ではsudoパッケージがインストールされていないので、apkコマンドを利用してパッケージをインストールし、visudoコマンドで/etc/sudoersを編集してwheelグループに権限を付与します。

apk add sudo
visudo
addgroup takesako wheel

もしもあなたが(emacs派、nano派など)宗教上の理由でvisudoコマンドを使いたくない場合は、以下のように設定しても大丈夫です。

echo "takesako ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/takesako
chmod 440 /etc/sudoers.d/takesako

ちなみに、Alpine Linuxでは設定が豊富なsudoではなく、OpenBSDで開発が進められていたdoasコマンドも利用できますので、そちらを使うのも良いでしょう。

3. alpine-sdkのインストール

apkパッケージのビルドに必要なalpine-sdkパッケージをインストールします。

apk add alpine-sdk

これは中身のないメタパッケージとなっており、依存関係のあるパッケージ群が自動でインストールされます。

alpine-sdkのAPKBUILDファイル(~/aports/main/alpine-sdk/APKBUILD)は以下の通りです。

# Maintainer: Natanael Copa <ncopa@alpinelinux.org>
pkgname=alpine-sdk
pkgver=1.0
pkgrel=1
url="https://git.alpinelinux.org/"
pkgdesc="Alpine Software Development Kit meta package"
depends="abuild build-base git"
arch="noarch"
license="GPL-2.0"

build() {
        # meta package
        return 0
}

package() {
        mkdir -p "$pkgdir"
}

APKBUILDのリファレンスはhttps://wiki.alpinelinux.org/wiki/APKBUILD_Referenceにありますが、 depends="abuild build-base git"という個所で必要な他のパッケージの依存関係を指定していて、apk addしたときに依存関係のあるabuildパッケージと、build-baseパッケージと、gitパッケージが足りなければ自動でインストールされます。

4. 自分の名前とメールアドレスの設定

パッケージをビルドするユーザでログインして、git configで自分の名前とメールアドレスを設定します。

login takesako
git config --global user.name "Your Full Name"
git config --global user.email "your@email.address"

他に/etc/abuild.confファイルにも自分の名前とメールアドレスを記載する場所があるので、

sudo vi /etc/abuild.conf

コマンドを実行して、以下を書き換えます。

PACKAGER="Your Full Name <your@email.address>"
MAINTAINER="$PACKAGER"

5. キャッシュディレクトリの権限追加

ダウンロードしたソースコードを置くキャッシュディレクトリとして/var/cache/distfilesがあるので、abuildグループ権限で書き込みができるようにしておくと便利です。

sudo mkdir -p /var/cache/distfiles
sudo chgrp abuild /var/cache/distfiles
sudo chmod g+w /var/cache/distfiles

6. 公開鍵と秘密鍵の生成

abuild-keygenというスクリプトが用意されているので、-a(--append)と-i(--install)オプションを指定して公開鍵と秘密鍵を生成します。

abuild-keygen -a -i

鍵の保存場所を聞かれますが、そのままエンターキーを押してデフォルトの場所のままにしておいても良いでしょう。

>>> Generating public/private rsa key pair for abuild
Enter file in which to save the key [/home/takesako/.abuild/your@email.address-1a2b3c4d.rsa]:
Generating RSA private key, 2048 bit long modulus (2 primes)
................+++++
.........................+++++
e is 65537 (0x010001)
writing RSA key
>>> Installing /home/takesako/.abuild/your@email.address-1a2b3c4d.rsa.pub to /etc/apk/keys...
>>>
>>> Please remember to make a safe backup of your private key:
>>> /home/takesako/.abuild/your@email.address-1a2b3c4d.rsa
>>>

ここでビルドしたapkファイルを他のマシン上でインストールするには、ここで生成した公開鍵の.rsa.pubファイルを/etc/apk/keys/以下に置く必要があります。(※.pubのない.rsaファイルは秘密鍵です)

7. APKBUILDの雛形作成

APKBUILDファイルの雛形作成には、newapkbuildコマンドを利用すると便利です。

newapkbuild 3.7.0-r0 - generate a new APKBUILD
Usage: newapkbuild [-n PKGNAME] [-d PKGDESC] [-l LICENSE] [-u URL]
       [-a | -C | -m | -p | -y | -r] [-s] [-c] [-f] [-h]
       PKGNAME[-PKGVER] | SRCURL
Options:
  -n  Set package name to PKGNAME (only use with SRCURL)
  -d  Set package description to PKGDESC
  -l  Set package license to LICENSE, use identifiers from:
      <https://spdx.org/licenses/>
  -u  Set package URL
  -a  Create autotools package (use ./configure ...)
  -C  Create CMake package (Assume cmake/ is there)
  -m  Create meson package (Assume meson.build is there)
  -p  Create perl package (Assume Makefile.PL is there)
  -y  Create python package (Assume setup.py is there)
  -r  Crate rust package (Assume Cargo.toml is there)
  -s  Use sourceforge source URL
  -c  Copy a sample init.d, conf.d, and install script
  -f  Force even if directory already exists
  -h  Show this help

たとえば、何もオプションを指定せずに、

newapkbuild aaa

コマンドを実行すると、aaa/APKBUILDに以下のファイルが生成されます。

# Contributor: Yoshinori Takesako <takesako@namazu.org>
# Maintainer: Yoshinori Takesako <takesako@namazu.org>
pkgname=aaa
pkgver=
pkgrel=0
pkgdesc=""
url=""
arch="all"
license=""
depends=""
makedepends=""
install=""
subpackages="$pkgname-dev $pkgname-doc"
source=""
builddir="$srcdir/"

build() {
        # Replace with proper build command(s)
        :
}

check() {
        # Replace with proper check command(s)
        :
}

package() {
        # Replace with proper package command(s)
        :
}

これにビルドに必要な情報を埋めていけば、APKBUILDファイルの完成です。

8. 実用例:cycfx2progパッケージの作成

実用例として、cycfx2progパッケージを作成してみます。cycfx2progはEZ-USBなどのFX2系組み込みデバイスファームウェア書き込みに利用するコマンドです。

mkdir -p test/cycfx2prog
cd test/cycfx2prog
vi APKBUILD

作成するAPKBUILDファイルは以下の通りです。

# Contributor: Yoshinori Takesako <takesako@namazu.org>
# Maintainer: Yoshinori Takesako <takesako@namazu.org>
pkgname=cycfx2prog
pkgver=0.47
pkgrel=0
pkgdesc="download 8051 program into the FX2 board"
url="https://www.triplespark.net/elec/periph/USB-FX2/software/"
arch="all"
license="GPL2"
depends="libusb-compat"
makedepends="libusb-compat-dev"
install=""
subpackages=""
source="https://www.triplespark.net/elec/periph/USB-FX2/software/$pkgname-$pkgver.tar.gz
        Makefile.patch
        "
builddir="$srcdir/$pkgname-$pkgver"

build() {
        cd "$builddir"
        make
}

check() {
        return 0
}

package() {
        cd "$builddir"
        install -D -m 755 cycfx2prog "$pkgdir"/usr/bin/cycfx2prog
}

あと、Makefile.patchファイルも以下で作っておきます。

cat<<EOF>Makefile.patch
--- cycfx2prog-0.47/Makefile
+++ cycfx2prog-0.47-new/Makefile
@@ -9,7 +9,7 @@

 # NOTE: Also add sources to the "dist:" target!
 cycfx2prog: cycfx2prog.o cycfx2dev.o
-       $(CC) $(LDFLAGS) cycfx2prog.o cycfx2dev.o -o cycfx2prog
+       $(CC) cycfx2prog.o cycfx2dev.o -o cycfx2prog $(LDFLAGS)
EOF

これは$(LDFLAGS)を後ろに書かないと、libusbのリンクに失敗する問題を修正しています。

9. ソースコードのダウンロードとchecksumの更新

abuild checksumコマンドを実行して、ソースコードのダウンロードとchecksumの更新を行います。

abuild checksum

実行すると、以下のようにダウンロード画面が表示されます。

>>> cycfx2prog: Fetching https://www.triplespark.net/elec/periph/USB-FX2/software/cycfx2prog-0.47.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  8768  100  8768    0     0   5299      0  0:00:01  0:00:01 --:--:--  5301
>>> cycfx2prog: Updating the sha512sums in APKBUILD...

ダウンロード終了後、sha512ハッシュ値が計算され、APKBUILDファイルにchecksumの値が書き込まれます。

sha512sums="089895f0c4b45012f9f9fc607a30c2e2897f360d270973354fa739cc456d2728080733461f6a3681422049599947461c05e5d9e7e598fc3c9fd6d5a7d89e346c  cycfx2prog-0.47.tar.gz
ac62c7b1a13d144f5ceacc425a6c1487bf390273d4cf33cfe1c3ee5498d47b3c80c72ab5b8f189171d294da3bb001b42b1978d46937cf59578b35772c724d679  Makefile.patch"

10. パッケージビルドの実行

abuild -rコマンドを実行して、パッケージをビルドします。

alpine:~/cycfx2prog$ abuild -r
>>> cycfx2prog: Building test/cycfx2prog 0.47-r0 (using abuild 3.7.0-r0) started Fri, 24 Dec 2021 13:52:20 +0000
>>> cycfx2prog: Checking sanity of /home/test/cycfx2prog/APKBUILD...
>>> cycfx2prog: Analyzing dependencies...
>>> cycfx2prog: Installing for build: build-base libusb-compat libusb-compat-dev
(1/5) Installing libusb (1.0.24-r1)
(2/5) Installing libusb-compat (0.1.5-r4)
(3/5) Installing libusb-dev (1.0.24-r1)
(4/5) Installing libusb-compat-dev (0.1.5-r4)
(5/5) Installing .makedepends-cycfx2prog (20211224.135220)
Executing busybox-1.32.1-r6.trigger
OK: 276 MiB in 103 packages
>>> cycfx2prog: Cleaning up srcdir
>>> cycfx2prog: Cleaning up pkgdir
>>> cycfx2prog: Fetching https://www.triplespark.net/elec/periph/USB-FX2/software/cycfx2prog-0.47.tar.gz
>>> cycfx2prog: Fetching https://www.triplespark.net/elec/periph/USB-FX2/software/cycfx2prog-0.47.tar.gz
>>> cycfx2prog: Checking sha512sums...
cycfx2prog-0.47.tar.gz: OK
Makefile.patch: OK
>>> cycfx2prog: Unpacking /var/cache/distfiles/cycfx2prog-0.47.tar.gz...
>>> cycfx2prog: Makefile.patch
patching file Makefile
Hunk #1 succeeded at 9 with fuzz 2.
gcc -pipe -c -O2 -fno-rtti -fno-exceptions -DCYCFX2PROG_VERSION=\"0.47\" -W -Wall -Wformat cycfx2prog.cc
gcc -pipe -c -O2 -fno-rtti -fno-exceptions -DCYCFX2PROG_VERSION=\"0.47\" -W -Wall -Wformat cycfx2dev.cc
In file included from cycfx2dev.cc:18:
cycfx2dev.cc: In member function 'int CypressFX2Device::_ProgramIHexLine(const char*, const char*, int)':
cycfx2dev.cc:393:16: warning: comparison of unsigned expression in '>= 0' is always true [-Wtype-limits]
  393 |   assert(nbytes>=0 && nbytes<256);
      |          ~~~~~~^~~
gcc -pipe cycfx2prog.o cycfx2dev.o -o cycfx2prog -lusb
>>> cycfx2prog: Entering fakeroot...
>>> cycfx2prog*: Running postcheck for cycfx2prog
>>> cycfx2prog*: Preparing package cycfx2prog...
>>> cycfx2prog*: Stripping binaries
fatal: not a git repository (or any of the parent directories): .git
fatal: not a git repository (or any of the parent directories): .git
>>> cycfx2prog*: Scanning shared objects
>>> cycfx2prog*: Tracing dependencies...
        libusb-compat
        so:libc.musl-x86.so.1
        so:libusb-0.1.so.4
>>> cycfx2prog*: Package size: 48.0 KB
>>> cycfx2prog*: Compressing data...
>>> cycfx2prog*: Create checksum...
>>> cycfx2prog*: Create cycfx2prog-0.47-r0.apk
>>> cycfx2prog: Build complete at Fri, 24 Dec 2021 13:52:21 +0000 elapsed time 0h 0m 1s
>>> cycfx2prog: Cleaning up srcdir
>>> cycfx2prog: Cleaning up pkgdir
>>> cycfx2prog: Uninstalling dependencies...
(1/5) Purging .makedepends-cycfx2prog (20211224.135220)
(2/5) Purging libusb-compat-dev (0.1.5-r4)
(3/5) Purging libusb-compat (0.1.5-r4)
(4/5) Purging libusb-dev (1.0.24-r1)
(5/5) Purging libusb (1.0.24-r1)
Executing busybox-1.32.1-r6.trigger
OK: 275 MiB in 98 packages
>>> cycfx2prog: Updating the test/x86 repository index...
>>> cycfx2prog: Signing the index...

エラーがなければ、~/packages/test/x86/ディレクトリに以下のファイルが書き込まれます。

ls -l ~/packages/test/x86/
total 20
-rw-r--r--    1 test     test           754 Dec 24 14:09 APKINDEX.tar.gz
-rw-r--r--    1 test     test         13795 Dec 24 14:09 cycfx2prog-0.47-r0.apk

ここでcycfx2prog-0.47-r0.apkファイルができていれば完成です。

ね、簡単でしょ。