Comrak on Akkoma
2024-01-02
First up: I’ve never done more than toy with Elixir before, and never with Nix or Rust, so this “simply stuff Nix, Elixir and Rust into a magic hat” trick was guaranteed to be at least a little bit Fun™. And it was! :)
Stock Akkoma uses Earmark, which looks like a lovely library, but maybe a lil out of date and out of step with CommonMark/GFM. We deserve Comrak.
Happily enough, a Google search revealed a Nathan Faucett had already done
most of the hard work of using Comrak from Elixir in
ex-markdown
. Thank you! This never gets old.
Ported it for Comrak and Rustler
changes in the last 5 years, and then learned about the various ways to
juggle Elixir and Mix releases/deps in Nix. Several hundred lines of hack-ish
later and
ex-markdown
was now fit for purpose.
Special care was taken to ensure both nix develop
- and nix build
-based
builds work — this one always wants to be able to quickly iterate in my checkout
without waiting all day for non-incremental builds, but at the end a nix build
should:
- match the behaviour of a clean
nix develop --command bash -c "mix deps.get && mix test"
; - always cleanly succeed; and,
- run
mix test
itself as a post-install check so we don’t get blindsided by differences in the dev shell/closure-built artefact only when later using it (i.e. in Akkoma).
This required some finesse: we want to build the native Rust dependency as
usual when doing nix build
, which means doing the usual Cargo/Nix dance
and compiling that artefact as its own derivation (and all its crate deps as
their own, etc. etc.). On the other hand, in nix develop
we want the usual
compile-on-demand to happen. Happily, Rustler is portable enough to support this
workflow! (see the MARKDOWN_NATIVE_SKIP_COMPILATION
env var.)
One tricky thing is the fucken Mix dependencies. The ex-markdown
derivation
itself needs to introduce its own Mix deps to beamPackages.buildMix
so it can
actually build and test. But that’s no good when we’re building Akkoma — we want
to use a release-wide resolved version of those dependencies, with all BEAM deps
in the one closure and no overlap.
For now we hack it somewhat, and reproduce some of ex-markdown
’s derivation in
our Akkoma fork — beamPackages
doesn’t have anything like overrideBeamAttrs
or overrideMixAttrs
at the moment.
There’s a fair bit more Nix
involved therein.
We started with upstream Nixpkgs’ Akkoma package definition (again, copying the
original as a base due to lack of override), add our :markdown
package to the
mixNixDeps
— we pull the source, native package and toolchain deps through the
ex-markdown
flake :)! —, adjust the call-sites, and then as a cherry on top,
expose a NixOS module that sets config.services.akkoma.package
to the package
exposed. Using the new Akkoma in my personal config is as simple as referring
the module.
And there you have it!
Future work
-
ex-markdown
only used a native call for parsing the input; the rendering is done in Elixir. Let’s add the missing NIF for rendering too! - Working out a nicer way to share the
ex-markdown
derivation for use in downstream projects’mixNixDeps
. - Working out a nicer way to override some properties of Nixpkgs’ Akkoma derivation.
- Unify version numbers and revisions.
- I’ve just noticed below that Comrak’s (GFM-compliant) autolink feature breaks remote user refs by turning them into mailto’s! Oops.
Reflections
Having never really touched Elixir much, this was a reasonably intimidating circus of interdependent parts to dive right into. It was super fun and — as usual — I credit Nix with making this at all possible, and more importantly worthwhile. The fact that I don’t have to worry about accumulating platform tools (or getting them installed on the target server etc.) is only a small part of it.
I did indeed spend quite a while fucking around with Making All This Shit Work With Nix, but I’d probably have spent as long or longer if I was just doing this on some pleb distro because of build artefacts left over from successive attempts — and of course, most of the work would be rendered null next time I had to set up a new server! The amount of discovery (and number of dead ends) I got to rebase into concrete learnings is incredible.
(I once again express my thanks to those who got me here — especially @cadey@pony.social for putting the idea in my head years ago, and my ex-qpf for using Nix in the year of our lord 2023, which was a strong enough signal to finally Just Do It.)