Files
money/scripts/pdftotext.Containerfile
T
nikolaandClaude Opus 5.5 5847245638 Bundle a static pdftotext into the binary
The PDF parsers shell out to pdftotext, so every machine running money needed
poppler-utils installed. scripts/build-bundled.sh now builds a deployable
binary that carries its own: pdftotext is compiled in a container from a
checksum-pinned poppler release as a fully static musl executable, then
embedded with `go build -tags bundled`. The result is one file that runs on
any Linux of that architecture with nothing installed alongside it.

It is still the real pdftotext, run as a subprocess. Linking poppler through
cgo would have cost the pure-Go build, and its C++ text API is not guaranteed
to space columns the way pdftotext -layout does, which is what the parsers
were tuned on. Only what text extraction needs is compiled in -- no
fontconfig, cairo or image codecs -- and its output is byte-identical to a
full distro build on the same PDF.

At runtime the embedded copy is written to the user cache directory, not
/tmp, which servers often mount noexec. It is named by content hash, so a
newer build never runs an older copy, and verified before reuse, so a write cut
short by a killed process is replaced rather than trusted. `money config` says
which pdftotext is in use.

The tag is opt-in: plain go build and go test never need the 5 MB executable,
which is gitignored rather than committed. Building with the tag for anything
but linux/amd64 or linux/arm64 fails with a message saying so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:02:46 +02:00

71 lines
2.7 KiB
Docker

# Builds a fully static pdftotext for `go build -tags bundled`, so the money
# binary can carry it rather than depend on poppler-utils on the host.
#
# Only what text extraction needs is compiled in. Fonts are never rendered, so
# fontconfig, cairo and the image codecs (JPEG, JPEG 2000, TIFF) are left out,
# as are colour management, HTTP and signature support. Freetype and zlib are
# the two dependencies poppler will not build without.
#
# Built against musl because glibc cannot be linked statically in any way that
# survives a different host.
#
# Run it through scripts/build-bundled.sh rather than by hand.
FROM docker.io/library/alpine:3.22 AS build
RUN apk add --no-cache build-base cmake samurai pkgconf \
freetype-dev freetype-static zlib-dev zlib-static \
libpng-dev libpng-static bzip2-static brotli-static
# Pinned by checksum: this binary ends up inside money, so the source it is
# built from must be exactly the one that was reviewed.
ARG POPPLER_VERSION=26.09.0
ARG POPPLER_SHA256=8059eadb6805340768f138c465b57f8164c92b4a0773c37ef031ea6c0d987b2e
WORKDIR /src
RUN wget -q "https://poppler.freedesktop.org/poppler-${POPPLER_VERSION}.tar.xz" \
&& echo "${POPPLER_SHA256} poppler-${POPPLER_VERSION}.tar.xz" | sha256sum -c - \
&& tar xf "poppler-${POPPLER_VERSION}.tar.xz" --strip-components=1 \
&& rm "poppler-${POPPLER_VERSION}.tar.xz"
# The find modules would otherwise settle on the shared libraries, and freetype's static
# archive does not carry its own dependencies (png, bzip2, brotli), so they are
# appended from what pkg-config says a static freetype needs.
RUN cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DFREETYPE_LIBRARY_RELEASE=/usr/lib/libfreetype.a \
-DPNG_LIBRARY_RELEASE=/usr/lib/libpng.a \
-DZLIB_LIBRARY_RELEASE=/usr/lib/libz.a \
-DCMAKE_EXE_LINKER_FLAGS=-static \
-DCMAKE_CXX_STANDARD_LIBRARIES="$(pkg-config --static --libs freetype2)" \
-DFONT_CONFIGURATION=generic \
-DENABLE_UTILS=ON \
-DENABLE_CPP=OFF \
-DENABLE_GLIB=OFF \
-DENABLE_GOBJECT_INTROSPECTION=OFF \
-DENABLE_QT5=OFF \
-DENABLE_QT6=OFF \
-DENABLE_BOOST=OFF \
-DENABLE_LIBOPENJPEG=OFF \
-DENABLE_LIBJPEG=OFF \
-DENABLE_LCMS=OFF \
-DENABLE_LIBCURL=OFF \
-DENABLE_LIBTIFF=OFF \
-DENABLE_NSS3=OFF \
-DENABLE_GPGME=OFF \
-DENABLE_HARFBUZZ=OFF \
-DBUILD_GTK_TESTS=OFF \
-DBUILD_QT5_TESTS=OFF \
-DBUILD_QT6_TESTS=OFF \
-DBUILD_CPP_TESTS=OFF \
-DBUILD_MANUAL_TESTS=OFF \
&& cmake --build build --target pdftotext \
&& strip build/utils/pdftotext \
# Refuse to hand over anything that still wants a dynamic loader.
&& ! readelf -l build/utils/pdftotext | grep -q INTERP \
&& build/utils/pdftotext -v
FROM scratch
COPY --from=build /src/build/utils/pdftotext /pdftotext