A mountain landscape

How to Build an Alpine Linux Package

The Alpine Linux wiki al­ready has a de­cent ex­pla­na­tion about how to build pack­ages; how­ever, I wanted throw some­thing to­gether a lit­tle more con­ver­sa­tional. This writeup should get you build­ing a pack­age quickly. The only re­quire­ment to fol­low along is a work­ing Alpine Linux in­stal­la­tion. I'll strongly im­ply us­ing Docker, cause that's what I've used. Regardless of how your setup, the gen­eral steps will be same. There's just less setup with Docker.

APKBUILD

The main thing we'll need is an APKBUILD file.

For my own ad­ven­ture, I didn't need to make an APKBUILD file from scratch. So, I'll have less to say about that. Thus, I'm not go­ing to at­tempt to cover all the de­tails of the APKBUILD file here. You can glance over the of­fi­cial doc­u­men­ta­tion and there are plenty of ex­am­ples in the of­fi­cial repos­i­tory.

Instead, I'll go over a high level overview and show how to gen­er­ate a patch file. Patch files al­low us to make mod­i­fi­ca­tions to the source files, with­out hav­ing those changes live in a repo. This con­cept is use­ful if we need to make some spe­cific change to our Alpine build that wouldn't be use­ful any­where else–or maybe you can't get the repo own­ers to make the change for some rea­son. Regardless, we can still tweak the build or the tests if needed.

Here's an ex­am­ple of an APKBUILD file. This one has most of the stuff go­ing on, so it should be use­ful for learn­ing a thing or two.

# Contributor: prspkt <prspkt@protonmail.com>
# Maintainer: prspkt <prspkt@protonmail.com>
pkgname=brotli
pkgver=1.0.9
pkgrel=1
pkgdesc="Generic lossless compressor"
url="https://github.com/google/brotli"
arch="all"
license="MIT"
makedepends="cmake python3-dev"
subpackages="$pkgname-doc $pkgname-static $pkgname-dev $pkgname-libs py3-$pkgname:py3"
source="$pkgname-$pkgver.tar.gz::https://github.com/google/brotli/archive/v$pkgver.tar.gz
optimize-mips-s390x.patch
build-tool-against-shared-lib.patch
838.patch
"


# secfixes:
# 1.0.9-r0:
# - CVE-2020-8927

prepare() {
default_prepare
sed -i 's,/usr/bin/env bash,/bin/sh,' tests/*.sh
}

build() {
if [ "$CBUILD" != "$CHOST" ]; then
CMAKE_CROSSOPTS="-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_HOST_SYSTEM_NAME=Linux"
fi
cmake -B build \
-DCMAKE_BUILD_TYPE=None \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_INSTALL_LIBDIR=lib \
-DBUILD_SHARED_LIBS=True \
-DCMAKE_C_FLAGS="$CFLAGS" \
$CMAKE_CROSSOPTS
make -C build
}

check() {
make -C build test
}

package() {
make -C build DESTDIR="$pkgdir" install

local man; for man in docs/*.?; do
install -D -m644 $man "$pkgdir"/usr/share/man/man${man##*.}/${man##*/}
done
}

py3() {
cd "$builddir"
python3 setup.py install --prefix=/usr --root="$subpkgdir"
}

sha512sums="b8e2df955e8796ac1f022eb4ebad29532cb7e3aa6a4b6aee91dbd2c7d637eee84d9a144d3e878895bb5e62800875c2c01c8f737a1261020c54feacf9f676b5f5 brotli-1.0.9.tar.gz
59e934578ce23b703f8f25f56578f8e9fd1466a9844b6f67b542acd6496df352548e3762697dc3851cfd0f8e1bad170bfdaa3e8a87c901fe81e2f3042e3aee84 optimize-mips-s390x.patch
f4a7653a0f7ef69f059d7f744a48c7731c8e66f977ce2e66cd106f697e82aa1f005923898d216a3d8be143b2dc8db1927c09daedb981818e752640a333d75fbc build-tool-against-shared-lib.patch
58ef677595f0db80b7d1353e42603cc30ef9b0b9530927f731ee31ac60ad9a3b2aac960a5cd100f8b10e547c9534e1ebf78c53550b52eed6fb3b7fb853317d20 838.patch"

