tqcq acf9c363d2 CI: split pipeline into build/verify/package stages
Three chained jobs per arch connected by a duckdb-binary-<arch> artifact
(unstripped binary + wireduck test pcap). Isolates stage failures and
allows re-running a single stage (e.g. re-verify) without a full rebuild.
arm64 build stage still exceeds the runner's 3h default deadline under
QEMU — needs a runner-side timeout raise or native cross-compile.
2026-08-31 10:36:51 +08:00

duckdb-static-build

Build a fully-static DuckDB v1.5.5 CLI for Linux (glibc/Ubuntu 24.04) with 50 statically-linked extensions, including a patched WireDuck extension (lazy-load + silent output). Builds run in Docker on both amd64 (native) and arm64 (via QEMU), triggered from Gitea Actions.

Layout

Path Purpose
docker/Dockerfile.build Ubuntu 24.04 + toolchain + pinned vcpkg builder image (multi-arch)
scripts/build.sh Fetch pinned duckdb/wireduck sources, apply patches, make extension_configuration + static release build, stage out/duckdb
scripts/verify.sh Post-build verification of out/duckdb (staticness, version, 50 static extensions, output cleanliness, GGUF inference smoke test)
scripts/package.sh Split debug symbols, strip, checksum, produce out/dist/*.tar.zst
extensions_static.cmake Extension config selecting the 50 statically-linked extensions
patches/wireduck/ WireDuck patches (*.patch) + wireduck_override.cmake
.gitea/workflows/build.yml CI: dual-arch build, artifact upload, tag-triggered release
build/ Reference checkout of duckdb v1.5.5 (ground-truth docs; gitignored, not used by the build itself)

Local build quickstart

docker build -f docker/Dockerfile.build -t duckdb-static-builder:local .

# build (sources are cloned into ./build/, binary staged at ./out/duckdb)
docker run --rm -v "$PWD":/work duckdb-static-builder:local

# verify
docker run --rm -v "$PWD":/work --entrypoint /bin/bash \
    duckdb-static-builder:local /opt/scripts/verify.sh

# package (writes ./out/dist/*.tar.zst)
docker run --rm -v "$PWD":/work --entrypoint /bin/bash \
    duckdb-static-builder:local /opt/scripts/package.sh

Useful env overrides for build.sh (pass with docker run -e): DUCKDB_TAG, DUCKDB_COMMIT, JOBS, EXTENSION_CONFIGS, EXTRA_CMAKE_VARIABLES.

CI

  • Trigger: push a tag matching v*, or manual workflow_dispatch (input arch: both (default), amd64, arm64).
  • Pipeline: three staged jobs per arch — buildverifypackage — chained by a duckdb-binary-<arch> artifact (unstripped out/duckdb + wireduck test pcap). Stage isolation means a failed stage can be re-run alone (e.g. re-verify without a full rebuild). The builder image is rebuilt per stage (runner cache is not shared).
  • Matrix: amd64 builds natively; arm64 builds the same image and runs the container with docker --platform linux/arm64 (requires QEMU/binfmt on the runner — see comments in the workflow file). Note the arm64 build stage alone takes ~5h under QEMU, past this Gitea instance's 3h default job deadline.
  • Artifacts: per-arch uploads (duckdb-static-<arch>) from the package stage, and on tag pushes a Gitea Release is created with all dist files attached.

Artifacts

File Contents
duckdb-static-linux-<arch>.tar.zst stripped duckdb binary + SHA256SUMS (covers both archives)
duckdb-static-linux-<arch>-debug.tar.zst duckdb.debug split debug symbols

<arch> is amd64 or arm64.

Verification summary (scripts/verify.sh)

  • file reports a statically linked ELF (no "dynamically"); ldd reports not a dynamic executable
  • duckdb --version completes in under 2 seconds and reports v1.5.5
  • SELECT count(*) FROM duckdb_extensions() WHERE install_mode='STATICALLY_LINKED' returns exactly 50
  • duckdb --csv -c "SELECT 42" first stdout line is exactly 42; no [WireDuck] lines in --version or query output
  • no initializing glossary noise in --version output
  • https fetch smoke test (TLS + DNS via c-ares) returns 200 (skipped offline)
  • read_pcap functional smoke test over the wireduck test pcap (needs tshark)
  • laduck GGUF inference smoke test: llm_load_model + llm_complete + llm_unload_model with ggml-org's 260K-param stories model (~1.3 MB, downloaded into build/models/; skipped offline)

Notes

  • DuckDB is built with the v1.5.5 static-extension mechanism (make extension_configuration merged-vcpkg workflow + USE_MERGED_VCPKG_MANIFEST=1, EXTENSION_CONFIGS pointing at extensions_static.cmake and the wireduck override). The Makefile target is make release (there is no make build target in v1.5.5); full static linking is forced via -DCMAKE_EXE_LINKER_FLAGS=-static.
  • Sources are pinned by commit: duckdb d8cdaa33fda8df955cc76ef58a280f68f4cd43fa (tag v1.5.5), wireduck 19c4018cc8ebad08547d621bcdc25df86294ca2b.
  • laduck pins hamidr/laduck@330c946 with llama.cpp (third_party/llama.cpp@69c28f1) built as a static subproject — local GGUF inference (llm_complete / llm_embed / llm_classify) runs in-process, no server/subprocess. Upstream is dormant; the pin is effectively owned by this repo.

Known quirks of this binary

  • Process exit skips global teardown on purpose (scripts/static_cli_runtime_overrides.c, linked into the shell target): a fully-static binary bundling OpenSSL + libmariadbclient + two Rust staticlibs + 50 extensions has cross-library atexit handlers that race and double-free after all real work is done. exit() flushes stdio and calls _exit(); getenv() force-returns QUERY_FARM_TELEMETRY_OPT_OUT=1 so query-farm extensions never spawn their startup telemetry threads (their concurrent first-use OpenSSL init races in this build). Side effect: memory is not freed at exit (invisible for a CLI).
  • fts: PRAGMA create_fts_index('main.t', 'x', 'txt') — qualify the table name (or quote it); the bare unqualified form misparses in this fts pin against duckdb v1.5.5. Query with fts_main_<table>.match_bm25(...). Note upstream fts (v1.5.5 pin) also drops some tokens during indexing — verified identical on a reference v1.5.5-dev23 build, not a static-build artifact.
  • Rust extensions (prql, lindel): the two Rust archives embed duplicate copies of three std symbols (EMPTY_PANIC, ARGV_INIT_ARRAY, rust_eh_personality); the link is permitted via -Wl,--allow-multiple-definition (verified: exactly these three duplicates exist and they are bit-identical).
  • h3 bridge: h3's vendored C-API header is an older API table (2856 B) than duckdb v1.5.5's (4368 B); the global table symbol is defined once with the v1.5.5 type in src/h3_static_api_init.cpp (see patches/h3/). Without that, the API-table copy overflows into adjacent .bss and corrupts unrelated globals (OpenSSL's default_context_int among others).
  • curl is built against c-ares: static glibc cannot dlopen NSS modules, which crashed hostname resolution (SIGFPE inside getaddrinfo). The merged vcpkg manifest patches curl's resolve feature to c-ares.
S
Description
No description provided
Readme
106 KiB
Languages
Shell 59.8%
CMake 27.9%
C 12.3%