Skip to content

Commit ccba46d

Browse files
author
Denis Jelovina
committed
Remove wait_for_testpypi_release script and simplify TestPyPI propagation handling; bump version to 0.8.33
2 parents cda713a + 74cfd52 commit ccba46d

15 files changed

+1019
-72
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env python3
2+
"""Print basic information about the installed `pyalp` package.
3+
4+
This script is intended to be invoked from CI after installing the package
5+
from TestPyPI. It prints the package file, available binary modules, and
6+
the runtime build metadata exposed by the package.
7+
"""
8+
import pkgutil
9+
import sys
10+
11+
try:
12+
import pyalp
13+
except Exception:
14+
print('ERROR: failed to import pyalp', file=sys.stderr)
15+
raise
16+
17+
print('pyalp package:', getattr(pyalp, '__file__', None))
18+
print('available modules in package:', [m.name for m in pkgutil.iter_modules(pyalp.__path__)])
19+
try:
20+
print('build metadata:', pyalp.get_build_metadata())
21+
except Exception as e:
22+
print('metadata error:', e)
23+
print('listed backends via helper:', pyalp.list_backends())
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env python3
2+
"""Run the repo's backend smoke runner against an installed pyalp package.
3+
4+
This script is intended to be invoked from CI after installing pyalp from
5+
TestPyPI. It accepts a single argument (backend name) and will skip if that
6+
backend is not present in the installed package.
7+
"""
8+
import sys
9+
import subprocess
10+
11+
try:
12+
import pyalp
13+
except Exception:
14+
print('ERROR: failed to import pyalp', file=sys.stderr)
15+
raise
16+
17+
18+
def main(argv):
19+
if len(argv) < 2:
20+
print('Usage: run_backend_smoke_installed.py <backend_name>', file=sys.stderr)
21+
return 2
22+
backend = argv[1]
23+
backends = pyalp.list_backends()
24+
print('discovered backends:', backends)
25+
if backend not in backends:
26+
print(f'backend {backend} not present in installed package, skipping')
27+
return 0
28+
29+
rc = subprocess.call([sys.executable, 'tests/python/backend_smoke_runner.py', backend])
30+
if rc != 0:
31+
print(f'backend {backend} smoke runner failed with exit {rc}', file=sys.stderr)
32+
return rc
33+
34+
35+
if __name__ == '__main__':
36+
sys.exit(main(sys.argv))
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Promote release to PyPI
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
tag:
7+
description: 'Git tag / release to promote (e.g. pyalp.v0.8.14)'
8+
required: true
9+
push:
10+
tags:
11+
- 'pyalp.v*'
12+
13+
# Request OIDC id-token permissions at workflow level so actions can use
14+
# the GitHub Actions OIDC provider. The pypa publish action requires this
15+
# for trusted publisher flow when using repository-provided credentials.
16+
permissions:
17+
id-token: write
18+
contents: read
19+
20+
jobs:
21+
promote:
22+
runs-on: ubuntu-latest
23+
# Require approval from the `production` environment before the job can
24+
# access environment-scoped secrets (e.g. the PyPI API token). Create the
25+
# environment in the repository settings and add the secret `PYPI_API_TOKEN`.
26+
environment: production
27+
# Also explicitly request id-token at the job level to be extra clear.
28+
permissions:
29+
id-token: write
30+
contents: read
31+
steps:
32+
- name: Checkout (for local scripts)
33+
uses: actions/checkout@v4
34+
35+
- name: Download release assets (via GitHub API)
36+
env:
37+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
REPO: ${{ github.repository }}
39+
TAG: ${{ github.event.inputs.tag || github.ref_name }}
40+
run: |
41+
set -euo pipefail
42+
echo "Downloading release assets for ${REPO} tag ${TAG}"
43+
mkdir -p release_assets
44+
# Fetch release metadata for the tag
45+
release_json=$(curl -sSf -H "Authorization: Bearer ${GITHUB_TOKEN}" "https://api.github.com/repos/${REPO}/releases/tags/${TAG}")
46+
if [ -z "${release_json}" ]; then
47+
echo "No release metadata found for tag ${TAG}" >&2
48+
exit 1
49+
fi
50+
51+
# Iterate assets and download each one using the assets API (requires Accept header)
52+
echo "$release_json" | jq -r '.assets[] | [.id, .name] | @tsv' | while IFS=$'\t' read -r id name; do
53+
echo "Downloading asset: ${name} (id ${id})"
54+
curl -sSfL -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "Accept: application/octet-stream" "https://api.github.com/repos/${REPO}/releases/assets/${id}" -o "release_assets/${name}"
55+
done
56+
echo "Downloaded files:" && ls -la release_assets || true
57+
58+
- name: List downloaded assets
59+
run: |
60+
echo "Assets in release_assets:"
61+
ls -la release_assets || true
62+
63+
- name: Show package name and version (diagnostic)
64+
run: |
65+
python -c "import importlib,importlib.util,sys,pathlib; spec=importlib.util.find_spec('tomllib') or importlib.util.find_spec('tomli'); name=spec.name if spec else sys.exit(print('No TOML parser available (tomllib/tomli), skipping')); toml=importlib.import_module(name); p=pathlib.Path('pyalp/pyproject.toml'); (sys.exit(print('pyalp/pyproject.toml not found at', p)) if not p.exists() else None); data=toml.loads(p.read_text()); proj=data.get('project',{}); print('project.name =', proj.get('name')); print('project.version =', proj.get('version'))"
66+
67+
- name: Publish to PyPI (alp-graphblas)
68+
uses: pypa/gh-action-pypi-publish@release/v1
69+
with:
70+
packages-dir: release_assets/
71+
env:
72+
TWINE_USERNAME: __token__
73+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

