From 13da122601d7f467260122b23fcbbca6a3663c7f Mon Sep 17 00:00:00 2001 From: Anant Sharma Date: Wed, 8 Jul 2026 13:31:32 -0700 Subject: [PATCH 1/4] fix: build the aiconfigurator wheel as manylinux_2_28 The wheel carries a native Rust/PyO3 (abi3) extension but was built on ubuntu:jammy and tagged linux_x86_64 -- a bare platform tag that PyPI rejects and that effectively pins the wheel to the build host's glibc. Build it as a portable manylinux_2_28_x86_64 wheel instead (glibc >= 2.28; RHEL 8+ / Ubuntu 20.04+). Move the build stage onto quay.io/pypa/manylinux_2_28_x86_64 (glibc 2.28) with an explicit rustup toolchain and uv, pinned to the image's CPython 3.12. maturin's built-in auditwheel checks the manylinux_2_28 policy and stamps the tag -- no separate auditwheel step. uv build makes maturin's PEP517 backend force --compatibility off, so the target is re-asserted via MATURIN_PEP517_ARGS on the build command. README notes the new tag. Verified: auditwheel reports the wheel consistent with manylinux_2_28_x86_64 (only whitelisted libc/libm/libpthread/libgcc_s referenced, nothing bundled); it installs and runs on the jammy runtime image; verify_release_wheels.py passes. Signed-off-by: Anant Sharma --- README.md | 9 +++++---- docker/Dockerfile | 49 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f6faef2ea..b127169ca 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,11 @@ Let's get started. ### Install from PyPI > **Supported platform: Linux x86-64 only.** The published `aiconfigurator` wheel -> bundles a native (Rust/PyO3) extension and is built for `linux_x86_64`. macOS, -> Windows, and other architectures (including Linux aarch64) are **not supported** -> and have no published release wheels — `pip3 install aiconfigurator` will not -> find a compatible wheel there. Please install from source for non-Linux platforms. +> bundles a native (Rust/PyO3) extension and is built as a `manylinux_2_28_x86_64` +> wheel (Linux x86-64, glibc >= 2.28). macOS, Windows, and other architectures +> (including Linux aarch64) are **not supported** and have no published release +> wheels — `pip3 install aiconfigurator` will not find a compatible wheel there. +> Please install from source for non-Linux platforms. ```bash pip3 install aiconfigurator diff --git a/docker/Dockerfile b/docker/Dockerfile index 66b9f66f2..f1446e13e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,15 +15,33 @@ RUN mkdir /opt/aiconfigurator && \ ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" ENV MPLCONFIGDIR=/tmp/matplotlib -FROM base AS build # The `aiconfigurator` wheel owns the Rust extension, SDK/data payload, CLI, -# generator, webapp, and Spica packages. The small `aiconfigurator-core` wheel -# built afterward is only a compatibility metapackage. Maturin needs the Rust -# crate sources, a C linker (gcc), and ca-certificates so cargo can fetch crates -# from crates.io over TLS. Rust itself is bootstrapped by maturin's build backend. -RUN apt-get update && \ - apt-get install -y --no-install-recommends ca-certificates gcc libc6-dev && \ - rm -rf /var/lib/apt/lists/* +# generator, webapp, and Spica packages. It is built inside the PyPA +# manylinux_2_28 image so the native (Rust/PyO3) `_aiconfigurator_core.*.so` +# links against glibc 2.28; maturin's built-in auditwheel (driven by +# `[tool.maturin] compatibility = "manylinux_2_28"` in pyproject.toml) then +# checks the manylinux_2_28 policy, bundles any non-whitelisted shared libs +# (the abi3 extension needs only glibc/libgcc, so none), and stamps the portable +# `manylinux_2_28_x86_64` tag. The small `aiconfigurator-core` wheel built +# afterward is a pure-Python compatibility metapackage and needs no repair. +# +# The manylinux image already ships auditwheel, patchelf, a gcc toolset, and +# ca-certificates; we add the static `uv` build frontend and a rustup toolchain +# so maturin's cargo build can fetch crates from crates.io over TLS. +FROM quay.io/pypa/manylinux_2_28_x86_64 AS build +WORKDIR /workspace +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +ENV RUSTUP_HOME=/opt/rust/rustup \ + CARGO_HOME=/opt/rust/cargo \ + PATH=/opt/rust/cargo/bin:${PATH} +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --profile minimal --default-toolchain stable + +# Pin the build interpreter to the image's CPython 3.12 so uv/maturin never fetch +# a standalone one. abi3 (abi3-py310) means the single wheel serves CPython >=3.10. +ENV UV_PYTHON=/opt/python/cp312-cp312/bin/python3 + COPY src/ /workspace/src/ COPY rust/ /workspace/rust/ COPY LICENSE pyproject.toml README.md /workspace/ @@ -38,9 +56,18 @@ RUN set -e; \ echo "Run 'git lfs install && git lfs pull' in the source tree before building." >&2; \ exit 1; \ fi -RUN uv build --wheel --out-dir /workspace/dist && \ - cd src/aiconfigurator-core && uv build --wheel --out-dir /workspace/dist && \ - cd /workspace && python tools/verify_release_wheels.py /workspace/dist +# Build the main wheel. Under a frontend like `uv build`, maturin's PEP517 +# backend injects `--compatibility off` (plain linux) by default, and that CLI +# flag overrides `[tool.maturin] compatibility`. MATURIN_PEP517_ARGS re-asserts +# the manylinux_2_28 target -- maturin appends it after the injected `off`, so it +# wins -- which drives maturin's built-in auditwheel to check the 2_28 policy and +# stamp the `manylinux_2_28_x86_64` tag. The `aiconfigurator-core` metapackage is +# pure-Python. `python` is not on PATH in the manylinux image, so the verifier +# runs via ${UV_PYTHON}. +RUN MATURIN_PEP517_ARGS="--compatibility manylinux_2_28" \ + uv build --wheel --out-dir /workspace/dist && \ + ( cd src/aiconfigurator-core && uv build --wheel --out-dir /workspace/dist ) && \ + "${UV_PYTHON}" tools/verify_release_wheels.py /workspace/dist FROM base AS runtime COPY --from=build /workspace/dist /wheelhouse From 64d77b20f21de9a5987f4aba06e1e0f7c47b1ee9 Mon Sep 17 00:00:00 2001 From: Anant Sharma Date: Wed, 8 Jul 2026 13:54:01 -0700 Subject: [PATCH 2/4] ci: build aiconfigurator wheels for arm64 as well as x86-64 Parameterize the wheel build stage by target architecture so it produces both manylinux_2_28_x86_64 and manylinux_2_28_aarch64 wheels from one Dockerfile. BuildKit populates TARGETARCH (amd64/arm64) from --platform (default: the build host's arch); `FROM manylinux-${TARGETARCH}` selects the matching PyPA base (quay.io/pypa/manylinux_2_28_{x86_64,aarch64}) and only that stage is built. MATURIN_PEP517_ARGS is unchanged -- maturin appends the arch, so the tag resolves to _x86_64 or _aarch64 automatically. The crate is architecture-agnostic (no target_arch code / x86 intrinsics) and the parquet data payload is identical across arches. Build an aarch64 wheel natively on ARM, or from an x86 host via QEMU/buildx: docker buildx build --platform linux/arm64 --target build --load . Also drop a stale comment that referenced a [tool.maturin] compatibility value that is not set (the target is asserted via MATURIN_PEP517_ARGS). amd64 build re-verified unchanged; the aarch64 base pull and rustup toolchain install validated under buildx QEMU emulation. Signed-off-by: Anant Sharma --- docker/Dockerfile | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index f1446e13e..514d4da5a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -18,17 +18,27 @@ ENV MPLCONFIGDIR=/tmp/matplotlib # The `aiconfigurator` wheel owns the Rust extension, SDK/data payload, CLI, # generator, webapp, and Spica packages. It is built inside the PyPA # manylinux_2_28 image so the native (Rust/PyO3) `_aiconfigurator_core.*.so` -# links against glibc 2.28; maturin's built-in auditwheel (driven by -# `[tool.maturin] compatibility = "manylinux_2_28"` in pyproject.toml) then -# checks the manylinux_2_28 policy, bundles any non-whitelisted shared libs -# (the abi3 extension needs only glibc/libgcc, so none), and stamps the portable -# `manylinux_2_28_x86_64` tag. The small `aiconfigurator-core` wheel built +# links against glibc 2.28; maturin's built-in auditwheel then checks the +# manylinux_2_28 policy, bundles any non-whitelisted shared libs (the abi3 +# extension needs only glibc/libgcc, so none), and stamps the portable +# `manylinux_2_28_` tag. The small `aiconfigurator-core` wheel built # afterward is a pure-Python compatibility metapackage and needs no repair. # # The manylinux image already ships auditwheel, patchelf, a gcc toolset, and # ca-certificates; we add the static `uv` build frontend and a rustup toolchain # so maturin's cargo build can fetch crates from crates.io over TLS. -FROM quay.io/pypa/manylinux_2_28_x86_64 AS build +# +# The base is selected per target architecture: BuildKit populates TARGETARCH +# (amd64/arm64) from `--platform` (default: the build host's arch), and only the +# matching stage below is built. Build an aarch64 wheel with: +# docker build -f docker/Dockerfile --platform linux/arm64 --target build . +# natively on ARM, or via QEMU emulation / `docker buildx` on an x86 host. The +# crate is architecture-agnostic (no target_arch code); the parquet data payload +# is identical across arches. +FROM quay.io/pypa/manylinux_2_28_x86_64 AS manylinux-amd64 +FROM quay.io/pypa/manylinux_2_28_aarch64 AS manylinux-arm64 +ARG TARGETARCH +FROM manylinux-${TARGETARCH} AS build WORKDIR /workspace COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ @@ -61,7 +71,7 @@ RUN set -e; \ # flag overrides `[tool.maturin] compatibility`. MATURIN_PEP517_ARGS re-asserts # the manylinux_2_28 target -- maturin appends it after the injected `off`, so it # wins -- which drives maturin's built-in auditwheel to check the 2_28 policy and -# stamp the `manylinux_2_28_x86_64` tag. The `aiconfigurator-core` metapackage is +# stamp the `manylinux_2_28_` tag. The `aiconfigurator-core` metapackage is # pure-Python. `python` is not on PATH in the manylinux image, so the verifier # runs via ${UV_PYTHON}. RUN MATURIN_PEP517_ARGS="--compatibility manylinux_2_28" \ From 15fe3c5b859c4f5fbe8ec78153bb1e345b11bea4 Mon Sep 17 00:00:00 2001 From: Anant Sharma Date: Wed, 8 Jul 2026 15:09:00 -0700 Subject: [PATCH 3/4] fix: harden Rust bootstrap and tighten maturin pin for wheel builds docker/Dockerfile: download the rustup installer to disk instead of piping `curl | sh`, so a failed download aborts the build (the pipeline previously reported only sh's exit status and silently skipped rustup). Add curl retries for transient network blips and pin the toolchain to 1.96.0 so release wheels build reproducibly. pyproject.toml: require maturin>=1.5.1. 1.5.1 fixed `--compatibility` handling when run as a PEP517 backend, which the manylinux_2_28 wheel build depends on (MATURIN_PEP517_ARGS="--compatibility manylinux_2_28"); 1.5.0 ignored it. Signed-off-by: Anant Sharma --- docker/Dockerfile | 8 ++++++-- pyproject.toml | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 514d4da5a..cf929e59d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -45,8 +45,12 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ ENV RUSTUP_HOME=/opt/rust/rustup \ CARGO_HOME=/opt/rust/cargo \ PATH=/opt/rust/cargo/bin:${PATH} -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ - | sh -s -- -y --profile minimal --default-toolchain stable +# Download to disk (not `curl | sh`, which hides a failed download behind sh's +# exit status); pin the toolchain for reproducible release wheels. +RUN curl --proto '=https' --tlsv1.2 -sSf --retry 5 --retry-connrefused --retry-delay 2 \ + https://sh.rustup.rs -o /tmp/rustup-init.sh \ + && sh /tmp/rustup-init.sh -y --profile minimal --default-toolchain 1.96.0 \ + && rm /tmp/rustup-init.sh # Pin the build interpreter to the image's CPython 3.12 so uv/maturin never fetch # a standalone one. abi3 (abi3-py310) means the single wheel serves CPython >=3.10. diff --git a/pyproject.toml b/pyproject.toml index 24dd583b7..d0d86487d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,9 @@ GitHub = "https://github.com/ai-dynamo/aiconfigurator" Repository = "https://github.com/ai-dynamo/aiconfigurator.git" [build-system] -requires = ["maturin>=1.5,<2"] +# >=1.5.1 fixed `--compatibility` under the PEP517 backend, which the +# manylinux_2_28 wheel build relies on (see docker/Dockerfile). +requires = ["maturin>=1.5.1,<2"] build-backend = "maturin" [project.scripts] From 66d64c6d9bad791f1cee9d809891e22cad7c168b Mon Sep 17 00:00:00 2001 From: Anant Sharma Date: Wed, 8 Jul 2026 15:14:00 -0700 Subject: [PATCH 4/4] docs: condense wheel-build comments in Dockerfile Trim the verbose block comments added for the manylinux_2_28 wheel build to their essential rationale (why manylinux/glibc 2.28, why MATURIN_PEP517_ARGS overrides the PEP517 compatibility default, why pin the interpreter). Signed-off-by: Anant Sharma --- docker/Dockerfile | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index cf929e59d..85b009a7a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,26 +15,14 @@ RUN mkdir /opt/aiconfigurator && \ ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" ENV MPLCONFIGDIR=/tmp/matplotlib -# The `aiconfigurator` wheel owns the Rust extension, SDK/data payload, CLI, -# generator, webapp, and Spica packages. It is built inside the PyPA -# manylinux_2_28 image so the native (Rust/PyO3) `_aiconfigurator_core.*.so` -# links against glibc 2.28; maturin's built-in auditwheel then checks the -# manylinux_2_28 policy, bundles any non-whitelisted shared libs (the abi3 -# extension needs only glibc/libgcc, so none), and stamps the portable -# `manylinux_2_28_` tag. The small `aiconfigurator-core` wheel built -# afterward is a pure-Python compatibility metapackage and needs no repair. +# The `aiconfigurator` wheel (Rust extension + SDK/data payload + CLI, generator, +# webapp, Spica) is built in the PyPA manylinux_2_28 image so the native PyO3 +# `.so` links glibc 2.28 and maturin's auditwheel stamps the portable +# `manylinux_2_28_` tag; `aiconfigurator-core` is a pure-Python metapackage. +# We add `uv` and a rustup toolchain so cargo can fetch crates over TLS. # -# The manylinux image already ships auditwheel, patchelf, a gcc toolset, and -# ca-certificates; we add the static `uv` build frontend and a rustup toolchain -# so maturin's cargo build can fetch crates from crates.io over TLS. -# -# The base is selected per target architecture: BuildKit populates TARGETARCH -# (amd64/arm64) from `--platform` (default: the build host's arch), and only the -# matching stage below is built. Build an aarch64 wheel with: -# docker build -f docker/Dockerfile --platform linux/arm64 --target build . -# natively on ARM, or via QEMU emulation / `docker buildx` on an x86 host. The -# crate is architecture-agnostic (no target_arch code); the parquet data payload -# is identical across arches. +# TARGETARCH (amd64/arm64) selects the base per `--platform`; only the matching +# stage builds. aarch64: `docker build --platform linux/arm64 --target build .`. FROM quay.io/pypa/manylinux_2_28_x86_64 AS manylinux-amd64 FROM quay.io/pypa/manylinux_2_28_aarch64 AS manylinux-arm64 ARG TARGETARCH @@ -52,8 +40,7 @@ RUN curl --proto '=https' --tlsv1.2 -sSf --retry 5 --retry-connrefused --retry-d && sh /tmp/rustup-init.sh -y --profile minimal --default-toolchain 1.96.0 \ && rm /tmp/rustup-init.sh -# Pin the build interpreter to the image's CPython 3.12 so uv/maturin never fetch -# a standalone one. abi3 (abi3-py310) means the single wheel serves CPython >=3.10. +# Pin to the image's CPython 3.12 so uv/maturin never fetch a standalone one. ENV UV_PYTHON=/opt/python/cp312-cp312/bin/python3 COPY src/ /workspace/src/ @@ -70,14 +57,9 @@ RUN set -e; \ echo "Run 'git lfs install && git lfs pull' in the source tree before building." >&2; \ exit 1; \ fi -# Build the main wheel. Under a frontend like `uv build`, maturin's PEP517 -# backend injects `--compatibility off` (plain linux) by default, and that CLI -# flag overrides `[tool.maturin] compatibility`. MATURIN_PEP517_ARGS re-asserts -# the manylinux_2_28 target -- maturin appends it after the injected `off`, so it -# wins -- which drives maturin's built-in auditwheel to check the 2_28 policy and -# stamp the `manylinux_2_28_` tag. The `aiconfigurator-core` metapackage is -# pure-Python. `python` is not on PATH in the manylinux image, so the verifier -# runs via ${UV_PYTHON}. +# `uv build` runs maturin's PEP517 backend, which defaults to `--compatibility +# off`; MATURIN_PEP517_ARGS overrides it to stamp the manylinux_2_28 tag (maturin +# appends it last, so it wins). `python` isn't on PATH here -> verify via UV_PYTHON. RUN MATURIN_PEP517_ARGS="--compatibility manylinux_2_28" \ uv build --wheel --out-dir /workspace/dist && \ ( cd src/aiconfigurator-core && uv build --wheel --out-dir /workspace/dist ) && \