Install libvips JPEG XL support and require it at startup (part of #222) #226

Merged
clawbot merged 1 commits from issue-222-jxl-build-support into next 2026-10-08 06:19:06 +02:00
7 changed files with 113 additions and 11 deletions
+6 -3
View File
@@ -22,8 +22,9 @@ FROM golang:1.25.4-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66
WORKDIR /src WORKDIR /src
# script/bootstrap --cgo installs the build dependencies (a C compiler # script/bootstrap --cgo installs the build dependencies (a C compiler,
# and the libvips and libheif headers) and downloads the Go modules. # the libvips and libheif headers, and libvips' JPEG XL support, which
# the tests need) and downloads the Go modules.
COPY script/ ./script/ COPY script/ ./script/
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN script/bootstrap --cgo RUN script/bootstrap --cgo
@@ -80,9 +81,11 @@ RUN version="${VERSION:-$(git describe --tags --always)}"; \
# alpine:3.21, 2026-02-25 # alpine:3.21, 2026-02-25
FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 FROM alpine:3.21@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
# Install runtime dependencies only # Install runtime dependencies only. vips-jxl is libvips' JPEG XL
# support, without which pixad does not start.
RUN apk add --no-cache \ RUN apk add --no-cache \
vips \ vips \
vips-jxl \
libheif \ libheif \
ca-certificates \ ca-certificates \
tzdata \ tzdata \
+6 -3
View File
@@ -89,7 +89,10 @@ another part of pixa failed to stop. A request not finished by then is cut off.
`docker stop` waits 10 seconds before it kills the container. `docker stop` waits 10 seconds before it kills the container.
Outside Docker, pixa needs libvips (the image has 8.15) and libheif to run, as Outside Docker, pixa needs libvips (the image has 8.15) and libheif to run, as
it uses libvips through CGO; building it also needs their development files, it uses libvips through CGO. pixad does not start unless libvips has its JPEG XL
support, which on Alpine is the `vips-jxl` package and which the nix and brew
packages of libvips include, as do the apt ones from Debian 12 and Ubuntu 24.04
on. Building pixa also needs the development files of libvips and libheif,
`pkg-config` and a C compiler. `script/bootstrap --cgo` installs all of these, `pkg-config` and a C compiler. `script/bootstrap --cgo` installs all of these,
as the `Dockerfile` does where it compiles pixa. Plain `script/bootstrap`, which as the `Dockerfile` does where it compiles pixa. Plain `script/bootstrap`, which
`script/setup` and `script/cibuild` run, installs git, make and Go, and Node, `script/setup` and `script/cibuild` run, installs git, make and Go, and Node,
@@ -585,8 +588,8 @@ provide:
- `script/bootstrap` — install git, make, Go, Node, Yarn and prettier and - `script/bootstrap` — install git, make, Go, Node, Yarn and prettier and
download the Go modules (idempotent); with `--cgo`, the C compiler and the download the Go modules (idempotent); with `--cgo`, the C compiler and the
libvips and libheif libraries that compiling pixa needs instead of Node, Yarn libvips (with its JPEG XL support) and libheif libraries that compiling and
and prettier testing pixa need instead of Node, Yarn and prettier
- `script/setup` — make a fresh clone ready for development (bootstrap, then - `script/setup` — make a fresh clone ready for development (bootstrap, then
install-precommit) install-precommit)
- `script/projectname` — output the project name ("pixa") - `script/projectname` — output the project name ("pixa")
+8
View File
@@ -30,6 +30,14 @@ P2: security: per-IP rate limiting on the image routes
# Completed Steps # Completed Steps
- 2026-10-08 libvips' JPEG XL support is installed and required (part of #222):
`script/bootstrap --cgo` installs `vips-jxl` when its package manager is apk,
as Alpine's `vips` package lacks the support, and the runtime stage of the
`Dockerfile` installs it too. `imgcache.NewService` fails, naming the fix,
when `imageprocessor.CheckJPEGXLSupport` finds that libvips cannot load and
save JPEG XL, so pixad does not start without it. A test saves an image as
JPEG XL with govips and loads it back. JPEG XL is not yet a format pixa
serves.
- 2026-10-08 SQLite writes no longer fail with "database is locked" under load - 2026-10-08 SQLite writes no longer fail with "database is locked" under load
(closes #223): `internal/database` opens the database with one connection, so (closes #223): `internal/database` opens the database with one connection, so
pixa's own reads and writes run on it one at a time instead of competing for pixa's own reads and writes run on it one at a time instead of competing for
+18
View File
@@ -37,6 +37,24 @@ func initVips() {
}) })
} }
// errNoJPEGXL is returned by CheckJPEGXLSupport.
var errNoJPEGXL = errors.New("libvips lacks JPEG XL support: install " +
"vips-jxl on Alpine, or use a libvips built with libjxl")
// CheckJPEGXLSupport returns an error, naming the fix, when libvips
// cannot load and save JPEG XL.
func CheckJPEGXLSupport() error {
initVips()
// govips counts a format as supported when libvips has its loader;
// libvips builds the JPEG XL loader and saver together.
if !vips.IsTypeSupported(vips.ImageTypeJXL) {
return errNoJPEGXL
}
return nil
}
// Format represents supported output image formats. // Format represents supported output image formats.
type Format string type Format string
@@ -0,0 +1,52 @@
package imageprocessor
import (
"testing"
"github.com/davidbyttow/govips/v2/vips"
)
// TestCheckJPEGXLSupport fails when libvips lacks JPEG XL support, as on
// Alpine without the vips-jxl package.
func TestCheckJPEGXLSupport(t *testing.T) {
t.Parallel()
err := CheckJPEGXLSupport()
if err != nil {
t.Fatalf("CheckJPEGXLSupport() error = %v", err)
}
}
// TestLibvipsSavesAndLoadsJPEGXL saves an image as JPEG XL with govips and
// loads it back. It fails when libvips lacks JPEG XL support, as on Alpine
// without the vips-jxl package.
func TestLibvipsSavesAndLoadsJPEGXL(t *testing.T) {
t.Parallel()
img, err := vips.NewImageFromBuffer(createTestJPEG(t, 64, 48))
if err != nil {
t.Fatalf("failed to load test JPEG: %v", err)
}
defer img.Close()
jxl, _, err := img.ExportJxl(vips.NewJxlExportParams())
if err != nil {
t.Fatalf("ExportJxl() error = %v", err)
}
loaded, err := vips.NewImageFromBuffer(jxl)
if err != nil {
t.Fatalf("failed to load the JPEG XL image: %v", err)
}
defer loaded.Close()
if loaded.Format() != vips.ImageTypeJXL {
t.Errorf("loaded format = %s, want jxl", vips.ImageTypes[loaded.Format()])
}
if loaded.Width() != 64 || loaded.Height() != 48 {
t.Errorf("loaded size = %dx%d, want 64x48", loaded.Width(), loaded.Height())
}
}
+7
View File
@@ -100,6 +100,13 @@ func NewService(cfg *ServiceConfig) (*Service, error) {
allowHTTP = cfg.FetcherConfig.AllowHTTP allowHTTP = cfg.FetcherConfig.AllowHTTP
} }
// JPEG XL is to become the default output format, so pixad does not
// start without it.
err := imageprocessor.CheckJPEGXLSupport()
if err != nil {
return nil, err
}
maxResponseSize := fetcherCfg.MaxResponseSize maxResponseSize := fetcherCfg.MaxResponseSize
processor := imageprocessor.New(imageprocessor.Params{ processor := imageprocessor.New(imageprocessor.Params{
MaxInputBytes: maxResponseSize, MaxInputBytes: maxResponseSize,
+16 -5
View File
@@ -14,11 +14,12 @@
# script/fmt-check: all the host needs, as # script/fmt-check: all the host needs, as
# the checks compile pixa in Docker # the checks compile pixa in Docker
# script/bootstrap --cgo git, make, Go, and a C compiler and the # script/bootstrap --cgo git, make, Go, and a C compiler and the
# CGO image libraries (pkg-config, vips, # CGO image libraries (pkg-config, vips
# libheif) for the govips bindings instead # with its JPEG XL support, libheif) for
# of Node: to compile pixa, in the # the govips bindings instead of Node: to
# Dockerfile's test phase and build stage, # compile pixa, in the Dockerfile's test
# which format nothing # phase and build stage, which format
# nothing
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
@@ -150,6 +151,16 @@ ensure_cgo_deps() {
if ! pkg-config --exists vips; then if ! pkg-config --exists vips; then
pkg_install vips libvips-dev vips vips-dev pkg_install vips libvips-dev vips vips-dev
fi fi
# libvips' JPEG XL loader and saver are in the nix and brew vips
# packages, and in apt's from Debian 12 and Ubuntu 24.04 on, but in
# the package vips-jxl on Alpine. detect_pkgmgr is called only where
# apk exists, as on apt it updates the package lists.
if ! missing apk; then
detect_pkgmgr
fi
if [ "$PKGMGR" = "apk" ] && ! apk info -e vips-jxl >/dev/null; then
apk add --no-cache vips-jxl
fi
if ! pkg-config --exists libheif; then if ! pkg-config --exists libheif; then
pkg_install libheif libheif-dev libheif libheif-dev pkg_install libheif libheif-dev libheif libheif-dev
fi fi