Use Podman for container builds
ober
08e60d4cd40e10bae8bbd6ef8b8e685a413cbe6e
new file mode 100644 --- /dev/null +++ b/.containerignore @@ -0,0 +1,17 @@ +vendor/jerboa/ +vendor/jerboa-shell/ +vendor/jerboa-aws/ +vendor/jerboa-pcre2/ +vendor/jerboa-scintilla/ +vendor/jerboa-qt/ +vendor/jerboa-ssl/ +vendor/jerboa-https/ + +.git/ +*.o +*.so +*.dylib +*.wpo +*.boot +jemacs +jemacs-qt deleted file mode 100644 --- a/.dockerignore +++ /dev/null @@ -1,17 +0,0 @@ -vendor/jerboa/ -vendor/jerboa-shell/ -vendor/jerboa-aws/ -vendor/jerboa-pcre2/ -vendor/jerboa-scintilla/ -vendor/jerboa-qt/ -vendor/jerboa-ssl/ -vendor/jerboa-https/ - -.git/ -*.o -*.so -*.dylib -*.wpo -*.boot -jemacs -jemacs-qt --- a/AGENTS.md +++ b/AGENTS.md @@ -371,7 +371,6 @@ improved versions of the above. When working in a Jerboa project, **ONLY modify files in the current repo** unless the user explicitly names another path. Common sibling repos that exist but must NOT be touched without explicit instruction: -- `~/mine/jerboa-emacs` — **NEVER touch**. Another model owns it. - `~/mine/jerboa-mcp` — Legacy node MCP, superseded. The active MCP server now lives in THIS repo at `mcp/` + `data/`. Don't modify the legacy repo unless told. - `~/mine/jerboa-shell` — Only modify when user explicitly says to work there. - `~/mine/gerbil-mcp` — **NEVER touch**. Deprecated. @@ -414,8 +413,8 @@ Run `jerboa_stale_static` to detect stale `.so` files before debugging "why does **ALWAYS** run a clean build **before** committing any code to this repository. Pick the right target for the *current* platform: -- **Linux**: run `make docker-build` — the Docker image must build cleanly against the full musl-static release pipeline. -- **macOS / FreeBSD / other**: run `make binary` — the native local build must succeed. Do **not** run `make docker-build` here; Docker on non-Linux hosts is slow and not the canonical pipeline for those platforms. +- **Linux**: run `make static-qt` — the Podman image must build cleanly against the full musl-static release pipeline. +- **macOS / FreeBSD / other**: run `make binary` — the native local build must succeed. Do **not** run `make static-qt` here; Podman on non-Linux hosts is slow and not the canonical pipeline for those platforms. Do not commit if the build fails. new file mode 100644 --- /dev/null +++ b/Containerfile @@ -0,0 +1,337 @@ +# Intermediate image for jemacs-qt static builds. +# Bakes static Qt6 + QScintilla from source, plus all Chez Scheme +# dependencies (jerboa, jsh, jerboa-pcre2, jerboa-scintilla, +# jerboa-qt) so that subsequent jemacs-qt builds only compile jemacs +# itself (~5-10 min instead of ~30 min). +# +ARG ARCH=x86_64 +FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d + +# ── Phase 1: Alpine build deps ────────────────────────────────────────── +RUN apk add --no-cache \ + su-exec \ + cmake samurai perl python3 linux-headers patchelf \ + libxcb-dev xcb-util-dev xcb-util-image-dev \ + xcb-util-keysyms-dev xcb-util-renderutil-dev \ + xcb-util-wm-dev xcb-util-cursor-dev \ + libx11-dev libxkbcommon-dev \ + fontconfig-dev freetype-dev harfbuzz-dev \ + libpng-dev zlib-dev mesa-dev \ + pcre2-dev \ + at-spi2-core-dev libdrm-dev \ + zlib-static libxcb-static \ + fontconfig-static freetype-static harfbuzz-static \ + libpng-static bzip2-static expat-static brotli-static \ + libx11-static graphite2-static libxkbcommon-static \ + ncurses-dev ncurses-static \ + util-linux-dev util-linux-static \ + libvterm-dev libvterm-static \ + gcc g++ binutils make git curl wget + +# Build static libXau (no Alpine -static package available) +ARG LIBXAU_SHA256=74d0e4dfa3d39ad8939e99bda37f5967aba528211076828464d2777d477fc0fb +RUN apk add --no-cache libxau-dev && \ + cd /tmp && \ + wget -q https://xorg.freedesktop.org/releases/individual/lib/libXau-1.0.12.tar.xz && \ + echo "${LIBXAU_SHA256} libXau-1.0.12.tar.xz" | sha256sum -c - && \ + tar xf libXau-1.0.12.tar.xz && \ + cd libXau-1.0.12 && \ + ./configure --prefix=/usr --enable-static --disable-shared && \ + make -j$(nproc) && make install && \ + cd / && rm -rf /tmp/libXau-1.0.12* + +# Build static libxcb-util (no Alpine -static package; needed by xcb-image) +ARG XCB_UTIL_SHA256=5abe3bbbd8e54f0fa3ec945291b7e8fa8cfd3cccc43718f8758430f94126e512 +RUN cd /tmp && \ + wget -q https://xcb.freedesktop.org/dist/xcb-util-0.4.1.tar.xz && \ + echo "${XCB_UTIL_SHA256} xcb-util-0.4.1.tar.xz" | sha256sum -c - && \ + tar xf xcb-util-0.4.1.tar.xz && \ + cd xcb-util-0.4.1 && \ + ./configure --prefix=/usr --enable-static --disable-shared && \ + make -j$(nproc) && make install && \ + cd / && rm -rf /tmp/xcb-util-0.4.1* + +# Build static OpenSSL (needed by jerboa-ssl for AWS API calls) +# Alpine's openssl-dev only ships shared libs; we need .a files for the static binary. +ARG OPENSSL_VERSION=3.5.7 +ARG OPENSSL_SHA256=a8c0d28a529ca480f9f36cf5792e2cd21984552a3c8e4aa11a24aa31aeac98e8 +RUN apk add --no-cache openssl-dev perl && \ + wget -q "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz" && \ + echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c - && \ + tar xf "openssl-${OPENSSL_VERSION}.tar.gz" && \ + cd "openssl-${OPENSSL_VERSION}" && \ + ./Configure no-shared no-tests -O2 --prefix=/usr && \ + make -j$(nproc) && \ + ./apps/openssl version | grep -F "OpenSSL ${OPENSSL_VERSION}" && \ + ./apps/openssl version -a > /usr/lib/jemacs-openssl-build.txt && \ + cp libssl.a libcrypto.a /usr/lib/ && \ + cd / && rm -rf "openssl-${OPENSSL_VERSION}" "openssl-${OPENSSL_VERSION}.tar.gz" + +# ── Phase 2: Build Qt6 qtbase static ──────────────────────────────────── +ARG QT6_VERSION=6.8.3 +ARG QTBASE_SHA256=56001b905601bb9023d399f3ba780d7fa940f3e4861e496a7c490331f49e0b80 +RUN wget -q https://download.qt.io/official_releases/qt/6.8/${QT6_VERSION}/submodules/qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ + echo "${QTBASE_SHA256} qtbase-everywhere-src-${QT6_VERSION}.tar.xz" | sha256sum -c - && \ + tar xf qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ + rm qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ + cmake -S qtbase-everywhere-src-${QT6_VERSION} -B qt6-build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/opt/qt6-static \ + -DBUILD_SHARED_LIBS=OFF \ + -DQT_BUILD_EXAMPLES=OFF \ + -DQT_BUILD_TESTS=OFF \ + -DQT_BUILD_BENCHMARKS=OFF \ + -DFEATURE_xcb=ON \ + -DFEATURE_sql=OFF \ + -DFEATURE_network=OFF \ + -DFEATURE_testlib=OFF \ + -DFEATURE_printsupport=ON \ + -DFEATURE_dbus=OFF \ + -DFEATURE_opengl=OFF \ + -DFEATURE_vulkan=OFF \ + -DFEATURE_glib=OFF \ + -DFEATURE_icu=OFF && \ + cmake --build qt6-build --parallel && \ + cmake --install qt6-build && \ + rm -rf qtbase-everywhere-src-${QT6_VERSION} qt6-build + +# ── Phase 3: Build QScintilla static ──────────────────────────────────── +ARG QSCI_VERSION=2.14.1 +ARG QSCINTILLA_SHA256=dfe13c6acc9d85dfcba76ccc8061e71a223957a6c02f3c343b30a9d43a4cdd4d +RUN wget -q https://www.riverbankcomputing.com/static/Downloads/QScintilla/${QSCI_VERSION}/QScintilla_src-${QSCI_VERSION}.tar.gz && \ + echo "${QSCINTILLA_SHA256} QScintilla_src-${QSCI_VERSION}.tar.gz" | sha256sum -c - && \ + tar xf QScintilla_src-${QSCI_VERSION}.tar.gz && \ + rm QScintilla_src-${QSCI_VERSION}.tar.gz && \ + cd QScintilla_src-${QSCI_VERSION}/src && \ + /opt/qt6-static/bin/qmake CONFIG+=staticlib && \ + make -j$(nproc) && \ + make install && \ + cd / && rm -rf QScintilla_src-${QSCI_VERSION} + +# ── Phase 4: Generate pkg-config files with full transitive deps ───────── +# Static Qt6 cmake doesn't generate .pc files. We extract direct deps +# from .prl files, then add known transitive deps manually. +RUN mkdir -p /opt/qt6-static/lib/pkgconfig && \ + prl_libs() { \ + grep '^QMAKE_PRL_LIBS ' "$1" | \ + sed 's/^QMAKE_PRL_LIBS *= *//' | \ + tr ' ' '\n' | grep '^-l' | \ + grep -v '^-lQt6' | tr '\n' ' '; \ + } && \ + CORE_PRIVATE=$(prl_libs /opt/qt6-static/lib/libQt6Core.prl) && \ + GUI_PRL=$(prl_libs /opt/qt6-static/lib/libQt6Gui.prl) && \ + XCB_PRL=$(prl_libs /opt/qt6-static/plugins/platforms/libqxcb.prl) && \ + TRANSITIVE="-lgraphite2 -lbz2 -lbrotlidec -lbrotlicommon -lexpat -lXau -lXdmcp" && \ + GUI_PRIVATE="$GUI_PRL $TRANSITIVE" && \ + XCB_TRANSITIVE="-lxcb-util -lxcb -lXau -lXdmcp" && \ + XCB_PRIVATE="$XCB_PRL $TRANSITIVE $XCB_TRANSITIVE" && \ + echo "Core deps: $CORE_PRIVATE" && \ + echo "Gui deps: $GUI_PRIVATE" && \ + echo "XCB deps: $XCB_PRIVATE" && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: Qt6Core' 'Description: Qt6 Core' 'Version: 6.8.3' \ + 'Cflags: -I${includedir} -I${includedir}/QtCore' \ + "Libs: -L\${libdir} -lQt6Core" \ + "Libs.private: $CORE_PRIVATE" \ + > /opt/qt6-static/lib/pkgconfig/Qt6Core.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: Qt6Gui' 'Description: Qt6 Gui' 'Version: 6.8.3' \ + 'Requires: Qt6Core' \ + 'Cflags: -I${includedir} -I${includedir}/QtGui' \ + "Libs: -L\${libdir} -lQt6Gui" \ + "Libs.private: $GUI_PRIVATE" \ + > /opt/qt6-static/lib/pkgconfig/Qt6Gui.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: Qt6Widgets' 'Description: Qt6 Widgets' 'Version: 6.8.3' \ + 'Requires: Qt6Gui' \ + 'Cflags: -I${includedir} -I${includedir}/QtWidgets -I${includedir}/QtGui -I${includedir}/QtCore' \ + "Libs: -L\${libdir} -lQt6Widgets" \ + > /opt/qt6-static/lib/pkgconfig/Qt6Widgets.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: Qt6PrintSupport' 'Description: Qt6 PrintSupport' 'Version: 6.8.3' \ + 'Requires: Qt6Widgets' \ + 'Cflags: -I${includedir} -I${includedir}/QtPrintSupport' \ + "Libs: -L\${libdir} -lQt6PrintSupport" \ + > /opt/qt6-static/lib/pkgconfig/Qt6PrintSupport.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + 'plugindir=${prefix}/plugins' \ + '' \ + 'Name: Qt6XcbPlugin' 'Description: Qt6 XCB platform plugin' 'Version: 6.8.3' \ + 'Requires: Qt6Gui' \ + "Libs: -L\${libdir} -L\${plugindir}/platforms -lqxcb -lQt6XcbQpa" \ + "Libs.private: $XCB_PRIVATE" \ + > /opt/qt6-static/lib/pkgconfig/Qt6XcbPlugin.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: QScintilla' 'Description: QScintilla for Qt6' 'Version: 2.14.1' \ + 'Requires: Qt6Widgets Qt6PrintSupport' \ + 'Cflags: -I${includedir} -I${includedir}/Qsci' \ + "Libs: -L\${libdir} -lqscintilla2_qt6" \ + > /opt/qt6-static/lib/pkgconfig/QScintilla.pc && \ + echo "Generated .pc files:" && ls /opt/qt6-static/lib/pkgconfig/ + +# ── Phase 5: Build Chez Scheme from source for musl ───────────────────── +# Build WITHOUT --static so libkernel.a retains full dlopen support. +# This is needed because jemacs embeds the program as a .so and loads it +# at runtime via Sscheme_script (which calls dlopen internally). +# musl's static libdl.a provides dlopen in the final static binary. +ARG CHEZ_COMMIT=fdf6b3f5d069bf53082bb827f46714f2de8f11f5 +RUN git init -q /tmp/ChezScheme && \ + git -C /tmp/ChezScheme remote add origin https://github.com/cisco/ChezScheme && \ + git -C /tmp/ChezScheme fetch -q --depth 1 origin ${CHEZ_COMMIT} && \ + test "$(git -C /tmp/ChezScheme rev-parse FETCH_HEAD)" = "${CHEZ_COMMIT}" && \ + git -C /tmp/ChezScheme checkout -q --detach ${CHEZ_COMMIT} && \ + cd /tmp/ChezScheme && \ + ./configure --threads --installprefix=/opt/chez && \ + make -j$(nproc) && \ + make install && \ + rm -rf /tmp/ChezScheme + +# ── Phase 5.5: Build tree-sitter + grammars (static) ────────────────── +ARG TS_VERSION=0.24.7 +ARG TS_COMMIT=5e8760bf462ce7b19b3d2396d5b7860f3906a297 +RUN git init -q /tmp/tree-sitter && \ + git -C /tmp/tree-sitter remote add origin https://github.com/tree-sitter/tree-sitter && \ + git -C /tmp/tree-sitter fetch -q --depth 1 origin ${TS_COMMIT} && \ + test "$(git -C /tmp/tree-sitter rev-parse FETCH_HEAD)" = "${TS_COMMIT}" && \ + git -C /tmp/tree-sitter checkout -q --detach ${TS_COMMIT} && \ + cd /tmp/tree-sitter && \ + cc -c -O2 -Ilib/include lib/src/lib.c -o lib/src/lib.o && \ + mkdir -p /opt/tree-sitter-lib && \ + ar rcs /opt/tree-sitter-lib/libtree-sitter.a lib/src/lib.o && \ + mkdir -p /opt/tree-sitter-include/tree_sitter && \ + cp lib/include/tree_sitter/api.h /opt/tree-sitter-include/tree_sitter/ && \ + rm -rf /tmp/tree-sitter + +# Build tree-sitter grammar static archives +RUN mkdir -p /opt/tree-sitter-grammars && \ + build_grammar() { \ + name=$1; repo=$2; commit=$3; subdir=${4:-.}; \ + git init -q /tmp/ts-$name && \ + git -C /tmp/ts-$name remote add origin "https://github.com/$repo" && \ + git -C /tmp/ts-$name fetch -q --depth 1 origin "$commit" && \ + test "$(git -C /tmp/ts-$name rev-parse FETCH_HEAD)" = "$commit" && \ + git -C /tmp/ts-$name checkout -q --detach "$commit" && \ + cd /tmp/ts-$name/$subdir && \ + SRC_DIR=src && \ + gcc -c -O2 -I/opt/tree-sitter-include -I$SRC_DIR \ + $SRC_DIR/parser.c -o parser.o && \ + if [ -f $SRC_DIR/scanner.c ]; then \ + gcc -c -O2 -I/opt/tree-sitter-include -I$SRC_DIR \ + $SRC_DIR/scanner.c -o scanner.o && \ + ar rcs /opt/tree-sitter-grammars/libtree-sitter-$name.a parser.o scanner.o; \ + else \ + ar rcs /opt/tree-sitter-grammars/libtree-sitter-$name.a parser.o; \ + fi && \ + cd / && rm -rf /tmp/ts-$name; \ + } && \ + build_grammar c tree-sitter/tree-sitter-c 2a265d69a4caf57108a73ad2ed1e6922dd2f998c && \ + build_grammar cpp tree-sitter/tree-sitter-cpp f41e1a044c8a84ea9fa8577fdd2eab92ec96de02 && \ + build_grammar python tree-sitter/tree-sitter-python bffb65a8cfe4e46290331dfef0dbf0ef3679de11 && \ + build_grammar javascript tree-sitter/tree-sitter-javascript 3a837b6f3658ca3618f2022f8707e29739c91364 && \ + build_grammar rust tree-sitter/tree-sitter-rust 3d087c3df25286140393ddecc339208fae107149 && \ + build_grammar go tree-sitter/tree-sitter-go 3c3775faa968158a8b4ac190a7fda867fd5fb748 && \ + build_grammar bash tree-sitter/tree-sitter-bash 487734f87fd87118028a65a4599352fa99c9cde8 && \ + build_grammar json tree-sitter/tree-sitter-json ee35a6ebefcef0c5c416c0d1ccec7370cfca5a24 && \ + build_grammar ruby tree-sitter/tree-sitter-ruby 71bd32fb7607035768799732addba884a37a6210 && \ + build_grammar java tree-sitter/tree-sitter-java 94703d5a6bed02b98e438d7cad1136c01a60ba2c && \ + build_grammar css tree-sitter/tree-sitter-css c0d581e32d183a536731ed6c3a72758b27e20411 && \ + build_grammar html tree-sitter/tree-sitter-html 5a5ca8551a179998360b4a4ca2c0f366a35acc03 && \ + build_grammar lua tree-sitter-grammars/tree-sitter-lua 534c461d2b75b0887ec968ef9635f4460b0878b7 && \ + build_grammar scheme 6cdh/tree-sitter-scheme c6cb7c7d7a04b3f5d999c28e2e9c0c31b2d50ece && \ + echo "Grammars built:" && ls /opt/tree-sitter-grammars/ + +# ── Phase 6: Jerboa dependencies ─────────────────────────────────────── +ENV PKG_CONFIG_PATH=/opt/qt6-static/lib/pkgconfig +ENV SCHEME=/opt/chez/bin/scheme + +# Copy dependency sources from build contexts +COPY --from=jerboa-src . /deps/jerboa +COPY --from=jsh-src . /deps/jsh +COPY --from=pcre2-src . /deps/jerboa-pcre2 +COPY --from=sci-src . /deps/jerboa-scintilla +COPY --from=qt-src . /deps/jerboa-qt +RUN mkdir -p /deps/qt-shim +COPY support/vendor-overrides/qt_shim.cpp support/vendor-overrides/qt_shim.h /deps/qt-shim/ +# qt_chez_shim.c is maintained in support/vendor-overrides/ (not in jerboa-qt). +COPY support/vendor-overrides/qt_chez_shim.c support/vendor-overrides/qt_shim.h /deps/jerboa-qt/ + +# jerboa-shell keeps these support libraries as jerbuild input files. +# Transpile only the libraries imported by support/static-precompile-deps.ss. +RUN mkdir -p /tmp/jsh-jerbuild-src/compat /tmp/jsh-jerbuild-src/jsh && \ + cp /deps/jsh/jerboa-src/src/compat/gambit.ss /tmp/jsh-jerbuild-src/compat/ && \ + cp /deps/jsh/jerboa-src/src/jsh/ffi.ss /tmp/jsh-jerbuild-src/jsh/ && \ + cp /deps/jsh/jerboa-src/src/jsh/static-compat.ss /tmp/jsh-jerbuild-src/jsh/ && \ + /opt/chez/bin/scheme --libdirs /deps/jerboa/lib \ + --script /deps/jsh/support/jerbuild.ss /tmp/jsh-jerbuild-src /deps/jsh/src --force && \ + rm -rf /tmp/jsh-jerbuild-src + +# Pre-compile all Jerboa library dependencies. +# These .so files are baked into the image so jemacs builds only +# need to compile jemacs-specific modules. +COPY support/static-precompile-deps.ss /tmp/static-precompile-deps.ss +RUN /opt/chez/bin/scheme \ + --libdirs /deps/jerboa/lib:/deps/jsh/src:/deps/jsh/vendor/jerboa-coreutils/lib:/deps/jsh/vendor/jerboa-awk/lib:/deps/jsh/vendor/jerboa-sed/lib:/deps/jerboa-pcre2:/deps/jerboa-scintilla/lib:/deps/jerboa-qt/lib \ + --compile-imported-libraries \ + --script /tmp/static-precompile-deps.ss + +# Build static pcre2 shim object +RUN PCRE2_CFLAGS=$(pkg-config --cflags libpcre2-8 2>/dev/null || echo "") && \ + gcc -c -O2 -o /deps/jerboa-pcre2/pcre2_shim.o \ + /deps/jerboa-pcre2/jerboa_pcre2_shim.c $PCRE2_CFLAGS -Wall + +# Build static jsh FFI shim object +RUN gcc -c -O2 -o /deps/jsh/jsh_ffi_shim.o /deps/jsh/ffi-shim.c -Wall + +# Build static qt_chez_shim object +RUN QT_CFLAGS=$(pkg-config --cflags Qt6Widgets 2>/dev/null || \ + echo "-I/opt/qt6-static/include -I/opt/qt6-static/include/QtCore \ + -I/opt/qt6-static/include/QtGui -I/opt/qt6-static/include/QtWidgets") && \ + gcc -c -O2 -fPIC -o /deps/jerboa-qt/qt_chez_shim.o \ + /deps/jerboa-qt/qt_chez_shim.c \ + -I/deps/qt-shim $QT_CFLAGS -Wall + +# Build static libqt_shim.a from qt_shim.cpp +# qt_static_plugins.o must be separate (NOT in archive) — it contains +# Q_IMPORT_PLUGIN static constructors that the linker drops from archives. +RUN QT_CFLAGS=$(pkg-config --cflags Qt6Widgets 2>/dev/null || \ + echo "-I/opt/qt6-static/include -I/opt/qt6-static/include/QtCore \ + -I/opt/qt6-static/include/QtGui -I/opt/qt6-static/include/QtWidgets") && \ + QSCI_FLAGS="-DQT_SCINTILLA_AVAILABLE \ + $(pkg-config --cflags QScintilla 2>/dev/null || \ + echo "-I/opt/qt6-static/include -I/opt/qt6-static/include/Qsci")" && \ + g++ -c -fPIC -std=c++17 \ + $QT_CFLAGS $QSCI_FLAGS \ + /deps/qt-shim/qt_shim.cpp \ + -o /deps/qt-shim/qt_shim_static.o && \ + ar rcs /deps/qt-shim/libqt_shim.a \ + /deps/qt-shim/qt_shim_static.o && \ + printf '#include <QtPlugin>\nQ_IMPORT_PLUGIN(QXcbIntegrationPlugin)\n' \ + > /deps/qt-shim/qt_static_plugins.cpp && \ + g++ -c -fPIC -std=c++17 $QT_CFLAGS \ + /deps/qt-shim/qt_static_plugins.cpp \ + -o /deps/qt-shim/qt_static_plugins.o + +WORKDIR /src new file mode 100644 --- /dev/null +++ b/Containerfile.qt @@ -0,0 +1,333 @@ +# Podman-compatible recipe for building a fully static jemacs-qt with Qt6. +# +# Multi-stage build: +# Stage 1 (Alpine): Build Qt6 static, QScintilla, tree-sitter, libvterm (musl) +# Stage 1.5 (Alpine+shim): Compile libqt_shim.a with musl-compatible g++ +# Stage 2 (jerboa21/jerboa): Copy Qt artifacts + Jerboa deps, build jemacs-qt +# +# Usage: +# podman build -f Containerfile.qt -t jemacs-qt-builder . +# id=$(podman create jemacs-qt-builder) +# podman cp $id:/out/jemacs-qt ./jemacs-qt && podman rm $id +# chmod +x jemacs-qt + +ARG JERBOA_IMAGE=jerboa21/jerboa@sha256:9c4034f1296601d5a9f7b219a0decdd5d62862a2be4347976b3d96b13ed7701e + +FROM ${JERBOA_IMAGE} AS jerboa-runtime + +# ── Stage 1: Build Qt6 + QScintilla + tree-sitter + libvterm on Alpine ─────── +FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d AS qt-alpine + +ARG QT6_VERSION=6.8.3 +ARG QSCI_VERSION=2.14.1 +ARG TS_VERSION=0.24.7 +ARG TS_COMMIT=5e8760bf462ce7b19b3d2396d5b7860f3906a297 +ARG LIBXAU_SHA256=74d0e4dfa3d39ad8939e99bda37f5967aba528211076828464d2777d477fc0fb +ARG XCB_UTIL_SHA256=5abe3bbbd8e54f0fa3ec945291b7e8fa8cfd3cccc43718f8758430f94126e512 +ARG QTBASE_SHA256=56001b905601bb9023d399f3ba780d7fa940f3e4861e496a7c490331f49e0b80 +ARG QSCINTILLA_SHA256=dfe13c6acc9d85dfcba76ccc8061e71a223957a6c02f3c343b30a9d43a4cdd4d + +# ── Phase 1: Alpine build deps ────────────────────────────────────────── +RUN apk add --no-cache \ + cmake samurai perl python3 linux-headers patchelf file \ + libxcb-dev xcb-util-dev xcb-util-image-dev \ + xcb-util-keysyms-dev xcb-util-renderutil-dev \ + xcb-util-wm-dev xcb-util-cursor-dev \ + libx11-dev libxkbcommon-dev \ + fontconfig-dev freetype-dev harfbuzz-dev \ + mesa-dev \ + libpng-dev zlib-dev \ + pcre2-dev \ + at-spi2-core-dev libdrm-dev \ + zlib-static libxcb-static \ + fontconfig-static freetype-static harfbuzz-static \ + libpng-static bzip2-static expat-static brotli-static \ + libx11-static graphite2-static libxkbcommon-static \ + ncurses-dev ncurses-static \ + util-linux-dev util-linux-static \ + libvterm-dev libvterm-static \ + gcc g++ binutils make git curl wget + +# Build static libXau (no Alpine -static package available) +RUN apk add --no-cache libxau-dev && \ + cd /tmp && \ + wget -q https://xorg.freedesktop.org/releases/individual/lib/libXau-1.0.12.tar.xz && \ + echo "${LIBXAU_SHA256} libXau-1.0.12.tar.xz" | sha256sum -c - && \ + tar xf libXau-1.0.12.tar.xz && \ + cd libXau-1.0.12 && \ + ./configure --prefix=/usr --enable-static --disable-shared && \ + make -j$(nproc) && make install && \ + cd / && rm -rf /tmp/libXau-1.0.12* + +# Build static libxcb-util (no Alpine -static package; needed by xcb-image) +RUN cd /tmp && \ + wget -q https://xcb.freedesktop.org/dist/xcb-util-0.4.1.tar.xz && \ + echo "${XCB_UTIL_SHA256} xcb-util-0.4.1.tar.xz" | sha256sum -c - && \ + tar xf xcb-util-0.4.1.tar.xz && \ + cd xcb-util-0.4.1 && \ + ./configure --prefix=/usr --enable-static --disable-shared && \ + make -j$(nproc) && make install && \ + cd / && rm -rf /tmp/xcb-util-0.4.1* + +# ── Phase 2: Build Qt6 qtbase static ──────────────────────────────────── +RUN wget -q https://download.qt.io/official_releases/qt/6.8/${QT6_VERSION}/submodules/qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ + echo "${QTBASE_SHA256} qtbase-everywhere-src-${QT6_VERSION}.tar.xz" | sha256sum -c - && \ + tar xf qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ + rm qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ + cmake -S qtbase-everywhere-src-${QT6_VERSION} -B qt6-build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/opt/qt6-static \ + -DBUILD_SHARED_LIBS=OFF \ + -DQT_BUILD_EXAMPLES=OFF \ + -DQT_BUILD_TESTS=OFF \ + -DQT_BUILD_BENCHMARKS=OFF \ + -DFEATURE_xcb=ON \ + -DFEATURE_sql=OFF \ + -DFEATURE_network=OFF \ + -DFEATURE_testlib=OFF \ + -DFEATURE_printsupport=ON \ + -DFEATURE_dbus=OFF \ + -DINPUT_opengl=no \ + -DFEATURE_opengl=OFF \ + -DFEATURE_opengl_desktop=OFF \ + -DFEATURE_opengles2=OFF \ + -DFEATURE_egl=OFF \ + -DFEATURE_vulkan=OFF \ + -DFEATURE_glib=OFF \ + -DFEATURE_icu=OFF && \ + cmake --build qt6-build --parallel && \ + cmake --install qt6-build && \ + rm -rf qtbase-everywhere-src-${QT6_VERSION} qt6-build + +# ── Phase 3: Build QScintilla static ──────────────────────────────────── +RUN wget -q https://www.riverbankcomputing.com/static/Downloads/QScintilla/${QSCI_VERSION}/QScintilla_src-${QSCI_VERSION}.tar.gz && \ + echo "${QSCINTILLA_SHA256} QScintilla_src-${QSCI_VERSION}.tar.gz" | sha256sum -c - && \ + tar xf QScintilla_src-${QSCI_VERSION}.tar.gz && \ + rm QScintilla_src-${QSCI_VERSION}.tar.gz && \ + cd QScintilla_src-${QSCI_VERSION}/src && \ + /opt/qt6-static/bin/qmake CONFIG+=staticlib && \ + make -j$(nproc) && \ + make install && \ + cd / && rm -rf QScintilla_src-${QSCI_VERSION} + +# ── Phase 4: Generate pkg-config files with full transitive deps ───────── +RUN mkdir -p /opt/qt6-static/lib/pkgconfig && \ + prl_libs() { \ + grep '^QMAKE_PRL_LIBS ' "$1" | \ + sed 's/^QMAKE_PRL_LIBS *= *//' | \ + tr ' ' '\n' | grep '^-l' | \ + grep -v '^-lQt6' | tr '\n' ' '; \ + } && \ + CORE_PRIVATE=$(prl_libs /opt/qt6-static/lib/libQt6Core.prl) && \ + GUI_PRL=$(prl_libs /opt/qt6-static/lib/libQt6Gui.prl) && \ + XCB_PRL=$(prl_libs /opt/qt6-static/plugins/platforms/libqxcb.prl) && \ + TRANSITIVE="-lgraphite2 -lbz2 -lbrotlidec -lbrotlicommon -lexpat -lXau -lXdmcp" && \ + GUI_PRIVATE="$GUI_PRL $TRANSITIVE" && \ + XCB_TRANSITIVE="-lxcb-util -lxcb -lXau -lXdmcp" && \ + XCB_PRIVATE="$XCB_PRL $TRANSITIVE $XCB_TRANSITIVE" && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: Qt6Core' "Description: Qt6 Core" "Version: ${QT6_VERSION}" \ + 'Cflags: -I${includedir} -I${includedir}/QtCore' \ + "Libs: -L\${libdir} -lQt6Core" \ + "Libs.private: $CORE_PRIVATE" \ + > /opt/qt6-static/lib/pkgconfig/Qt6Core.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: Qt6Gui' "Description: Qt6 Gui" "Version: ${QT6_VERSION}" \ + 'Requires: Qt6Core' \ + 'Cflags: -I${includedir} -I${includedir}/QtGui' \ + "Libs: -L\${libdir} -lQt6Gui" \ + "Libs.private: $GUI_PRIVATE" \ + > /opt/qt6-static/lib/pkgconfig/Qt6Gui.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: Qt6Widgets' "Description: Qt6 Widgets" "Version: ${QT6_VERSION}" \ + 'Requires: Qt6Gui' \ + 'Cflags: -I${includedir} -I${includedir}/QtWidgets -I${includedir}/QtGui -I${includedir}/QtCore' \ + "Libs: -L\${libdir} -lQt6Widgets" \ + > /opt/qt6-static/lib/pkgconfig/Qt6Widgets.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: Qt6PrintSupport' "Description: Qt6 PrintSupport" "Version: ${QT6_VERSION}" \ + 'Requires: Qt6Widgets' \ + 'Cflags: -I${includedir} -I${includedir}/QtPrintSupport' \ + "Libs: -L\${libdir} -lQt6PrintSupport" \ + > /opt/qt6-static/lib/pkgconfig/Qt6PrintSupport.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + 'plugindir=${prefix}/plugins' \ + '' \ + 'Name: Qt6XcbPlugin' "Description: Qt6 XCB platform plugin" "Version: ${QT6_VERSION}" \ + 'Requires: Qt6Gui' \ + "Libs: -L\${libdir} -L\${plugindir}/platforms -lqxcb -lQt6XcbQpa" \ + "Libs.private: $XCB_PRIVATE" \ + > /opt/qt6-static/lib/pkgconfig/Qt6XcbPlugin.pc && \ + printf '%s\n' \ + 'prefix=/opt/qt6-static' \ + 'includedir=${prefix}/include' \ + 'libdir=${prefix}/lib' \ + '' \ + 'Name: QScintilla' "Description: QScintilla for Qt6" "Version: ${QSCI_VERSION}" \ + 'Requires: Qt6Widgets Qt6PrintSupport' \ + 'Cflags: -I${includedir} -I${includedir}/Qsci' \ + "Libs: -L\${libdir} -lqscintilla2_qt6" \ + > /opt/qt6-static/lib/pkgconfig/QScintilla.pc && \ + echo "Generated .pc files:" && ls /opt/qt6-static/lib/pkgconfig/ + +# ── Phase 5: Build tree-sitter + grammars (static) ────────────────────── +RUN git init -q /tmp/tree-sitter && \ + git -C /tmp/tree-sitter remote add origin https://github.com/tree-sitter/tree-sitter && \ + git -C /tmp/tree-sitter fetch -q --depth 1 origin ${TS_COMMIT} && \ + test "$(git -C /tmp/tree-sitter rev-parse FETCH_HEAD)" = "${TS_COMMIT}" && \ + git -C /tmp/tree-sitter checkout -q --detach ${TS_COMMIT} && \ + cd /tmp/tree-sitter && \ + cc -c -O2 -Ilib/include lib/src/lib.c -o lib/src/lib.o && \ + mkdir -p /opt/tree-sitter-lib && \ + ar rcs /opt/tree-sitter-lib/libtree-sitter.a lib/src/lib.o && \ + mkdir -p /opt/tree-sitter-include/tree_sitter && \ + cp lib/include/tree_sitter/api.h /opt/tree-sitter-include/tree_sitter/ && \ + rm -rf /tmp/tree-sitter + +RUN mkdir -p /opt/tree-sitter-grammars && \ + build_grammar() { \ + name=$1; repo=$2; commit=$3; subdir=${4:-.}; \ + git init -q /tmp/ts-$name && \ + git -C /tmp/ts-$name remote add origin "https://github.com/$repo" && \ + git -C /tmp/ts-$name fetch -q --depth 1 origin "$commit" && \ + test "$(git -C /tmp/ts-$name rev-parse FETCH_HEAD)" = "$commit" && \ + git -C /tmp/ts-$name checkout -q --detach "$commit" && \ + cd /tmp/ts-$name/$subdir && \ + SRC_DIR=src && \ + gcc -c -O2 -I/opt/tree-sitter-include -I$SRC_DIR \ + $SRC_DIR/parser.c -o parser.o && \ + if [ -f $SRC_DIR/scanner.c ]; then \ + gcc -c -O2 -I/opt/tree-sitter-include -I$SRC_DIR \ + $SRC_DIR/scanner.c -o scanner.o && \ + ar rcs /opt/tree-sitter-grammars/libtree-sitter-$name.a parser.o scanner.o; \ + else \ + ar rcs /opt/tree-sitter-grammars/libtree-sitter-$name.a parser.o; \ + fi && \ + cd / && rm -rf /tmp/ts-$name; \ + } && \ + build_grammar c tree-sitter/tree-sitter-c 2a265d69a4caf57108a73ad2ed1e6922dd2f998c && \ + build_grammar cpp tree-sitter/tree-sitter-cpp f41e1a044c8a84ea9fa8577fdd2eab92ec96de02 && \ + build_grammar python tree-sitter/tree-sitter-python bffb65a8cfe4e46290331dfef0dbf0ef3679de11 && \ + build_grammar javascript tree-sitter/tree-sitter-javascript 3a837b6f3658ca3618f2022f8707e29739c91364 && \ + build_grammar rust tree-sitter/tree-sitter-rust 3d087c3df25286140393ddecc339208fae107149 && \ + build_grammar go tree-sitter/tree-sitter-go 3c3775faa968158a8b4ac190a7fda867fd5fb748 && \ + build_grammar bash tree-sitter/tree-sitter-bash 487734f87fd87118028a65a4599352fa99c9cde8 && \ + build_grammar json tree-sitter/tree-sitter-json ee35a6ebefcef0c5c416c0d1ccec7370cfca5a24 && \ + build_grammar ruby tree-sitter/tree-sitter-ruby 71bd32fb7607035768799732addba884a37a6210 && \ + build_grammar java tree-sitter/tree-sitter-java 94703d5a6bed02b98e438d7cad1136c01a60ba2c && \ + build_grammar css tree-sitter/tree-sitter-css c0d581e32d183a536731ed6c3a72758b27e20411 && \ + build_grammar html tree-sitter/tree-sitter-html 5a5ca8551a179998360b4a4ca2c0f366a35acc03 && \ + build_grammar lua tree-sitter-grammars/tree-sitter-lua 534c461d2b75b0887ec968ef9635f4460b0878b7 && \ + build_grammar scheme 6cdh/tree-sitter-scheme c6cb7c7d7a04b3f5d999c28e2e9c0c31b2d50ece && \ + echo "Grammars built:" && ls /opt/tree-sitter-grammars/ + +# ── Phase 6: Stage Qt transitive static deps for the final link ───────── +RUN mkdir -p /opt/qt-static-deps/lib && \ + for lib in \ + libpng.a libharfbuzz.a libfreetype.a libfontconfig.a libxkbcommon.a \ + libxkbcommon-x11.a libgraphite2.a libbz2.a libbrotlidec.a \ + libbrotlicommon.a libexpat.a libXau.a libXdmcp.a libxcb-util.a \ + libxcb-cursor.a libxcb-icccm.a libxcb-image.a libxcb-keysyms.a \ + libxcb-randr.a libxcb-render-util.a libxcb-shm.a libxcb-sync.a \ + libxcb-xfixes.a libxcb-render.a libxcb-shape.a libxcb-xkb.a \ + libxcb.a libX11-xcb.a libX11.a; do \ + test -f "/usr/lib/$lib" || { echo "missing static Qt dependency: $lib" >&2; exit 1; }; \ + cp -L "/usr/lib/$lib" "/opt/qt-static-deps/lib/$lib"; \ + done + +# ── Stage 1.5: Compile libqt_shim.a with musl-compatible Alpine g++ ────────── +# Separate stage so Qt6 cache is preserved when qt_shim.cpp changes +FROM qt-alpine AS qt-shim-alpine + +COPY support/vendor-overrides/qt_shim.cpp support/vendor-overrides/qt_shim.h /tmp/qt-shim-src/ + +RUN mkdir -p /tmp/qt-shim-out && \ + QT_CFLAGS="-I/opt/qt6-static/include \ + -I/opt/qt6-static/include/QtCore \ + -I/opt/qt6-static/include/QtGui \ + -I/opt/qt6-static/include/QtWidgets" && \ + QSCI_FLAGS="-DQT_SCINTILLA_AVAILABLE \ + -I/opt/qt6-static/include \ + -I/opt/qt6-static/include/Qsci" && \ + g++ -c -fPIC -std=c++17 -DJEMACS_CHEZ_SMP \ + $QT_CFLAGS $QSCI_FLAGS \ + /tmp/qt-shim-src/qt_shim.cpp \ + -o /tmp/qt-shim-out/qt_shim_static.o && \ + ar rcs /tmp/qt-shim-out/libqt_shim.a \ + /tmp/qt-shim-out/qt_shim_static.o && \ + printf '#include <QtPlugin>\nQ_IMPORT_PLUGIN(QXcbIntegrationPlugin)\n' \ + > /tmp/qt-shim-out/qt_static_plugins.cpp && \ + g++ -c -fPIC -std=c++17 $QT_CFLAGS \ + /tmp/qt-shim-out/qt_static_plugins.cpp \ + -o /tmp/qt-shim-out/qt_static_plugins.o && \ + echo "libqt_shim.a built successfully" && \ + ls -lh /tmp/qt-shim-out/ + +# ── Stage 2: Alpine/musl builder + Chez from jerboa21/jerboa ───────────────── +FROM qt-shim-alpine AS builder + +COPY --from=jerboa-runtime /build/chez-musl /opt/chez +COPY --from=jerboa-runtime /usr/local /opt/chez-dynamic +COPY --from=jerboa-runtime /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/libm.so.6 +COPY --from=jerboa-runtime /lib/x86_64-linux-gnu/libtinfo.so.6 /lib/x86_64-linux-gnu/libtinfo.so.6 +COPY --from=jerboa-runtime /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 +COPY --from=jerboa-runtime /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 + +RUN mkdir -p /opt/qt-shim && \ + cp -a /tmp/qt-shim-out/. /opt/qt-shim/ && \ + cp -L /opt/qt-static-deps/lib/*.a /usr/local/lib/ && \ + if [ -e /usr/lib/ld-musl-x86_64.so.1 ]; then \ + ln -sf /usr/lib/ld-musl-x86_64.so.1 /lib/libc.musl-x86_64.so.1; \ + fi && \ + if [ -e /usr/lib/ld-musl-aarch64.so.1 ]; then \ + ln -sf /usr/lib/ld-musl-aarch64.so.1 /lib/libc.musl-aarch64.so.1; \ + fi && \ + chez_dir="$(ls -d /opt/chez/lib/csv*/ta6le | head -1)" && \ + printf '%s\n' \ + '#!/bin/sh' \ + "exec \"$chez_dir/scheme\" --boot \"$chez_dir/petite.boot\" --boot \"$chez_dir/scheme.boot\" \"\$@\"" \ + > /opt/chez/bin/scheme-wrapper && \ + chmod +x /opt/chez/bin/scheme-wrapper && \ + /opt/chez/bin/scheme-wrapper --version + +# Copy source and run the in-container build +COPY . /src +WORKDIR /src + +ARG JEMACS_FEATURES="" +ENV JEMACS_FEATURES=${JEMACS_FEATURES} +RUN musl_chez_dir="$(ls -d /opt/chez/lib/csv*/ta6le | head -1)" && \ + make linux-qt-local \ + MUSL_CHEZ_ROOT=/opt/chez \ + MUSL_CHEZ_DIR="$musl_chez_dir" \ + MUSL_SCHEME=/opt/chez-dynamic/bin/scheme + +# Verify +RUN ./jemacs-qt --version && \ + file jemacs-qt && \ + echo "--- Binary info ---" && \ + ls -lh jemacs-qt + +# ── Final stage: extract binary ─────────────────────────────────────────────── +FROM ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90 +COPY --from=builder /src/jemacs-qt /out/jemacs-qt +CMD ["cat", "/out/jemacs-qt"] new file mode 100644 --- /dev/null +++ b/Containerfile.tui @@ -0,0 +1,29 @@ +# Podman-compatible container recipe for building static jemacs (TUI). +# +# Produces a fully static jemacs binary with zero runtime dependencies. +# +# Usage: +# podman build -f Containerfile.tui -t jemacs-tui-builder . +# id=$(podman create jemacs-tui-builder) +# podman cp $id:/out/jemacs ./jemacs && podman rm $id +# chmod +x jemacs + +FROM jerboa21/jerboa@sha256:9c4034f1296601d5a9f7b219a0decdd5d62862a2be4347976b3d96b13ed7701e AS builder + +# ── Copy jerboa-emacs source ──────────────────────────────────────────────── +COPY . /src + +# ── Build jemacs-musl ──────────────────────────────────────────────────────── +WORKDIR /src +RUN make linux-tui-local + +# ── Verify ─────────────────────────────────────────────────────────────────── +RUN ./jemacs --version && \ + file jemacs && \ + echo "--- Binary info ---" && \ + ls -lh jemacs + +# ── Output ─────────────────────────────────────────────────────────────────── +FROM ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90 +COPY --from=builder /src/jemacs /out/jemacs +CMD ["cat", "/out/jemacs"] deleted file mode 100644 --- a/Dockerfile +++ /dev/null @@ -1,337 +0,0 @@ -# Intermediate image for jemacs-qt static builds. -# Bakes static Qt6 + QScintilla from source, plus all Chez Scheme -# dependencies (jerboa, jsh, jerboa-pcre2, jerboa-scintilla, -# jerboa-qt) so that subsequent jemacs-qt builds only compile jemacs -# itself (~5-10 min instead of ~30 min). -# -ARG ARCH=x86_64 -FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d - -# ── Phase 1: Alpine build deps ────────────────────────────────────────── -RUN apk add --no-cache \ - su-exec \ - cmake samurai perl python3 linux-headers patchelf \ - libxcb-dev xcb-util-dev xcb-util-image-dev \ - xcb-util-keysyms-dev xcb-util-renderutil-dev \ - xcb-util-wm-dev xcb-util-cursor-dev \ - libx11-dev libxkbcommon-dev \ - fontconfig-dev freetype-dev harfbuzz-dev \ - libpng-dev zlib-dev mesa-dev \ - pcre2-dev \ - at-spi2-core-dev libdrm-dev \ - zlib-static libxcb-static \ - fontconfig-static freetype-static harfbuzz-static \ - libpng-static bzip2-static expat-static brotli-static \ - libx11-static graphite2-static libxkbcommon-static \ - ncurses-dev ncurses-static \ - util-linux-dev util-linux-static \ - libvterm-dev libvterm-static \ - gcc g++ binutils make git curl wget - -# Build static libXau (no Alpine -static package available) -ARG LIBXAU_SHA256=74d0e4dfa3d39ad8939e99bda37f5967aba528211076828464d2777d477fc0fb -RUN apk add --no-cache libxau-dev && \ - cd /tmp && \ - wget -q https://xorg.freedesktop.org/releases/individual/lib/libXau-1.0.12.tar.xz && \ - echo "${LIBXAU_SHA256} libXau-1.0.12.tar.xz" | sha256sum -c - && \ - tar xf libXau-1.0.12.tar.xz && \ - cd libXau-1.0.12 && \ - ./configure --prefix=/usr --enable-static --disable-shared && \ - make -j$(nproc) && make install && \ - cd / && rm -rf /tmp/libXau-1.0.12* - -# Build static libxcb-util (no Alpine -static package; needed by xcb-image) -ARG XCB_UTIL_SHA256=5abe3bbbd8e54f0fa3ec945291b7e8fa8cfd3cccc43718f8758430f94126e512 -RUN cd /tmp && \ - wget -q https://xcb.freedesktop.org/dist/xcb-util-0.4.1.tar.xz && \ - echo "${XCB_UTIL_SHA256} xcb-util-0.4.1.tar.xz" | sha256sum -c - && \ - tar xf xcb-util-0.4.1.tar.xz && \ - cd xcb-util-0.4.1 && \ - ./configure --prefix=/usr --enable-static --disable-shared && \ - make -j$(nproc) && make install && \ - cd / && rm -rf /tmp/xcb-util-0.4.1* - -# Build static OpenSSL (needed by jerboa-ssl for AWS API calls) -# Alpine's openssl-dev only ships shared libs; we need .a files for the static binary. -ARG OPENSSL_VERSION=3.5.7 -ARG OPENSSL_SHA256=a8c0d28a529ca480f9f36cf5792e2cd21984552a3c8e4aa11a24aa31aeac98e8 -RUN apk add --no-cache openssl-dev perl && \ - wget -q "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz" && \ - echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c - && \ - tar xf "openssl-${OPENSSL_VERSION}.tar.gz" && \ - cd "openssl-${OPENSSL_VERSION}" && \ - ./Configure no-shared no-tests -O2 --prefix=/usr && \ - make -j$(nproc) && \ - ./apps/openssl version | grep -F "OpenSSL ${OPENSSL_VERSION}" && \ - ./apps/openssl version -a > /usr/lib/jemacs-openssl-build.txt && \ - cp libssl.a libcrypto.a /usr/lib/ && \ - cd / && rm -rf "openssl-${OPENSSL_VERSION}" "openssl-${OPENSSL_VERSION}.tar.gz" - -# ── Phase 2: Build Qt6 qtbase static ──────────────────────────────────── -ARG QT6_VERSION=6.8.3 -ARG QTBASE_SHA256=56001b905601bb9023d399f3ba780d7fa940f3e4861e496a7c490331f49e0b80 -RUN wget -q https://download.qt.io/official_releases/qt/6.8/${QT6_VERSION}/submodules/qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ - echo "${QTBASE_SHA256} qtbase-everywhere-src-${QT6_VERSION}.tar.xz" | sha256sum -c - && \ - tar xf qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ - rm qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ - cmake -S qtbase-everywhere-src-${QT6_VERSION} -B qt6-build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/opt/qt6-static \ - -DBUILD_SHARED_LIBS=OFF \ - -DQT_BUILD_EXAMPLES=OFF \ - -DQT_BUILD_TESTS=OFF \ - -DQT_BUILD_BENCHMARKS=OFF \ - -DFEATURE_xcb=ON \ - -DFEATURE_sql=OFF \ - -DFEATURE_network=OFF \ - -DFEATURE_testlib=OFF \ - -DFEATURE_printsupport=ON \ - -DFEATURE_dbus=OFF \ - -DFEATURE_opengl=OFF \ - -DFEATURE_vulkan=OFF \ - -DFEATURE_glib=OFF \ - -DFEATURE_icu=OFF && \ - cmake --build qt6-build --parallel && \ - cmake --install qt6-build && \ - rm -rf qtbase-everywhere-src-${QT6_VERSION} qt6-build - -# ── Phase 3: Build QScintilla static ──────────────────────────────────── -ARG QSCI_VERSION=2.14.1 -ARG QSCINTILLA_SHA256=dfe13c6acc9d85dfcba76ccc8061e71a223957a6c02f3c343b30a9d43a4cdd4d -RUN wget -q https://www.riverbankcomputing.com/static/Downloads/QScintilla/${QSCI_VERSION}/QScintilla_src-${QSCI_VERSION}.tar.gz && \ - echo "${QSCINTILLA_SHA256} QScintilla_src-${QSCI_VERSION}.tar.gz" | sha256sum -c - && \ - tar xf QScintilla_src-${QSCI_VERSION}.tar.gz && \ - rm QScintilla_src-${QSCI_VERSION}.tar.gz && \ - cd QScintilla_src-${QSCI_VERSION}/src && \ - /opt/qt6-static/bin/qmake CONFIG+=staticlib && \ - make -j$(nproc) && \ - make install && \ - cd / && rm -rf QScintilla_src-${QSCI_VERSION} - -# ── Phase 4: Generate pkg-config files with full transitive deps ───────── -# Static Qt6 cmake doesn't generate .pc files. We extract direct deps -# from .prl files, then add known transitive deps manually. -RUN mkdir -p /opt/qt6-static/lib/pkgconfig && \ - prl_libs() { \ - grep '^QMAKE_PRL_LIBS ' "$1" | \ - sed 's/^QMAKE_PRL_LIBS *= *//' | \ - tr ' ' '\n' | grep '^-l' | \ - grep -v '^-lQt6' | tr '\n' ' '; \ - } && \ - CORE_PRIVATE=$(prl_libs /opt/qt6-static/lib/libQt6Core.prl) && \ - GUI_PRL=$(prl_libs /opt/qt6-static/lib/libQt6Gui.prl) && \ - XCB_PRL=$(prl_libs /opt/qt6-static/plugins/platforms/libqxcb.prl) && \ - TRANSITIVE="-lgraphite2 -lbz2 -lbrotlidec -lbrotlicommon -lexpat -lXau -lXdmcp" && \ - GUI_PRIVATE="$GUI_PRL $TRANSITIVE" && \ - XCB_TRANSITIVE="-lxcb-util -lxcb -lXau -lXdmcp" && \ - XCB_PRIVATE="$XCB_PRL $TRANSITIVE $XCB_TRANSITIVE" && \ - echo "Core deps: $CORE_PRIVATE" && \ - echo "Gui deps: $GUI_PRIVATE" && \ - echo "XCB deps: $XCB_PRIVATE" && \ - printf '%s\n' \ - 'prefix=/opt/qt6-static' \ - 'includedir=${prefix}/include' \ - 'libdir=${prefix}/lib' \ - '' \ - 'Name: Qt6Core' 'Description: Qt6 Core' 'Version: 6.8.3' \ - 'Cflags: -I${includedir} -I${includedir}/QtCore' \ - "Libs: -L\${libdir} -lQt6Core" \ - "Libs.private: $CORE_PRIVATE" \ - > /opt/qt6-static/lib/pkgconfig/Qt6Core.pc && \ - printf '%s\n' \ - 'prefix=/opt/qt6-static' \ - 'includedir=${prefix}/include' \ - 'libdir=${prefix}/lib' \ - '' \ - 'Name: Qt6Gui' 'Description: Qt6 Gui' 'Version: 6.8.3' \ - 'Requires: Qt6Core' \ - 'Cflags: -I${includedir} -I${includedir}/QtGui' \ - "Libs: -L\${libdir} -lQt6Gui" \ - "Libs.private: $GUI_PRIVATE" \ - > /opt/qt6-static/lib/pkgconfig/Qt6Gui.pc && \ - printf '%s\n' \ - 'prefix=/opt/qt6-static' \ - 'includedir=${prefix}/include' \ - 'libdir=${prefix}/lib' \ - '' \ - 'Name: Qt6Widgets' 'Description: Qt6 Widgets' 'Version: 6.8.3' \ - 'Requires: Qt6Gui' \ - 'Cflags: -I${includedir} -I${includedir}/QtWidgets -I${includedir}/QtGui -I${includedir}/QtCore' \ - "Libs: -L\${libdir} -lQt6Widgets" \ - > /opt/qt6-static/lib/pkgconfig/Qt6Widgets.pc && \ - printf '%s\n' \ - 'prefix=/opt/qt6-static' \ - 'includedir=${prefix}/include' \ - 'libdir=${prefix}/lib' \ - '' \ - 'Name: Qt6PrintSupport' 'Description: Qt6 PrintSupport' 'Version: 6.8.3' \ - 'Requires: Qt6Widgets' \ - 'Cflags: -I${includedir} -I${includedir}/QtPrintSupport' \ - "Libs: -L\${libdir} -lQt6PrintSupport" \ - > /opt/qt6-static/lib/pkgconfig/Qt6PrintSupport.pc && \ - printf '%s\n' \ - 'prefix=/opt/qt6-static' \ - 'includedir=${prefix}/include' \ - 'libdir=${prefix}/lib' \ - 'plugindir=${prefix}/plugins' \ - '' \ - 'Name: Qt6XcbPlugin' 'Description: Qt6 XCB platform plugin' 'Version: 6.8.3' \ - 'Requires: Qt6Gui' \ - "Libs: -L\${libdir} -L\${plugindir}/platforms -lqxcb -lQt6XcbQpa" \ - "Libs.private: $XCB_PRIVATE" \ - > /opt/qt6-static/lib/pkgconfig/Qt6XcbPlugin.pc && \ - printf '%s\n' \ - 'prefix=/opt/qt6-static' \ - 'includedir=${prefix}/include' \ - 'libdir=${prefix}/lib' \ - '' \ - 'Name: QScintilla' 'Description: QScintilla for Qt6' 'Version: 2.14.1' \ - 'Requires: Qt6Widgets Qt6PrintSupport' \ - 'Cflags: -I${includedir} -I${includedir}/Qsci' \ - "Libs: -L\${libdir} -lqscintilla2_qt6" \ - > /opt/qt6-static/lib/pkgconfig/QScintilla.pc && \ - echo "Generated .pc files:" && ls /opt/qt6-static/lib/pkgconfig/ - -# ── Phase 5: Build Chez Scheme from source for musl ───────────────────── -# Build WITHOUT --static so libkernel.a retains full dlopen support. -# This is needed because jemacs embeds the program as a .so and loads it -# at runtime via Sscheme_script (which calls dlopen internally). -# musl's static libdl.a provides dlopen in the final static binary. -ARG CHEZ_COMMIT=fdf6b3f5d069bf53082bb827f46714f2de8f11f5 -RUN git init -q /tmp/ChezScheme && \ - git -C /tmp/ChezScheme remote add origin https://github.com/cisco/ChezScheme && \ - git -C /tmp/ChezScheme fetch -q --depth 1 origin ${CHEZ_COMMIT} && \ - test "$(git -C /tmp/ChezScheme rev-parse FETCH_HEAD)" = "${CHEZ_COMMIT}" && \ - git -C /tmp/ChezScheme checkout -q --detach ${CHEZ_COMMIT} && \ - cd /tmp/ChezScheme && \ - ./configure --threads --installprefix=/opt/chez && \ - make -j$(nproc) && \ - make install && \ - rm -rf /tmp/ChezScheme - -# ── Phase 5.5: Build tree-sitter + grammars (static) ────────────────── -ARG TS_VERSION=0.24.7 -ARG TS_COMMIT=5e8760bf462ce7b19b3d2396d5b7860f3906a297 -RUN git init -q /tmp/tree-sitter && \ - git -C /tmp/tree-sitter remote add origin https://github.com/tree-sitter/tree-sitter && \ - git -C /tmp/tree-sitter fetch -q --depth 1 origin ${TS_COMMIT} && \ - test "$(git -C /tmp/tree-sitter rev-parse FETCH_HEAD)" = "${TS_COMMIT}" && \ - git -C /tmp/tree-sitter checkout -q --detach ${TS_COMMIT} && \ - cd /tmp/tree-sitter && \ - cc -c -O2 -Ilib/include lib/src/lib.c -o lib/src/lib.o && \ - mkdir -p /opt/tree-sitter-lib && \ - ar rcs /opt/tree-sitter-lib/libtree-sitter.a lib/src/lib.o && \ - mkdir -p /opt/tree-sitter-include/tree_sitter && \ - cp lib/include/tree_sitter/api.h /opt/tree-sitter-include/tree_sitter/ && \ - rm -rf /tmp/tree-sitter - -# Build tree-sitter grammar static archives -RUN mkdir -p /opt/tree-sitter-grammars && \ - build_grammar() { \ - name=$1; repo=$2; commit=$3; subdir=${4:-.}; \ - git init -q /tmp/ts-$name && \ - git -C /tmp/ts-$name remote add origin "https://github.com/$repo" && \ - git -C /tmp/ts-$name fetch -q --depth 1 origin "$commit" && \