Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions .github/workflows/build-pyscf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# This workflow follows upstream's own release-pypi-aarch64 job:
# https://github.com/pyscf/pyscf/blob/master/.github/workflows/publish.yml
name: Build pyscf wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/pyscf.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-pyscf.yml'
- 'docs/packages/pyscf.yaml'
push:
branches: [main]
paths:
- '.github/workflows/build-pyscf.yml'
- 'docs/packages/pyscf.yaml'

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

env:
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
LIBCINT_VERSION: v6.1.3
LIBXC_VERSION: '7.0.0'
XCFUN_REF: a89b783

jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: pyscf
version: ${{ inputs.version }}

build_wheels:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
name: Build pyscf ${{ matrix.version }} py3-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 1440
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
env:
PYSCF_VERSION: ${{ matrix.version }}

steps:
- name: Checkout pyscf v${{ env.PYSCF_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: pyscf/pyscf
ref: v${{ env.PYSCF_VERSION }}
path: pyscf
persist-credentials: false

- name: Build wheel
run: |
mkdir -p wheelhouse
set -o pipefail
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" -w /work/pyscf \
-e LIBCINT_VERSION -e LIBXC_VERSION -e XCFUN_REF \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT' 2>&1 | tee build.log
set -eux

dnf -y install openblas-devel gcc

# Upstream's own official Linux wheels ship no licence for any of these;
# send this fix upstream too.
curl -fsSLo LICENSE.libcint "https://raw.githubusercontent.com/sunqm/libcint/${LIBCINT_VERSION}/LICENSE"
curl -fsSLo LICENSE.libxc "https://gitlab.com/libxc/libxc/-/raw/${LIBXC_VERSION}/COPYING"
curl -fsSLo LICENSE.xcfun "https://raw.githubusercontent.com/dftlibs/xcfun/${XCFUN_REF}/LICENSE.md"
cp /usr/share/licenses/openblas/LICENSE LICENSE.openblas

PYTHON_BIN=/opt/python/cp312-cp312/bin/python
export PATH="/opt/python/cp312-cp312/bin:${PATH}"
"${PYTHON_BIN}" -m pip install -q -U pip setuptools wheel auditwheel

export PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
export PIP_ONLY_BINARY=cmake
# WITH_F12/ENABLE_SMD=OFF matches upstream's own macOS build; the Linux
# jobs turn ENABLE_SMD on, which also links in pyscf's own GPLv3-licensed
# MNSOL solvent code (pyscf/lib/solvent), kept out of scope here.
export CMAKE_CONFIGURE_ARGS="-DWITH_F12=OFF -DENABLE_SMD=OFF"
export CMAKE_BUILD_PARALLEL_LEVEL=4
"${PYTHON_BIN}" -m pip wheel -v --no-deps --no-clean -w /tmp/wheelhouse .

auditwheel repair /tmp/wheelhouse/pyscf-*.whl --lib-sdir /lib -w /work/wheelhouse
SCRIPT

- name: Upload build log
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pyscf-${{ env.PYSCF_VERSION }}-build-log
path: build.log

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: pyscf-${{ env.PYSCF_VERSION }}-py3-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error

- name: Test wheel
run: |
docker run --rm -i --network=host \
-v "${GITHUB_WORKSPACE}:/work" -w /work/pyscf \
-e PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/ \
"${MANYLINUX_RISCV64_IMAGE}" \
bash <<'SCRIPT'
set -eux

PYTHON_BIN=/opt/python/cp312-cp312/bin/python
"${PYTHON_BIN}" -m pip install -q --only-binary=:all: /work/wheelhouse/*.whl
"${PYTHON_BIN}" -m pip install -q pytest

# cd out of /work/pyscf first (gotcha 612): the build step's --no-clean
# pip wheel leaves pyscf.egg-info there, and `python -c` puts cwd on
# sys.path[0], so importlib.metadata resolves that stale egg-info instead
# of the installed wheel and the licence check below sees an empty set.
cd /tmp
licences=$("${PYTHON_BIN}" -c "
import importlib.metadata as m
print(sorted(p.name for p in m.files('pyscf') if '.dist-info/licenses/' in str(p)))
")
echo "$licences"
"${PYTHON_BIN}" -c "
import importlib.metadata as m
names = {p.name for p in m.files('pyscf') if '.dist-info/licenses/' in str(p)}
expected = {'LICENSE', 'NOTICE', 'LICENSE.libcint', 'LICENSE.libxc', 'LICENSE.xcfun', 'LICENSE.openblas'}
assert expected <= names, names
"

echo 'pbc_tools_pbc_fft_engine = "NUMPY"' > .pyscf_conf.py
echo 'scf_hf_SCF_mute_chkfile = True' >> .pyscf_conf.py
export PYSCF_CONFIG_FILE=/tmp/.pyscf_conf.py
export OMP_NUM_THREADS=1
ulimit -s 20000
"${PYTHON_BIN}" -c "import pyscf; from pyscf import gto, scf
mol = gto.M(atom='H 0 0 0; H 0 0 0.74', basis='sto-3g')
mf = scf.RHF(mol).run()
assert abs(mf.e_tot - (-1.1167)) < 1e-3, mf.e_tot
print('HF energy ok:', mf.e_tot)"

# Rename the checkout's package dir before collection (gotcha 616): even under
# pytest.ini's --import-mode=importlib, a test nested inside the package gets its
# ancestor packages imported straight off disk by walking up __init__.py files, so
# pyscf itself loads from this in-place build, not the installed wheel. Renaming
# breaks the name match and pyscf/__init__.py's own `from pyscf import lib` then
# falls through to the installed copy via normal sys.path.
mv /work/pyscf/pyscf /work/pyscf/pyscf-src
"${PYTHON_BIN}" -m pytest /work/pyscf/pyscf-src -c /work/pyscf/pytest.ini \
--ignore=/work/pyscf/pyscf-src/adc \
--ignore=/work/pyscf/pyscf-src/pbc/df \
--ignore=/work/pyscf/pyscf-src/pbc/cc \
--ignore=/work/pyscf/pyscf-src/solvent \
--ignore=/work/pyscf/pyscf-src/grad/test/test_ucasci.py
SCRIPT

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:
PYSCF_VERSION: ${{ matrix.version }}

steps:
- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# OpenBLAS is compiled with gfortran and pyscf links OpenMP, so
# auditwheel vendors the image's libgfortran and libgomp.
- 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: pyscf-${{ env.PYSCF_VERSION }}-gpl-sources
path: gpl-sources.tar
if-no-files-found: error

publish:
name: Publish pyscf ${{ 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: pyscf-${{ matrix.version }}-py3-manylinux_riscv64
gpl-sources-artifact: pyscf-${{ matrix.version }}-gpl-sources
gpl-sources-description: gcc
5 changes: 5 additions & 0 deletions docs/packages/pyscf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package-name: pyscf
source-code: https://github.com/pyscf/pyscf
license: Apache-2.0
versions:
- version: 2.14.0
Loading