Revision history for Vips::Native
0.7.1 2026-10-25T21:52:55Z
- BUGFIX: [2026-10-25] 0.7.0 failed to install: t/11-build-platform
read .github/workflows/build-binaries.yml, which the distribution
does not ship, and died. t/11 now reads only shipped files; its
check that the release workflow builds every asset Build can ask
for is xt/02-workflow-assets, which test.yml runs on every push.
- CI: [2026-10-25] test.yml also installs from only the files the
distribution ships, with zef running t/, as a user's install does.
0.7.0 2026-10-25T21:32:18Z
- CHANGE: [2026-10-25] The Linux prebuilts install on glibc 2.17 and
later, where Build asked for 2.35: they are conda-forge builds
against glibc 2.17, and no library in them needs newer. Ubuntu
20.04, Debian 11 and RHEL 8 now get the prebuilt instead of falling
back to their own, older libvips. A Linux pack fails to build if a
library in it needs a glibc newer than Build's $MIN-GLIBC
(scripts/ci/check-glibc-floor.sh).
- CHANGE: [2026-10-25] Vips::Native needs libvips 8.12 or later: the
first with the fail_on load option that strict decoding passes
(against libvips 8.9, every strict decode failed), and the libvips
Ubuntu 22.04 ships. Only a system libvips can be older -- the
prebuilts carry 8.18.7 -- and an older one is refused when the
module loads, naming its version, where it was loaded from, and
how to use the bundled libvips instead. Vips::Native::FFI exports
vips-version, VIPS_MIN_VERSION and ensure-vips-version.
- CI: [2026-10-25] old-distros.yml replaces glibc-fallback.yml.
Ubuntu 20.04 gets the prebuilt and runs the suite against it, then
checks that its own libvips 8.9 is refused at load; Ubuntu 22.04
runs the suite against its system libvips 8.12.1, the floor.
- TEST: [2026-10-25] t/09's child process answers with its exit code
rather than its output: GLib writes VIPS_INFO's messages to stdout,
so under CI's VIPS_INFO=1 the expected word was buried. New
t/13-license-texts checks that every Windows licence text the
manifest lists is in the repository; t/14-libvips-floor checks the
libvips version loaded and the floor's arithmetic.
- CHANGE: [2026-10-25] The macOS arm64 and Linux prebuilts carry no
ImageMagick. conda-forge's ImageMagick links Graphviz, whose
EPL-1.0 the FSF reads as incompatible with the GPL code (FFTW,
poppler) in the same packs, so those packs leave out libvips's
ImageMagick module and everything only it needed: ImageMagick,
Graphviz, OpenEXR. They have no magickload or magicksave, so the
formats only ImageMagick reads -- BMP, ICO and CUR, PSD, TGA,
OpenEXR, DDS, DPX, PCX, QOI, SGI, Sun raster, XBM, XPM, PAM, MIFF,
MNG and JNG, MATLAB and VIFF files among them -- no longer open
there. The r11 macOS pack opened them; no Linux pack ever did. The
Windows packs keep ImageMagick. Both bundlers fail a pack holding
an ImageMagick or Graphviz library, and the release probe fails a
macOS or Linux pack where magickload is registered (its GIF fixture
is now written by hand: these builds have no GIF saver).
- CHANGE: [2026-10-25] The Windows prebuilts carry every component's
own licence texts (some 330 files), taken from the source each was
built from, where they carried libvips's alone and flagged the
rest. scripts/ci/license-texts/windows/manifest.json lists each
component's SPDX licence, version, source archive and texts, every
text with its SHA-256; THIRD-PARTY.md names each component's
licence and source. Components built into another's DLL ship with
it: the mingw-w64 runtime and LLVM's compiler-rt (every DLL),
proxy-libintl (GLib), the Rust crates and standard library
(librsvg), Imath (OpenEXR), uthash (libdicom). libvips's licence
ships once, as LICENSES/vips/LICENSE. A Windows pack fails to build
if a component in it is not in the manifest for its architecture,
the manifest lists one the script does not know or gives a
built-in one another host, a version disagrees with the release's
versions.json, the manifest is for another release, a component
has no text and is not public domain (SQLite is), or a text does
not match its hash. THIRD-PARTY.md also says what our shim links:
the MSYS2 toolchain's mingw-w64 runtime, and libgcc (x86_64, with
the GCC exception's text) or compiler-rt (arm64).
- CHANGE: [2026-10-25] Every pack's THIRD-PARTY.md lists, under
"Where to get the source", the source archive of each GPL, LGPL and
MPL component in it, with its SHA-256 and the recipe that built it,
and the crates.io pages of the MPL-2.0 Rust crates in librsvg; a
pack whose copyleft component has no such archive fails to build.
This found that the recipe parser dropped every conda package's
SHA-256 (a regex consumed the line break before it); it is fixed.
- CHANGE: [2026-10-25] THIRD-PARTY.md, and the README, carry the
statements licences ask a binary distribution's documentation for:
FreeType's credit (with the bundled release's year), the IJG's for
libjpeg-turbo and mozjpeg, and Adobe's gain-map notice for
libultrahdr (Windows). Every pack carries the Unicode License v3
(LICENSES/Unicode-3.0.txt, vendored from unicode.org and SHA-256
pinned) for the Unicode data tables compiled into GLib, FriBidi
and HarfBuzz, whose upstreams ship no Unicode licence text.
- BUGFIX: [2026-10-25] A burst of first decodes on worker threads
could still crash on macOS, on a few cold launches in a hundred:
a segfault inside libffi, "Failure to set up FFI call interface",
or a memcpy marshalled from garbage argument types. memcpy was
bound with an empty library name, which MoarVM builds against
dlopen(NULL) but re-opens verbatim when NativeCall checks whether
the call site is built; macOS's dlopen("") fails, so the site
never read as built and was rebuilt in place on every dispatch
recording while other threads copied it half-written. memcpy now
binds by a complete library filename on every platform. t/08
checks that cause directly, and its cold launches now fail when
a child is killed by a signal: they used to pass segfaults,
because a signalled child reports exit code 0.
- BUGFIX: [2026-10-25] The Linux packs' libgomp.so.1 (LibRaw's
OpenMP runtime) is credited to libgomp, GPL-3.0 with the GCC
Runtime Library Exception, and ships with those texts. The notices
kit credited a bundled file to the package that installed its
path, and that path is a link _openmp_mutex (BSD-3-Clause)
installs; it now follows a link to the file it leads to, and
credits the package that installed that. The README names libgomp
with the rest of the GCC runtime.
- CHANGE: [2026-10-25] THIRD-PARTY.md calls out the weak-copyleft
licences (MPL, EPL, CDDL) beside the GPL and LGPL ones, and the
README names the MPL-2.0 code in the macOS and Linux packs: NSS and
NSPR (through poppler), the Public Suffix List built into libpsl,
and librsvg's MPL-2.0 Rust crates; and in the Windows packs:
librsvg's crates, and proxy-libintl (LGPL) and zstd and FreeType
(GPL or another licence). It called everything that was not (L)GPL
permissive.
- CHANGE: [2026-10-25] The macOS and Linux packs carry Lerc's NOTICE
(LICENSES/lerc/NOTICE), which its conda package lacks, kept byte
for byte from the Lerc 4.2.0 source archive the package was built
from (scripts/ci/license-texts/lerc-4.2.0/NOTICE, SHA-256 pinned).
A pack with another Lerc version, or without the NOTICE, fails to
build.
- CHANGE: [2026-10-25] The macOS and Linux packs carry the permission
notices of FreeType's BDF and PCF font drivers, which are compiled
into libfreetype under X11-style licences and which conda-forge's
freetype package lacks (it ships FTL.TXT and GPLv2.TXT only):
LICENSES/libfreetype6/src/bdf/README and src/pcf/README, whole and
byte for byte from the freetype-2.14.3 source archive the package
was built from (scripts/ci/license-texts/freetype-2.14.3/, SHA-256
pinned; the same files the Windows packs ship). A pack with another
FreeType version, one built from another source archive, or a kept
text missing or edited, fails to build; Lerc's NOTICE is checked
the same way.
- CHANGE: [2026-10-25] A prebuilt pack fails to build when a package
in it declares a copyleft licence (GPL, AGPL, GPL-or-other, GPL
with the GCC exception, LGPL, MPL, EPL, CDDL) that
scripts/ci/disclosed.json does not list for its platform, and when
a package in it has no licence text and does not declare a
public-domain dedication (SQLite's "blessing" is one). A package
without a text used to be flagged and shipped anyway.
- TEST: [2026-10-25] t/12-disclosure fails unless the README and its
Pod name, in each platform's disclosure item, every package
scripts/ci/disclosed.json lists for that platform, and the list
has one entry per prebuilt pack.
- TEST: [2026-10-25] t/09-strict-decode decodes its corrupt PNG in a
child process started with VIPS_WARNING=1, so a test run's stderr
stays empty: libvips printed two warnings for it ("Not enough
image data", "error in tile 0 x 0"). The variable has to be set
before the process starts -- libvips reads it once, in vips_init,
which runs as the module loads.
- TEST: [2026-10-25] xt/01-third-party-kit runs
scripts/ci/third-party.py against mock conda environments and a
mock build-win64-mxe release (both architectures), checks the kit
it writes, and each refusal: an undisclosed or textless package, a
copyleft package without a source SHA-256, a kept text (Lerc's
NOTICE, FreeType's driver notices) for another version, from
another source archive, missing or edited, an unknown FreeType
credit year, a librsvg without its crate list, and a manifest
that is missing a component, lists an unknown one, gives a built-in
one the wrong host, lists one for the other architecture only,
disagrees with versions.json, is for another release, or names a
text whose bytes do not match.
- CI: [2026-10-25] The Linux release lanes create their conda
environment with --override-channels, from conda-forge alone, as
the macOS lane does. The Windows release lanes list each packaged
archive with 7z and fail unless the whole notices kit is in it --
THIRD-PARTY.md, the GPL-3.0 text, a LICENSES// for
every component with texts, every file the kit step wrote -- as
the macOS and Linux probe already checks. test.yml's comment says
what VIPS_WARNING does: it silences libvips's warnings.
- CHANGE: [2026-10-25] .gitattributes keeps the licence texts and
NOTICE files under scripts/ci/license-texts/ byte for byte (no
line-ending conversion), since each is checked against a SHA-256
of its upstream bytes, some of which have CRLF line endings.
- CHANGE: [2026-10-23] Raku code is indented with tabs: Build.rakumod,
lib/, bin/vips_smart_resize and the tests. Whitespace only.
- CHANGE: [2026-10-05] Prebuilt binaries move to
binaries-vips-8.18.7-r12 (libvips 8.18.7). The macOS arm64
prebuilt is now built from conda-forge's libvips instead of
Homebrew's bottle, whose 8.18.7 supports only the newest macOS: it
runs on macOS 11 and later (the r11 bundle needed macOS 14), with
the same codec set as Linux. x86_64 macOS -- an Intel Mac, or an
x86_64 Raku under Rosetta -- still has no prebuilt; its install now
says what to use instead (an x86_64 libvips, or VIPS_NATIVE_LIB_DIR).
- BUGFIX: [2026-10-05] The Linux prebuilts carry the HEIF/AVIF, JPEG
XL, PDF and OpenSlide loaders they were missing.
conda-forge builds those as separately loaded libvips modules, and
the r11 Linux bundles shipped none of them, so those formats failed
to open on Linux installs. r12 ships the modules, every library they
need -- including conda-forge's C++ runtime, which they require and
which is newer than Ubuntu 22.04's -- and the lookup that finds them:
VIPSHOME now points at the staged lib directory on macOS and Linux,
where both platforms' libvips builds find their modules.
- CHANGE: [2026-10-05] Every prebuilt pack carries a THIRD-PARTY.md
and a LICENSES/ directory, staged beside the libraries: each bundled
library mapped to the package it came from, with its version,
licence and source, and the licence texts themselves. The packs
contain GPL-licensed code (FFTW and poppler everywhere; x265 and LZO
on macOS and Linux) as well as LGPL-licensed code; the r11 macOS
pack's libimagequant 4.x (GPL-3.0) is in none of them. The README's
new "Binary distributions and licensing" section lists them per
platform, says where each pack comes from, and how to install with
no prebuilt at all (VIPS_NATIVE_PREFER_SYSTEM=1). Installation
refuses an archive without its notices, and empties the stage --
directories too -- before unpacking one.
- CHANGE: [2026-10-05] The strict in-memory decode shim symbol
(vips_shim_image_new_from_buffer_strict) and the Windows shim fixes
below ship in the r12 prebuilts, so strict decoding works from the
bundled shim on every platform, with no local compile.
- CHANGE: [2026-10-05] Prebuilt releases publish only from a pushed
binaries-vips-* tag equal to BINARY_TAG; a manual run of the release
workflow builds and probes every platform and publishes nothing.
Before release, each macOS and Linux pack is unpacked as an install
would unpack it and every loader must decode a real file, with
nothing loaded from outside the bundle.
- TEST: [2026-10-05] t/11-build-platform pins which prebuilt each
platform asks for (and that every asset Build can ask for is one the
release builds and resources/checksums.txt pins), what x86_64 macOS
is told instead, and that an unpacked archive is only accepted with
its libraries and its notices.
- BUGFIX: [2026-10-01] Windows installs compile the varargs shim when
the staged one is out of date. Build returned early on Windows, so
the shim only ever came from the prebuilt bundle, and a bundle cut
before the source gained a function (the r11 bundles lack
vips_shim_image_new_from_buffer_strict; r12 ships it) left strict
decoding dying with "Cannot locate symbol". It now compiles the way
build-binaries.yml's Windows job does -- a MinGW `cc` (or
VIPS_NATIVE_CC) linking straight against libvips-.dll and
libglib-2.0-.dll from the stage, VIPS_NATIVE_LIB_DIR or PATH --
to a temporary file, checks with `nm` that every vips_shim_* the
source defines is exported, and only then renames it into place,
writes the sidecar and parks a copy in the cache. Freshness there
comes from the .srchash sidecar alone: IO::Path.modified on
Windows puts every file within seconds of 1970 (a MoarVM overflow),
so the old mtime fallback compared noise. No compiler, no `nm`, a
failed link, a missing export, or a shim held open by a running
application each say so and leave the stage as it was.
- BUGFIX: [2026-10-01] the shim's .srchash sidecar is never mistaken
for the shim. The bundle-only lenient filename match accepted
libvips_shim..srchash, so a stage holding the sidecar but not
the shim handed dlopen a text file.
- BUGFIX: [2026-10-01] VIPS_NATIVE_LIB_DIR is read one way by
everything. The runtime-env step skipped all its setup for any
non-empty value while the resolver ignored one that was not a
directory, so a mistyped override loaded the staged libvips with
none of its setup (on Windows, its DLLs not findable). Now an
override that is not a directory is ignored by both; one that
holds libvips also gets the Windows DLL-directory setup it cannot
load without (VIPSHOME and GIO stay the user's); and the shim is
looked for there first, as libvips is.
- BUGFIX: [2026-10-01] on Windows the download and shim cache lives
under %LOCALAPPDATA%. Windows sets no HOME, so the default came out
as ./.cache -- inside the directory zef ran the build from, the
dist's own source tree for a local `zef install .`.
- CHANGE: [2026-10-01] orphaned stages are removed in Raku rather
than with `rm -rf`, which a stock Windows lacks; a link inside a
stage is removed as a link, never followed.
- CHANGE: [2026-10-01] the Windows CI job writes
libvips_shim.dll.srchash, as the macOS and Linux jobs already do,
and checks every vips_shim_* the source defines is exported by
name, where it used to accept any one. The r12 prebuilts are the
first built this way; the macOS job runs the same check.
- TEST: [2026-10-01] t/10-build-shim pins the build's pure parts
(source functions, nm parsing, missing exports, DLL and toolchain
discovery, stage removal) and that every shim function FFI binds
is one the C source defines; t/06 adds the override and the shim
resolution, including a child process started with the override.
- CI: [2026-09-29] test.yml and glibc-fallback.yml run every lane on
Rakudo 2026.07 (the last release with the legacy frontend) and
2026.09 (RakuAST the default), where they ran on 'latest'.
- CHANGE: [2026-09-14] Add opt-in strict in-memory decode with a
fixed-arity native shim; reject loader warnings and corrupt lazy
pixel data without changing default decoding behavior.
- CHANGE: [2026-09-06] A .gitattributes keeps every text file LF, in
the repository and in every checkout whatever core.autocrlf says
(batch files reach the disk as CRLF), and never converts the
binary types (images, archives, libraries, fonts, PDFs).
- BUGFIX: [2026-09-06] On Windows the staged lib dir is now prepended
to the process's real `Path` entry. The runtime-env step wrote
`%*ENV`, but the entry a Windows process is handed is
spelled `Path`, and Raku's %*ENV is a case-sensitive Hash — so
the real search path was never touched and a second `PATH` key
appeared holding only the staged dir. A child process spawned
afterwards inherited both, and Windows could hand it the short
one as its whole DLL search path: a consumer app's child-process
tests failed with `sqlcipher.dll not found` for exactly that
reason. The key is now found case-insensitively, and t/06 pins
that loading the module leaves one PATH entry, with the staged
dir at its head on Windows.
- BUGFIX: [2026-09-05] `native-copy-of` no longer underflows the
heap for an empty Blob. It sized its CArray with
`CArray[uint8].allocate($n)` for every $n, and Rakudo implements
`allocate` as a bind to index $n - 1 — so a zero-byte input
bound index -1 and wrote one byte before the freshly allocated
storage. Nothing was observable at the callsite on macOS or
Linux; on Windows the heap checker caught the corrupted
neighbour at some later, unrelated free and aborted the process
with STATUS_HEAP_CORRUPTION (0xC0000374), which surfaced as
t/07-bulk-copy dying mid-run and `zef install` refusing the
dist. Empty inputs now get `CArray[uint8].new` (a valid
zero-element array, non-NULL pointer, no bind). The test's own
reference writer had the same bug and is fixed the same way; a
regression test churns the allocator after empty copies so a
repeat is caught in this file rather than downstream.
- BUGFIX: [2026-08-27] the library-path resolvers are no longer
racy on first touch. Each memoised its lookup in a bare
`state $r = `, and a `state` initialiser is not atomic —
the slot is flagged initialised independently of the value
landing in it, so a thread arriving mid-initialisation read
`Any`. The probes are nested directory walks taking
milliseconds on a cold page cache, which is exactly how long a
burst of worker threads decoding their first images at app
startup needs to collide: 16 of 25 cold launches failed at 64
threads before the fix, 0 of 60 after. Symptoms were a
"expected Bool but got Any" type-check failure out of the shim
predicate, or a `Str` type object handed to dlopen as a library
name — in a consumer that wraps decodes in `try`, an image that
silently never appears. Resolution now happens under a lock,
and every resolver is primed once from the module mainline on
the loading thread, so the concurrent first touch cannot arise
in the first place. New regression test (t/08-cold-start-race)
drives 64 real OS threads through a cold decode burst across
ten child processes.
- Pixel buffers now cross between libvips's heap and Raku in a
single memcpy instead of a per-byte Raku loop, so a 4000×3000
decode-to-rgba copies its 48 MB in ~5 ms rather than ~11.7 s
(letterbox-to-buffer, smart-resize-buffer and the encoded-input
side get the same treatment). New Vips::Native::FFI helpers
buf-from-native / native-copy-of; memcpy resolves out of
libSystem on macOS, glibc's libc.so.6 on Linux and ucrtbase.dll
on Windows (see the 2026-10-25 entry). Blobs whose elements
are wider than a byte are now rejected at the boundary rather
than silently truncated.
0.6.4 2026-08-21T21:24:59+01:00
- [2026-08-12] vips_init is now deterministic: exactly once, at
module load, on the loading thread, via a lock-guarded
ensure-vips-init that every public entry point also calls.
Previously each entry point called vips_init lazily, so the
first decode — typically on a consumer's worker thread — ran
vips_load_plugins' dlopen of every bundled vips-module
(heif/jxl/magick/openslide/poppler, new with the r11 bundle)
concurrently with other threads' dlopens. Sampled live in a
consumer app: the worker held dyld's API lock inside a plugin
initializer chain (libgpg-error → libintl rwlock) while the
main thread's unrelated dlopen waited on dyld and every other
thread parked at the VM's GC barrier — a silent whole-process
freeze, nondeterministic by timing. Init at load time removes
the competing-dlopen window; VIPS_NOVIPS in the environment
remains the escape hatch to skip plugin loading entirely.
0.6.3 2026-08-11T15:38:39+01:00
- Attempt to fix CI
0.6.2 2026-08-11T15:29:11+01:00
- Fix the system-libvips fallback, a regression introduced in
0.6.0. That release refactored the resolvers from
`constant $vips-lib = _resolve-lib(...)` + `is native($vips-lib)`
to state-cached subs + `is native(&vips-lib)` (correctly — it
stops a resolved path being baked into precompiled bytecode
across a BINARY_TAG bump), but NativeCall only name-mangles the
*string* form of the trait. A Callable's return value goes to
dlopen() verbatim, so the old "return the bare short name and
let the OS loader decorate it" fallback became dlopen("vips")
and every system-libvips install failed with "Cannot locate
native library 'vips'". The sub/Callable structure stays; the
fallback now returns an absolute path (or, at worst, a
fully-formed "libvips." filename) and never a short name.
- New system probe behind that fallback, in order: the dynamic
loader's own search env vars (DYLD_LIBRARY_PATH +
DYLD_FALLBACK_LIBRARY_PATH / LD_LIBRARY_PATH / PATH), then the
platform's library dirs, then `ldconfig -p`'s cache listing on
Linux. On macOS that includes per-formula Homebrew opt kegs
(/opt/vips/lib, /opt/glib/lib) — required,
because dyld's default search doesn't include /opt/homebrew/lib
at all and an *unlinked* vips keg has nothing in /lib.
On Linux it includes the Debian multiarch tuple dirs, computed
from the machine and cross-checked by globbing.
- Library filename matching is now strict: exactly libvips.,
libvips.., libvips.. or libvips-..
The old lenient rule also matched libvips-cpp.* — the C++
binding that ships beside libvips in every distro package and
brew keg — which would have been a silent mis-link now that we
probe directories full of other people's libraries. The lenient
rule is retained only for directories we populate ourselves
(the staged bundle / $VIPS_NATIVE_LIB_DIR), and strict matching
is tried first there too.
- VIPSHOME / GIO_MODULE_DIR are no longer set just because the
staged dir exists. In system mode Build.rakumod stages only the
varargs shim there, and pointing a *system* libvips at a
prefix with no vips-modules-./ silently disabled every
split-out format loader (Homebrew ships the split layout).
Both settings now require libvips itself to be staged.
- CI: the system-libvips lane sets VIPS_NATIVE_DATA_DIR to the
same scratch dir for the install step and the test step, so the
compiled shim is actually visible to the tests (mandatory on
Apple arm64) while libvips/libgobject still genuinely fall
through to the system probe. Also fixed the macOS failure
diagnostic, which still referenced the pre-0.6.0 $vips-lib
constant and would have failed to compile.
- New t/06-lib-resolution.rakutest: 63 offline assertions over the
strict matcher (including the libvips-cpp rejections on all
three platform spellings), the ldconfig-cache parser (stale
entries skipped) and probe ordering. Runs with no libvips
present.
0.6.1 2026-08-11T14:46:26+01:00
- Bump BINARY_TAG to binaries-vips-8.18.2-r11 — first bundle
that ships the shim .srchash sidecar.
- Shim freshness is now content-based, not mtime-based. A
libvips_shim..srchash sidecar (SHA-256 of the shim source)
ships in the prebuilt bundles and is written by every local
compile; the old mtime comparison made every fresh ecosystem
install think the dist's shim source was newer than the
bundle's prebuilt shim and recompiled on every install (found
via the identical bug in Notcurses-Native on a toolchain-less
Alpine CI container — on arm64 the Vips shim is mandatory, so
a toolchain-less machine lost image loading entirely despite
the bundle shipping a good shim).
- Successful shim compiles are parked in a content-addressed
build cache (/shims/.), so a machine
that compiled once never recompiles for the same shim source +
binary tag, even though extraction wipes the staged dir on
every install.
0.6.0 2026-08-10T20:09:05+01:00
- In-memory decode + RGBA pipeline (for rendering encrypted /
transient image bytes without spilling plaintext to disk):
* src/vips_native_shim.c + FFI: new shims/bindings
`vips_image_new_from_buffer` (decode an encoded image from
a Blob, not a file path), `vips_colourspace`, `vips_addalpha`,
`vips_pngsave_buffer`, plus the `VIPS_INTERPRETATION_sRGB`
constant.
* lib/Vips/Native.rakumod: `class RawRgba` (width/height + a
tightly-packed 4-band RGBA Buf, the layout
`ncvisual_from_rgba` consumes); `decode-to-rgba(Blob|IO::Path
--> RawRgba)` — aspect + alpha preserving, sRGB-normalised
(greyscale / CMYK / 16-bit handled), the display complement
of `letterbox-to-buffer`; `smart-resize-buffer(Blob, w, h
--> Buf)` — buffer→PNG-buffer analogue of `smart-resize` for
generating thumbnails/avatars with no temp files.
* Lifetime note: `vips_image_new_from_buffer` references its
input lazily; both helpers root the encoded CArray across the
decode (down to write_to_memory / pngsave_buffer).
* t/05-decode-rgba.rakutest + fixtures alpha-split.png (alpha
preservation) and rgb-solid.png (addalpha path).
- lib/Vips/Native/FFI.rakumod: convert the `$vips-lib`,
`$gobject-lib`, `$shim-lib` library-path bindings from
`constant` to state-cached subs (`sub vips-lib { state $r =
_resolve-lib(...); $r }`). `constant X = _resolve-lib(...)`
ran at compile time and baked the resolved path into the
precompiled bytecode — and Rakudo doesn't track
`resources/BINARY_TAG` as a precomp dependency. A BINARY_TAG
bump (which moves staged libs to a new versioned directory
and may GC the previous one) would leave the precomp pointing
at the old path, producing "Cannot locate native library"
errors on freshly installed packages until the user manually
ran `rm -rf ~/.raku/precomp/`. Deferring resolution to first
sub-call means each process picks up the current tag,
regardless of when the precomp was built. NativeCall accepts
a Callable for `is native()` and invokes it lazily on first
use of each bound sub. All `is native($X-lib)` callsites
updated to `is native(&X-lib)`, including the
`is native($shim-lib // '')` defensive-fallback variant
(the fallback is no longer needed — the sub call always
returns a Str, possibly the type object, and NativeCall
surfaces missing-lib errors at call site rather than at
binding declaration). The `$USE-SHIM` module-init Bool became
a state-cached `USE-SHIM()` sub for the same reason, calling
`shim-lib()` rather than reading the old constant.
- Same fix applied across all three native libs in this release
window (Notcurses::Native, Vips::Native, ONNX::Native), so
consumers upgrading any of them past the constant-baking
precomp bug get the fix consistently.
0.5.0 2026-04-29T19:56:21+01:00
- Windows prebuilts now ship `libvips_shim.dll` on both x86_64
and arm64. Built via `msys2/setup-msys2@v2` with the
appropriate per-arch toolchain (UCRT64 / `mingw-w64-ucrt-x86_64-toolchain`
for x86_64; CLANGARM64 / `mingw-w64-clang-aarch64-toolchain` for
arm64), linking directly against `$EXTRACT_DIR/bin/libvips-42.dll`
and `libglib-2.0-0.dll` via `-l:filename` syntax. The earlier
`-L$EXTRACT_DIR/lib -lvips` approach worked on UCRT64 (MinGW
`ld` finds `.lib` import libs by `-l` search since
binutils 2.36+) but failed on CLANGARM64 (`lld` in GNU mode
doesn't search `.lib` files). `-l:filename` bypasses search
entirely; clang/lld + GNU ld both auto-derive imports from
the DLL's export directory. arm64 runs on the
`windows-11-arm` runner (mirrors Notcurses-Native's pattern;
avoids cross-compile complexity). `letterbox-to-buffer` now
works on every target platform — the previous "requires
libvips_shim" failure on Windows is fixed.
- BINARY_TAG bumped to `binaries-vips-8.18.2-r10`. r9 was
published with a broken arm64 Windows shim (compiled against
x86_64 toolchain on a `windows-2022` runner before the matrix
was extended to use `windows-11-arm` + CLANGARM64). r10 is the
first release where every Windows arch actually ships a
working shim. macOS / Linux content is functionally unchanged
from r8 but the archives are re-built and re-hashed by CI;
refresh `resources/checksums.txt` from the r10 release's
`checksums.txt` once CI publishes.
0.4.1 2026-04-29T19:09:29+01:00
- Release binaries.
0.4.0 2026-04-29T18:43:31+01:00
- Added `letterbox-to-buffer($path, :$size = 448, :@bg = (255, 255, 255))`,
a high-level helper that resizes an image to fit inside a square
of the given size, pads the gap with the chosen background colour,
composites alpha onto that background, and returns the raw 8-bit
RGB pixel bytes (Buf[uint8] of size × size × 3). The preprocess
pipeline ML image classifiers want — fixed input dimensions,
deterministic gap fill, NHWC byte order, no GC entanglement with
libvips's heap. Built on three new FFI bindings: `vips_flatten`,
`vips_embed`, and `vips_image_write_to_memory` (plus a
`vips_shim_free` wrapper around `g_free` for the memory the latter
returns).
- The `vips_flatten` and `vips_embed` bindings are shim-only — both
take `VipsArrayDouble *` for the background colour, which is
awkward to construct via direct NativeCall. They die with a clear
"reinstall with a C toolchain" message if the libvips_shim isn't
present, rather than silently corrupting memory. `vips_image_write_to_memory`
is non-variadic and binds directly.
- `Build.rakumod`'s `try-compile-shim` now rebuilds the shim when
the source mtime is newer than the staged binary, instead of
always skipping when a staged shim exists. Avoids the
prebuilt-bundle-shadows-local-changes trap when developing
shim additions against an older bundled `libvips_shim.{dylib,so}`.
- `build-binaries.yml`: link the shim against `libglib-2.0` on both
macOS and Linux. `vips_shim_free` wraps `g_free` (a libglib
symbol), and the prior link command only mentioned `libvips`,
so the macOS arm64 build failed with "_g_free undefined".
Glob the versioned filename instead of hardcoding it; on Linux
symlink `libglib-2.0.so` → the SONAME-stamped file so `-lglib-2.0`
resolves cleanly. Both arches fail loudly if libglib isn't
present in the bundle.
- BINARY_TAG bumped to `binaries-vips-8.18.2-r8`. r7 lacks
`vips_shim_flatten`, `vips_shim_embed`, and `vips_shim_free`,
so any caller of `letterbox-to-buffer` against an r7 bundle
would die at NativeCall time with "Cannot locate symbol …".
0.3.0 2026-04-16T02:48:04+01:00
- Root-caused the macOS image-load failures to a C varargs
ABI bug that had been latent across all platforms. libvips's
public API is entirely variadic (`vips_image_new_from_file
(filename, ...)` etc.), and Raku's NativeCall marshals every
declared argument according to the *non*-variadic ABI since
it has no way to mark a binding as variadic. On Apple arm64
specifically, the variadic ABI diverges: named args go in
registers (x0–x7), unnamed args go on the stack. When we
put our NULL terminator in a register, libvips read the
stack looking for the first vararg and found whatever
garbage happened to be there — error messages like
"pngload: no property named `\xa0\x13Yk\x01'" from bytes
that were stack memory, not a property name. Linux x86_64
and Windows x64 happened to work because their variadic
ABIs reuse the same registers as non-variadic for the
first several args.
- Fix: `src/vips_native_shim.c` — a tiny non-variadic shim
that wraps each variadic libvips entry point in C (where
the compiler knows the signature is variadic) and exposes
honest non-variadic symbols Raku's NativeCall can bind
directly. Shipped on all platforms where the ABI matters:
* macOS arm64: `libvips_shim.dylib` compiled in
build-binaries.yml, shipped in the prebuilt tarball.
* Linux x86_64 + aarch64: `libvips_shim.so` compiled
in build-binaries.yml, shipped in the prebuilt tarball.
aarch64 has the same AAPCS64 variadic divergence as
macOS arm64; x86_64 is defence-in-depth (eliminates
latent UB even though the current ABI happens to work).
* Windows: shim not yet compiled in prebuilt (needs
MinGW setup; x64 ABI overlap means tests pass without
it; arm64 Windows Raku doesn't exist yet). TODO
documented in build-binaries.yml.
* System-libvips fallback (all POSIX): Build.rakumod
compiles the shim at install time via `cc` with
`-undefined dynamic_lookup` (macOS) or plain `-shared`
(Linux). Symbols resolve lazily against the system
libvips already loaded by NativeCall. Non-fatal if no
C compiler present — falls back to direct variadic
bindings with a warning about arm64 risk.
FFI.rakumod detects the shim at module load and routes
the variadic entry points through it when present; falls
back to direct libvips bindings otherwise. Raku-facing
signatures unchanged — tests didn't need to move.
- test.yml: run prebuilt-path install + tests *before*
`brew install vips` / `apt install libvips-dev` so the
self-contained bundle is exercised with no system libvips
present. Also saves several minutes on every failed run
since the slow system-libvips install is only reached
after the fast path passes.
- BINARY_TAG bumped to binaries-vips-8.18.2-r7 (r6 shipped
without the shim on Linux/Windows; r7 includes it on all
platforms where we compile it).
- macOS build-binaries.yml: recursive transitive-dep bundler
for vips-modules. dylibbundler only follows link-time deps,
so the vips-* modules (heif/jxl/magick/openslide/poppler) —
which libvips dlopens at runtime — left their own deps
(libheif, libjxl, libMagickCore, libopenslide, libpoppler
and everything those transitively pulled, incl. libgio via
libpoppler-glib) pointing at Homebrew absolute paths. At
runtime on macOS, that meant dyld loaded Homebrew copies of
libgio alongside ours, lighting up the duplicate-ObjC-class
and duplicate-GObject-type registration paths and breaking
image loads on runners whose Homebrew glib happens to match
our bundled version. The new recursive pass walks every
module's otool -L deps, copies missing transitive deps into
bundle/ at top level with @loader_path-relative install_names,
and rewrites every ref so the whole bundle is self-contained.
Follows symlinks to copy the real file (keeps the ref-site
basename). Best-effort on @rpath refs via a brew-prefix find.
Post-bundle audit fails the build if any Mach-O still
references /opt/homebrew or /usr/local/Cellar — ship-time
guarantee that no surprise Homebrew lib gets loaded at
runtime. User-visible effect: full HEIF / JXL / Magick /
OpenSlide / Poppler support in the bundled install,
previously only available on the system-libvips fallback
path.
- BINARY_TAG bumped to binaries-vips-8.18.2-r5.
- FFI.rakumod: env var setup (VIPSHOME, GIO_MODULE_DIR) now
goes through a libc setenv(3) NativeCall, not Raku's %*ENV.
Raku's %*ENV = Y on macOS writes to a Raku-internal env
table that *doesn't* propagate to Darwin's __environ array
— verified empirically: `getenv("VIPSHOME")` returns NULL
inside the same process where `%*ENV` shows a
value. Same class of issue as Windows' CRT-vs-kernel32 split
we hit earlier for DLL search. libvips / GLib / everything
linked against libc reads via getenv(3), so we go through
libc directly. %*ENV is still updated too, so subprocess
spawns inherit a correct environ. This was the root cause
of the r3/r4 macOS test failures — VIPSHOME + GIO_MODULE_DIR
were set from Raku's perspective but completely invisible
to libvips, so libvips kept falling back to its compile-time
Homebrew prefix and loading Homebrew's modules (→ Homebrew's
libgio via transitive loads → ObjC dup-class crash).
- macOS: third pass on the r3 bundle fixes. r3 extracted
cleanly and libvips loaded, but libvips then searched
Homebrew's vips-modules- dir at init time and dlopened
Homebrew's modules — dragging in Homebrew's libgio
(duplicate ObjC class registration → GIO module scan bypass
didn't help, because the modules were vips's not GIO's).
Root-caused via VIPS-INFO diagnostics:
* I wrongly assumed libvips honours a VIPS_MODULEDIR env
var. It doesn't — libvips's `vips_guess_prefix` checks
VIPSHOME → probes argv[0] on PATH → falls back to
compile-time prefix. When argv[0] is `raku`, probe
fails; with no VIPSHOME set, libvips landed at Homebrew's
compile-time prefix. FFI.rakumod now sets VIPSHOME to
the staged parent dir; respects pre-set VIPSHOME.
* Our bundled libvips.42.dylib had LC_ID_DYLIB still set
to `/opt/homebrew/opt/vips/lib/libvips.42.dylib` —
dylibbundler only rewrites deps on its -x target, not
the target's own install_name. Added an explicit
install_name_tool -id '@loader_path/libvips.42.dylib'
post-dylibbundler so dyld identifies the bundled copy
as authoritative and doesn't double-load Homebrew's.
* Bundled modules dir was named `vips-modules` — libvips
computes the path as
`$VIPSHOME/lib/vips-modules-./` with the
version suffix. Preserve the exact dir name from the
source (`$(basename $MODULES_SRC)` → `vips-modules-8.18`
for Homebrew 8.18, `vips-modules-8.18` from build-win64-
mxe) so the runtime lookup resolves.
- BINARY_TAG bumped to binaries-vips-8.18.2-r4.
- macOS: point GIO_MODULE_DIR at a guaranteed-nonexistent path
(`/dev/null/vips-native-no-gio-modules`) at module load. Our
bundled libgio was compiled with GIO_MODULE_DIR baked to the
Homebrew cellar used to produce the bundle; at g_type_init
time, GIO scanned that dir and loaded extension modules built
against the *builder's* libgio, ending with two
libgio-2.0.0.dylibs in-process and duplicate ObjC class
registration (GNotificationCenterDelegate etc.) — which macOS
treats as a fatal load error. libvips only needs GIO's core
type system (statically linked into libgio itself); the
extension modules aren't touched by our tests, so disabling
module loading is safe. Applies on Linux too (same mechanism,
just doesn't crash without ObjC) for symmetry. Respects a
pre-set GIO_MODULE_DIR if the user wants to override.
- Second pass on the r2 macOS + Windows bundle fixes. r2
shipped broken:
* macOS: the modules-pass `dylibbundler -d bundle` call
re-entered bundle/ and rewrote libvips.42.dylib's own
install_names with the modules' `@loader_path/../`
prefix, breaking sibling-dep resolution (every lookup
landed in the parent dir where nothing was staged).
Replaced with direct install_name_tool -change walks
per module — bundle/ is now read-only with respect to
the modules pass.
* Windows: %*ENV prepend didn't propagate to the
Win32 loader's DLL search. Added a direct
SetDllDirectoryW call via kernel32 (always pre-loaded)
alongside the PATH prepend so LoadLibrary finds bundled
sibling DLLs regardless of CRT/env-propagation state.
- BINARY_TAG bumped to binaries-vips-8.18.2-r3. The r2 release
artefacts remain published but are known-broken; do not
use. checksums.txt will refresh on the r3 release.
0.2.2 2026-04-16T00:29:10+01:00
- Fix macOS + Windows test failures against the
binaries-vips-8.18.2-r1 archives by reworking the bundle
layout and adding runtime env setup.
* macOS: libvips 8.15+ splits format loaders (pngload,
jpegload, heifload, …) into dlopen'd modules under
$VIPS_MODULEDIR. dylibbundler only follows link-time
deps so these never made it into the r1 bundle; libvips
loaded fine, but vips_image_new_from_file() silently
returned NULL on every format. build-binaries.yml now
stages $VIPS_CELLAR/lib/vips-modules-*/ into
bundle/vips-modules/, rebundles each module's refs via
`dylibbundler -p '@loader_path/../'`, and codesigns them.
* Windows: NativeCall loads libvips-42.dll by absolute
path, which makes Windows resolve libvips's own DLL
deps starting from raku.exe's directory rather than
libvips-42.dll's — so every sibling DLL in the staged
lib dir was invisible and libvips-42.dll failed to
load with ERROR_MOD_NOT_FOUND (0x7e). FFI.rakumod now
prepends the staged lib dir to %*ENV at module
load time (before any `is native(...)` binding fires).
build-binaries.yml also now includes bin/vips-modules/
from build-win64-mxe, which the r1 copy-glob missed.
* All platforms: FFI.rakumod sets %*ENV
to /vips-modules when the dir exists. No-op on
the conda-forge Linux build (loaders baked in), load-
bearing on macOS/Windows, forward-compatible if any
future Linux bundle source switches to split modules.
Respects VIPS_NATIVE_LIB_DIR / VIPS_MODULEDIR overrides.
- BINARY_TAG bumped to binaries-vips-8.18.2-r2 to force a
re-download; r1 archives remain broken and should not be
used. resources/checksums.txt will refresh on the next
release.
0.2.1 2026-04-16T00:01:51+01:00
- Build.rakumod: detect system glibc via `ldd --version` and
skip the prebuilt download when it's older than the prebuilt
target (currently v2.35, matching the ubuntu-22.04 CI runner).
Previously, users on Ubuntu 20.04 / Debian 11 / RHEL 8
downloaded a prebuilt libvips that loaded but failed at first
symbol use with "GLIBC_2.xx not found". The guard fires before
the download and routes to this module's existing system-
libvips fallback (Native.rakumod resolves via the OS dynamic
loader) — users just need `libvips` from their distro.
VIPS_NATIVE_BINARY_ONLY=1 now hard-fails with a clear message
on old-glibc systems rather than producing a broken install.
- New CI workflow .github/workflows/glibc-fallback.yml: runs
`zef install .` inside an ubuntu:20.04 container (glibc 2.31,
with apt-installed libvips42) and asserts both that the
fallback message appears in the build log and that vips_init()
against the system libvips succeeds.
0.2.0 2026-04-15T04:11:52+01:00
- Prebuilt-binary-first install path. Build.rakumod now attempts
to download a per-platform archive from the repo's GitHub
Releases (libvips + libgobject + libglib + the format-handling
libs for jpeg / png / webp / tiff / gif / heif / avif, all
relocated to load via @loader_path / $ORIGIN / sibling-DLL),
before falling back to the original behaviour of resolving
'vips' and 'gobject-2.0' via the OS dynamic loader from a
system-installed libvips.
- Sources we repackage from (the trust chain): Homebrew bottle
for macOS arm64, conda-forge for Linux x86_64 / aarch64,
libvips/build-win64-mxe for Windows x86_64. We don't compile
vips ourselves — the upstream sources are reputable and well-
tested, and rebuilding vips's dep tree from source in CI
would be a maintenance nightmare.
- SHA256 verification against bundled resources/checksums.txt;
refuses any prebuilt whose hash isn't recorded. Empty /
unverifiable hashes fail safe to system-libvips fallback.
- Stage native libs to $XDG_DATA_HOME/Vips-Native//
lib/ (or $LOCALAPPDATA on Windows, ~/.local/share fallback)
under their real filenames — NOT into META6 resources, because
zef hashes resource filenames and that breaks vips's inter-lib
refs (libvips needs libgobject-2.0.0.so next to it on disk by
that exact name). Same fix Notcurses-Native uses.
- Refactored lib/Vips/Native/FFI.rakumod resolver: env override
(VIPS_NATIVE_LIB_DIR) → XDG-staged dir (versioned-aware
filename matching) → bare short name for OS dynamic loader.
Drops the hard MacOS::NativeLib dependency since the resolver
now handles macOS lib lookup itself; macOS users still get
Homebrew vips just as cleanly via the fallback path.
- New BINARY_TAG file at repo root + resources/BINARY_TAG (the
tiny text file that survives zef's resource-hashing intact)
as single source of truth for the pinned binary release tag.
- New .github/workflows/build-binaries.yml: dispatches per
libvips version, downloads from each upstream source, repack-
ages with @loader_path / $ORIGIN / sibling-DLL relocation,
publishes to GitHub Releases.
- Test workflow now exercises both paths: prebuilt-via-zef-
install AND a second pass with VIPS_NATIVE_PREFER_SYSTEM=1.
- New env knobs: VIPS_NATIVE_PREFER_SYSTEM=1 to skip prebuilt
download; VIPS_NATIVE_BINARY_ONLY=1 to refuse the system
fallback; VIPS_NATIVE_BINARY_URL to override release base URL;
VIPS_NATIVE_CACHE_DIR for download cache; VIPS_NATIVE_DATA_
DIR for staged-libs base; VIPS_NATIVE_LIB_DIR for runtime
lib-dir override.
- All five platforms in the prebuilt matrix: macOS arm64, Linux
x86_64 + aarch64 glibc, Windows x86_64 + arm64.
0.1.3 2025-07-10T16:06:03+01:00
- Document script usage
- Make script die with a useful message when params not provided
0.1.2 2025-07-10T15:48:10+01:00
- Add vips install info to readme
0.1.1 2025-07-10T15:45:21+01:00
- Remove windows from test runner, installing vips on github actions is too awkward
0.1.0 2025-07-10T15:43:17+01:00
- Initial version