Packaging Guidelines¶
Provides, priorities, alternatives, and forking packages¶
The APK concepts around selecting conflicting packages for a same purpose, e.g: udev implementations is complex, barely documented, and full of foot guns. Mistakes on this have caused a fair share of issues and bugs in the past, and is important to understand them to avoid them in the future.
Versioned provides¶
Note
Upstream documentation exists but is remarkably limited. Generic documentation here could be potentially moved upstream if alpine docs improve in the future.
Every APK package can be installed and identified by its name. But APK has a feature for packages to identify themselves with alternative names for installation. This is called “provides”. Provides are most commonly versioned, meaning that if two packages provide the same thing, they are considered in conflict and not allowed to be installed concurrently.
Note
abuild automatically adds provides for shared libraries under /usr/lib and
executables under the PATH. As a convention, shared libraries have a so:
prefix, and executables a cmd: prefix.
Let’s look at an example:
$ apk info --provides musl
musl-1.2.5-r21 provides:
so:libc.musl-x86_64.so.1=1
$ apk info --provides musl-utils
musl-utils-1.2.5-r21 provides:
libc-utils=1.2.5-r21
cmd:getconf=1.2.5-r21
cmd:getent=1.2.5-r21
cmd:iconv=1.2.5-r21
cmd:ldconfig=1.2.5-r21
cmd:ldd=1.2.5-r21
We can see, that the musl package is not only identified as musl with version
1.2.5-r21, but also as so:libc.musl-x86_64.so.1 with version 1. And that
musl-utils package can also be identified by all the executables it provides,
and as libc-utils=1.25-r21.
This logic and alternaties can become quite handy in multiple situations:
Tracing dependencies: When building packages, abuild adds dependencies of packages in an equivalent from as it adds provides. If it detecs that a package depends on a certain library, instead of locking to the package that provides it, it depends on the
so:-prefixed provider. This allows to replace any library with an alternative version of the same library without having to touch anything else. For example, we can see below how glib depends onso:libc.musl-x86_64.so.1, instead of depending direcly onmusl.
$ apk info --depends glib
glib-2.86.3-r1 depends on:
/bin/sh
so:libc.musl-x86_64.so.1
so:libffi.so.8
so:libintl.so.8
so:libmount.so.1
so:libpcre2-8.so.0
so:libz.so.1
Renaming packages: If we want to change the name of a package, but there is a chance that users have that package in their
/etc/apk/world, then upgrades will fail, unless the renamed package can be backwards compatible. This is what we see whenmusl-utilsprovideslibc-utils=1.2.5-r21. Indeed, on to of the line that adds it upstream there is this comment:# for backwards compatibility.Providing default functionality: For example, even though there are usually multiple versions of the
cephfilesystem available in Alpine, only one of them provides thecephname, allowing users to get a sane default instead of being pinned to a single version.
These are all simple, straight-forward uses of the provides feature. However, there is more complexity and things that can be achieved with it.
Virtual provides¶
Provides can not only have a version, but also have no version. This are called “virtual” provides. Such provides work differently to versioned provides in the sense that multiple packages that provide them can be installed simultaneously. This can be helpful in situations where we can afford ourselves more flexibility. For example, with the shell!
Note
abuild also automatically adds a /bin/sh dependency when it detects that
packages have script files that depend on having a shell installed.
$ apk info --provides busybox-binsh
busybox-binsh-1.37.0-r31 provides:
/bin/sh
cmd:sh=1.37.0-r31
$ apk info --provides dash-binsh
dash-binsh-0.5.13.1-r2 provides:
/bin/sh
cmd:sh=0.5.13.1-r2
$ apk info --depends glib
glib-2.86.3-r1 depends on:
/bin/sh
so:libc.musl-x86_64.so.1
so:libffi.so.8
so:libintl.so.8
so:libmount.so.1
so:libpcre2-8.so.0
so:libz.so.1
We can see how glib depends on /bin/sh, but that is provided by multiple
packages, without a version. glib needs a shell, but any POSIX-shell is
enough. At the same time, multiple POSIX shells can be installed simultaneously,
and that is not an issue!
Note
In practice installing dash and busybox concurrently is possible, but
installing dash-binsh and busybox-binsh is not due to the versioned cmd:
provider. We leave an example that would work without cmd: as an exercise to
the reader.
Other good examples to research are linux-firmware-any, and
initramfs-generator virtual providers.
Automatic selection: provider_priority¶
Since providers of virtual packages can all be installed at the same time, apk
does not have a direct way to decide what to do when asked to install
one. Should it install one, the other, or all of them? By default, when asked to
execute this impossible task apk will error out and ask the user to choose which
package providing the virtual name should be installed. However, generally there
is a desired default for such virtual package. If a provider_priority is added
to the APKBUILD together with a virtual provider, this will be used to select
the virtual to install (highest number) instead of prompting the user. A good
and easy example to research on this topic is the initramfs-generator.
Forks, alternatives, and priorities¶
Unfortunately, the way providers are designed make it hard to reason about
forks. When forking packages, the most important things is to always use
versioned provides. Virtual providers might work in some situations, but APK
might randomly switch packages or consider conflicts depending on the status of
the user’s world file. The forked package and the original package should
never be installed concurrently, thus the need of the versioned provides.
Warning
There is a common misconception that provider_priority works with versioned
providers. This is due to the re-use of provides name, historical bugs, and
copy-paste of those bugs. provider_priority has no use together with versioned
provides, and any package that uses both should just remove the
provider_priority to avoid extending the confusion.
However, most common bugs related to forks come from the difficulty understanding which package will be installed when multiple provide the same versioned provider. This is made specially complex because updates and new installations can (and usually do!) behave differently, making reproducing problems harder.
By default, versioned providers get installed based on the one with the greatest
version. This is straight forward when a fork should always be used. For
example, a temporary libcamera fork in temp providing 99990.6 will always
be selected instead of version 0.7:
$ apk info -P libcamera
libcamera-0.7.0-r0 provides:
cmd:libcamerify=0.7.0-r0
so:libcamera-base.so.0.7=0.7.0
so:libcamera.so.0.7=0.7.0
libcamera-99990.6.0-r1 provides:
cmd:libcamerify=99990.6.0-r1
so:libcamera-base.so.0.6=0.6.0
so:libcamera.so.0.6=0.6.0
$ apk list --installed libcamera
libcamera-99990.6.0-r1 x86_64 {libcamera} (LGPL-2.1-or-later AND GPL-2.0-or-later) [installed]
This is also the reason why virtual providers seem to work when mixed with versioned providers. A provider with any version is given priority over one without a version, but given that one is versioned would still conflict. For example.
Warning
The example below shows a packaging bug. Forks should have never used virtual providers! This is helpful to showcase this common pitfall
$ apk info -P alsa-ucm-conf
alsa-ucm-conf-1.2.15.1-r0 provides:
alsa-ucm-conf-qcom-sdm660-1.2.14_git20251011-r0 provides:
alsa-ucm-conf
alsa-ucm-conf-qcom-sdm670-1.2.9_git20250707-r0 provides:
alsa-ucm-conf
alsa-ucm-conf-qcom-sm7150-1.2.14_git20251026-r1 provides:
alsa-ucm-conf
alsa-ucm-conf-sdm845-1-r1 provides:
alsa-ucm-conf
alsa-ucm-conf-unisoc-ums9230-1.2.9_git20251211-r0 provides:
alsa-ucm-conf
soc-qcom-msm8916-ucm-25-r1 provides:
alsa-ucm-conf
soc-qcom-msm8953-ucm-18-r1 provides:
alsa-ucm-conf
soc-qcom-msm8996-ucm-2-r0 provides:
alsa-ucm-conf
soc-qcom-msm89x7-ucm-5-r0 provides:
alsa-ucm-conf
soc-qcom-sm7125-ucm-2-r1 provides:
alsa-ucm-conf
$ apk list --installed alsa-ucm-conf
alsa-ucm-conf-1.2.15.3-r0 aarch64 {alsa-ucm-conf} (BSD-3-Clause) [installed]
So, what happens when we want to install a fork only in a specific-device, or only in some specific circumstances? In those situations we have to:
Make sure that the fork we want installed is installed in the specific conditions we want. For example, by depending on the fork in the device package. This will make the fork replace the original package.
Consider what will happen to installations that shouldn’t use the fork:
If the fork has a lower version than the original package, then the problem is solved for us by apk, as the higher version gets priority unless there are other constraints (like the device package dependency on the fork).
If the fork has an equal or higher version that then original package, however, we might bump into the situation that installs that shouldn’t have the fork still get it installed due to the higher version. The solution to that problem is to force a dependency on the non-forked version in a common package used by most users. For example,
postmarketos-ui-gnomeadded!gnome-shell-mobileto avoid regular installs getting the mobile versions. Sometimes, however, this gets complicated due to the fact that the provider is the name of the original package (like in the case ofalsa-ucm-confabove). In those situations, it might be needed to introduce an alternative versioned provider to disambiguate (e.g:provides="audio-conf=$pkgver-r$pkgrel"), or to force the forked packages to provide a lower version than the other package.