.github/workflows/pyalp-publish.yml renamed to .github/workflows/publish-to-testpypi.yml

Lines changed: 155 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: pyalp wheels (cibuildwheel)
1+
name: alp-graphblas wheels (cibuildwheel)
22

33
on:
44
push:
@@ -82,6 +82,22 @@ jobs:
8282
printf '%s\n' "import os, runpy; ROOT=os.path.dirname(os.path.abspath(__file__)); PKG=os.path.join(ROOT, 'pyalp'); os.chdir(PKG); runpy.run_path(os.path.join(PKG, 'setup.py'), run_name='__main__')" > setup.py
8383
# Configure from repository root; enable pyalp and choose NUMA setting per-platform
8484
PYEXEC=$(python -c 'import sys; print(sys.executable)')
85+
# Gather Git metadata and package version to pass into CMake so the
86+
# generated runtime metadata contains accurate values even in CI.
87+
# Prefer environment-provided values when available (GITHUB_SHA/REF_NAME)
88+
ALP_GIT_COMMIT="${GITHUB_SHA:-$(git rev-parse --short HEAD)}"
89+
# GITHUB_REF_NAME is available in Actions; fallback to git branch
90+
ALP_GIT_BRANCH="${GITHUB_REF_NAME:-$(git rev-parse --abbrev-ref HEAD)}"
91+
# Try to pick a semantic/alp version from tags (prefer nearest tag)
92+
ALP_VERSION=$(git describe --tags --match "v*" --abbrev=0 2>/dev/null || true)
93+
if [ -z "${ALP_VERSION}" ]; then
94+
# Fall back to a describe-style value
95+
ALP_VERSION=$(git describe --tags --match "v*" --always 2>/dev/null || echo "unknown")
96+
fi
97+
# Read the pyalp package version from pyalp/pyproject.toml (simple grep)
98+
PYALP_VERSION=$(grep -E '^version\s*=\s*"' pyalp/pyproject.toml | head -n1 | sed -E 's/^version\s*=\s*"([^"]+)".*/\1/')
99+
PYALP_VERSION=${PYALP_VERSION:-0.0.0}
100+
echo "[cibw] Derived ALP_VERSION=${ALP_VERSION}, ALP_GIT_COMMIT=${ALP_GIT_COMMIT}, ALP_GIT_BRANCH=${ALP_GIT_BRANCH}, PYALP_VERSION=${PYALP_VERSION}"
85101
# Use a per-ABI build directory to avoid cross-ABI contamination
86102
ABI_TAG=$(python -c 'import sys; print(f"cp{sys.version_info[0]}{sys.version_info[1]}")')
87103
BUILD_DIR="build/${ABI_TAG}"
@@ -152,8 +168,19 @@ jobs:
152168
# interprocedural optimization (LTO) to improve portability of the produced wheels.
153169
PORTABLE_FLAG="-DALP_PORTABLE_BUILD=ON"
154170
LTO_FLAG="-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF"
155-
cmake -S . -B "${BUILD_DIR}" -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_PYALP=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_FIND_FRAMEWORK=NEVER ${MACOS_FLAGS} ${NUMA_FLAG} ${CMAKE_PREFIX_HINT:-} ${OSX_DEPLOY_FLAG:-} ${PORTABLE_FLAG} ${LTO_FLAG} -DPython3_EXECUTABLE="${PYEXEC}"
156-
cmake --build "${BUILD_DIR}" --target pyalp_ref --parallel
171+
# Only enable OMP and nonblocking backends on Linux runners where libomp
172+
# and required build support are available. macOS wheels will build the
173+
# stable reference backend only to avoid SDK/ABI compile issues.
174+
if [ "$(uname -s)" = "Linux" ]; then
175+
BACKEND_FLAGS="-DWITH_OMP_BACKEND=ON -DWITH_NONBLOCKING_BACKEND=ON"
176+
BUILD_TARGETS="pyalp_ref pyalp_omp pyalp_nonblocking"
177+
else
178+
BACKEND_FLAGS="-DWITH_OMP_BACKEND=OFF -DWITH_NONBLOCKING_BACKEND=OFF"
179+
BUILD_TARGETS="pyalp_ref"
180+
fi
181+
182+
cmake -S . -B "${BUILD_DIR}" -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_PYALP=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_FIND_FRAMEWORK=NEVER ${MACOS_FLAGS} ${NUMA_FLAG} ${CMAKE_PREFIX_HINT:-} ${OSX_DEPLOY_FLAG:-} ${PORTABLE_FLAG} ${LTO_FLAG} ${BACKEND_FLAGS} -DPython3_EXECUTABLE="${PYEXEC}" -DALP_VERSION="${ALP_VERSION}" -DALP_GIT_COMMIT_SHA="${ALP_GIT_COMMIT}" -DALP_GIT_BRANCH="${ALP_GIT_BRANCH}" -Dpyalp_VERSION="${PYALP_VERSION}"
183+
cmake --build "${BUILD_DIR}" --target ${BUILD_TARGETS} --parallel
157184
# Debug: show the generated metadata file (if present) to the CI logs
158185
echo "[cibw] Checking for generated metadata file: ${CMAKE_BUILD_DIR}/pyalp_metadata.py"
159186
if [ -f "${CMAKE_BUILD_DIR}/pyalp_metadata.py" ]; then
@@ -169,21 +196,130 @@ jobs:
169196
- name: Upload wheels
170197
uses: actions/upload-artifact@v4
171198
with:
172-
name: pyalp-wheels-${{ matrix.os }}
199+
name: alp-graphblas-wheels-${{ matrix.os }}
173200
path: wheelhouse/*.whl
174201

175-
# Placeholder for publish job; enable when versioning is PEP 440 compliant
176-
# publish:
177-
# needs: build-wheels
178-
# runs-on: ubuntu-latest
179-
# permissions:
180-
# id-token: write
181-
# steps:
182-
# - name: Download wheels
183-
# uses: actions/download-artifact@v4
184-
# with:
185-
# path: dist
186-
# - name: Publish to PyPI
187-
# uses: pypa/gh-action-pypi-publish@release/v1
188-
# with:
189-
# packages-dir: dist
202+
publish:
203+
needs: build-wheels
204+
runs-on: ubuntu-latest
205+
environment:
206+
name: testpypi
207+
url: https://test.pypi.org/p/alp-graphblas
208+
permissions:
209+
id-token: write
210+
contents: write
211+
steps:
212+
- name: Checkout repository (for tests)
213+
uses: actions/checkout@v4
214+
with:
215+
fetch-depth: 0
216+
217+
- name: Download all wheels
218+
uses: actions/download-artifact@v4
219+
with:
220+
path: dist
221+
pattern: alp-graphblas-wheels-*
222+
merge-multiple: true
223+
- name: Publish to TestPyPI
224+
uses: pypa/gh-action-pypi-publish@release/v1
225+
with:
226+
repository-url: https://test.pypi.org/legacy/
227+
packages-dir: dist/
228+
verbose: true
229+
230+
- name: Create GitHub Release and upload wheels
231+
uses: softprops/action-gh-release@v1
232+
with:
233+
tag_name: ${{ github.ref_name }}
234+
name: ${{ github.ref_name }}
235+
files: dist/*.whl
236+
237+
- name: Skip in-publish verification
238+
shell: bash
239+
run: |
240+
echo "Installation verification moved to 'verify-installed' job"
241+
242+
verify-installed:
243+
needs: publish
244+
runs-on: ubuntu-latest
245+
strategy:
246+
fail-fast: false
247+
matrix:
248+
backend: [pyalp_ref, pyalp_omp, pyalp_nonblocking, _pyalp]
249+
steps:
250+
- name: Checkout repository (for tests)
251+
uses: actions/checkout@v4
252+
with:
253+
fetch-depth: 0
254+
255+
- name: Set up Python
256+
uses: actions/setup-python@v5
257+
with:
258+
python-version: '3.11'
259+
260+
- name: Verify installed backend
261+
shell: bash
262+
env:
263+
BACKEND: ${{ matrix.backend }}
264+
run: |
265+
set -euo pipefail
266+
# Determine package version from pyalp/pyproject.toml
267+
PYALP_VERSION=$(grep -E '^version\s*=\s*"' pyalp/pyproject.toml | head -n1 | sed -E 's/^version\s*=\s*"([^\"]+)".*/\1/')
268+
echo "Testing alp-graphblas version: ${PYALP_VERSION}"
269+
270+
PY=$(which python3 || which python)
271+
echo "Using python: ${PY}"
272+
VENV_DIR="./.venv_test"
273+
rm -rf "${VENV_DIR}"
274+
${PY} -m venv "${VENV_DIR}"
275+
source "${VENV_DIR}/bin/activate"
276+
python -m pip install --upgrade pip setuptools wheel numpy
277+
278+
# Short sleep to allow TestPyPI to propagate the newly uploaded files
279+
# before attempting the install. Keep logic minimal to reduce workflow
280+
# complexity (the previous retry loop was removed per request).
281+
echo "Sleeping 60s to allow TestPyPI propagation before install..."
282+
sleep 60
283+
echo "Installing alp-graphblas==${PYALP_VERSION} from TestPyPI"
284+
python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple alp-graphblas==${PYALP_VERSION} --no-deps -v
285+
286+
# Inspect installed package using the script moved out of the workflow
287+
echo "Inspecting installed package"
288+
python .github/scripts/inspect_installed_pyalp.py
289+
290+
# Run the smoke runner script for the backend for this matrix job
291+
echo "Running backend smoke runner for ${BACKEND}"
292+
python .github/scripts/run_backend_smoke_installed.py "${BACKEND}"
293+
294+
publish-to-pypi:
295+
# Disabled by default to avoid triggering PyPI uploads from this workflow.
296+
# PyPI publisher was configured to accept uploads from `promote-to-pypi.yml`.
297+
# Keep the job present for maintainers, but skip execution unless intentionally enabled.
298+
if: false
299+
needs: verify-installed
300+
runs-on: ubuntu-latest
301+
permissions:
302+
id-token: write
303+
contents: read
304+
# This job publishes the already-built artifacts to the real PyPI index.
305+
# It requires a PyPI API token stored in the repository secrets as PYPI_API_TOKEN.
306+
steps:
307+
- name: Checkout repository
308+
uses: actions/checkout@v4
309+
with:
310+
fetch-depth: 0
311+
312+
- name: Download built wheels
313+
uses: actions/download-artifact@v4
314+
with:
315+
path: dist
316+
pattern: alp-graphblas-wheels-*
317+
merge-multiple: true
318+
319+
- name: Publish to PyPI (alp-graphblas)
320+
uses: pypa/gh-action-pypi-publish@release/v1
321+
with:
322+
packages-dir: dist/
323+
env:
324+
TWINE_USERNAME: __token__
325+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

