#!/usr/bin/env bash# Build and install the optional primesieve.numpy extension from source (NumPy 2.x).# ==================================================================================## Builds and installs the optional `primesieve.numpy` extension into the# project's virtual environment.## Why this script exists:# The PyPI `primesieve` sdist ships a *pre-generated* `_numpy.cpp` that was# Cythonized against the legacy NumPy 1.x C-API, and its `setup.py` only re-runs# Cython when a `.pyx` source is present (which the sdist omits). Under NumPy 2.x# that stale C++ fails to compile (`PyArray_Descr->subarray`, `NPY_OWNDATA` were# removed), so a plain `pip install primesieve` builds only the base extension# and silently skips the NumPy one — leaving `primesieve.numpy.primes()` broken.## This script fetches the real `.pyx`/`.pxd` sources from upstream, re-Cythonizes# them against the venv's current Cython + NumPy, patches the one remaining# NumPy-1-ism (`NPY_OWNDATA` -> `NPY_ARRAY_OWNDATA`), then builds and installs a# wheel with build isolation disabled so the venv's NumPy headers are used.## Prerequisites (apt, via ./scripts/setup/dev_env.sh install primesieve c):# - libprimesieve-dev (primesieve.h + libprimesieve to link against)# - a C/C++ toolchain (build-essential, gcc)# Python build deps (cython, numpy, setuptools) are ensured in the venv below.## Author: Vikas Munshi <vikas.munshi@gmail.com># Copyright (c) 2024. All rights reserved.# Licensed under the MIT License.set-euopipefail# --------------------------------------------------------------------------# Configuration# --------------------------------------------------------------------------# Project root is two levels up from this script (scripts/setup/<this>).PROJECT_ROOT="$(cd"$(dirname"${BASH_SOURCE[0]}")/../.."&&pwd)"# The venv to install into; overridable via the VENV environment variable.VENV="${VENV:-${PROJECT_ROOT}/.venv}"PYTHON="${VENV}/bin/python"PIP="${PYTHON} -m pip"# Upstream repository providing the Cython sources omitted from the sdist.UPSTREAM_RAW="https://raw.githubusercontent.com/shlomif/primesieve-python"# Git ref to fetch the .pyx/.pxd sources from; override via PRIMESIEVE_REF.PRIMESIEVE_REF="${PRIMESIEVE_REF:-master}"usage(){echo"Usage: $0 [install|check|uninstall]"echoecho" install Build and install the primesieve.numpy extension (default)"echo" check Verify the primesieve.numpy extension imports and works"echo" uninstall Remove the primesieve package from the venv"}# --------------------------------------------------------------------------# Helpers# --------------------------------------------------------------------------# Aborts with a message if the project venv is missing.require_venv(){if[[!-x"${PYTHON}"]];thenecho"Error: venv python not found at ${PYTHON}">&2echo"Create the venv first (e.g. 'make install-minimal' or 'python3.14 -m venv .venv').">&2exit1fi}# Returns 0 if the compiled primesieve.numpy extension imports and produces the# expected primes for a known range, 1 otherwise.numpy_ext_works(){"${PYTHON}"-<<'PY' >/dev/null 2>&1import primesieve.numpy as psnpassert psnp.primes(20).tolist() == [2, 3, 5, 7, 11, 13, 17, 19]PY}# Ensures the Python build dependencies are present in the venv.ensure_build_deps(){if!"${PYTHON}"-c"import cython, numpy, setuptools">/dev/null2>&1;thenecho"Installing build dependencies (cython, numpy, setuptools)..."${PIP}install--upgradecythonnumpysetuptoolsfi}# --------------------------------------------------------------------------# Actions# --------------------------------------------------------------------------# Builds the NumPy-enabled primesieve wheel from a patched sdist and installs it.install_extension(){require_venvifnumpy_ext_works;thenecho"primesieve.numpy extension already installed and working — nothing to do."return0fiensure_build_depslocalworkdirsrcext_dirworkdir="$(mktemp-d)"# shellcheck disable=SC2064trap"rm -rf '${workdir}'"RETURNecho"Downloading primesieve sdist..."${PIP}download--no-binary:all:--no-deps--no-build-isolation\primesieve-d"${workdir}"echo"Extracting sdist..."tar-C"${workdir}"-xzf"${workdir}"/primesieve-*.tar.gzsrc="$(find"${workdir}"-maxdepth1-typed-name'primesieve-*'|head-n1)"if[[-z"${src}"]];thenecho"Error: could not locate extracted primesieve source directory">&2return1fiext_dir="${src}/primesieve"echo"Fetching Cython sources from ${PRIMESIEVE_REF}..."curl-fsSL"${UPSTREAM_RAW}/${PRIMESIEVE_REF}/primesieve/_primesieve.pyx"\-o"${ext_dir}/_primesieve.pyx"curl-fsSL"${UPSTREAM_RAW}/${PRIMESIEVE_REF}/primesieve/cpp_primesieve.pxd"\-o"${ext_dir}/cpp_primesieve.pxd"curl-fsSL"${UPSTREAM_RAW}/${PRIMESIEVE_REF}/primesieve/numpy/_numpy.pyx"\-o"${ext_dir}/numpy/_numpy.pyx"curl-fsSL"${UPSTREAM_RAW}/${PRIMESIEVE_REF}/primesieve/numpy/cpp_numpy.pxd"\-o"${ext_dir}/numpy/cpp_numpy.pxd"# Patch the one NumPy-1-ism: the C-API flag alias was renamed in NumPy 2.x.# No-op if upstream has already fixed it.sed-i's/np\.NPY_OWNDATA/np.NPY_ARRAY_OWNDATA/'"${ext_dir}/numpy/_numpy.pyx"# Drop the stale generated C++ so the presence of the .pyx switches setup.py# to its Cython path and regenerates against the venv's NumPy.rm-f"${ext_dir}/_primesieve.cpp""${ext_dir}/numpy/_numpy.cpp"echo"Building wheel (no build isolation, using the venv's Cython + NumPy)..."${PIP}wheel"${src}"--no-build-isolation--no-deps-w"${workdir}/wheel"echo"Installing wheel..."${PIP}install--force-reinstall--no-deps--no-cache-dir\"${workdir}"/wheel/primesieve-*.whlifnumpy_ext_works;thenecho"✓ primesieve.numpy extension installed and verified."elseecho"Error: build completed but the primesieve.numpy extension still fails.">&2return1fi}# Verifies the extension; exits nonzero if it is missing or broken.check_extension(){require_venvifnumpy_ext_works;thenecho"✓ primesieve.numpy extension is installed and working."elseecho"✗ primesieve.numpy extension is missing or broken. Run: $0 install">&2return1fi}# Removes the primesieve package from the venv.uninstall_extension(){require_venv${PIP}uninstall-yprimesieveecho"✓ primesieve removed from the venv."}# --------------------------------------------------------------------------# Main# --------------------------------------------------------------------------main(){localaction="${1:-check}"case"${action}"ininstall)install_extension;;check)check_extension;;uninstall)uninstall_extension;;-h|--help|help)usage;;*)echo"Error: unknown action '${action}'">&2;usage;exit1;;esac}main"$@"