diff --git a/.github/workflows/build-ngt.yml b/.github/workflows/build-ngt.yml new file mode 100644 index 000000000000..5b1d4a03a835 --- /dev/null +++ b/.github/workflows/build-ngt.yml @@ -0,0 +1,194 @@ +# SPDX-FileCopyrightText: 2026 The RISE Project +# SPDX-License-Identifier: MIT +--- +# Based on upstream's own riscv64-adjacent recipe in python/pyproject.toml's +# [tool.cibuildwheel.linux] (no GitHub Actions wheel-building workflow exists +# upstream; this documented recipe is what their own baseline `ngt` wheels on +# PyPI are built from): +# https://github.com/yahoojapan/NGT/blob/v2.8.1/python/pyproject.toml +name: Build ngt wheels (riscv64) + +on: + workflow_dispatch: + inputs: + version: + description: 'Version glob to (re)build; empty builds every version of docs/packages/ngt.yaml not released yet' + required: false + default: '' + pull_request: + branches: [main] + paths: + - '.github/workflows/build-ngt.yml' + - 'docs/packages/ngt.yaml' + - 'patches/ngt/**' + push: + branches: [main] + paths: + - '.github/workflows/build-ngt.yml' + - 'docs/packages/ngt.yaml' + - 'patches/ngt/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +env: + # kept in sync with the gpl_sources job below + MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64 + +jobs: + setup: + uses: $/.github/workflows/_setup.yml + with: + package: ngt + version: ${{ inputs.version }} + + build_wheels: + needs: [setup] + if: needs.setup.outputs.versions != '[]' + name: Build ngt ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64 + runs-on: ubuntu-24.04-riscv + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + version: ${{ fromJSON(needs.setup.outputs.versions) }} + python: ["cp312", "cp313", "cp314", "cp314t"] + + env: + NGT_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout NGT v${{ env.NGT_VERSION }} + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: yahoojapan/NGT + ref: v${{ env.NGT_VERSION }} + persist-credentials: false + + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: python-wheels + persist-credentials: false + + - name: Apply patches + run: git apply python-wheels/patches/ngt/${{ env.NGT_VERSION }}/*.patch + + # python/pyproject.toml's own [tool.cibuildwheel.linux] before-all + # expects an `ngt-src/` checkout staged next to the package (comment: + # "ngt-src/ is staged next to the package") - upstream's own tooling + # does this outside of what's committed to the repo. + - name: Stage the NGT core checkout next to the Python package + run: | + mkdir -p python/ngt-src + cp -r CMakeLists.txt LICENSE README.md VERSION bin lib samples utils python/ngt-src/ + + - name: Build wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + package-dir: python + output-dir: wheelhouse/ + env: + CIBW_ARCHS: riscv64 + CIBW_BUILD: ${{ matrix.python }}-manylinux_riscv64 + CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }} + CIBW_BEFORE_ALL_LINUX: >- + dnf -y install cmake blas-devel lapack-devel && + cd {package} && + cmake -S ngt-src -B build-ci -DCMAKE_BUILD_TYPE=Release + -DNGT_AVX_DISABLED=ON -DNGT_MARCH_NATIVE_DISABLED=ON && + cmake --build build-ci --parallel $(nproc) && + cp build-ci/lib/NGT/libngt.a . && + cp build-ci/lib/NGT/defines.h build-ci/lib/NGT/version_defs.h ngt-src/lib/NGT/ + CIBW_ENVIRONMENT_LINUX: >- + NGT_STATIC_LIBRARY_PATH=libngt.a + NGT_INCLUDE_DIR=ngt-src/lib + NGT_BUILD_STATIC=1 + PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ + PIP_ONLY_BINARY=numpy + # Upstream ships no pytest suite for the Python bindings (only a + # demo script with hardcoded relative paths to its sample + # dataset); synthesize a round-trip create/insert/search smoke test. + CIBW_TEST_COMMAND: >- + python3 -c "import ngtpy, random; + ngtpy.create(path=b'/tmp/ngt-smoke-index', dimension=16, distance_type='L2'); + idx = ngtpy.Index(b'/tmp/ngt-smoke-index'); + vecs = [[random.random() for _ in range(16)] for _ in range(200)]; + idx.batch_insert(vecs); + idx.save(); + results = idx.search(vecs[0], size=5); + assert len(results) == 5, results; + assert results[0][0] == 0, results; + print('ngt smoke test ok')" + + - name: Check wheel contents + run: | + python3 - wheelhouse/*.whl <<'EOF' + import sys, zipfile + for path in sys.argv[1:]: + names = zipfile.ZipFile(path).namelist() + assert any(n.startswith("ngtpy") and n.endswith(".so") for n in names), (path, names) + print(path, "ok") + EOF + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ngt-${{ env.NGT_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: + NGT_VERSION: ${{ matrix.version }} + + steps: + - name: Checkout python-wheels + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # OpenMP (find_package(OpenMP REQUIRED)) links libgomp, which + # auditwheel vendors into ngt.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: ngt-${{ env.NGT_VERSION }}-gpl-sources + path: gpl-sources.tar + if-no-files-found: error + + publish: + name: Publish ngt ${{ 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: ngt-${{ matrix.version }}-*-manylinux_riscv64 + gpl-sources-artifact: ngt-${{ matrix.version }}-gpl-sources + gpl-sources-description: gcc diff --git a/docs/packages/ngt.yaml b/docs/packages/ngt.yaml new file mode 100644 index 000000000000..2a0efc11d6bc --- /dev/null +++ b/docs/packages/ngt.yaml @@ -0,0 +1,5 @@ +package-name: ngt +source-code: https://github.com/yahoojapan/NGT +license: Apache-2.0 +versions: +- version: 2.8.1 diff --git a/patches/ngt/2.8.1/0001-cmake-drop-the-arm-only-mno-neon-flag-on-non-arm-no.patch b/patches/ngt/2.8.1/0001-cmake-drop-the-arm-only-mno-neon-flag-on-non-arm-no.patch new file mode 100644 index 000000000000..2659650e92ef --- /dev/null +++ b/patches/ngt/2.8.1/0001-cmake-drop-the-arm-only-mno-neon-flag-on-non-arm-no.patch @@ -0,0 +1,44 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ludovic Henry +Date: Fri, 16 Oct 2026 00:11:43 +0000 +Subject: [PATCH] cmake: drop the ARM-only -mno-neon flag on non-ARM, non-x86 + targets + +Upstream-Status: Inappropriate [riscv64 is neither ARM nor x86; -mno-neon is a GCC ARM-backend-only flag and fails to configure on riscv64] + +The NGT_AVX_DISABLED/NGT_SIMD_DISABLED Release branch picks the portable +non-SIMD distance-computation path (PrimitiveComparatorNoArch.h is +selected whenever neither __x86_64__ nor NGT_NEON is defined, see +lib/NGT/PrimitiveComparator.h) on every architecture, but its final +else() -- meant for 32-bit ARM, where disabling NEON needs an explicit +flag -- unconditionally appends -mno-neon regardless of the actual +target. GCC rejects that flag outright on riscv64 +("unrecognized command-line option '-mno-neon'"), aborting the +configure step before any NGT/riscv64-specific code is even reached. +Drop the flag for this branch; the aarch64/arm64 branch above already +shows the same compile-options list works with no extra flag once SIMD +is disabled. + +Signed-off-by: Ludovic Henry +--- + CMakeLists.txt | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 8be7119..d3cfc66 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -87,8 +87,8 @@ if(${UNIX}) + message(STATUS "ARM64 detected: NEON is mandatory, disable SIMD logically") + add_compile_options(-O3 -ffast-math -DNDEBUG) + else() +- message(STATUS "NEON will not be used to compute distances.") +- add_compile_options(-O3 -ffast-math -mno-neon -DNDEBUG) ++ message(STATUS "Not an ARM/x86 target: no arch-specific SIMD flag needed.") ++ add_compile_options(-O3 -ffast-math -DNDEBUG) + endif() + endif() + else() +-- +2.43.0 +