CMakeLists.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,43 @@ set( MINORVERSION 7 )
3131
set( BUGVERSION 0 )
3232
set( VERSION "${MAJORVERSION}.${MINORVERSION}.${BUGVERSION}" )
3333

34+
# Export a canonical ALP version string for subprojects and packaging. This
35+
# defaults to the VERSION defined above but can be overridden by -DALP_VERSION
36+
# on the cmake command line (CI may pass this explicitly).
37+
if(NOT DEFINED ALP_VERSION)
38+
set(ALP_VERSION "${VERSION}" CACHE STRING "ALP project version (for packaging)")
39+
else()
40+
# Keep user-provided ALP_VERSION in cache so subprojects see it
41+
set(ALP_VERSION "${ALP_VERSION}" CACHE STRING "ALP project version (for packaging)" FORCE)
42+
endif()
43+
3444
# set the project name
3545
project( GraphBLAS
3646
VERSION ${VERSION}
3747
DESCRIPTION "The ultimate engine for sparse computation"
3848
LANGUAGES CXX C
3949
)
50+
51+
# Find Git and get repository information for metadata
52+
find_package(Git QUIET)
53+
if(GIT_FOUND)
54+
execute_process(
55+
COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
56+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
57+
OUTPUT_VARIABLE ALP_GIT_COMMIT
58+
OUTPUT_STRIP_TRAILING_WHITESPACE
59+
)
60+
execute_process(
61+
COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
62+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
63+
OUTPUT_VARIABLE ALP_GIT_BRANCH
64+
OUTPUT_STRIP_TRAILING_WHITESPACE
65+
)
66+
else()
67+
set(ALP_GIT_COMMIT "unknown")
68+
set(ALP_GIT_BRANCH "unknown")
69+
endif()
70+
4071
set( CMAKE_CXX_STANDARD 11 )
4172
set( CMAKE_CXX_STANDARD_REQUIRED ON )
4273

0 commit comments

Comments
 (0)