This builds libvips and as many of its format libraries as I could get working, all as
static .a files, compiled from source inside an Alpine/musl container. Nothing comes
from apk - every dependency here is a git submodule pinned to a specific version, built
by hand with whatever flags it took to keep it static on musl.
I got tired of the usual problems with libvips packaging: whatever your distro ships is a few versions behind, you don't get any say in which codecs are compiled in, and if you use the shared library you now have to track its exact version alongside your own binary at deploy time. Building it static from source fixes the version tracking problem outright and gives you real control over what's compiled in.
Also, most people using libvips don't need every format it supports. If you're serving JPEG/PNG/WebP/AVIF thumbnails you don't need PDF rendering or all of ImageMagick's delegate libraries riding along in your binary for no reason.
The Makefile has one target per dependency (binary/.stamps/<name>), so if something
dies halfway through a build you just run make build again and it picks up from there
instead of starting over. Everything installs into binary/ - separate .a and .pc
files per library, not one big merged archive, so you link against whichever ones you
actually need (pkg-config --static --libs vips, or by hand).
Not everything here was equally easy to get working on musl. JPEG, PNG, WebP, TIFF, EXIF and GIF-save were straightforward. JPEG2000, AVIF/HEIC decode, JPEG XL, OpenEXR, lcms2, pyramid save (libarchive), and the performance libraries (highway, orc, fftw) took more work but got there. A few formats aren't in here yet: librsvg and the current imagequant/quantizr are both Rust now, so no Rust toolchain means no indexed PNG/GIF save and no SVG loading. PDF (poppler/pdfium), ImageMagick, and OpenSlide all need either a huge dependency tree or something that doesn't fit cleanly into a static musl build, so those are on hold for later.
podman build -t libvips-static -f Dockerfile .
podman run -d --name libvips-build -v $(pwd):/work:Z -w /work libvips-static sleep infinity
podman exec libvips-build make build
That builds every dependency in order and then libvips itself. Run make verify
afterward - it compiles a small test binary against whatever got built and actually
exercises it: round-trips real images through each codec, checks that EXIF metadata
survives a save/load cycle, runs a real ICC transform through lcms2, and pushes an image
through libvips's FFTW-backed FFT path. That's a better check than just "did it compile."
The static libraries and headers land in binary/lib/*.a and binary/include/.
binary/bin only has two things left in it - glib-mkenums and glib-genmarshal -
because those are the only tools libvips's own build actually calls. Every other CLI tool
each dependency installs (cjpeg, gio, dec265, fftw-wisdom, and so on) gets deleted right
after that dependency finishes.
FORMATS is a Makefile variable:
make build FORMATS="jpeg png webp avif"
make formats # see all the available keys
The Makefile figures out which dependencies and which libvips build flags that needs.
Pick jxl on its own and it still correctly builds highway, brotli, and lcms2 underneath
it, because libjxl's own Makefile target already lists those as its own dependencies and
Make follows that automatically - nothing extra to work out by hand. Since this only ever
builds what you asked for in the first place, there's nothing to clean up afterward.
Change FORMATS between runs and libvips gets reconfigured and rebuilt correctly, even
when you're removing formats, which normally Make wouldn't notice since none of the files
on disk get any older. There's a small sentinel file, binary/.formats-used, that exists
just to catch that case.
librsvg, PDF, ImageMagick, OpenSlide, and the scientific formats (matio, nifti, cfitsio) - see above for why. GIF save and indexed PNG save need a quantizer, and the only ones around now are written in Rust, which this build avoids.