Guix Tricks: Soft-pinning channels in a channels.scm file.



Guix is a very powerful tool to integrate into a project's toolchain. This isn't just for build systems. It can also apply to tools like interactive interpreters and debuggers. By creating a project-wide channels.scm file, you can prepend tool scripts with both guix time-machine and guix shell invocations. This allows for easy integration of additional Guix channels and ensures that every developer has development tools available to them that aren't reliant on the outside environment.

You can see an example of me using this here to invoke a Guile REPL with the Guix and rsent-freakingpenguin channels. This includes a dependency of rsent-freakingpenguin: guile-blocks. I also use it in the ./bin/serve script to build and locally deploy the website for testing.

However, one challenge of guix time-machine is that unless you are willing to pin channels to specific commits, it will ALWAYS try to pull the latest version of that channel. Because channels are always changing (particularly the Guix channel) this can introduce significant delays when, say, invoking a Guile REPL after a new Guix commit was pushed.

As mentioned, one solution is to pin channels to specific version or branches. However, this has several drawbacks:

  1. The commit needs to be updated regularly to ensure compatibility with newer tools.
  2. When first run, developers still need to pull an entirely different commit to what they're running.
  3. It implies that the developer tools require that specific commit to function even if that is not the case.

My proposed solution to this is a concept I'll call "soft-pinning", which means "try to use whatever commit of this channel that is currently present on the user's profile". This will let us mix and match channels into different categories. One category is kept up to date at all times while the other is only updated as the user updates their system.


To implement this, we can take advantage of the fact that channels.scm is Guile Scheme code and use the current-channels procedure in the (guix describe) module. Here's the code:

;; Use this file with time-machine -C to get an up to date copy of
;; this channel and any dependencies.
(use-modules (srfi srfi-1)
             (guix channels)
             (guix describe)
             (ice-9 control))

(define (soft-pin-channel input-channel)
  "Given @var{channel}, return a new version of channel that uses the
current profile's commit of said channel. If the channel is not present,
commit is #f."
  (define (desired-channel? channel)
    (equal? (channel-name input-channel)
            (channel-name channel)))

  (define (profile-commit input-channel)
     (lambda (return)
       (map (lambda (x)
              (when (desired-channel? x)
                (return (channel-commit x))))

   (inherit input-channel)
   (commit (profile-commit input-channel))))

;; Note that if you try running this in a container (say, the
;; project's interactive Guile), the commit picked up will match the
;; Guix package in the container's profile. It will not match the
;; user's current profile channel commit.
  (name 'freakingpenguin)
  (url (string-append "file://" (getcwd))))
  (soft-pin-channel %default-guix-channel)))

Now, invoking the Guile REPL is instantaneous as long as the freakingpenguin channel does not change. I no longer have a minute delay or more every time a new Guix commit is pushed, while still not having to pin Guix at a specific commit.