From fe3f1e3077513f903417a9e8fdd856739e372e25 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 24 Sep 2026 01:45:27 +0000 Subject: [PATCH 1/3] Add qiskit-aer riscv64 wheel build CPU-only default build: CUDA/ROCm/MPI are all opt-in in upstream's own CMakeLists/setup.py, and the only x86 SIMD (AVX2) is compiled out entirely on non-x86_64 hosts rather than emulated. Mirrors the cvxopt/scs openblas-devel pattern for BLAS. cp314t is dropped: qiskit, a hard runtime and test dependency, only has a cp310-abi3 wheel on the registry, which a free-threaded interpreter cannot install. --- .github/workflows/build-qiskit-aer.yml | 160 +++++++++++++++++++++++++ docs/packages/qiskit-aer.yaml | 5 + 2 files changed, 165 insertions(+) create mode 100644 .github/workflows/build-qiskit-aer.yml create mode 100644 docs/packages/qiskit-aer.yaml diff --git a/.github/workflows/build-qiskit-aer.yml b/.github/workflows/build-qiskit-aer.yml new file mode 100644 index 0000000000..cc08f05925 --- /dev/null +++ b/.github/workflows/build-qiskit-aer.yml @@ -0,0 +1,160 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# This workflow is based on: https://github.com/Qiskit/qiskit-aer/blob/0.17.2/.github/workflows/deploy.yml +# (wheel-build job) and pyproject.toml's [tool.cibuildwheel] table. +name: Build qiskit-aer wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/qiskit-aer.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-qiskit-aer.yml' + - 'docs/packages/qiskit-aer.yaml' + push: + branches: [main] + paths: + - '.github/workflows/build-qiskit-aer.yml' + - 'docs/packages/qiskit-aer.yaml' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: qiskit-aer + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build qiskit-aer ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 720 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + # cp314t is dropped: qiskit (a hard runtime and test-time dependency, + # imported by tools/verify_wheels.py) is published on our registry as + # a single cp310-abi3 wheel, which is not installable on a + # free-threaded interpreter. + python: + - "cp312" + - "cp313" + - "cp314" + + env: + QISKIT_AER_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout qiskit-aer ${{ env.QISKIT_AER_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: Qiskit/qiskit-aer + ref: ${{ env.QISKIT_AER_VERSION }} + persist-credentials: false + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + output-dir: wheelhouse/ + only: ${{ matrix.python }}-manylinux_riscv64 + env: + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + # Replaces upstream's manylinux2014/yum before-all (pyproject.toml + # [tool.cibuildwheel.linux]); also stages OpenBLAS' licence, which + # auditwheel bundles the library without. + CIBW_BEFORE_ALL_LINUX: >- + dnf -y install openblas-devel && + cp /usr/share/licenses/openblas/LICENSE {project}/LICENSE.openblas + CIBW_ENVIRONMENT: >- + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + PIP_ONLY_BINARY=numpy,scipy,qiskit,rustworkx + + - name: Check the wheel ships the extension and all licences + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + for whl in sys.argv[1:]: + names = zipfile.ZipFile(whl).namelist() + assert any(n.startswith("qiskit_aer/backends/controller_wrappers") + and n.endswith(".so") for n in names), whl + licences = {n.rsplit("/", 1)[1] + for n in names if ".dist-info/licenses/" in n} - {""} + assert licences == {"LICENSE.txt", "LICENSE.openblas"}, (whl, licences) + print(whl, "ok") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: qiskit-aer-${{ env.QISKIT_AER_VERSION }}-${{ matrix.python }}-manylinux_riscv64 + path: wheelhouse/*.whl + if-no-files-found: error + + gpl_sources: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + name: Collect GPL sources + runs-on: ubuntu-24.04-riscv + + env: + QISKIT_AER_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # OpenBLAS is compiled with gfortran, so auditwheel vendors the image's + # libgfortran into qiskit_aer.libs/. + - uses: ./actions/collect-gpl-sources + with: + image: ${{ env.MANYLINUX_RISCV64_IMAGE }} + packages: gcc + output: gpl-sources.tar + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: qiskit-aer-${{ env.QISKIT_AER_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish qiskit-aer ${{ matrix.version }} + needs: [setup, build_wheels, gpl_sources] + if: needs.setup.outputs.versions != '[]' + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + permissions: + contents: write + pull-requests: write + uses: $/.github/workflows/_publish-wheel.yml + secrets: + app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }} + with: + artifact-pattern: qiskit-aer-${{ matrix.version }}-*-manylinux_riscv64 + gpl-sources-artifact: qiskit-aer-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc diff --git a/docs/packages/qiskit-aer.yaml b/docs/packages/qiskit-aer.yaml new file mode 100644 index 0000000000..32f67a7bd0 --- /dev/null +++ b/docs/packages/qiskit-aer.yaml @@ -0,0 +1,5 @@ +package-name: qiskit-aer +source-code: https://github.com/Qiskit/qiskit-aer +license: Apache-2.0 +versions: +- version: 0.17.2 From 70a8ec1b01cc021094b025ab69ed4946af148682 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 24 Sep 2026 02:21:29 +0000 Subject: [PATCH 2/3] Seed a riscv64-aware Conan profile before qiskit-aer's fmt build Conan 1.x (pinned by pyproject.toml's conan<2.0.0) has no riscv64 in its settings.yml, and its arch autodetection matches "riscv64" against the generic "64 in machine" fallback and misreports arch=x86_64. That makes its CMakeToolchain generator inject an x86-only -m64 flag when it builds fmt/8.0.1 from source, which the riscv64 GCC toolchain rejects. Pre-seed Conan's settings.yml and default profile with the real arch before CMake invokes it, so it never runs autodetection. --- .github/workflows/build-qiskit-aer.yml | 27 ++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/build-qiskit-aer.yml b/.github/workflows/build-qiskit-aer.yml index cc08f05925..14ce03d635 100644 --- a/.github/workflows/build-qiskit-aer.yml +++ b/.github/workflows/build-qiskit-aer.yml @@ -86,6 +86,33 @@ jobs: CIBW_ENVIRONMENT: >- PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ PIP_ONLY_BINARY=numpy,scipy,qiskit,rustworkx + # cmake/dependency_utils.cmake's setup_conan() runs Conan 1.x + # (conan<2.0.0, pinned by pyproject.toml) to build fmt/8.0.1 from + # source. Conan 1.x's settings.yml has no riscv64 arch entry, and + # its autodetection matches "riscv64" on the generic `"64" in + # machine` fallback and misreports arch=x86_64, so its + # CMakeToolchain then adds an x86-only -m64 flag. Seed a + # riscv64-aware settings.yml and default profile before Conan + # runs so it never autodetects. + CIBW_BEFORE_BUILD: | + set -ex + pip install -q 'conan<2.0.0' + conan config init + conan_home=$(conan config home) + sed -i 's/arch: \[x86, x86_64,/arch: [x86, x86_64, riscv64,/' "$conan_home/settings.yml" + grep -q 'arch: \[x86, x86_64, riscv64,' "$conan_home/settings.yml" + mkdir -p "$conan_home/profiles" + cat > "$conan_home/profiles/default" <<'PROFILE' + [settings] + os=Linux + os_build=Linux + arch=riscv64 + arch_build=riscv64 + build_type=Release + [options] + [build_requires] + [env] + PROFILE - name: Check the wheel ships the extension and all licences run: | From c44bca5e70518542147ea89e21ca638e62d66535 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Thu, 24 Sep 2026 02:40:27 +0000 Subject: [PATCH 3/3] qiskit-aer: patch arch_build/arch_target too, not just arch, in Conan's settings.yml The first fix only added riscv64 to settings.yml's arch: enum, but Conan 1.x validates arch_build/arch_target against two SEPARATE enum lists in the same file - the default profile sets arch_build=riscv64, which still failed validation ('Invalid setting riscv64 is not a valid arch_build value') even after the arch fix let the build get further than before. Verified locally against the real Conan 1.66.0 settings.yml that patching all three resolves it. --- .github/workflows/build-qiskit-aer.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-qiskit-aer.yml b/.github/workflows/build-qiskit-aer.yml index 14ce03d635..640fe8a2d3 100644 --- a/.github/workflows/build-qiskit-aer.yml +++ b/.github/workflows/build-qiskit-aer.yml @@ -93,14 +93,24 @@ jobs: # machine` fallback and misreports arch=x86_64, so its # CMakeToolchain then adds an x86-only -m64 flag. Seed a # riscv64-aware settings.yml and default profile before Conan - # runs so it never autodetects. + # runs so it never autodetects. settings.yml validates arch, + # arch_build and arch_target as three SEPARATE enum lists (not + # one shared list), so all three need the same riscv64 addition - + # our profile only sets arch/arch_build, but arch_target is + # patched too for anyone who later adds a cross-compile profile. CIBW_BEFORE_BUILD: | set -ex pip install -q 'conan<2.0.0' conan config init conan_home=$(conan config home) - sed -i 's/arch: \[x86, x86_64,/arch: [x86, x86_64, riscv64,/' "$conan_home/settings.yml" - grep -q 'arch: \[x86, x86_64, riscv64,' "$conan_home/settings.yml" + sed -i \ + -e 's/^arch_build: \[x86, x86_64,/arch_build: [x86, x86_64, riscv64,/' \ + -e 's/^arch_target: \[x86, x86_64,/arch_target: [x86, x86_64, riscv64,/' \ + -e 's/^arch: \[x86, x86_64,/arch: [x86, x86_64, riscv64,/' \ + "$conan_home/settings.yml" + grep -q '^arch_build: \[x86, x86_64, riscv64,' "$conan_home/settings.yml" + grep -q '^arch_target: \[x86, x86_64, riscv64,' "$conan_home/settings.yml" + grep -q '^arch: \[x86, x86_64, riscv64,' "$conan_home/settings.yml" mkdir -p "$conan_home/profiles" cat > "$conan_home/profiles/default" <<'PROFILE' [settings]