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 ļ¬le.

For my own adĀ­venĀ­ture, I didĀ­nā€™t need to make an APKBUILD ļ¬le 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 ļ¬le here. You can glance over the ofĀ­ļ¬Ā­cial docĀ­uĀ­menĀ­taĀ­tion and there are plenty of exĀ­amĀ­ples in the ofĀ­ļ¬Ā­cial reposĀ­iĀ­tory.

Instead, Iā€™ll go over a high level overview and show how to genĀ­erĀ­ate a patch ļ¬le. Patch ļ¬les alĀ­low us to make modĀ­iĀ­ļ¬Ā­caĀ­tions to the source ļ¬les, withĀ­out havĀ­ing those changes live in a repo. This conĀ­cept is useĀ­ful if we need to make some speĀ­ciļ¬c change to our Alpine build that wouldĀ­nā€™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 ļ¬le. 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 ļ¬les. 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 ļ¬le, you may see some patch ļ¬les. These are baĀ­siĀ­cally diffs shoved into a text ļ¬le. 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 ļ¬le. 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 ļ¬le setup, weā€™ll need to genĀ­erĀ­ate checkĀ­sums for the source ļ¬les.

This process is a simĀ­ple comĀ­mand: abuild checksum, which needs to be run from the same diĀ­recĀ­tory as the APKBUILD ļ¬le. There is no need to downĀ­load the source ļ¬les manĀ­uĀ­ally as the abuild tool will hanĀ­dle that for us. Executing this via Docker isĀ­nā€™t too difĀ­ferĀ­ent. Once again, usĀ­ing this imĀ­age, we can ļ¬re 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 ļ¬le, if needed. Obviously, if youā€™ve alĀ­ready genĀ­erĀ­ated a checkĀ­sum beĀ­foreā€“and no source ļ¬les 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 ļ¬les.

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 ļ¬‚ag: 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 ļ¬le, 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 isĀ­nā€™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 ļ¬leĀ­name of the key. Now, we should be able to run the comĀ­mand apk add package-name, with no --allow-untrusted ļ¬‚ag.

Build It!

We have our APKBUILD ļ¬le 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 ļ¬le. 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, ļ¬re 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 ļ¬Ā­nally packĀ­age the source code up into one or more apk ļ¬les.

By deĀ­fault, the apk ļ¬les 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 ļ¬le).

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 Dockerļ¬le 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 ļ¬le (or adĀ­just the bind mount). I recĀ­omĀ­mend lookĀ­ing at the Dockerļ¬le 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.