Use GUIX to fix an upstream package issue

I am using a Talos II PowerPC (ppc64le) machine as my daily computer. This poses some challenges every now and then as the support for that architecture is less ubiquitous than the default x86-64. It's the price to pay for having a completely open, documented machine.

On this system I run archpower distribution to get a linux kernel onto the machine, but I prefer to run GUIX on top of it for package management. I still need to run the native pacman package manager though, some packages are either not in GUIX yet (not so much of a problem in practice) or do not support ppc64le at all which is a bigger problem.

There's a third category, which I'd like to write about in this post. Packages that do support the architecture, but it's not a first class citizen. With this I mean that support is there, but the PowerPC package gets less attention and testing. This is somewhat inevitable as far less people use these machines rather than a normal pc, so it's likely they won't get as much eyes.

Depending on your distribution and package manager you can get stuck on a certain version for a package if there are limited options to upgrade or building the package locally is an effort out of your reach or time availability. Often the only option, for me anyways, is waiting for upstream to fix the situation. For this reason I still can't have packages that depend on rust in GUIX.

Because GUIX is basically a scheme library, you can use it to define how packages get into your machine and there is usually a better option to create a solution without having to figure out all the build details of the package itself.

One example of this is a recent ppc64le specific issue with OpenSSH.

The problem was the release (9.6p1) which was packaged for GUIX, and got eventually into my system on a GUIX pull command. However, the package build failed, apparently only on ppc64le machines due to this issue.

Because OpenSSH is used by many other packages, its failing build affected all those packages.

Here's what I did in GUIX to get to the new OpenSSH version which contained a number of security fixes which I wanted to have.

First, I created a package definition called openssh-next which takes the existing openssh package in GUIX and inherit from it in such a way that the fix for the problem outlined above will be included. In this case, take the commit just after the release from upstream.

 1  ;; Define openssh-next package which takes openssh from upstream which has the fix applied
 2  ;; See https://github.com/openssh/openssh-portable/commit/1036d77b34a5fa15e56f516b81b9928006848cbd
 3  (define-public openssh-next
 4    (let ((xcommit "1036d77b34a5fa15e56f516b81b9928006848cbd"))
 5      (package
 6        (inherit openssh)
 7        (name "openssh-next")
 8        (version "9.6p1-1")
 9        (native-inputs
10         (list autoconf
11               automake
12               pkg-config))
13        (source
14         (origin
15           (method git-fetch)
16           (uri (git-reference
17                 (url "https://github.com/openssh/openssh-portable.git")
18                 (commit xcommit)))
19           (file-name (git-file-name name version))
20           (patches (search-patches "openssh-trust-guix-store-directory.patch"))
21           (sha256
22            (base32 "1sary1ig972l4zjvpzncf9whfp5ab8snff2fw9sy5a8pda5n2a7w")))))))

The crux in the above snippets is the inherit line which hides all the package definition complexity and the adapted source block which takes a specific git commit from the OpenSSH repository. Another way would be to add an extra patch line in the source block which contains just the upstream fix for ppc64.

With this new package definition, the new version can be installed, but all packages which depend on openssh are still using the original version. We want some way to go over everything that depends on openssh and replace its dependency with the new openssh-next package.

This is where GUIX can make your life easier. The GUIX api provides a couple of ways to do this. I built it up around the functions package-input-rewriting/spec and package-mapping.

The first takes a list of replacements, where each element of the list is a pair of a package spec and a procedure passed with a package to replace it with.

The second is a function to apply a function to a package to apply the defined replacement to a package. I wrapped both functions to make the call for the relevant packages a bit simpler.

 1  ;; Given a package spec, Replace input `old` with `new` for that package incl. its dependents
 2  ;; Return a procedure which takes the package as parameter
 3  (define (package-input-replace old new)
 4    (package-input-rewriting/spec
 5     `((,old . ,(const (specification->package new))))))
 6
 7  ;; Apply the input replace for openssh
 8  ;; pass each package which fails to build due to openssh as dependency
 9  (define (openssh-fix package)
10    ((package-mapping
11      (package-input-replace "openssh" "openssh-next"))
12     (specification->package package)))
13
14  ;; Two examples on what to put in the manifest
15  (openssh-fix "gvfs")
16  (openssh-fix "remmina")

With this solution in place, the whole local package set builds again (takes a while though, as it often does with GUIX) and I can take advantage of the new OpenSSH release. There is a bit of 'keeping an eye on it' involved from this point on though. If upstream fixes the issue I want to take the above scheme code out of my manifest again and start using the upstream openssh package again instead of my openssh-next definition.