Rand Stats

Vips::Native

zef:apogee
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