How to publish custom Homebrew taps for OCaml / Jan 2025
Now that I've switched to a new website, I'm working on open-sourcing its components. I've got a lot of small OCaml scripts that are all work-in-progress, and so not quite suitable to be published to the central opam-repository but I still need be able to run them conveniently on my own self-hosted infrastructure.
I mainly use a variety of macOS and Linux hosts[1] and I want a workflow as simple as "brew install avsm/ocaml/srcsetter
" and have it install a working binary version of my CLI utility. In this case, it's srcsetter, a simple tool I knocked up to generate the responsive images on this website. Luckily, Homebrew has made this really easy for us! They have a BrewTestBot that integrates with GitHub Actions to automate the compilation of binary packages for us, all from a convenient PR-like workflow.
First, we need to set up a GitHub Homebrew "tap" repository. Mine is avsm/homebrew-ocaml which allows for the tap to be referred to as avsm/ocaml
(Homebrew special-cases these to expand to the full GitHub repository). We then add in a couple of GitHub Actions to activate the testbot:
- .github/workflows/tests.yml runs in response to pull requests to that repository and does a full Brew build of the package.
- .github/workflows/publish.yml allows us to simply add a
pr-pull
label to a successful PR and have it be merged automatically by the bot.
Secondly, we need to create a Homebrew package for the opam package. For this, I just added a very simple script to the srcsetter repository called .opambuild.sh which builds a local binary using a temporary opam installation. In the future, we should be able to use dune package management to remove the need for this script, but I'm blocked on some teething issues there in the short-term.
export OPAMROOT=`pwd`/_opamroot
export OPAMYES=1
export OPAMCONFIRMLEVEL=unsafe-yes
opam init -ny --disable-sandboxing
opam switch create .
opam exec -- dune build --profile=release
Once this is present in the repository we're building, I just need to open a pull request with the Homebrew formula for my CLI tool.
class Srcsetter < Formula
desc "Webp image generator for responsive HTML sites"
homepage "https://github.com/avsm/srcsetter/"
url "https://github.com/avsm/srcsetter.git", branch: "main"
version "0.0.1"
license "ISC"
depends_on "gpatch"
depends_on "opam"
def install
system "bash", "./.opambuild.sh"
bin.install "_opam/bin/srcsetter"
end
end
The formula is fairly self-explanatory: I just point Homebrew at the source repository, give it some descriptive metadata, and tell it to invoke the binary build script and make the sole resulting binary available as the contents of the package. At this point, the BrewBot will run against the PR and report any build failures on both macOS and Ubuntu. Most of these were swiftly fixed by running brew style
(as instructed in the build failures) to take of fairly minor issues.
When the PR went green, all I then had to do was to add the pr-pull
label, and the bot takes care of uploading the binary artefacts to my homebrew tap repo and merging the PR. It also takes care of adding checksums to the merged Formula, so what actually got merged is:
class Srcsetter < Formula
desc "Webp image generator for responsive HTML sites"
homepage "https://github.com/avsm/srcsetter/"
url "https://github.com/avsm/srcsetter.git", branch: "main"
version "0.0.1"
license "ISC"
bottle do
root_url "https://github.com/avsm/homebrew-ocaml/releases/download/srcsetter-0.0.1"
sha256 cellar: :any_skip_relocation, arm64_sequoia: "b3e1289965d8bcf086db06b18e6c2865f9949a9e1202b8fafa640f3e363b6bd4"
sha256 cellar: :any_skip_relocation, ventura: "9b61e8e4be5f777e3ef98672f275909a80c3cc3f82d6886ca1a90b66ea7bb9f8"
sha256 cellar: :any_skip_relocation, x86_64_linux: "d8279f11f30edf865368a3c6f63d811d31c1a9ca019ef86e93afeb6624232850"
end
depends_on "gpatch"
depends_on "opam"
def install
system "bash", "./.opambuild.sh"
bin.install "_opam/bin/srcsetter"
end
end
The end result is that brew install avsm/ocaml/srcsetter
now works, without me having to cut a release of the tool more centrally. I'd love to incorporate some aspects of this workflow into the OCaml opam-repository, as users are currently responsible for the checksumming generation themselves via dune-release or opam-publish. It's an interesting twist to automate this part of the process and let the humans focus on the core package metadata instead. Thanks for all the help, Brewbot!