zealcharm's website pennant zealcharm's website

About me - Works - Toys - Blog - Contact

A method for maintaining locally patched Arch Linux packages

Due to my stubbornness fondness for testing the latest and greatest software releases, I often run into problems, and then I need to apply patches on top of Arch Linux's packages to fix them.

When the number of patched packages grows large enough, this may devolve into chaos, so this is a rough sketch of the method I have used over the years to keep this under control.

Identifying patched packages

The first challenge is marking the packages that I have patched, so both pacman and I can understand what is installed on my system.

I do this by simply appending a snippet like this at the end of each of my patched PKGBUILDs:

custompkgrel=1
pkgrel="${custompkgrel}00${pkgrel}"

This will turn a package version like helloworld-1.23-4 into helloworld-1.23-1004.

This both makes it identifiable (usually, packages have a small pkgrel), and makes pacman detect the patched packages as updates of the unpatched ones.

When I patch the package further, I bump custompkgrel, so that each patched package has a unique version.

Applying .patch files

The straightforward way to apply .patch files is to put them inline with the rest of the PKGBUILD, like this:

 # Maintainer: John Smith <john.smith@example.com>
 pkgname=helloworld
 pkgver=1.23
 pkgrel=4
 pkgdesc="A simple Hello World program"
 url='https://www.example.com'
 arch=('any')
 license=('MIT')
 source=("hello.tar.gz"
+        "helloworld-i18n-support.patch"
         "helloworld-fix-typo.patch")
 sha256sums=('d267ac47beecb944222bc2e8f7d6dd5e3ec9602cf4a4f3ed70b1e424c8213400'
+            '87d2f5b9365fbd0b2342f6632fc202f56d7237097596aa30e3aaf92aa2d19b86'
             '5635261bac29f1c0ed8cfbb4044e56bda9e85e9f50898fc0e6d1ca00f205f891')

 prepare() {
+  patch -Np1 -i helloworld-i18n-support.patch
   patch -Np1 -i helloworld-fix-typo.patch
 }

 build() {
   gcc -o hello hello.c
 }

 package() {
   install -Dm755 "$srcdir/hello" "$pkgdir/usr/bin/hello"
 }
+
+custompkgrel=1
+pkgrel="${custompkgrel}00${pkgrel}"

However, my experience is that it often takes a long time for a patch to be polished, submitted, accepted and released upstream; and during that time, keeping the package synchronized with the Arch Linux repositories or the AUR (via git pull) turns into a chore of resolving frequent spurious merge conflicts.

Instead, I have settled on a hack to keep all my patches at the end of the PKGBUILD. Behold:

# [...the original, unchanged PKGBUILD...]

custompkgrel=1
pkgrel="${custompkgrel}00${pkgrel}"

# This monkey-patches "prepare" to append a line of code at the beginning
# Note that this can be called multiple times, each appending a further line of code
before_prepare() {
  if [ -z "${before_prepare_script+x}" ]; then
    before_prepare_script=""
    eval "$(echo 'prepare() { eval "$before_prepare_script";'; declare -f prepare | head -n -1 | tail +3; echo '}')"
  fi
  before_prepare_script="$before_prepare_script$(printf "%q " "$@");"$'\n'
}

source+=("helloworld-i18n-support.patch")
sha256sums+=('87d2f5b9365fbd0b2342f6632fc202f56d7237097596aa30e3aaf92aa2d19b86')
before_prepare patch -Np1 -i helloworld-i18n-support.patch

With this method, all my changes are an appendage to the PKGBUILD, so they don't conflict. You might object that this is contrary to version control best practices, where a merge conflict is a strong hint that you must look at whatever has changed and confirm that it is correct.

However, my experience is that for this very particular case of a personal patch repository, this greatly improves the UX, while not sacrificing much: The patch command will fail when the code targeted by the .patch file changes, so there is still a conflict detection method in place.

Putting it all together

Usually, one of my patched PKGBUILD files looks like this:

# [...the original, unchanged PKGBUILD...]

custompkgrel=1
source "../custompkg.sh" || exit 1
# ZEALCHARM START Add i18n support
# See https://example.com/helloworld-org/helloworld/pull/1234
source+=("helloworld-i18n-support.patch")
sha256sums+=('87d2f5b9365fbd0b2342f6632fc202f56d7237097596aa30e3aaf92aa2d19b86')
before_prepare patch -Np1 -i helloworld-i18n-support.patch
# ZEALCHARM END Add i18n support

Here, I have refactored the previous utility into a custompkg.sh script, so I can share the common parts across all my patched packages.

The overall flow

When I decide to patch a PKGBUILD, what I do is:

To keep the package up to date, the process is similar:

I have some scripts that automate most of this process, so the overall amount of work to keep the patched packages up to date is pretty small.