Skip to content

Bundle libxml2 + libicu74 to support Ubuntu 25.10 / 26.04#14

Merged
nicoloboschi merged 2 commits into
mainfrom
fix/ubuntu-libxml2-icu-bundling
May 5, 2026
Merged

Bundle libxml2 + libicu74 to support Ubuntu 25.10 / 26.04#14
nicoloboschi merged 2 commits into
mainfrom
fix/ubuntu-libxml2-icu-bundling

Conversation

@nicoloboschi
Copy link
Copy Markdown
Collaborator

Summary

Ubuntu 25.10 (Plucky) shipped libxml2 2.14 with a SONAME bump from `.so.2` to `.so.16` (and renamed the apt package to `libxml2-16`); ICU jumped from 74 to 76. The theseus-rs PostgreSQL binaries pg0 ships are linked against `libxml2.so.2` and `libicu*.so.74`, both unavailable on 25.10 - and on the upcoming 26.04 LTS that inherits from it. Result: `pg0 start` fails with "missing required system libraries" out of the box.

The fix: download `libxml2` (2.9.14) and `libicu74` (74.2-1ubuntu3.1) from the Ubuntu 24.04 archive at build time, embed the `.so` files in the pg0 binary, extract them next to the bundled postgres at first run, create the SONAME symlinks the dynamic linker resolves (`libxml2.so.2` -> `libxml2.so.2.9.14`, etc.), and prepend that lib dir to `LD_LIBRARY_PATH` so initdb / postgres / pg_ctl / psql find them.

The bundle ships only on Linux GNU targets. macOS, Windows and the musl Linux builds get an empty bundle - they either have the libs system-side in a forward-compatible way (Alpine 3.20 / 3.21) or ship them through other means (macOS frameworks, Windows DLLs alongside postgres.exe).

Changes

  • `versions.env`: pins exact `.deb` URLs + SHA256s for libxml2 + libicu74 (amd64 + arm64).
  • `build.rs`: new `bundle_runtime_libs()` cracks each `.deb` open (ar archive containing `data.tar.zst`), extracts the four `.so` files we need, and repackages them as `runtime_libs.tar.gz` in `OUT_DIR`. Empty bundle for non-Linux-GNU targets.
  • `Cargo.toml`: `ar`, `zstd`, `tar`, `flate2`, `sha2`, `hex` added as build-dependencies for the `.deb` extraction.
  • `src/main.rs`: `RUNTIME_LIBS_BUNDLE` const, `ensure_runtime_libs()` extractor that creates SONAME symlinks, `prepend_lib_dir_to_ld_library_path()`, and call sites in `extract_bundled_postgresql` (runs even on cache hit so existing installs upgrade smoothly), `pg0 psql`, and `pg0 install-extension`.
  • `docker-tests/test_ubuntu_amd64.sh`: new test covering `ubuntu:24.04` and `ubuntu:25.10`. The 25.10 run reproduces the original failure on v0.13.0 and passes on the new binary.
  • `docker-tests/test_missing_libs.sh`: canary changed from `libxml2` (now bundled) to `libgssapi-krb5-2` so the missing-lib detection code path stays exercised.
  • `docker-tests/run_all_tests.sh` + `docker-tests/README.md`: index the new test.
  • `.github/workflows/ci.yml`: adds the `ubuntu-amd64` job to the docker-tests matrix, building the binary fresh (same approach as the existing `missing-libs` job).
  • `README.md`: drops `libxml2` / `libicu*` from the runtime-deps snippets, adds `tzdata` and `libreadline8`, updates the bundled-libs table, fixes the binary-name table, and adds a new "Tested and Supported Platforms" table that calls out exactly which Alpine versions work (3.20 / 3.21) vs not (3.22+) and the Ubuntu 24.04 / 25.10 / 26.04 story.

Cost

Linux GNU binaries grow by ~10 MB (matches the gz-compressed runtime libs bundle). macOS / Windows / musl binaries are unchanged.

Backwards compatibility

  • Existing installs (`~/.pg0/installation//` already populated): `ensure_runtime_libs` runs even on the cache-hit path, so the libs land in `lib/` on the first start with the new binary.
  • Existing data dirs: untouched.
  • Hosts that already have `libxml2.so.2` (Debian, Ubuntu 24.04, etc.): `LD_LIBRARY_PATH` prefers ours; bundled `.so.2` is ABI-compatible with theirs, no-op.
  • Hosts with a different SONAME (`libxml2.so.16` on 25.10): no collision, the loader picks up our bundled `.so.2` exclusively.
  • CLI / SDK API: unchanged.

Verified locally

  • `docker-tests/test_ubuntu_amd64.sh` passes (24.04 + 25.10) against the freshly built binary.
  • Released v0.13.0 still fails on 25.10 in the same test harness (reproduces the bug).
  • `docker-tests/test_debian_amd64.sh` and `docker-tests/test_alpine_amd64.sh` still pass.
  • `docker-tests/test_missing_libs.sh` passes with the new canary lib.
  • Upgrade path: install via v0.13.0 (extracts postgres, fails to start), then run new binary - libs extracted into existing install, postgres starts, psql works.
  • `ldd` on the bundled postgres on 25.10 confirms `libxml2.so.2`, `libicuuc.so.74`, `libicudata.so.74` resolve to the bundled copies under `~/.pg0/installation/18.1.0/lib/`.

Test plan

  • CI: macOS + Windows builds (empty runtime libs bundle path).
  • CI: `docker-tests/test_debian_amd64.sh` (no regression).
  • CI: `docker-tests/test_alpine_amd64.sh` (no regression).
  • CI: `docker-tests/test_ubuntu_amd64.sh` (new - validates the fix on 25.10).
  • CI: `docker-tests/test_missing_libs.sh` (still detects missing libs via the new canary).
  • CI: Python wheels build and pass their post-install start/stop test.
  • CI: sdist install test still works.

Ubuntu 25.10 (Plucky) shipped libxml2 2.14, which bumped the SONAME from
.so.2 to .so.16, and renamed the apt package from libxml2 to libxml2-16.
ICU also moved from 74 to 76. The theseus-rs PostgreSQL binaries that
pg0 bundles are linked against libxml2.so.2 and libicu*.so.74, both of
which are unavailable on 25.10 (and inherited on the upcoming 26.04 LTS),
so `pg0 start` failed with "missing required system libraries".

Reproduction added in docker-tests/test_ubuntu_amd64.sh: ubuntu:24.04
passes against the released v0.13.0 binary; ubuntu:25.10 fails.

Fix: download libxml2 (2.9.14) and libicu74 (74.2-1ubuntu3.1) from the
Ubuntu 24.04 archive at build time, embed the .so files into the pg0
binary, extract them into <installation>/<ver>/lib/ at runtime alongside
the postgres binary, create the SONAME symlinks (libxml2.so.2 ->
libxml2.so.2.9.14 etc.), and prepend that lib dir to LD_LIBRARY_PATH so
postgres / initdb / pg_ctl / psql resolve through the bundled copies.

Bundle is built only for Linux GNU targets (x86_64 + aarch64). macOS,
Windows, and the musl Linux builds get an empty bundle - on those
platforms the libs are either system-provided in a forward-compatible
way (Alpine 3.20/3.21 with ICU 74) or shipped by other means (macOS
frameworks, Windows .dlls beside postgres.exe).

Upgrade safety: the runtime extraction runs even when postgres is
already extracted from a previous pg0 release, so users on existing
~/.pg0/installation/ trees pick up the new libs automatically without
having to wipe the directory.

Other:
- versions.env now pins exact .deb URLs + SHA256s for reproducibility.
- ar / zstd / tar / flate2 / sha2 / hex added as build-dependencies for
  cracking open the .deb (ar archive of zstd-compressed tar).
- Linux GNU binary grows ~10 MB (matches the gz-compressed bundle).
- test_missing_libs.sh now uses libgssapi-krb5-2 as the canary missing
  lib, since libxml2 is no longer required from the host.
- New docker-tests/test_ubuntu_amd64.sh covers ubuntu:24.04 + ubuntu:25.10
  and is wired into CI (with PG0_BINARY_PATH built fresh, like the
  existing missing-libs job).
- README updated: removes libxml2 / libicu from the apt install snippets,
  adds a "Tested and Supported Platforms" table that explicitly calls
  out which Alpine versions work (3.20 / 3.21) vs not (3.22+), and adds
  Ubuntu 24.04 / 25.10 / 26.04 as supported.
…eline

The previous commit pulled libxml2 and libicu74 from Ubuntu 24.04, but
those .so files require GLIBC 2.38, which is newer than the manylinux_2_35
baseline (Ubuntu 22.04 / Debian 12) the wheel ships under. CI's
Linux x86_64-gnu wheel test failed on the ubuntu-22.04 runner with:
  postgres: libm.so.6: version `GLIBC_2.38' not found
            (required by libxml2.so.2)

Root-cause realization: the bundled postgres binary itself does NOT have
libicu in DT_NEEDED. It only links against libxml2.so.2; libxml2 is what
pulls libicu in transitively. So the ICU major version we ship just
needs to match whatever ICU the bundled libxml2 was built against - it
does not have to match the original SONAME `.so.74` from Ubuntu 24.04.

Switch to Ubuntu 22.04 sources:
  - libxml2 2.9.13+dfsg-1ubuntu0.11 (.so.2 SONAME, transitively wants .so.70)
  - libicu70 70.1-2ubuntu1 (icudata + icui18n + icuuc)

These libs require at most GLIBC 2.34, comfortably below our 2.35 wheel
baseline, and are still ABI-compatible everywhere we care about (22.04,
24.04, 25.10, 26.04). They sit alongside whatever libicu the host has
installed - on 24.04 the system libicu74 is unused; on 25.10 the system
libicu76 is unused; in both cases the bundled postgres resolves through
our libxml2 -> our libicu70.

Verified locally:
  - Vanilla ubuntu:22.04 (glibc 2.35, no libxml2/libicu installed):
    pg0 start + psql + stop works.
  - ubuntu:24.04 + ubuntu:25.10: docker-tests/test_ubuntu_amd64.sh passes.
  - python:3.11-slim debian: docker-tests/test_debian_amd64.sh passes.
  - missing-libs detection still works.

Comments in build.rs and main.rs updated to reflect the new sourcing
rationale; README's Ubuntu 25.10 row updated from "ICU 74" to "libicu70".
@nicoloboschi nicoloboschi merged commit e09968c into main May 5, 2026
15 checks passed
@nicoloboschi nicoloboschi deleted the fix/ubuntu-libxml2-icu-bundling branch May 5, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant