diff --git a/Dockerfile b/Dockerfile index f3f7a89..c003f61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,21 +21,65 @@ COPY funannotate2 ./funannotate2 # Install from the lockfile; no-op if the lockfile already matches. RUN pixi install --locked -# Rebuild pytantan from source with AVX2 disabled (SSE4-only baseline). -# The PyPI/bioconda wheel ships with AVX2 SIMD which SIGILLs on CPUs that lack -# it — notably Rosetta 2 on Apple Silicon — and AVX2 gives no meaningful win -# for this pipeline. `pip` uses build isolation, so scikit-build-core/cython/ -# scoring-matrices are pulled in transparently; we only need the C/C++ toolchain. +# Rebuild pytantan from source with all SIMD backends disabled. +# +# Background: pytantan's wheels ship with AVX2 enabled, and prior to v0.1.4 +# the generic path was also polluted with AVX2 flags via `add_compile_options` +# in the project-level CMakeLists.txt. Either way, on x86_64 CPUs without AVX2 +# (notably Rosetta 2 on Apple Silicon) any code path that executes an AVX2 +# instruction raises SIGILL. The runtime-dispatch added in v0.1.4 does not +# help us here because we want a build that is portable to *any* x86_64. +# +# pytantan's CMakeLists.txt uses FindAVX2/FindSSE4/FindNEON which auto-detect +# on the build host (GitHub Actions runners have AVX2), so we have to force +# the HAVE_* flags OFF. Pre-defining them in FindAVX2.cmake's `if((DEFINED ...))` +# guard short-circuits detection entirely. +# +# We pass these via pip's `--config-settings=cmake.define.*` rather than +# CMAKE_ARGS/SKBUILD_CMAKE_ARGS env vars, since that route goes directly into +# scikit-build-core's parser and is not subject to env-var filtering. ARG PYTANTAN_VERSION RUN apt-get update && \ apt-get install -y --no-install-recommends \ - git build-essential cmake zlib1g-dev ca-certificates && \ + git build-essential cmake zlib1g-dev binutils ca-certificates && \ rm -rf /var/lib/apt/lists/* -RUN CMAKE_ARGS="-DHAVE_AVX2=OFF -DAVX2_C_FLAGS= -DHAVE_SSE4=OFF -DSSE4_C_FLAGS= -DHAVE_NEON=OFF -DNEON_C_FLAGS=" \ - /app/.pixi/envs/default/bin/pip install --no-deps --force-reinstall \ +RUN /app/.pixi/envs/default/bin/pip install \ + --no-deps --no-cache-dir --force-reinstall -v \ + --config-settings=cmake.define.HAVE_AVX2=OFF \ + --config-settings=cmake.define.HAVE_SSE4=OFF \ + --config-settings=cmake.define.HAVE_NEON=OFF \ + --config-settings=cmake.define.AVX2_C_FLAGS= \ + --config-settings=cmake.define.SSE4_C_FLAGS= \ + --config-settings=cmake.define.NEON_C_FLAGS= \ "pytantan @ git+https://github.com/althonos/pytantan.git@v${PYTANTAN_VERSION}" && \ rm -rf /root/.cache/pip +# Verify the rebuild was effective: only `generic` should be present in the +# platform/ directory and neither lib.*.so nor generic.*.so should contain +# any AVX/AVX2 instructions (no `ymm`/`zmm` register references, no `vex`- +# encoded vector ops). Fail the build loudly otherwise so we never ship an +# image that SIGILLs at import time. +RUN set -eux; \ + PY=/app/.pixi/envs/default/bin/python; \ + PT_DIR=$("$PY" -c 'import pytantan, os; print(os.path.dirname(pytantan.__file__))'); \ + echo "pytantan installed at: $PT_DIR"; \ + ls -la "$PT_DIR/platform/"; \ + SIMD_MODS=$(ls "$PT_DIR/platform/" | grep -E '^(avx2|sse4|neon)\.' || true); \ + if [ -n "$SIMD_MODS" ]; then \ + echo "ERROR: SIMD platform modules were built despite HAVE_*=OFF: $SIMD_MODS"; \ + exit 1; \ + fi; \ + BAD=""; \ + for so in $(find "$PT_DIR" -maxdepth 2 -name '*.so'); do \ + if objdump -d -M intel --no-show-raw-insn "$so" 2>/dev/null \ + | grep -Eq '\b(ymm[0-9]+|zmm[0-9]+|vpbroadcast|vextracti128|vinserti128)\b'; then \ + echo "ERROR: $so contains AVX/AVX2 instructions"; \ + BAD="${BAD} ${so}"; \ + fi; \ + done; \ + if [ -n "$BAD" ]; then exit 1; fi; \ + "$PY" -c "import pytantan; from pytantan import Alphabet, RepeatFinder, default_scoring_matrix; print('pytantan smoke test OK', pytantan.__version__)" + # Pre-generate an activation script so the final image doesn't need pixi. RUN mkdir -p /app/bin && \ { echo '#!/bin/bash'; \