GitHub Actions for R on *BSD

In this post I show how to use GitHub Actions for R packages on the four most popular BSD systems: FreeBSD, OpenBSD, NetBSD, and DragonFly BSD.

Infrastructure

The pieces:

I include the important details for each piece below.

Setup actions

I wrote four setup actions, one for each OS. They are at r-hub/actions and they are called r-hub/actions/setup-r-*bsd, e.g. for FreeBSD it is r-hub/actions/setup-r-freebsd, etc. These actions are wrappers on the vmactions/*bsd-vm actions, and perform some additional steps to be able to run R reliably. This is what they do:

  • Install and configure R.
  • Install pak. (pak has now builds for FreeBSD, OpenBSD, NetBSD and DragonFly BSD.)
  • Set up pak for parallel package builds.
  • Set up R_LIBS_USER to be “shared” between the runner and the BSD VM, such that r-lib/actions/setup-r-dependencies can cache the R packages.
  • Install system packages to be able to build the Tidyverse.
  • Install a custom Rscript shell on the runner, that runs an R script on the BSD VM. See below.

Custom shells

To be able to run the r-lib/actions on the BSD VM, I created a custom GHA shell called Rscript, which is automatically picked up by these actions. This lets me run the usual setup-r-dependencies and check-r-package actions on *BSD, after some minor tweaks.

The four custom shells for the four BSD systems only have minor differences, it would probably make sense to unify them in a single script in the future. I include the one for FreeBSD here as an example.

Custom Rscript for FreeBSDSee on GitHub
1
2
3
4
5
6
7
8
#! /usr/bin/env bash
env_file=$(basename `mktemp`)
out_file=$(basename `mktemp`)
cch_dir=$(basename `mktemp`)

ssh -T freebsd <<EOF
touch "/tmp/${env_file}" "/tmp/${out_file}"
EOF

I create files for $GITHUB_ENV and $GITHUB_OUT on the VM first, so that actions running on the VM will be able to set environment variables and produce output.

9
10
11
12
# copy files to VM
echo "::group::Copy workspace to VM"
bash /home/runner/work/_actions/vmactions/freebsd-vm/v1/run.sh rsyncToVM
echo "::endgroup::"

Then I copy the workspace to the VM. It is important to synchronize the workspace files before and after and Rscript command, otherwise caching does not work properly.

The VM Actions also support a shared sshfs file system, but I found that there are at least two issues with this. The first is that I would need to map user and group ids between the runner and the VM manually, and this seems cumbersome. The second is that on some OSes it simply fails and I can’t access the mounted files on the BSD VM.

So it is simpler to use rsync to explicitly copy everything under $GITHUB_WORKSPACE back and forth. This is fast and works well.

Next I run the R script on the VM, after forwarding and setting some environment variables:

13
14
15
16
17
18
19
20
# GITHUB_* is automatically forwarded, as is LC_* (!)
ssh -T -o SendEnv=NOT_CRAN -o SendEnv=CI -o SendEnv=R_LIBS_USER freebsd \
"cd $GITHUB_WORKSPACE ; " \
GITHUB_ENV="/tmp/${env_file}" GITHUB_OUTPUT="/tmp/${out_file}" \
R_PKG_CACHE_DIR="/tmp/${cch_dir}" XDG_CACHE_HOME="/tmp/${cch_dir}" \
R_LIB_FOR_PAK=/usr/local/lib/R/site-library \
R --no-save -q < $1
status=$?

Ideally, I would also forward every environment variable that was set via $GITHUB_ENV or directly from the workflow file. I am planning to do that in the future.

ssh does not have an option for setting the working directory, so I set the working directory explicitly to $GITHUB_WORKSPACE. I also set R_PKG_CACHE_DIR for pak and XDG_CACHE_HOME for R packages that use a persistent configuration or cache directory. R_LIB_FOR_PAK is needed for the r-lib/actions/setup-r-dependencies@v2 action.

21
22
23
24
# copy files back from VM
echo "::group::Copy workspace from from VM"
bash /home/runner/work/_actions/vmactions/freebsd-vm/v1/run.sh rsyncBackFromVM
echo "::endgroup::"

After the R script has finished I copy back $GITHUB_WORKSPACE from the VM.

25
26
27
28
29
30
31
32
33
34
35
scp freebsd:/tmp/${env_file} /tmp/${env_file} || true
scp freebsd:/tmp/${out_file} /tmp/${out_file} || true
touch /tmp/${env_file} /tmp/${out_file}
cat /tmp/${env_file} >> $GITHUB_ENV
cat /tmp/${out_file} >> $GITHUB_OUTPUT

if [ "$status" != "0" ]; then
echo "::error ::Command failed."
fi

exit $status

Finally, I also copy back $GITHUB_ENV and $GITHUB_OUT and append them to the runner’s real $GITHUB_ENV and $GITHUB_OUT files.

At the end Rscript must exit with the original exit status of the R script on the BSD VM.

The Rscript shells of the other BSD systems are very similar, they are in the r-hub/actions repo.

Example workflows

Using the setup actions, I can now create workflows to run R CMD check on R packages on BSD systems. Again, I include the workflow for FreeBSD. The others are very similar and they are part of the r-hub/actions repo.

Custom Rscript for FreeBSDSee on GitHub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
on:
push:
branches: [main, master]
pull_request:
workflow_dispatch:
inputs:
release:
description: 'FreeBSD release'
required: true
type: choice
options:
- '14.1'
- '14.0'
- '13.4'
- '13.3'
- '13.2'
- '12.4'
default: '14.1'

It is handy to be able to select the FreeBSD release when triggering the workflow manually.

20
21
22
23
24
25
26
27
28
29
30
31
name: freebsd.yaml

jobs:
freebsd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: r-hub/actions/setup-r-freebsd@v1
with:
release: {{ github.event.inputs.release || '14.1' }}
- uses: r-hub/actions/platform-info@v1

I need to run this on ubuntu-latest for the VM to work correctly. After calling the r-hub/actions/setup-r-freebsd action, I can use r-hub/actions/platform-info, which will pick up the special Rscript shell to run its R code in the FreeBSD VM.

32
33
34
35
36
37
38
39
- uses: r-lib/actions/setup-r-dependencies@v2
with:
pak-version: none
install-pandoc: false
install-quarto: false
extra-packages: any::rcmdcheck
needs: check

I can reuse the r-lib/actions/setup-r-dependencies action as well, to install dependencies, because that uses the Rscript shell as well, and the custom Rscript shell will install the packages in the FreeBSD VM. It’ll also make sure that the workspace (as in $GITHUB_WORKSPACE) is synchronized between the Ubuntu host and the FreeBSD VM, both before and after running the R script. Synchronization ensures that caching the R package library works seamlessly in r-lib/actions/setup-r-dependencies.

I set some extra parameters:

  • pak-version: none because pak is already installed, pak now has binary builds for FreeBSD (and the other three *BSD systems as well), so this is not strictly necessary,
  • install-pandoc: false and install-quarto: false because the actions setup-r-dependencies uses to install Pandoc and Quarto do not work on *BSD.
40
41
42
43
- uses: r-lib/actions/check-r-package@v2
with:
build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")'
upload-snapshots: true

I can also reuse r-lib/actions/check-r-package to run the package check. Uploading the snapshots and check failures as artifacts works because of the $GITHUB_WORKSPACE synchronization.

Interactive debugging

The r-hub/actions/platform-info@v1 action currently automatically uses r-hub/actions/debug-shell@v1, which uses tmate to start a tmate session on the GHA VM, if two conditions are true:

  • the workflow run is a re-run, and
  • debug logging is turned on.

This means that if a workflow run fails, then I can request a re-run with debug logging from the web UI, and get an interactive shell to the VM. This works on Linux, macOS and Windows runners, but there is an extra step for *BSD because R is running in a QEMU VM. After connecting to the GHA VM, I need to further use ssh to log in to the FreeBSD VM:

1
ssh freebsd

The VM’s name matches the OS’s name, so on OpenBSD, this would be ssh openbsd, etc. When using ssh this was, $GITHUB_WORKSPACE is not synchronized! I can also call the custom Rscript shell on the runner, which will also synchronize $GITHUB_WORKSPACE.

Future improvements

This system is still new, and I am sure that I’ll need many improvements.

Environment variables

An obvious improvement would be to pick up the environment variables from the env context, and forward them to the VM. All environment variables that start with GITHUB_ are automatically forwarded to the VM via the ssh configuration on the runner, so if I can choose the name of the env var myself, then I can pick a name that’ll be forwarded.

  • Almost all the hard work that made this possible was done in the VM Actions project.
  • The setup actions are at r-hub/actions, with example workflows for each BSD OS.
  • See r-lib/actions for actions for R projects and packages.