<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>The Zanc Report</title><id>https://carlo.zancanaro.id.au/feeds/tags/guix.xml</id><subtitle>Tag: guix</subtitle><updated>2026-07-30T04:10:10Z</updated><link href="https://carlo.zancanaro.id.au/feeds/tags/guix.xml" rel="self" /><link href="https://carlo.zancanaro.id.au" /><entry><title>Forgejo, AGit, and Pull Request Templates</title><id>https://carlo.zancanaro.id.au/posts/forgejo-agit-and-pull-request-templates.html</id><author><name>Carlo Zancanaro</name><email>carlo@zancanaro.id.au</email></author><updated>2026-02-21T00:00:00Z</updated><link href="https://carlo.zancanaro.id.au/posts/forgejo-agit-and-pull-request-templates.html" rel="alternate" /><content type="html">&lt;p&gt;I've raised a few PRs against &lt;a href=&quot;https://codeberg.org/guix/guix&quot;&gt;the Guix Codeberg repository&lt;/a&gt; recently, and each time I've done so with &lt;a href=&quot;https://forgejo.org/docs/latest/user/agit-support/&quot;&gt;Forgejo's AGit workflow&lt;/a&gt;. This workflow is pretty nice, and allows me to raise a PR entirely from within Emacs. To do that, I've been using this code in my Emacs config to add an extra option to the &lt;code&gt;magit-push&lt;/code&gt; transient to use the AGit flow to push to the upstream branch:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;emacs-lisp&quot;&gt;(transient-define-suffix magit-push-current-agit (source target args)
  :if #'magit-get-current-branch
  :description (lambda () (concat (magit-push--upstream-description) &amp;quot; (agit)&amp;quot;))
  (interactive
   (let ((source (or (magit-get-current-branch)
                     (user-error &amp;quot;No branch is checked out&amp;quot;))))
     (list source
           (magit-get-upstream-branch source)
           (magit-push-arguments))))

  (magit-push-refspecs
   (magit-get &amp;quot;branch&amp;quot; source &amp;quot;remote&amp;quot;)
   (concat source &amp;quot;:&amp;quot;
           ;; Forgejo uses /refs/for/{branch} to target. Locally,
           ;; setting the upstream branch sets the &amp;quot;merge&amp;quot; config item
           ;; to refs/heads/{branch}, so we can easily construct what we need
           (s-replace &amp;quot;/heads/&amp;quot; &amp;quot;/for/&amp;quot;
                      (magit-get &amp;quot;branch&amp;quot; (magit-get-current-branch) &amp;quot;merge&amp;quot;))
           &amp;quot;/&amp;quot; source)
   `(,@args
     ,@(if (or (member &amp;quot;--force&amp;quot; args)
               (member &amp;quot;--force-with-lease&amp;quot; args))
           '(&amp;quot;-o&amp;quot; &amp;quot;force-push=true&amp;quot;)
         '()))))

(transient-insert-suffix 'magit-push
  '(1 0)
  (list &amp;quot;a&amp;quot; #'magit-push-current-agit))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once I've set an upstream branch, this makes it easy to push to Codeberg. Either &lt;code&gt;P a&lt;/code&gt; to create a new PR, or &lt;code&gt;P - f a&lt;/code&gt; to update an existing PR.&lt;/p&gt;
&lt;p&gt;One downside to the AGit workflow is that I completely bypass the usual PR template that the Guix project has set up in &lt;code&gt;.forgejo/pull_request_template.md&lt;/code&gt;. Instead, it just uses the commit message of the last commit that's being pushed. That's often &lt;em&gt;fine&lt;/em&gt;, but I'd like to add at least some of my own commentary. With my existing Magit action I would usually have to go and open the PR to change the description to say what I wanted to say.&lt;/p&gt;
&lt;p&gt;I've always been a bit uncomfortable about this, particularly because I think it's rude to just ignore the template.&lt;/p&gt;
&lt;p&gt;My discomfort finally got strong enough that I solved this problem tonight, with this change to the final &lt;code&gt;if&lt;/code&gt; in the above code snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;emacs-lisp&quot;&gt;,@(if (or (member &amp;quot;--force&amp;quot; args)
          (member &amp;quot;--force-with-lease&amp;quot; args))
      '(&amp;quot;-o&amp;quot; &amp;quot;force-push=true&amp;quot;)
    (let ((info (cz/read-pull-request-from-buffer
                 (concat (magit-gitdir) &amp;quot;../.forgejo/pull_request_template.md&amp;quot;))))
      `(&amp;quot;-o&amp;quot; ,(concat &amp;quot;title={base64}&amp;quot; (base64-encode-string (car info) t))
        &amp;quot;-o&amp;quot; ,(concat &amp;quot;description={base64}&amp;quot; (base64-encode-string (cdr info) t)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a force push, we don't do anything (because we're just changing the commits on an existing PR). If we're &lt;em&gt;not&lt;/em&gt; force pushing then we're creating a new PR, so we prompt for the title and description, base64 encode them, and send them through as options on the push.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;cz/read-pull-request-from-buffer&lt;/code&gt; function, and its associated mode, are not quite so easy. They read out the pull request template, then go into a &lt;a href=&quot;https://www.gnu.org/software/emacs/manual/html_node/emacs/Recursive-Edit.html&quot;&gt;&lt;code&gt;recursive-edit&lt;/code&gt;&lt;/a&gt; in a &lt;code&gt;markdown-mode&lt;/code&gt; buffer with the template's contents (after skipping the Forgejo header). &lt;em&gt;Technically&lt;/em&gt; I'm not using the template correctly, because I should extract the title from the template and add it to the template, but I don't care.&lt;/p&gt;
&lt;p&gt;At some point I might extract this out of my config and make a proper package for it. Let me know if that would be helpful to you - it might motivate me to actually do it!&lt;/p&gt;
&lt;p&gt;The full relevant section of my config now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;emacs-lisp&quot;&gt;(define-derived-mode cz/agit-pull-request-mode markdown-mode &amp;quot;AGit PR&amp;quot;)

(defvar-local cz/agit-pull-request-title nil)
(defvar-local cz/agit-pull-request-description nil)

(defun cz/agit-pull-request-submit ()
  (interactive)
  (when (y-or-n-p &amp;quot;Are you sure you want to submit this pull request? &amp;quot;)
    (setq-local cz/agit-pull-request-title
                (save-excursion
                  (beginning-of-buffer)
                  (buffer-substring-no-properties
                   (+ (point) 2)
                   (line-end-position))))
    (setq-local cz/agit-pull-request-description
                (save-excursion
                  (beginning-of-buffer)
                  (next-line)
                  (buffer-substring-no-properties
                   (point)
                   (point-max))))
    (exit-recursive-edit)))

(keymap-set cz/agit-pull-request-mode-map &amp;quot;C-c C-c&amp;quot; #'cz/agit-pull-request-submit)

(defun cz/agit-pull-request-cancel ()
  (interactive)
  (abort-recursive-edit))

(keymap-set cz/agit-pull-request-mode-map &amp;quot;C-c C-k&amp;quot; #'cz/agit-pull-request-cancel)

(defun cz/read-pull-request-from-buffer (template-file)
  (if (get-buffer &amp;quot;*Pull Request*&amp;quot;)
      (user-error &amp;quot;Other pull request already open - aborting this one.&amp;quot;)
    (let ((buffer (get-buffer-create &amp;quot;*Pull Request*&amp;quot;)))
      (with-current-buffer buffer
        (insert &amp;quot;# &amp;quot;)
        (save-excursion
          (insert &amp;quot;\n\n&amp;quot;)
          (when (file-exists-p template-file)
            (let ((start (point)))
              (insert-file-contents-literally template-file)
              (beginning-of-buffer)
              (save-match-data
                (re-search-forward &amp;quot;^---&amp;quot;)
                (re-search-forward &amp;quot;^---&amp;quot;)
                (next-line)
                (beginning-of-line))
              (delete-region start (point)))))
        (cz/agit-pull-request-mode)
        (pop-to-buffer (current-buffer)
                       '(display-buffer-same-window)))
      (unwind-protect
          (progn
            (recursive-edit)
            (with-current-buffer buffer
              (if cz/agit-pull-request-title
                  (cons cz/agit-pull-request-title
                        cz/agit-pull-request-description)
                (user-error &amp;quot;No pull request information&amp;quot;))))
        (with-current-buffer buffer
          (if (get-buffer-window)
              (quit-window 'kill (get-buffer-window))
            (kill-buffer)))))))

;; Define a command for an AGit flow to upstream (this is suitable for
;; opening PRs on Codeberg)
(transient-define-suffix magit-push-current-agit (source target args)
  :if #'magit-get-current-branch
  :description (lambda () (concat (magit-push--upstream-description) &amp;quot; (agit)&amp;quot;))
  (interactive
   (let ((source (or (magit-get-current-branch)
                     (user-error &amp;quot;No branch is checked out&amp;quot;))))
     (list source
           (magit-get-upstream-branch source)
           (magit-push-arguments))))

  (magit-push-refspecs
   (magit-get &amp;quot;branch&amp;quot; source &amp;quot;remote&amp;quot;)
   (concat source &amp;quot;:&amp;quot;
           ;; Forgejo uses /refs/for/{branch} to target. Locally,
           ;; setting the upstream branch sets the &amp;quot;merge&amp;quot; config item
           ;; to refs/heads/{branch}, so we can easily construct what we need
           (s-replace &amp;quot;/heads/&amp;quot; &amp;quot;/for/&amp;quot;
                      (magit-get &amp;quot;branch&amp;quot; (magit-get-current-branch) &amp;quot;merge&amp;quot;))
           &amp;quot;/&amp;quot; source)
   `(,@args
     ,@(if (or (member &amp;quot;--force&amp;quot; args)
               (member &amp;quot;--force-with-lease&amp;quot; args))
           '(&amp;quot;-o&amp;quot; &amp;quot;force-push=true&amp;quot;)
         (let ((info (cz/read-pull-request-from-buffer
                      (concat (magit-gitdir) &amp;quot;../.forgejo/pull_request_template.md&amp;quot;))))
           `(&amp;quot;-o&amp;quot; ,(concat &amp;quot;title={base64}&amp;quot; (base64-encode-string (car info) t))
             &amp;quot;-o&amp;quot; ,(concat &amp;quot;description={base64}&amp;quot; (base64-encode-string (cdr info) t))))))))

(transient-insert-suffix 'magit-push
  '(1 0)
  (list &amp;quot;a&amp;quot; #'magit-push-current-agit))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm confident that this code can be improved, but I think it'll solve my problem for now.&lt;/p&gt;
&lt;h2&gt;Alternatives&lt;/h2&gt;
&lt;p&gt;After writing this post two alternatives have been mentioned:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://codeberg.org/halvin/agitjo&quot;&gt;agitjo&lt;/a&gt;, as a more fully-featured implementation of the AGit workflow, and&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sarg/dotfiles/blob/a948406b169ae46d76abdfcc0327fa99f206dd0c/emacs/.doom.d/config.org#agit&quot;&gt;sarg's config&lt;/a&gt;, which is similar to the code above, but also stores the branch title/description in the Git branch description.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There's also a &lt;a href=&quot;https://github.com/magit/magit/discussions/5508&quot;&gt;Magit discussion&lt;/a&gt; about having an AGit implementation upstream, which tarsius seems on board for.&lt;/p&gt;
</content></entry><entry><title>Setting Up Cuirass Locally</title><id>https://carlo.zancanaro.id.au/posts/setting-up-cuirass-locally.html</id><author><name>Carlo Zancanaro</name><email>carlo@zancanaro.id.au</email></author><updated>2026-02-15T00:00:00Z</updated><link href="https://carlo.zancanaro.id.au/posts/setting-up-cuirass-locally.html" rel="alternate" /><content type="html">&lt;p&gt;Recently I've been trying to get &lt;a href=&quot;https://codeberg.org/guix/guix/pulls/2796&quot;&gt;a PR merged with some fixes for Lua&lt;/a&gt;. I thought this would be a pretty straightforward thing to merge, but it turns out that modifying the Lua packages leads to 990 packages needing to be rebuilt. This is more than the 300 limit for a merge to &lt;code&gt;master&lt;/code&gt;, so instead of merging my changes directly Andreas has kindly pushed them to a &lt;code&gt;lua-team&lt;/code&gt; branch and queued it up behind &lt;code&gt;go-team&lt;/code&gt;, &lt;code&gt;gnome-team&lt;/code&gt;, and &lt;code&gt;rust-team&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While I'm waiting for that, I have some more drastic changes that I'd like to try out (e.g. packaging Lua 5.5.0, and using it as the default Lua for building packages). In order to do that, I'd like to be able to test my changes before the &lt;code&gt;lua-team&lt;/code&gt; branch gets to the front of the queue. I'm not a committer to Guix, so instead I figured I'd spin up Cuirass on my local machine to help me try things out.&lt;/p&gt;
&lt;h2&gt;Why Cuirass?&lt;/h2&gt;
&lt;p&gt;It's worth spending a moment on why I want to use Cuirass, rather than just doing the builds using Guix directly. &lt;code&gt;guix refresh -l&lt;/code&gt; already gives you a list of the packages that you need to build to test your changes, why not just use that with &lt;code&gt;guix build&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;There are two reasons why I want to use Cuirass: live feedback, and tracking changes over time.&lt;/p&gt;
&lt;p&gt;For live feedback, Cuirass gives me more information about what's going on that &lt;code&gt;guix build&lt;/code&gt; does. Even though I'm only running on my local machine, seeing the list of scheduled/succeeded/failed builds, and in-progress builds, is helpful for me to keep track of where things are up to. It lets me get started investigating build failures right away, without interrupting the rest of the builds.&lt;/p&gt;
&lt;p&gt;For tracking changes over time, Cuirass helps me by reporting not just whether a package has built or not, but whether that has changed since the last build. This tells me whether my changes are breaking things, or whether things were already broken before I started. I need to know this so I know where to spend effort trying to fix things.&lt;/p&gt;
&lt;p&gt;Given Cuirass is now a sunk cost for me, we're going to proceed with the assumption that this was a good idea. 🙂&lt;/p&gt;
&lt;h2&gt;Initial Setup&lt;/h2&gt;
&lt;p&gt;Setting up Cuirass on a Guix system is shockingly easy. I just added these three services to my &lt;code&gt;operating-system&lt;/code&gt;'s &lt;code&gt;services&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(service cuirass-service-type
         (cuirass-configuration
          (specifications
           #~(list (specification
                    (name &amp;quot;lua-team&amp;quot;)
                    (build '(manifests #$(local-file &amp;quot;lua-manifest.scm&amp;quot;)))
                    (channels (list (channel
                                     (name 'guix) ; has to be called guix for pull to work
                                     (url &amp;quot;https://git.sr.ht/~czan/guix&amp;quot;) ; my unlisted fork
                                     (branch &amp;quot;lua-team&amp;quot;)))))))))
(service postgresql-service-type
         (postgresql-configuration
          (postgresql (specification-&amp;gt;package &amp;quot;postgresql@16&amp;quot;))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This sets up a local instance of the &lt;code&gt;cuirass-service-type&lt;/code&gt; configured to poll the listed channels and build things locally whenever anything changes.&lt;/p&gt;
&lt;p&gt;The manifest that I wrote looks for all the packages which (transitively) depend on one of the Lua version. I took inspiration for the code for &lt;code&gt;guix refresh -l&lt;/code&gt;, and this is the result::&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;;; lua-manifest.scm
(use-modules (guix)
             (guix packages)
             (gnu packages)
             (guix profiles)
             (guix monads)
             (guix graph)
             (guix scripts graph)
             (ice-9 match)
             (gnu packages lua))

(define packages
  (list lua-5.4 lua lua-5.2 lua-5.1 luajit))

(packages-&amp;gt;manifest
 (with-store store
   (run-with-store store
     (mlet %store-monad ((edges (node-back-edges %bag-node-type
                                                 (package-closure (all-packages)))))
       (return (node-transitive-edges packages edges))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Problem: not authenticated&lt;/h2&gt;
&lt;p&gt;The first problem I ran into was that Guix will fail a pull for the &lt;code&gt;guix&lt;/code&gt; channel if it tries to authenticate the commits but can't. Since I'm not a committer, I can't authenticate my commits without doing some more work (setting up a key, adding to the keyring, signing a commit, setting up an introduction, ...). I didn't really want to do that, so I hunted through the Cuirass manual and found the &lt;code&gt;authenticate-channels?&lt;/code&gt; property. Setting it to &lt;code&gt;#f&lt;/code&gt; does an unauthenticated pull. Dangerous in some circumstances, but acceptable for what I'm doing.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(service cuirass-service-type
         (cuirass-configuration
          (specifications
           #~(list (specification
                    ;; ... As above ...
                    ;; I'm not a committer, so we can't authenticate my commits
                    (properties `((authenticate-channels? . #f))))))))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Problem: evaluations failing&lt;/h2&gt;
&lt;p&gt;I got the pulls working, but then the evaluations kept failing. It turns out the path listed in the &lt;code&gt;build&lt;/code&gt; option need to exist &lt;em&gt;in the pulled channels&lt;/em&gt;. In my first attempt I passed a &lt;code&gt;(local-file ...)&lt;/code&gt; call, but this did not work at all.&lt;/p&gt;
&lt;p&gt;I had to commit my manifest into the repository and reference it in the &lt;code&gt;build&lt;/code&gt; option:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(service cuirass-service-type
         (cuirass-configuration
          (specifications
           #~(list (specification
                    (name &amp;quot;lua-team&amp;quot;)
                    (build '(manifests &amp;quot;etc/teams/lua/lua-manifest.scm&amp;quot;))
                    ;; ... As above ...
                    )))))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Problem: no builds running&lt;/h2&gt;
&lt;p&gt;The next problem I ran into was that this configuration didn't run any builds. It would poll the &lt;code&gt;git&lt;/code&gt; repository, and successfully pull the changes and build Guix, but it would never get to building any packages. I couldn't see any reason for it not working, but the documentation for the local build mode also had this warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When Cuirass is used to build a large amount of jobs, the remote build mechanism described below should be preferred.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I figured I should try that. Again, configuring this in a Guix system to just use the local machine as a builder is easy. I just added the &lt;code&gt;remote-server&lt;/code&gt; setting to my &lt;code&gt;cuirass-configuration&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(service cuirass-service-type
         (cuirass-configuration
          (remote-server (cuirass-remote-server-configuration))
          (specifications
           ;; ... As above ...
           )))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and I added one more service to my list of services:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(service cuirass-remote-worker-service-type
         (cuirass-remote-worker-configuration))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that, Cuirass started to build my packages locally. I saw my CPUs get busy, and all was well.&lt;/p&gt;
&lt;h2&gt;Problem: killed my machine&lt;/h2&gt;
&lt;p&gt;Unfortunately, the default configuration for the Cuirass remote worker is to run a single build, and to let it use all the cores for its build. My machine has an i7-8700 with 6 cores running 12 threads, and 32GiB of RAM. It turns out that building some Guix packages will cause that configuration to lock up (I assume it's swapping, but I didn't investigate at all).&lt;/p&gt;
&lt;p&gt;I looked through the Cuirass manual to try to find some way to set the equivalent of &lt;code&gt;--cores&lt;/code&gt; in the builds it runs, but I couldn't find anything. I had a look at the &lt;a href=&quot;https://codeberg.org/guix/cuirass/src/commit/208ecf61a30c070f1b86c0f4833c41595c90887b/src/cuirass/scripts/remote-worker.scm#L332&quot;&gt;Cuirass source where the parameter is set&lt;/a&gt;, which itself comes from &lt;a href=&quot;https://codeberg.org/guix/cuirass/src/commit/208ecf61a30c070f1b86c0f4833c41595c90887b/src/cuirass/scripts/remote-worker.scm#L548&quot;&gt;dividing the number of CPU cores by the number of workers&lt;/a&gt;. This assumes that the Cuirass workers can take up all the cores on the machine (which is not &lt;em&gt;ideal&lt;/em&gt; for the computer that I use as my desktop), but it at least gives me enough control to avoid the builds locking up the whole machine.&lt;/p&gt;
&lt;p&gt;Once again, the configuration for this is straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(service cuirass-remote-worker-service-type
         (cuirass-remote-worker-configuration
          (workers 6)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I have six concurrent builds, with each using two threads. This has kept my memory usage under 50% thus far, and builds are continuing unimpeded.&lt;/p&gt;
&lt;h2&gt;Final configuration&lt;/h2&gt;
&lt;p&gt;The end result of my service configuration looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(service cuirass-service-type
         (cuirass-configuration
           (remote-server (cuirass-remote-server-configuration))
           (specifications
            #~(list (specification
                     (name &amp;quot;lua-team&amp;quot;)
                     (build '(manifests &amp;quot;etc/teams/lua/lua-manifest.scm&amp;quot;))
                     (channels (list (channel
                                       (name 'guix)
                                       (url &amp;quot;https://git.sr.ht/~czan/guix&amp;quot;)
                                       (branch &amp;quot;lua-team&amp;quot;))))
                     (properties `((authenticate-channels? . #f))))))))
(service postgresql-service-type
         (postgresql-configuration
           (postgresql (specification-&amp;gt;package &amp;quot;postgresql@16&amp;quot;))))
(service cuirass-remote-worker-service-type
         (cuirass-remote-worker-configuration
          (workers 6)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Want to help?&lt;/h2&gt;
&lt;p&gt;If you're interested in helping to maintain Lua packages in Guix (because I have no idea what I'm doing), &lt;a href=&quot;https://lists.gnu.org/archive/html/guix-devel/2026-02/msg00167.html&quot;&gt;get in touch on &lt;code&gt;guix-devel&lt;/code&gt; in the thread I started&lt;/a&gt;. I'm particularly interested in getting a Guix committer on board, so I don't have to keep bothering Andreas. (Thanks Andreas!)&lt;/p&gt;
</content></entry><entry><title>Ruby Development Using Guix</title><id>https://carlo.zancanaro.id.au/posts/ruby-development-using-guix.html</id><author><name>Carlo Zancanaro</name><email>carlo@zancanaro.id.au</email></author><updated>2026-02-08T00:00:00Z</updated><link href="https://carlo.zancanaro.id.au/posts/ruby-development-using-guix.html" rel="alternate" /><content type="html">&lt;p&gt;Up until a few months ago, I was working on a Ruby on Rails monolith. In the four years that I worked on the project, we went through a few ways of building a local development environment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;directly using &lt;a href=&quot;https://bundler.io&quot;&gt;Bundler&lt;/a&gt; on our machine, then&lt;/li&gt;
&lt;li&gt;using Bundler inside a Docker image, then&lt;/li&gt;
&lt;li&gt;using &lt;a href=&quot;https://github.com/nix-community/bundix&quot;&gt;Nix&lt;/a&gt;[], then&lt;/li&gt;
&lt;li&gt;back to using Bundler inside a Docker image (not everyone liked Nix).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these options were &lt;em&gt;fine&lt;/em&gt;, but my package manager of choice is &lt;a href=&quot;https://guix.gnu.org/&quot;&gt;Guix&lt;/a&gt;. I really wanted a way for me to work with my Ruby on Rails project alongside the rest of my team, but using Guix to build my development environment.&lt;/p&gt;
&lt;p&gt;In 2015 David Thompson &lt;a href=&quot;https://dthompson.us/posts/ruby-on-guix.html&quot;&gt;posted about using Guix for Ruby development&lt;/a&gt; which describes using Guix packages to build a Ruby development environment. Guix has a lot of Ruby packages, but it doesn't package all of &lt;a href=&quot;https://rubygems.org/&quot;&gt;RubyGems&lt;/a&gt;, nor does it package all the different versions of each Gem. This makes it difficult to use Guix to get precise dependencies that are needed for most real-world Ruby projects.&lt;/p&gt;
&lt;p&gt;To make things easier for myself, I built &lt;a href=&quot;https://git.sr.ht/~czan/guix-ruby&quot;&gt;Guix Ruby&lt;/a&gt;. A Guix channel that provides a &lt;code&gt;guix ruby&lt;/code&gt; command which imports a &lt;code&gt;Gemfile.lock&lt;/code&gt; and constructs Guix packages for all the listed dependencies.&lt;/p&gt;
&lt;p&gt;To install it, just need to add this to your channels file (&lt;code&gt;~/.config/guix/channels.scm&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(cons* (channel
        (name 'guix-ruby)
        (url &amp;quot;https://git.sr.ht/~czan/guix-ruby&amp;quot;)
        (branch &amp;quot;main&amp;quot;))
       %default-channels)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your next &lt;code&gt;guix pull&lt;/code&gt; will add a new Guix command: &lt;code&gt;guix ruby&lt;/code&gt;!&lt;/p&gt;
&lt;h2&gt;Basic Usage&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;guix ruby&lt;/code&gt; in the root of a Bundler project will read &lt;code&gt;Gemfile&lt;/code&gt; and &lt;code&gt;Gemfile.lock&lt;/code&gt; to produce a new file: &lt;code&gt;Gemfile.lock.scm&lt;/code&gt;. It looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;(use-modules ...) ; imports
(lambda* (#:key (ruby ruby) (groups '(default)) (gem-transformers %default-gem-transformers))
  ...) ; gem definitions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then &lt;code&gt;load&lt;/code&gt; this file in your &lt;code&gt;manifest.scm&lt;/code&gt; or &lt;code&gt;guix.scm&lt;/code&gt; file, and call the resulting function to get a list of Guix package objects which match your Bundler dependencies.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;;; An example manifest.scm
(define bundler-packages (load &amp;quot;Gemfile.lock.scm&amp;quot;))
(packages-&amp;gt;manifest
 (cons* ruby (bundler-packages #:groups '(default development test))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Too easy.&lt;/p&gt;
&lt;p&gt;When you're first setting up a project, you can ask &lt;code&gt;guix ruby&lt;/code&gt; to create a &lt;code&gt;manifest.scm&lt;/code&gt; file by passing the &lt;code&gt;--init&lt;/code&gt; flag. It will be a bit more complicated than the example above, because it is more explicit about the Ruby version that it uses.&lt;/p&gt;
&lt;h3&gt;Adding New Dependencies&lt;/h3&gt;
&lt;p&gt;When it's time to add dependencies, you may find that using &lt;code&gt;bundle install&lt;/code&gt; does not work due to a read-only file system. Instead, you can use &lt;code&gt;bundle lock&lt;/code&gt; to update &lt;code&gt;Gemfile.lock&lt;/code&gt;, then run &lt;code&gt;guix ruby&lt;/code&gt; to update &lt;code&gt;Gemfile.lock.scm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can also ask &lt;code&gt;guix ruby&lt;/code&gt; to do this in one step with the &lt;code&gt;--lock&lt;/code&gt; flag.&lt;/p&gt;
&lt;h3&gt;Updating Dependencies&lt;/h3&gt;
&lt;p&gt;At the moment, &lt;code&gt;guix ruby&lt;/code&gt; doesn't have a shortcut to update dependencies, but &lt;code&gt;bundle lock --update&lt;/code&gt; can still be used.&lt;/p&gt;
&lt;h2&gt;Git Dependencies&lt;/h2&gt;
&lt;p&gt;Sometimes it's useful to have Bundler clone dependencies directly from Git. This is supported by &lt;code&gt;guix ruby&lt;/code&gt;, which will write an appropriate Guix package definition. However, when using &lt;code&gt;bundle lock&lt;/code&gt; and &lt;code&gt;bundle lock --update&lt;/code&gt;, you may run into issues. Bundler tries to clone the referenced repository in order to get some information about it. Unfortunately, the way &lt;code&gt;Gemfile.lock.scm&lt;/code&gt; defines packages causes this to break (because it defines &lt;code&gt;GEM_HOME&lt;/code&gt; pointing to a read-only directory).&lt;/p&gt;
&lt;p&gt;Setting &lt;code&gt;GEM_HOME&lt;/code&gt; to point to some readable directory should be enough to get it working, at the expense of an extra clone of the repos:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;sh&quot;&gt;$ bundle lock --update # no good :(
...
There was an error accessing `/gnu/store/...`
The underlying system error is Errno::EROFS: Read-only file system @ dir_s_mkdir
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code infostring=&quot;sh&quot;&gt;$ GEM_HOME=/tmp/bundle-cache bundle lock --update # works :)
Fetching https://git.sr.ht/...
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
Writing lockfile to /.../Gemfile.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Gem Transformers&lt;/h2&gt;
&lt;p&gt;You may have noticed the &lt;code&gt;gem-transformers&lt;/code&gt; parameter defined in &lt;code&gt;Gemfile.lock.scm&lt;/code&gt;. This provides a mechanism for you to provide &amp;quot;transformations&amp;quot; to the automatically generated packages, to do things like adding inputs that are needed to build extensions. A &lt;a href=&quot;https://git.sr.ht/~czan/guix-ruby/tree/37f411115deddc44049b1d991e8cfa3883b7b6cc/item/guix-ruby/gems.scm#L121&quot;&gt;default set of transformers&lt;/a&gt; is built into Guix Ruby itself, but you can provide your own transformers if a Gem isn't already supported.&lt;/p&gt;
&lt;p&gt;I'm keen to support as much as we can in Guix Ruby, though, so if you find you need to define a transformer then let me know on &lt;a href=&quot;mailto:~czan/guix-ruby@lists.sr.ht&quot;&gt;the mailing list&lt;/a&gt; and I can add it to the default set.&lt;/p&gt;
&lt;h2&gt;Try it out!&lt;/h2&gt;
&lt;p&gt;I've been using &lt;code&gt;guix ruby&lt;/code&gt; for a while to manage the dependencies on my Ruby projects. I've managed to get a few complex projects working using it (including Rails!), and I'm finding it very convenient to use Guix for Ruby development. If you're looking to bring together Ruby and Guix, please try it out. If you run into any issues, let me know on &lt;a href=&quot;mailto:~czan/guix-ruby@lists.sr.ht&quot;&gt;the mailing list&lt;/a&gt; and I'll do my best to help you.&lt;/p&gt;
</content></entry></feed>