Fixing Raku’s Readline module to work on macOS
Due to a series of regrettable life choices that we don’t need to go into here, I find myself wanting to run Raku via Nix on macOS.
In theory I could put together a relatively straightforward flake.nix
to drop
myself into an environment that has Rakudo and zef available, then zef install
any modules I want and I’m all set.
Something like this should do the trick:
{
description = "An environment with Rakudo, zef, and readline";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11";
};
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
let
forAllSystems = function:
nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
"aarch64-darwin"
] (system: function nixpkgs.legacyPackages.${system});
in {
devShell = forAllSystems (pkgs:
let
rakudo_env = pkgs.buildEnv {
name = "rakudo-env";
paths = with pkgs; [
rakudo
zef
];
pathsToLink = [ "/bin" "/lib" "/share" ];
};
in
pkgs.mkShell {
buildInputs = [
rakudo_env
];
shellHook = ''
export LD_LIBRARY_PATH=${pkgs.readline}/lib
'';
}
);
};
}
The first module that I want is, of course, Readline
, as recommended when
starting a fresh Rakudo REPL.
Using modules that rely on native libraries involves a bit of extra work in Nix. By design, the packages that you install don’t interfere with each other. One consequence of this is that installing Rakudo and readline doesn’t automatically mean that any Rakudo modules will be able to see the readline library.
Adding ${pkgs.readline}/lib
to LD_LIBRARY_PATH
in the shellHook
should
allow Raku modules to discover the readline shared library, and in fact it
works fine on Linux.
Unfortunately, on macOS we get an error:
$ zef install Readline
===> Searching for: Readline
===> Staging Readline:ver<0.1.6>:auth<cpan:fooist>
===SORRY!===
Failed to get the directory contents of '/usr/local/opt/readline/lib': Failed to open dir: No such file or directory
/usr/local/opt...
? Hmmmmm...
Skipping past some tiresome investigation, it turns out that the Raku Readline module hides this little gem:
when 'darwin' {
# needs homebrew installed version of readline
# macOS ships with incompatible libedit
$library-match = rx/:i libreadline\.(\d+)\.dylib $/;
# set version to v0.0 so we can check if it was found
$version = v0.0;
# homebrew directory
@library-path = ('/usr/local/opt/readline/lib')
}
Yup. If you’re on macOS it only recognises the Homebrew version of Readline,
and only at that specific path. This is broken even on Homebrew, which nowadays
uses /opt/homebrew
for new installations. Obviously it’s a complete
non-starter within a Nix environment.
Let’s see if we can make this work with some tweaks to Readline.pm
’s
library-hunting logic.
Step 1: clone the perl6-readline
repo locally:
$ git clone https://github.com/lathropd/perl6-readline.git
$ cd perl6-readline.get
Step 2: add a special case for Nix on Darwin before falling back to the existing
logic. I don’t know why the Readline module does all this manual fiddling
around looking for the library instead of relying on Raku’s NativeCall
, but I’m
not inclined to mess with it any more than I need to.
when 'darwin' {
if $*EXECUTABLE.starts-with("/nix/store") {
$version = v8;
@library-path = %*ENV<LD_LIBRARY_PATH>.split(":");
}
else {
# needs homebrew installed version of readline
# macOS ships with incompatible libedit
$library-match = rx/:i libreadline\.(\d+)\.dylib $/;
# set version to v0.0 so we can check if it was found
$version = v0.0;
# homebrew directory
@library-path = ('/usr/local/opt/readline/lib')
}
}
Step 3: we can now install this and give it a try. Fortunately, Zef makes it easy to install a module straight from a local directory:
$ zef install $HOME/git/perl6-readline
# Successful installation!
$ raku
# ^a, ^e, etc, all work as expected. Yay.
Success! With this change to the Readline module I can enter a Nix environment
on macOS with nix develop
, and use Raku -- installed from nixpkgs
-- with
Readline support.
Step 4: for bonus points, let’s fix Homebrew usage while we’re here. I raised
a PR for this a couple of
years ago which just replaces the line starting @library-path...
with the
following:
@library-path = $*SPEC.join($,
(%*ENV<HOMEBREW_PREFIX> || '/usr/local'),
'opt/readline/lib'
)
This supports both current and legacy Homebrew layouts.
Publishing changes
After some discussion and encouragement on the #raku
IRC channel, I tidied up
these two changes and pushed them as Readline
v0.1.7. New users on macOS who just
run zef install Readline
as recommended by the documentation and REPL should
now have working line editing.