~clarkema >_

More Raku Readline fixes

03 December 2023

This is a follow-up to the last post about Raku; I recommend reading that if you haven't already.

While I was making the necessary fixes for Raku’s Readline module to install on macOS, I also noticed another special case in the code for OpenBSD. Today I figured I might as well check that out and see if it still works, since OpenBSD has a habit of exposing assumptions and GNU-isms in code that hasn’t been specifically tested on it.

OpenBSD

I installed a fresh copy of OpenBSD 7.4 in a test VM, and ran pkg_add rakudo to get Rakudo v2022.12. The next step was to use Zef to try and install the Readline package, but sadly Zef itself is not packaged on OpenBSD. It’s easily installed from source:

$ git clone https://github.com/ugexe/zef
$ cd zef
$ raku -I. bin/zef install .

Upon trying to run ~/.raku/bin/zef install Readline I got the following error:

Enabled extracting backends [git path] don't understand /tmp/.zef/[...]e5690da54e9e58e.tar.gz
You may need to configure one of the following backends, or install its underlying software - [tar unzip]

I’ve had enough past experiences with the differences between BSD and GNU tar to be immediately suspicious that Zef was not either not detecting a tar command, or was trying to run it in a way that didn’t work on OpenBSD. Since I’d installed Zef from source it was easy enough to go digging, and I soon turned up some OpenBSD-specific code in lib/Zef/Service/Shell/tar.rakumod.

if BEGIN $*VM.osname.lc.contains('openbsd') {
    # For OpenBSD run just `tar` and see if the output contains
    # any of the following words (which suggest the command exists)
    BEGIN my @needles = <archive file specify>;
    my $proc = Zef::zrun('tar', :!out, :err);
    my $stderr = $proc.err.slurp(:close).lc;
    return $probe-cache = any($stderr.words) ~~ any(@needles);
}

Prompted by this code, I ran tar manually:

$ tar
tar: Failed open to read on /dev/rst0: Permission denied

OpenBSD tar’s interface must have changed since the detection code was written; none of the magic words appear in the output, so the command isn’t detected. After some discussion with ugexe++ on the #raku IRC channel, we decided to test by trying to run tar -cf - instead, since OpenBSD tar has no --help or --version options.

if BEGIN $*VM.osname.lc.contains('openbsd') {
    # On OpenBSD `tar -cf -` should run successfully with no
    # output.  This would cause a warning with GNU tar.
    my $proc = Zef::zrun('tar', '-cf', '-', :!out, :!err);
    return $probe-cache = so $proc;
}

With that fixed, let’s try again:

===> Searching for: Readline
===> Extraction: Failed to find a META6.json file for Readline:ver<0.1.7>:auth<zef:clarkema> -- failure is likely
No meta file? Path: /tmp/.zef/1701622764.89702/6bf81b82026641a849aa03175e5690da54e9e58e.tar.gz

Hmm. Well, that file is the same dist tarball that works everywhere else, so it seems unlikely that it’s missing something as fundamental as its META6.json. A quick manual check confirmed that it was indeed present. I wasted a bit of time thinking that it might be due to differences in tar handling paths with the -C option, which has bitten me in the past, but the problem turned out to be much simpler.

In the ls-files method, Zef’s tar.rakumod lists the contents of an archive like this:

my $proc = Zef::zrun-async('tar', '-t', '-f', $archive-file.basename);

Let’s try running that by hand:

tar -t -f 6bf81b82026641a849aa03175e5690da54e9e58e.tar.gz                                                                                                              
tar: input compressed with gzip; use the -z option to decompress it

GNU tar and FreeBSD tar will both run this without complaint, assuming the missing -z if the tarball is gzipped — not so OpenBSD tar. Time for the world’s smallest patch:

- my $proc = Zef::zrun-async('tar', '-t', '-f', $archive-file.basename);
+ my $proc = Zef::zrun-async('tar', '-zt', '-f', $archive-file.basename);

That’s enough to allow Zef itself to work on OpenBSD, so I created a pull request with those two fixes and moved on to Readline.

Readline's OpenBSD code uses the following regular expression to look for candidate libraries:

$library-match = rx/:i libereadline\.so\.(\d+) $/;

It wasn’t finding anything, so I checked the contents of OpenBSD's readline library (installed via pkg install readline.) The .so file we’re interested in ends up at /usr/local/lib/libereadline.so.3.0. A quick mental match of that path against the regex above shows it won’t match, because the regex only permits .so.X, not .so.X.Y, and no symlink is provided from libereadline.so.3 -> libereadline.so.3.0

Relaxing the regex to allow more complex version numbers is all that’s required for the module to install correctly:

- $library-match = rx/:i libereadline\.so\.(\d+) $/;
+ $library-match = rx/:i libereadline\.so\.(<[ \d . ]>+) $/;

These are all trivial fixes — no more than a few characters — but they’re a good example of the kind of bit-rot that happens naturally over time when dealing with niche languages and operating systems; and their length belies the time required to investigate, patch, and test.

FreeBSD

Since I seem to be on a bit of a roll with different operating systems at the moment I figured I might as well test FreeBSD while I’m at it. I installed the newly-released 14.0 in a VM, and then installed Rakudo. There’s no package for FreeBSD, so this involves building from source, but it was painless.

Happily, the whole process was without issue; Zef and Readline were both fine and within a few minutes I had a REPL up and running with line editing.

Next!

MacPorts

Finally, MacPorts. This isn’t something I use myself, but there was a PR by opoku++ on the repository I forked from to add another path on macOS to support readline from MacPorts.

I rejigged this to work with my updated Homebrew code and committed it. opoku++ was kind enough to test it for me and confirm it works.

Another release

These two changes together to support OpenBSD and MacPorts have become Readline v0.1.8 and are now available via Zef.