You'll need to de­clare the pack­age name, ver­sion, var­i­ous sorts of de­pen­den­cies, and the lo­ca­tion of the source files. There some func­tions for start­ing the build, run­ning the tests, and gen­er­at­ing any needed sub­pack­ages. A sub­pack­age is ba­si­cally a way of split­ting a sin­gle build into mul­ti­ple pack­ages. A com­mon ex­am­ple may be to split the doc­u­men­ta­tion into its own pack­age, if it's quite large.

As men­tioned, in the same di­rec­tory as the APKBUILD file, you may see some patch files. These are ba­si­cally diffs shoved into a text file. In fact, that's re­ally all that they are. Assuming you're us­ing git, we can lit­er­ally use the git diff com­mand here.

# Using a POSIX shell here
git diff something...somethang > my-reasonably-named-patch-file.patch

I'm sure there's sev­eral ways to gen­er­ate these things. But it's only a diff, noth­ing fancy. We end up some­thing that looks like this:

Upstream: Yes
Reason: Fixes #11948
From 092446fafb4bfb81738853b7c7f76b293cd92a80 Mon Sep 17 00:00:00 2001
From: Evgenii Kliuchnikov <eustas.ru@gmail.com>
Date: Wed, 2 Sep 2020 10:49:49 +0200
Subject: [PATCH] Revert "Add runtime linker path to pkg-config files (#740)"

This reverts commit 31754d4ffce14153b5c2addf7a11019ec23f51c1.
---
scripts/libbrotlicommon.pc.in | 2 +-
scripts/libbrotlidec.pc.in | 2 +-
scripts/libbrotlienc.pc.in | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/scripts/libbrotlicommon.pc.in b/scripts/libbrotlicommon.pc.in
index 10ca969e..2a8cf7a3 100644
--- a/scripts/libbrotlicommon.pc.in
+++ b/scripts/libbrotlicommon.pc.in
@@ -7,5 +7,5 @@ Name: libbrotlicommon
URL: https://github.com/google/brotli
Description: Brotli common dictionary library
Version: @PACKAGE_VERSION@
-Libs: -L${libdir} -R${libdir} -lbrotlicommon
+Libs: -L${libdir} -lbrotlicommon
Cflags: -I${includedir}
diff --git a/scripts/libbrotlidec.pc.in b/scripts/libbrotlidec.pc.in
index e7c3124f..6f8ef2e4 100644
--- a/scripts/libbrotlidec.pc.in
+++ b/scripts/libbrotlidec.pc.in
@@ -7,6 +7,6 @@ Name: libbrotlidec
URL: https://github.com/google/brotli
Description: Brotli decoder library
Version: @PACKAGE_VERSION@
-Libs: -L${libdir} -R${libdir} -lbrotlidec
+Libs: -L${libdir} -lbrotlidec
Requires.private: libbrotlicommon >= 1.0.2
Cflags: -I${includedir}
diff --git a/scripts/libbrotlienc.pc.in b/scripts/libbrotlienc.pc.in
index 4dd0811b..2098afe2 100644
--- a/scripts/libbrotlienc.pc.in
+++ b/scripts/libbrotlienc.pc.in
@@ -7,6 +7,6 @@ Name: libbrotlienc
URL: https://github.com/google/brotli
Description: Brotli encoder library
Version: @PACKAGE_VERSION@
-Libs: -L${libdir} -R${libdir} -lbrotlienc
+Libs: -L${libdir} -lbrotlienc
Requires.private: libbrotlicommon >= 1.0.2
Cflags: -I${includedir}

Optionally, you can put a note at the top to tell every­one why the h*ck your us­ing a patch file. The ex­am­ple above did this. Documentation is fun and some­times help­ful? So maybe do the note thing?

Generate the Checksums

Once we have our APKBUILD file setup, we'll need to gen­er­ate check­sums for the source files.

This process is a sim­ple com­mand: abuild checksum, which needs to be run from the same di­rec­tory as the APKBUILD file. There is no need to down­load the source files man­u­ally as the abuild tool will han­dle that for us. Executing this via Docker isn't too dif­fer­ent. Once again, us­ing this im­age, we can fire off the com­mand like so:

docker run --rm -it -v "$PWD":/home/builder/package andyshinn/alpine-abuild:v12 checksum

The pro­gram will mod­ify your APKBUILD file, if needed. Obviously, if you've al­ready gen­er­ated a check­sum be­fore–and no source files have changed since the last in­vo­ca­tion–then there's noth­ing to up­date here.

