Skip to content

Validate architecture, exported ABI, and dynamic linkage for every release wheel #448

Description

@Alek99

Summary

Seven cross-built native wheel targets are published after archive/content checks only. The verifier confirms that one file with an expected suffix exists and that wheel tags are non-pure, but it does not prove the embedded binary format/machine matches the wheel tag, required C ABI symbols are exported, or dynamic dependencies/minimum-OS linkage are valid. Only host-architecture matrix entries are installed and loaded.

Evidence

  • The release matrix marks aarch64/armv7 glibc, all musl targets, macOS x86_64, Windows x86, and Windows arm64 as native: false:
    # Linux glibc (manylinux_2_17 floor) — cross-compiled with zig.
    - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, zigtarget: x86_64-unknown-linux-gnu.2.17, plat: manylinux_2_17_x86_64, zig: true, native: true }
    - { os: ubuntu-latest, target: aarch64-unknown-linux-gnu, zigtarget: aarch64-unknown-linux-gnu.2.17, plat: manylinux_2_17_aarch64, zig: true, native: false }
    - { os: ubuntu-latest, target: armv7-unknown-linux-gnueabihf, zigtarget: armv7-unknown-linux-gnueabihf.2.17, plat: manylinux_2_17_armv7l, zig: true, native: false }
    # Linux musl / Alpine. crt-static is musl's default, and rustc
    # silently *drops* cdylib output under it (a warning, not an error)
    # since a fully-static binary can't also be a shared library —
    # -C target-feature=-crt-static switches to dynamic linking against
    # musl libc so a real cdylib gets produced.
    - { os: ubuntu-latest, target: x86_64-unknown-linux-musl, zigtarget: x86_64-unknown-linux-musl, plat: musllinux_1_2_x86_64, zig: true, native: false, rustflags: "-C target-feature=-crt-static" }
    - { os: ubuntu-latest, target: aarch64-unknown-linux-musl, zigtarget: aarch64-unknown-linux-musl, plat: musllinux_1_2_aarch64, zig: true, native: false, rustflags: "-C target-feature=-crt-static" }
    - { os: ubuntu-latest, target: armv7-unknown-linux-musleabihf, zigtarget: armv7-unknown-linux-musleabihf, plat: musllinux_1_2_armv7l, zig: true, native: false, rustflags: "-C target-feature=-crt-static" }
    # macOS. Both build on the arm64 runner (macos-14): Apple Silicon
    # runners are plentiful, while the last Intel runner (macos-13) is
    # deprecated and frequently unschedulable. The Apple toolchain
    # cross-links x86_64 Mach-O natively, so the Intel wheel is a cross
    # build (native: false -> content-verified, not import-smoked, since
    # an x86_64 lib can't be imported on the arm64 runner).
    - { os: macos-14, target: x86_64-apple-darwin, plat: macosx_10_12_x86_64, zig: false, native: false, deployment: "10.12" }
    - { os: macos-14, target: aarch64-apple-darwin, plat: macosx_11_0_arm64, zig: false, native: true, deployment: "11.0" }
    # Windows x64 (native), x86 and arm64 (cross-compiled).
    - { os: windows-latest, target: x86_64-pc-windows-msvc, plat: win_amd64, zig: false, native: true }
    - { os: windows-latest, target: i686-pc-windows-msvc, plat: win32, zig: false, native: false }
    - { os: windows-latest, target: aarch64-pc-windows-msvc, plat: win_arm64, zig: false, native: false }
  • Cross targets run only the archive verifier; install/load is guarded by if: matrix.native:
    - name: Build the platform wheel
    shell: bash
    env:
    # The core is prebuilt above; the hook packs it and stamps the
    # platform tag, and builds the render client (node) into the wheel.
    # The client is required by default; REQUIRE_CARGO also forbids the
    # core from silently going missing.
    XY_REQUIRE_CARGO: "1"
    XY_SKIP_CARGO: "1"
    XY_CARGO_TARGET: ${{ matrix.target }}
    XY_WHEEL_PLATFORM: ${{ matrix.plat }}
    run: uv build --wheel
    - name: Verify wheel contents
    shell: bash
    run: |
    whl=$(ls dist/*.whl)
    python scripts/verify_wheel.py "$whl" --expect-native
    - name: Install-size budget (<= 15 MB)
    shell: bash
    run: |
    whl=$(ls dist/*.whl)
    size=$(wc -c < "$whl" | tr -d ' ')
    echo "wheel size: $size bytes ($whl)"
    test "$size" -le 15728640
    - name: Verify the wheel installs and loads the native core
    # Only host-arch wheels can be imported on the runner; cross-compiled
    # arches (aarch64/armv7/win-arm64/win32) are content-verified instead.
    if: matrix.native
    shell: bash
    run: |
    uv venv smoke
    uv pip install -p smoke dist/*.whl numpy anywidget "reflex>=0.9.6"
    ./smoke/bin/python -c "import importlib.metadata as m, reflex_xy, xy.kernels as k; assert k.BACKEND=='native', k.BACKEND; assert reflex_xy.__version__ == m.version('xy'); print('native', k.__file__)" \
    || ./smoke/Scripts/python.exe -c "import importlib.metadata as m, reflex_xy, xy.kernels as k; assert k.BACKEND=='native', k.BACKEND; assert reflex_xy.__version__ == m.version('xy'); print('native')"
  • verify_wheel.py recognizes a native artifact by path/suffix and checks count/pure tags, but never parses ELF/Mach-O/PE headers, exports, or dependencies:

    xy/scripts/verify_wheel.py

    Lines 386 to 415 in 99eda6d

    native_libs = sorted(n for n in names if NATIVE_LIB_RE.match(n))
    unexpected_native = sorted(
    n for n in names if n.endswith(NATIVE_ARTIFACT_SUFFIXES) and not NATIVE_LIB_RE.match(n)
    )
    if unexpected_native:
    raise AssertionError(f"wheel contains unexpected native artifacts: {unexpected_native}")
    if expect_native is True:
    if len(native_libs) != 1:
    raise AssertionError(
    f"native wheel must contain exactly one native lib, got {native_libs}"
    )
    if wheel.root_is_purelib:
    raise AssertionError("native wheel must set Root-Is-Purelib: false")
    if any(tag == "py3-none-any" for tag in wheel.tags):
    raise AssertionError(f"native wheel must not use a pure tag: {wheel.tags}")
    elif expect_native is False:
    if native_libs:
    raise AssertionError(
    f"pure (no-native) wheel must not contain native libs: {native_libs}"
    )
    if not wheel.root_is_purelib:
    raise AssertionError("pure (no-native) wheel must set Root-Is-Purelib: true")
    if "py3-none-any" not in wheel.tags:
    raise AssertionError(
    f"pure (no-native) wheel must advertise py3-none-any, got {wheel.tags}"
    )
    elif native_libs and wheel.root_is_purelib:
    raise AssertionError("wheel contains a native lib but is tagged pure")
    elif not native_libs and not wheel.root_is_purelib:
    raise AssertionError("wheel is tagged non-pure but contains no native lib")
  • PyEmscripten already models the stronger standard: content check followed by a real target-runtime load/kernel probe:
    # PyEmscripten WASM wheel for in-browser CPython. The platform ABI is
    # compiler-sensitive, so keep Rust, Emscripten, cibuildwheel, the wheel tag,
    # and the runtime probe pinned as one tested set
    # (see spec/process/production-readiness.md).
    wasm:
    name: Wheel PyEmscripten (runtime verified)
    runs-on: ubuntu-latest
    permissions:
    contents: read
    steps:
    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
    with:
    # Full history and tags: the distribution version is derived
    # from the latest `v*` tag, and a shallow clone has none.
    fetch-depth: 0
    - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
    with:
    toolchain: 1.97.0
    targets: wasm32-unknown-emscripten
    - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
    with:
    node-version: "22"
    - name: Install JS build toolchain
    run: npm ci
    # cibuildwheel installs the Pyodide 314 cross-environment and its patched
    # Emscripten 5.0.3 toolchain before CIBW_BEFORE_BUILD_PYODIDE runs. Build
    # the standalone Rust cdylib there, then tell the Hatch hook to package
    # that prebuilt artifact rather than invoke Cargo a second time.
    - name: Build the PyEmscripten wheel
    uses: pypa/cibuildwheel@294735312765b09d24a2fbec22660ce817587d55 # v4.1.0
    env:
    CIBW_PLATFORM: pyodide
    CIBW_BUILD: cp314-pyodide_wasm32
    CIBW_PYODIDE_VERSION: "314.0.0"
    CIBW_BEFORE_BUILD_PYODIDE: >-
    cargo build --release --target wasm32-unknown-emscripten
    CIBW_ENVIRONMENT_PYODIDE: >-
    XY_REQUIRE_CARGO=1
    XY_SKIP_CARGO=1
    XY_CARGO_TARGET=wasm32-unknown-emscripten
    XY_WHEEL_PLATFORM=pyemscripten_2026_0_wasm32
    RUSTFLAGS="-C panic=abort"
    - name: Verify wheel contents
    shell: bash
    run: |
    whl=$(ls wheelhouse/*.whl)
    python scripts/verify_wheel.py "$whl" --expect-native
    # "Builds" is not "loads": install the exact wheel in a real Pyodide 314
    # runtime and call both the C ABI version function and a native kernel.
    - name: PyEmscripten runtime load probe
    run: |
    npm i --no-save pyodide@314.0.0
    whl=$(ls wheelhouse/*.whl)
    python scripts/pyodide_load_smoke.py "$whl"

Acceptance criteria

  • Artifact verification parses ELF/Mach-O/PE headers and asserts format, machine/bitness, libc family, and minimum OS/ABI agree with the wheel tag/matrix entry.
  • Verify the required XY ABI/version symbol set (and reject unexpected missing/renamed exports).
  • Inspect dynamic dependencies against a per-platform allowlist; enforce the stated manylinux/musllinux/macOS floors and Windows runtime policy.
  • Every published target gets a native or emulated install/load + representative kernel call, or the release matrix is narrowed to targets that can be runtime-verified.
  • Publishing depends on these checks, with negative fixtures for wrong-arch/wrong-linkage binaries.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions