# Packaging Guidelines

This document explains the general logic behind APKBUILDs and the niche
or confusing parts of writing them.

## General Policy

The APKBUILDs in pmaports follow
[Alpine Linux's coding style guide](https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/CODINGSTYLE.md)
with a few small style and policy differences to make up for the differences
in the types of packages between aports and pmaports. These can be found below
and in the other sections.

## Maintainership

Every package is expected to have a maintainer. The only exception is packages
in the [Archived](#device-category-archived) category.

Maintainers are identified by the maintainer variable in the `APKBUILD` of the
package like:

```sh
maintainer="Jane Doe <jane@example.org>"
```

The email should be added to the gitlab user of the maintainer, to make it
possible for the user to be pinged when doing changes. The name can be any
identifier the maintainer uses online or in person.

Packages can also have Co-Maintainers, which are identified by a comment like:

```sh
# Co-Maintainer: John Doe <john@example.org>
```

Maintainers and Co-Maintainers will be pinged whenever changes to their packages
are made.

Packages shall not have contributors comments, which are commonly used in
Alpine with:

```sh
# Contributor: Jean Doe <jean@example.org>
```

For devices specifically: if it is useful to list someone who has worked on a
device who would be available for contact for questions about the device,
consider adding them under a "Maintainers" section of the
[device's wiki page](https://wiki.postmarketos.org/wiki/Device_specific_package).

## Package versioning (pkgver, pkgrel)

Whenever you update a package, the version should also be incremented. A version
consists of `$pkgver-r$pkgrel`, and either `pkgver` or `pkgrel` should be bumped,
even if the changes can be considered trivial. This is to instruct the builder to
rebuild the binary of the package. Likewise, an exception can be made to the rule
if the changes do not affect the built package, such as editing comments, code
style fixes, etc.

* **pkgver**: The version of the software being packaged.
* **pkgrel**: The release number of the package. Starts at `0`.

As a general rule, whenever a change is made to upstream sources, `pkgver` should
be increased, and `pkgrel` should be set to `0`. When packaging-related files
(`APKBUILD`, any post-install scripts, patch files to upstream sources, etc.) are
changed, `pkgrel` should be increased.

Packages without upstream sources are a bit special. These are `device-*` packages
and other packages like `main/postmarketos-base`, where the source files are
included in the package directory. For these packages, the `pkgver` should always
be incremented instead of the `pkgrel`, so that `pkgrel` remains at `0`.

## 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](https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#provides) 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:

```sh
$ 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 on `so:libc.musl-x86_64.so.1`, instead of depending direcly on `musl`.

```sh
$ 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 when `musl-utils` provides
  `libc-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 `ceph` filesystem available in Alpine, only
  one of them provides the `ceph` name, 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.
:::

```sh
$ 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`:

```sh
$ 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
:::

```sh
$ 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-gnome` added
    `!gnome-shell-mobile` to 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 of `alsa-ucm-conf`
    above). 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.

```{eval-rst}
.. toctree::
   :hidden:

   packaging/device-categorization

   packaging/device-packages
   packaging/kernel-packages

   packaging/firmware-packages
   packaging/ui-packages
```
