Random Musings

O for a muse of fire, that would ascend the brightest heaven of invention!


Running Elixir Graphics & ML tools on FreeBSD

Monday, 25 May 2026 Tags: elixirfreebsdgraphicslibvipsmlvix

Most of the interesting Elixir libraries for image processing and ML have one thing in common: they’re NIFs wrapping a native C/C++ library. Vix wraps libvips, Nx wraps XLA, Bumblebee reaches into ONNX runtime, Image sits on top of Vix, and so on. On Linux & macOS, the Hex ecosystem has converged on shipping precompiled NIFs so end users don’t have to install a C toolchain. I am not a fan of downloading large swathes of pre-compiled C libraries from random Github repositories.

So, this post walks through getting Vix, Image, and eVision to compile and run on FreeBSD 15.1-RELEASE, failure modes you hit on the default builds, and how to avoid them.

Vix

We’ll focus on Vix first, since it’s the foundation: Image is built on top of it, and eVision wraps OpenCV with the same precompiled-NIF assumptions we’re about to break.

Prerequisites

Everything below assumes a working FreeBSD build environment and the native libraries the three projects wrap. On 15.1-RELEASE this is one command:

# pkg install -yr FreeBSD-ports \
    graphics/vips \
    math/openblas \
    graphics/opencv \
    lang/elixir-devel \
    lang/erlang-runtime29

That gets us libvips (for Vix and Image), OpenBLAS (which OpenCV and most of the Nx-adjacent stack want for linear algebra), OpenCV itself (for eVision), a recent Elixir, and Erlang/OTP 29.

The default build, and where it goes wrong

Vix already needs to be told explicitly to use CC=cc instead of assuming gcc everywhere.

$ env CC=cc mix do deps.get --all + deps.compile && \
      env CC=cc mix test --trace --stale

Looking at c_src/Makefile shows that Vix actually has three build modes, controlled by VIX_COMPILATION_MODE:

VIX_COMPILATION_MODE ?= PRECOMPILED_NIF_AND_LIBVIPS
VALID_COMPILATION_MODES := PRECOMPILED_NIF_AND_LIBVIPS \
                           PRECOMPILED_LIBVIPS \
                           PLATFORM_PROVIDED_LIBVIPS
  • PRECOMPILED_NIF_AND_LIBVIPS — fetch a prebuilt libvips tarball from sharp-libvips, build only the small NIF shim against it.
  • PRECOMPILED_LIBVIPS — same idea but always rebuilds the NIF.
  • PLATFORM_PROVIDED_LIBVIPS — trust pkg-config to find a libvips the system already has.

That third mode is exactly what we want on FreeBSD, because the ports tree already has graphics/vips and friends.

$ env CC=cc VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS \
      mix compile --force
 CC     pipe.c
 CC     utils.c
 CC     vips_boxed.c
 CC     vips_foreign.c
 CC     vips_image.c
 CC     vips_interpolate.c
 CC     vips_operation.c
 CC     vix.c
 CC     g_boxed.c
 CC     g_object.c
 CC     g_param_spec.c
 CC     g_type.c
 CC     g_value.c
 LD     vix.so
Build complete: /tmp/vix/_build/dev/lib/vix/priv/vix.so

And then:

$ env CC=cc VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS \
      mix test --trace --stale
...
Finished in 0.4 seconds (0.4s async, 0.00s sync)

Result: 271 passed (151 doctests, 120 tests)