@@ -62,37 +62,47 @@ RUN cargo build --release --bin echidna
6262# #############################################################################
6363FROM docker.io/library/debian:bookworm-slim AS tamarin-installer
6464
65+ # maude is REQUIRED here, not just in the runtime stage: tamarin-prover
66+ # shells out to `maude --version` *before* printing its own version, so the
67+ # in-builder smoke-test below cannot pass without it. (This — NOT a dead URL —
68+ # was why tamarin always shipped a stub: the GitHub download succeeded but the
69+ # `--version` smoke-test exited 1 on missing maude. Brittle-pin tracking: #75.)
6570RUN apt-get update && apt-get install -y --no-install-recommends \
6671 curl \
6772 ca-certificates \
68- wget \
69- file \
73+ maude \
7074 && rm -rf /var/lib/apt/lists/*
7175
72- WORKDIR /opt/tamarin
76+ # Official prebuilt binary from the tamarin-prover/tamarin-prover GitHub
77+ # releases (GPL-3.0; anonymous; reliable). The linux64-ubuntu tarball contains
78+ # a single dynamically-linked x86_64 `tamarin-prover` at the archive root;
79+ # its only shared-lib deps are libm/libz/libgmp10/libc. It is a Maude
80+ # front-end and needs `maude` (hard) + graphviz `dot` (visualisation) at
81+ # runtime. Re-validate at https://github.com/tamarin-prover/tamarin-prover/releases.
82+ ARG TAMARIN_VERSION="1.12.0"
83+ ARG TAMARIN_TARBALL="tamarin-prover-${TAMARIN_VERSION}-linux64-ubuntu.tar.gz"
84+ ARG TAMARIN_URL="https://github.com/tamarin-prover/tamarin-prover/releases/download/${TAMARIN_VERSION}/${TAMARIN_TARBALL}"
7385
74- # Download prebuilt static binary from the tamarin-prover/tamarin-prover GitHub releases.
75- # The release tarball contains a static Linux x86_64 binary that runs without Haskell.
76- # Check https://github.com/tamarin-prover/tamarin-prover/releases for the latest version.
77- ARG TAMARIN_VERSION="1.8.0"
86+ # Deterministic export tree so the runtime COPY never hard-fails: the real
87+ # binary if download + smoke-test succeed, a stub otherwise.
7888RUN set -eux; \
79- TARBALL=" tamarin-prover-${TAMARIN_VERSION}-linux64-ubuntu.tar.gz" ; \
80- URL="https://github.com/tamarin-prover/tamarin-prover/releases/download/${TAMARIN_VERSION}/${TARBALL}"; \
81- curl -fsSL --retry 3 "${URL}" -o /tmp/tamarin.tar.gz \
82- && tar -xzf /tmp /tamarin.tar.gz -C /opt/ tamarin \
83- && rm /tmp /tamarin.tar.gz \
84- && chmod +x /opt/ tamarin/tamarin -prover \
85- && /opt/tamarin/tamarin-prover --version \
86- || { \
87- echo "WARNING: Prebuilt tamarin-prover binary download failed ."; \
88- echo "Fallback: build via Haskell Stack (uncomment the block below) ."; \
89- echo "Stack build takes several hours and ~8 GB RAM." ; \
90- echo "See: https:// tamarin-prover.com/installation/"; \
91- mkdir -p /opt/tamarin; \
92- printf '#!/bin/sh\necho " tamarin-prover not available (prebuilt download failed at image build time)"\nexit 1\n' \
93- > /opt/tamarin/tamarin-prover ; \
94- chmod +x /opt /tamarin/tamarin-prover ; \
95- }
89+ mkdir -p /opt/ tamarin-export ; \
90+ if curl -fsSL --retry 3 "${TAMARIN_URL}" -o /tmp/tamarin.tar.gz \
91+ && tar -xzf /tmp/tamarin.tar.gz -C /opt/tamarin-export \
92+ && chmod +x /opt /tamarin-export/ tamarin-prover \
93+ && /opt /tamarin-export/tamarin-prover --version; then \
94+ echo " tamarin-prover ${TAMARIN_VERSION} installed."; \
95+ else \
96+ echo "WARNING: tamarin-prover download/smoke-test failed."; \
97+ echo "Re-check https://github.com/ tamarin-prover/tamarin-prover/releases ."; \
98+ echo "Creating stub binary as fallback ."; \
99+ mkdir -p /opt/tamarin-export ; \
100+ printf '#!/bin/sh\necho " tamarin-prover not available (bundle install failed at image build time)"\nexit 1\n' \
101+ > /opt/tamarin-export/tamarin-prover ; \
102+ chmod +x /opt/ tamarin-export/tamarin- prover; \
103+ fi ; \
104+ rm -f /tmp /tamarin.tar.gz ; \
105+ touch /opt/tamarin-export/.keep
96106
97107# --- Fallback: Haskell Stack build (uncomment if prebuilt binary is unavailable) ---
98108# RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -113,20 +123,25 @@ LABEL org.opencontainers.image.licenses="PMPL-1.0-or-later"
113123LABEL org.opencontainers.image.version="2.3.0"
114124LABEL org.opencontainers.image.vendor="hyperpolymath"
115125
116- # Tamarin runtime deps: graphviz for its visualisation outputs, maude is the
117- # underlying rewriting engine tamarin links against (bundled in the static
118- # binary but maude files are loaded from the filesystem).
126+ # Tamarin runtime deps. The prebuilt binary is DYNAMICALLY linked (libm,
127+ # libz, libgmp10, libc) and is a Maude front-end: `maude` is a hard runtime
128+ # requirement (NOT bundled in the binary), graphviz `dot` is needed for
129+ # visualisation output.
119130RUN apt-get update && apt-get install -y --no-install-recommends \
120131 ca-certificates \
121- libssl3 \
122132 libgmp10 \
133+ zlib1g \
134+ maude \
123135 graphviz \
124136 && rm -rf /var/lib/apt/lists/*
125137
126138WORKDIR /app
127139
128140COPY --from=rust-builder /build/target/release/echidna /app/bin/echidna
129- COPY --from=tamarin-installer /opt/tamarin/tamarin-prover /usr/local/bin/tamarin-prover
141+ # Deterministic export tree (real binary if the GitHub download + smoke-test
142+ # succeeded, a one-file stub otherwise). A directory COPY never hard-fails.
143+ COPY --from=tamarin-installer /opt/tamarin-export/ /opt/tamarin/
144+ RUN ln -sf /opt/tamarin/tamarin-prover /usr/local/bin/tamarin-prover
130145
131146ENV PATH="/app/bin:/usr/local/bin:${PATH}"
132147ENV ECHIDNA_PROVER_PATH="/usr/local/bin"
@@ -547,23 +562,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
547562 gcc \
548563 && rm -rf /var/lib/apt/lists/*
549564
550- WORKDIR /build
551-
552- # Clone ACL2 from the official GitHub mirror.
553- # Use a versioned tag for reproducibility.
565+ # Clone ACL2 directly at its FINAL runtime path. ACL2 is NOT relocatable:
566+ # `make LISP=sbcl` writes a saved_acl2 wrapper that bakes the absolute path
567+ # to saved_acl2.core, and the SBCL core bakes the absolute ACL2 source/books
568+ # directory. Building at /build/acl2 then COPYing to /opt/acl2 produced a
569+ # saved_acl2 whose wrapper exec'd /build/acl2/saved_acl2.core — "open: No
570+ # such file or directory" at runtime. Build in-place at /opt/acl2 so the
571+ # baked paths are correct in the runtime image (ACL2_SYSTEM_BOOKS=/opt/acl2/books).
554572# Check https://github.com/acl2/acl2/releases for the latest version.
555573ARG ACL2_VERSION="8.6"
556574RUN git clone --depth 1 --branch "v${ACL2_VERSION}" \
557- https://github.com/acl2/acl2.git /build /acl2 \
575+ https://github.com/acl2/acl2.git /opt /acl2 \
558576 || git clone --depth 1 \
559- https://github.com/acl2/acl2.git /build /acl2
577+ https://github.com/acl2/acl2.git /opt /acl2
560578
561- WORKDIR /build /acl2
579+ WORKDIR /opt /acl2
562580
563581# Build ACL2 using SBCL. The `make` target `LISP=sbcl` compiles ACL2 and
564582# saves a SBCL core image. The resulting saved_acl2 is the executable image.
565583RUN make LISP=sbcl -j"$(nproc)" \
566- && test -f /build/acl2/saved_acl2 \
584+ && test -f /opt/acl2/saved_acl2 \
585+ && test -f /opt/acl2/saved_acl2.core \
567586 && echo "ACL2 build succeeded"
568587
569588# Create the acl2 wrapper script (mirrors the standard install convention)
@@ -591,7 +610,9 @@ WORKDIR /app
591610COPY --from=rust-builder /build/target/release/echidna /app/bin/echidna
592611
593612# Copy ACL2 saved image and source tree (ACL2 loads books from its source tree)
594- COPY --from=acl2-builder /build/acl2 /opt/acl2
613+ # Built in-place at /opt/acl2 → the saved_acl2 wrapper's baked core path and
614+ # the core's baked source/books root stay valid at runtime.
615+ COPY --from=acl2-builder /opt/acl2 /opt/acl2
595616
596617# Install the acl2 wrapper script
597618RUN printf '#!/bin/sh\nexec /opt/acl2/saved_acl2 "$@"\n' > /usr/local/bin/acl2 \
@@ -627,17 +648,24 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
627648 smlnj \
628649 && rm -rf /var/lib/apt/lists/*
629650
630- WORKDIR /build
651+ # Clone+build Twelf directly at its FINAL runtime path. SML/NJ heap images
652+ # are NOT relocatable: `make smlnj` bakes the absolute build directory into
653+ # bin/twelf-server's `sml @SMLload=...` and into bin/.heap/twelf-server.
654+ # Building at /build/twelf then COPYing to /opt/twelf produced a twelf-server
655+ # whose heap path /build/twelf/bin/.heap/twelf-server dangled at runtime
656+ # ("unable to open heap image"). Build in-place at /opt/twelf so the baked
657+ # paths are correct in the runtime image (which also uses TWELFDIR=/opt/twelf).
658+ WORKDIR /opt
631659
632660# Clone the Twelf source from the official repository.
633661# The canonical upstream is hosted at https://github.com/standardml/twelf.
634662# Twelf does not use versioned tags; we pin to the tip of the main branch and
635663# note the commit in the label below. For strict reproducibility, replace
636664# `--depth 1` with `--branch <commit-sha>`.
637665RUN git clone --depth 1 \
638- https://github.com/standardml/twelf.git /build /twelf
666+ https://github.com/standardml/twelf.git /opt /twelf
639667
640- WORKDIR /build /twelf
668+ WORKDIR /opt /twelf
641669
642670# Build Twelf using SML/NJ's ml-build.
643671# The Makefile target `smlnj` compiles a heap image; `twelf-server` is a
@@ -670,7 +698,9 @@ WORKDIR /app
670698COPY --from=rust-builder /build/target/release/echidna /app/bin/echidna
671699
672700# Copy the Twelf installation (src tree + compiled heap + wrapper scripts)
673- COPY --from=twelf-builder /build/twelf /opt/twelf
701+ # Built in-place at /opt/twelf → the baked absolute heap path in
702+ # bin/twelf-server / bin/.heap/twelf-server stays valid at runtime.
703+ COPY --from=twelf-builder /opt/twelf /opt/twelf
674704
675705# The twelf-server wrapper script references $TWELFDIR; make it a proper
676706# system binary by symlinking.
@@ -693,8 +723,55 @@ ENTRYPOINT ["/app/bin/echidna"]
693723CMD ["--help"]
694724
695725# #############################################################################
696- # TARGET: metamath — pure-Rust in-process verifier + bundled databases
726+ # TARGET: metamath — upstream metamath-exe CLI verifier + bundled databases
697727# #############################################################################
728+
729+ # metamath-exe — the upstream Metamath CLI verifier. The ECHIDNA Metamath
730+ # backend (src/rust/provers/metamath.rs) shells out to `metamath` and drives
731+ # `read "<file>" / verify proof * / exit` over stdin, so a REAL binary must be
732+ # on PATH at runtime. The previous stage installed NO metamath binary (the
733+ # "pure-Rust" comment was wrong) → "metamath: not found" at runtime.
734+ # metamath-exe is a tiny pure-C program (libc only); the GitHub source tarball
735+ # is the reliable anonymous source. NOTE: metamath-exe publishes NO GitHub
736+ # Releases — only git tags; do not query releases/latest.
737+ # Re-validate the tag at https://github.com/metamath/metamath-exe/tags (#75).
738+ FROM docker.io/library/debian:bookworm-slim AS metamath-builder
739+
740+ RUN apt-get update && apt-get install -y --no-install-recommends \
741+ curl \
742+ ca-certificates \
743+ gcc \
744+ make \
745+ && rm -rf /var/lib/apt/lists/*
746+
747+ ARG MM_VERSION="0.198"
748+ ARG MM_TARBALL="v${MM_VERSION}.tar.gz"
749+ ARG MM_URL="https://github.com/metamath/metamath-exe/archive/refs/tags/${MM_TARBALL}"
750+
751+ # Deterministic export tree so the runtime COPY never hard-fails: the real
752+ # binary if download + build + smoke-test succeed, a stub otherwise. The
753+ # GitHub tarball ships no generated ./configure; the README-documented
754+ # `gcc m*.c -o metamath` path is dependency-free and authoritative.
755+ RUN set -eux; \
756+ mkdir -p /opt/mm-export/bin; \
757+ if curl -fsSL --retry 3 "${MM_URL}" -o /tmp/mm.tgz \
758+ && tar -xzf /tmp/mm.tgz -C /tmp \
759+ && ( cd "/tmp/metamath-exe-${MM_VERSION}" \
760+ && gcc m*.c -o metamath -O2 \
761+ && printf 'exit\n' | ./metamath ) ; then \
762+ cp "/tmp/metamath-exe-${MM_VERSION}/metamath" /opt/mm-export/bin/metamath; \
763+ else \
764+ echo "WARNING: metamath-exe download/build/smoke-test failed."; \
765+ echo "Re-check https://github.com/metamath/metamath-exe/tags."; \
766+ echo "Creating stub binary as fallback."; \
767+ printf '#!/bin/sh\necho "metamath not available (build failed at image build time)"\nexit 1\n' \
768+ > /opt/mm-export/bin/metamath; \
769+ chmod +x /opt/mm-export/bin/metamath; \
770+ fi; \
771+ chmod +x /opt/mm-export/bin/metamath; \
772+ rm -rf /tmp/mm.tgz "/tmp/metamath-exe-${MM_VERSION}"; \
773+ touch /opt/mm-export/.keep
774+
698775FROM docker.io/library/debian:bookworm-slim AS mm-db-fetcher
699776
700777RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -725,13 +802,14 @@ FROM docker.io/library/debian:bookworm-slim AS metamath
725802
726803LABEL maintainer="Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>"
727804LABEL org.opencontainers.image.source="https://github.com/hyperpolymath/echidna"
728- LABEL org.opencontainers.image.description="ECHIDNA + Metamath - pure-Rust in-process verifier with bundled databases"
805+ LABEL org.opencontainers.image.description="ECHIDNA + Metamath - metamath-exe CLI verifier with bundled databases"
729806LABEL org.opencontainers.image.licenses="PMPL-1.0-or-later"
730807LABEL org.opencontainers.image.version="2.3.0"
731808LABEL org.opencontainers.image.vendor="hyperpolymath"
732809
733- # Metamath is a pure-Rust verifier with no external binary; runtime deps
734- # are only those needed by the ECHIDNA binary itself.
810+ # The Metamath backend shells out to the metamath-exe CLI (installed to
811+ # /usr/local/bin below). metamath-exe is pure C / libc-only, so runtime
812+ # deps here are only those needed by the ECHIDNA Rust binary itself.
735813RUN apt-get update && apt-get install -y --no-install-recommends \
736814 ca-certificates \
737815 libssl3 \
@@ -742,6 +820,12 @@ WORKDIR /app
742820
743821COPY --from=rust-builder /build/target/release/echidna /app/bin/echidna
744822
823+ # The metamath-exe CLI verifier. The Rust backend execs bare `metamath`
824+ # (provers/mod.rs default_executable), resolved via PATH; /usr/local/bin is
825+ # on the default Debian PATH. The export tree always contains a file (real
826+ # binary or stub) so this single-file COPY never hard-fails.
827+ COPY --from=metamath-builder /opt/mm-export/bin/metamath /usr/local/bin/metamath
828+
745829# Bundle the canonical Metamath databases at a well-known path.
746830# ECHIDNA reads ECHIDNA_METAMATH_DB_DIR at startup to pre-index databases.
747831COPY --from=mm-db-fetcher /opt/metamath-db /app/metamath-db
0 commit comments