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>
This commit is contained in:
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
# Builds money with a static pdftotext inside it: one file to copy to a server,
|
||||
# with nothing to install there — not even poppler-utils.
|
||||
#
|
||||
# scripts/build-bundled.sh # linux, this machine's architecture
|
||||
# scripts/build-bundled.sh arm64 # linux/arm64 (podman/docker need qemu)
|
||||
#
|
||||
# pdftotext is built in a container from a checksum-pinned poppler release
|
||||
# (scripts/pdftotext.Containerfile) and cached under internal/parser/bundled/,
|
||||
# so later runs only rebuild money. Delete that file to rebuild it.
|
||||
set -eu
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
arch=${1:-$(go env GOARCH)}
|
||||
case $arch in
|
||||
amd64 | arm64) ;;
|
||||
*)
|
||||
echo "build-bundled: no static pdftotext for linux/$arch; use amd64 or arm64" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
bin=internal/parser/bundled/pdftotext-linux-$arch
|
||||
if [ ! -f "$bin" ] || [ scripts/pdftotext.Containerfile -nt "$bin" ]; then
|
||||
engine=$(command -v podman || command -v docker) || {
|
||||
echo "build-bundled: building pdftotext needs podman or docker" >&2
|
||||
exit 1
|
||||
}
|
||||
out=$(mktemp -d)
|
||||
trap 'rm -rf "$out"' EXIT
|
||||
"$engine" build --platform "linux/$arch" -f scripts/pdftotext.Containerfile \
|
||||
--output "type=local,dest=$out" scripts
|
||||
mkdir -p internal/parser/bundled
|
||||
mv "$out/pdftotext" "$bin"
|
||||
fi
|
||||
|
||||
mkdir -p dist
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=$arch \
|
||||
go build -tags bundled -trimpath -o "dist/money-linux-$arch" ./cmd/money
|
||||
echo "dist/money-linux-$arch"
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user