To note, you can­not skip this step. The build will fail later on if the check­sums are not up to date with the source files.

We Need Keys

In Alpine Linux, repos­i­to­ries and pack­ages are signed us­ing asym­met­ric cryp­tog­ra­phy. Assuming you want down­stream users to be able to seam­lessly in­stall pack­ages you've built, you'll need a way to man­age keys. The al­ter­na­tive is to use this flag: apk add --allow-untrusted package-name. However, for our pur­poses, I'll as­sume we need to man­age keys.

Luckily, Alpine makes this easy. First, we should set the iden­tity of the pack­ager, which is us (or your com­pany, dog–who­ever re­ally). You can do this by ei­ther edit­ing your /etc/abuild.conf file, or you can set the PACKAGER en­vi­ron­ment vari­able. There is a gen­eral for­mat for the dig­i­tal nametag. It will end up look­ing some­thing like: PACKAGER="Matthew Parris <notmyrealemail@website.com>".

Next, all we to need to do is run this com­mand: abuild-keygen -n. Then, the pub­lic and pri­vate keys will both be gen­er­ated in the ~/.abuild di­rec­tory for the cur­rent user. The Docker ver­sion isn't much dif­fer­ent:

docker run --rm -it \
--entrypoint abuild-keygen \
-v "$PWD":/home/builder/.abuild \
-e PACKAGER="Matthew Parris <notmyrealemail@website.com>" \
andyshinn/alpine-abuild:v12 -n

The keys will then be placed in your cur­rent work­ing di­rec­tory.

In re­al­ity, we're gen­er­at­ing RSA 256 bit keys–which you can cre­ate man­u­ally us­ing openssl. The abuild-key­gen pro­gram is only a con­ve­nient script wrap­ping this be­hav­ior.

So how do we tell apk to trust our signed pack­ages and repos­i­to­ries? We need to copy our pub­lic key into the /etc/apk/keys di­rec­tory. Make sure you don't change the file­name of the key. Now, we should be able to run the com­mand apk add package-name, with no --allow-untrusted flag.

Build It!

We have our APKBUILD file setup and our sign­ing key ready. Let's ex­e­cute the build!

For the build process, you should only need your pri­vate key in place. We can man­u­ally set the lo­ca­tion of our pri­vate key with an en­vi­ron­ment vari­able PACKAGER_PRIVKEY. This vari­able will be set to a path like so: PACKAGER_PRIVKEY=/path/to/private/key. Like the other en­vi­ron­ment vari­ables we've seen so far, you can also set this in your /etc/abuild.conf file. The en­vi­ron­ment vari­able tech­nique is con­ve­nient when us­ing Docker, which we'll set later.

Assuming every­thing is setup, we are about ready to build. You'll prob­a­bly want to run the com­mand abuild-apk to up­date the pack­age cache–else abuild may not be able to down­load the pack­ages. Next, fire off the com­mand abuild -r. The abuild pro­gram should down­load the needed pack­ages for the build, start the build, run the tests, and fi­nally pack­age the source code up into one or more apk files.

By de­fault, the apk files will be placed in the ~/packages di­rec­tory for the cur­rent user. This de­fault can be changed by set­ting the en­vi­ron­ment vari­able REPODEST (or by mod­i­fy­ing the /etc/abuild.conf file).

Let's go through what this would look like on Docker. You'll end up run­ning a com­mand sim­i­lar to this:

docker run --rm -it \
-e RSA_PRIVATE_KEY="$(cat /path/to/private/key.rsa)" \
-e RSA_PRIVATE_KEY_NAME="key.rsa" \
-v "$PWD:/home/builder/package" \
-v "/where/you/want/to/put/the/packages:/packages" \
andyshinn/alpine-abuild:v12

To note, this Dockerfile does change some of the en­vi­ron­ment vari­ables around–like REPODEST is set to /packages. Also, make sure you run this from the di­rec­tory that con­tains the APKBUILD file (or ad­just the bind mount). I rec­om­mend look­ing at the Dockerfile it­self and play­ing around with this. For ex­am­ple, you'll likely want to ad­just the bind mounts or the key lo­ca­tion.

And that's pretty much it. Anyways, for more de­tail than what I've given here, check out the Alpine wiki. Thanks for read­ing.