Skip to content

Commit 0d8c242

Browse files
authored
Merge pull request GraphBLAS#1 from jim22k/cibw
Cibw
2 parents 49f1b70 + f15f08c commit 0d8c242

File tree

8 files changed

+268
-34
lines changed

8 files changed

+268
-34
lines changed

.github/workflows/wheels.yml

Lines changed: 162 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,171 @@ on:
44
release:
55
types: [created]
66

7+
# Enable Run Workflow button in GitHub UI
8+
workflow_dispatch:
9+
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
714
jobs:
8-
wheels:
15+
build_sdist:
16+
name: Build SDist
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v3
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Build SDist
24+
run: pipx run build --sdist
25+
26+
- name: Check metadata
27+
run: pipx run twine check dist/*
28+
29+
- uses: actions/upload-artifact@v3
30+
with:
31+
path: dist/*.tar.gz
32+
33+
34+
build_wheels:
35+
name: Wheels on ${{ matrix.platform_id }} - ${{ matrix.os }}
936
runs-on: ${{ matrix.os }}
10-
defaults:
11-
run:
12-
shell: bash -l {0}
1337
strategy:
1438
fail-fast: false
1539
matrix:
16-
os: ["ubuntu-latest"]
40+
# Loosely based on scikit-learn's config:
41+
# https://github.com/scikit-learn/scikit-learn/blob/main/.github/workflows/wheels.yml
42+
include:
43+
- os: windows-latest
44+
python-version: "3.8"
45+
platform_id: win_amd64
46+
47+
- os: ubuntu-latest
48+
python-version: "3.8"
49+
platform_id: manylinux_x86_64
50+
manylinux_image: manylinux2014
51+
- os: ubuntu-latest
52+
python-version: "3.8"
53+
platform_id: manylinux_aarch64
54+
manylinux_image: manylinux2014
55+
56+
# Use x86 macOS runner to build both x86 and ARM. GitHub does not offer M1/M2 yet (only self-hosted).
57+
- os: macos-latest
58+
python-version: "3.8"
59+
platform_id: macosx_x86_64
60+
1761
steps:
18-
- name: Checkout
19-
uses: actions/checkout@v3
20-
- name: Set up Python
21-
uses: actions/setup-python@v4
22-
with:
23-
python-version: '3.8'
24-
- name: Upgrade pip
25-
run: |
26-
python -m pip install --upgrade pip
27-
- name: Build manylinux Python wheels
28-
uses: RalfG/[email protected]_x86_64
29-
with:
30-
python-versions: 'cp38-cp38 cp39-cp39'
31-
build-requirements: 'cffi numpy>=1.19,<1.20 cython'
32-
pre-build-command: ${{ format('sh suitesparse.sh {0}', github.ref) }}
33-
- name: Publish wheels to PyPI
34-
env:
35-
TWINE_USERNAME: __token__
36-
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
37-
run: |
38-
pip install twine
39-
twine upload dist/*-manylinux*.whl
62+
- uses: actions/checkout@v3
63+
with:
64+
fetch-depth: 0
65+
66+
- uses: actions/setup-python@v4
67+
with:
68+
python-version: ${{ matrix.python-version }}
69+
70+
- name: Install tools (macOS)
71+
if: contains(matrix.os, 'macos')
72+
# Install coreutils which includes `nproc` used by `make -j` in suitesparse.sh
73+
#
74+
# GitHub actions comes with libomp already installed, but for its native arch only. Must build universal one
75+
# manually so that both x86 and arm builds can be built.
76+
run: |
77+
brew install coreutils
78+
brew install libomp
79+
sh add_arm_to_libomp_dylib.sh
80+
81+
- name: Build Wheels
82+
env:
83+
# very verbose
84+
CIBW_BUILD_VERBOSITY: 3
85+
86+
# Build SuiteSparse
87+
# CIBW_BEFORE_ALL: bash suitesparse.sh ${{ github.ref }}
88+
# TODO
89+
CIBW_BEFORE_ALL: bash suitesparse.sh refs/tags/7.4.3.0
90+
91+
# CMAKE_GNUtoMS=ON asks suitesparse.sh to build libraries in MSVC style on Windows.
92+
CIBW_ENVIRONMENT_WINDOWS: CMAKE_GNUtoMS=ON GRAPHBLAS_PREFIX="C:/GraphBLAS"
93+
94+
# macOS libomp requires special configs. BREW_LIBOMP=1 asks suitesparse.sh to include them.
95+
CIBW_ENVIRONMENT_MACOS: BREW_LIBOMP="1"
96+
97+
# Uncomment to only build CPython wheels
98+
# TODO
99+
CIBW_BUILD: "cp*"
100+
101+
# macOS: build x86_64 and arm64
102+
CIBW_ARCHS_MACOS: "x86_64 arm64"
103+
104+
# No 32-bit builds
105+
# TODO
106+
CIBW_SKIP: "*-win32 *_i686 *musl*"
107+
108+
# Use delvewheel on Windows.
109+
# This copies graphblas.dll into the wheel. "repair" in cibuildwheel parlance includes copying any shared
110+
# libraries from the build host into the wheel to make the wheel self-contained.
111+
# Cibuildwheel includes tools for this for Linux and macOS, and they recommend delvewheel for Windows.
112+
# Note: Currently using a workaround: --no-mangle instead of stripping graphblas.dll
113+
# see https://github.com/adang1345/delvewheel/issues/33
114+
CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel"
115+
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair --add-path \"C:\\GraphBLAS\\bin\" --no-mangle \"libgomp-1.dll;libgcc_s_seh-1.dll\" -w {dest_dir} {wheel}"
116+
117+
# make cibuildwheel install test dependencies from pyproject.toml
118+
CIBW_TEST_EXTRAS: "test"
119+
120+
# run tests
121+
CIBW_TEST_COMMAND: "pytest {project}/suitesparse_graphblas/tests"
122+
123+
# GitHub Actions macOS Intel runner cannot run ARM tests.
124+
CIBW_TEST_SKIP: "*-macosx_arm64"
125+
126+
run: |
127+
python -m pip install cibuildwheel
128+
python -m cibuildwheel --output-dir wheelhouse .
129+
shell: bash
130+
131+
- uses: actions/upload-artifact@v3
132+
id: uploadAttempt1
133+
continue-on-error: true
134+
with:
135+
path: wheelhouse/*.whl
136+
if-no-files-found: error
137+
138+
# Retry upload if first attempt failed. This happens somewhat randomly and for irregular reasons.
139+
# Logic is a duplicate of previous step.
140+
- uses: actions/upload-artifact@v3
141+
id: uploadAttempt2
142+
if: steps.uploadAttempt1.outcome == 'failure'
143+
continue-on-error: false
144+
with:
145+
path: wheelhouse/*.whl
146+
if-no-files-found: error
147+
148+
upload_all:
149+
name: Upload to PyPI
150+
needs: [build_wheels, build_sdist]
151+
runs-on: ubuntu-latest
152+
# if: github.event_name == 'release' && github.event.action == 'published'
153+
154+
steps:
155+
- uses: actions/setup-python@v4
156+
with:
157+
python-version: "3.x"
158+
159+
- uses: actions/download-artifact@v3
160+
with:
161+
name: artifact
162+
path: dist
163+
164+
- uses: pypa/gh-action-pypi-publish@release/v1
165+
with:
166+
# PyPI does not allow replacing a file. Without this flag the entire action fails if even a single duplicate exists.
167+
skip_existing: true
168+
verbose: true
169+
# Real PyPI:
170+
# password: ${{ secrets.PYPI_TOKEN }}
171+
172+
# Test PyPI:
173+
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
174+
repository_url: https://test.pypi.org/legacy/

add_arm_to_libomp_dylib.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/sh
2+
3+
#mkdir x86lib
4+
mkdir armlib
5+
6+
# download and unzip both x86 and arm libomp tarballs
7+
#brew fetch --force --bottle-tag=x86_64_monterey libomp
8+
brew fetch --force --bottle-tag=arm64_big_sur libomp
9+
10+
# untar
11+
#tar -xzf $(brew --cache --bottle-tag=x86_64_monterey libomp) --strip-components 2 -C x86lib
12+
tar -xzf $(brew --cache --bottle-tag=arm64_big_sur libomp) --strip-components 2 -C armlib
13+
14+
# merge
15+
lipo armlib/lib/libomp.dylib $(brew --prefix libomp)/lib/libomp.dylib -output libomp.dylib -create
16+
cp -f libomp.dylib $(brew --prefix libomp)/lib

build_graphblas_cffi.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
include_dirs.append(os.path.join(sys.prefix, "Library", "include"))
1717
library_dirs.append(os.path.join(sys.prefix, "Library", "lib"))
1818

19+
# wheels.yml configures suitesparse.sh to install GraphBLAS here.
20+
prefix = "C:\\GraphBLAS"
21+
include_dirs.append(os.path.join(prefix, "include"))
22+
library_dirs.append(os.path.join(prefix, "lib"))
23+
library_dirs.append(os.path.join(prefix, "bin"))
24+
1925
ffibuilder.set_source(
2026
"suitesparse_graphblas._graphblas",
2127
(ss_g / "source.c").read_text(),

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ requires = [
44
"setuptools >=64",
55
"setuptools-git-versioning",
66
"wheel",
7-
"cffi",
7+
"cffi>=1.11",
88
"cython",
99
"oldest-supported-numpy",
1010
]
1111

1212
[project]
1313
name = "suitesparse-graphblas"
14-
dynamic = ["version"]
14+
#dynamic = ["version"]
15+
# TODO
16+
version = "0.0.3"
1517
description = "SuiteSparse:GraphBLAS Python bindings."
1618
readme = "README.md"
1719
requires-python = ">=3.8"
@@ -45,7 +47,7 @@ classifiers = [
4547
]
4648
dependencies = [
4749
# These are super-old; can/should we update them?
48-
"cffi>=1.0.0",
50+
"cffi>=1.11",
4951
"numpy>=1.19",
5052
]
5153
[project.urls]

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@
5858
if use_cython:
5959
ext_modules = cythonize(ext_modules, include_path=include_dirs)
6060

61-
ext_modules.append(build_graphblas_cffi.get_extension(extra_compile_args=extra_compile_args))
61+
if build_graphblas_cffi.is_win:
62+
ext_modules.append(build_graphblas_cffi.get_extension(extra_compile_args=extra_compile_args))
6263

6364
setup(
6465
ext_modules=ext_modules,
66+
cffi_modules=None if build_graphblas_cffi.is_win else ["build_graphblas_cffi.py:ffibuilder"],
6567
)

suitesparse.sh

100644100755
Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,87 @@
1+
#!/bin/bash
12

3+
# parse SuiteSparse version from first argument, a git tag that ends in the version (no leading v)
24
if [[ $1 =~ refs/tags/([0-9]*\.[0-9]*\.[0-9]*)\..*$ ]];
35
then
46
VERSION=${BASH_REMATCH[1]}
57
else
8+
echo "Specify a SuiteSparse version, such as: $0 refs/tags/7.4.3.0"
69
exit -1
710
fi
811
echo VERSION: $VERSION
912

13+
NPROC="$(nproc)"
14+
if [ -z "${NPROC}" ]; then
15+
# Default for platforms that don't have nproc. Mostly Windows.
16+
NPROC="2"
17+
fi
18+
19+
cmake_params=()
20+
if [ -n "${BREW_LIBOMP}" ]; then
21+
# macOS OpenMP flags.
22+
# FindOpenMP doesn't find brew's libomp, so set the necessary configs manually.
23+
cmake_params+=(-DOpenMP_C_FLAGS="-Xclang -fopenmp -I$(brew --prefix libomp)/include")
24+
cmake_params+=(-DOpenMP_C_LIB_NAMES="libomp")
25+
cmake_params+=(-DOpenMP_libomp_LIBRARY="omp")
26+
export LDFLAGS="-L$(brew --prefix libomp)/lib"
27+
28+
# build both x86 and ARM
29+
export CFLAGS="-arch x86_64 -arch arm64"
30+
fi
31+
32+
if [ -n "${CMAKE_GNUtoMS}" ]; then
33+
# Windows needs .lib libraries, not .a
34+
cmake_params+=(-DCMAKE_GNUtoMS=ON)
35+
# Windows expects 'graphblas.lib', not 'libgraphblas.lib'
36+
cmake_params+=(-DCMAKE_SHARED_LIBRARY_PREFIX=)
37+
cmake_params+=(-DCMAKE_STATIC_LIBRARY_PREFIX=)
38+
fi
39+
40+
if [ -n "${GRAPHBLAS_PREFIX}" ]; then
41+
echo "GRAPHBLAS_PREFIX=${GRAPHBLAS_PREFIX}"
42+
cmake_params+=(-DCMAKE_INSTALL_PREFIX="${GRAPHBLAS_PREFIX}")
43+
fi
44+
1045
curl -L https://github.com/DrTimothyAldenDavis/GraphBLAS/archive/refs/tags/v${VERSION}.tar.gz | tar xzf -
1146
cd GraphBLAS-${VERSION}/build
12-
cmake .. -DCMAKE_BUILD_TYPE=Release
13-
make -j$(nproc)
47+
48+
# Disable optimizing some rarely-used types for significantly faster builds and significantly smaller wheel size.
49+
# Also the build with all types enabled sometimes stalls on GitHub Actions. Probably due to exceeded resource limits.
50+
# These can still be used, they'll just have reduced performance (AFAIK similar to UDTs).
51+
# TODO
52+
echo "#define GxB_NO_BOOL 1" >> ../Source/GB_control.h #
53+
echo "#define GxB_NO_FP32 1" >> ../Source/GB_control.h #
54+
echo "#define GxB_NO_FP64 1" >> ../Source/GB_control.h #
55+
echo "#define GxB_NO_FC32 1" >> ../Source/GB_control.h #
56+
echo "#define GxB_NO_FC64 1" >> ../Source/GB_control.h #
57+
echo "#define GxB_NO_INT16 1" >> ../Source/GB_control.h
58+
echo "#define GxB_NO_INT32 1" >> ../Source/GB_control.h
59+
echo "#define GxB_NO_INT64 1" >> ../Source/GB_control.h #
60+
echo "#define GxB_NO_INT8 1" >> ../Source/GB_control.h
61+
echo "#define GxB_NO_UINT16 1" >> ../Source/GB_control.h
62+
echo "#define GxB_NO_UINT32 1" >> ../Source/GB_control.h
63+
echo "#define GxB_NO_UINT64 1" >> ../Source/GB_control.h
64+
echo "#define GxB_NO_UINT8 1" >> ../Source/GB_control.h
65+
66+
# Disable all Source/Generated2 kernels. For workflow development only.
67+
#cmake_params+=(-DCMAKE_CUDA_DEV=1)
68+
69+
cmake .. -DCMAKE_BUILD_TYPE=Release -G 'Unix Makefiles' "${cmake_params[@]}"
70+
make -j$NPROC
1471
make install
72+
73+
if [ -n "${CMAKE_GNUtoMS}" ]; then
74+
if [ -z "${GRAPHBLAS_PREFIX}" ]; then
75+
# Windows default
76+
GRAPHBLAS_PREFIX="C:/Program Files (x86)"
77+
fi
78+
79+
# Windows:
80+
# CMAKE_STATIC_LIBRARY_PREFIX is sometimes ignored, possibly when the MinGW toolchain is selected.
81+
# Drop the 'lib' prefix manually.
82+
echo "manually removing lib prefix"
83+
mv "${GRAPHBLAS_PREFIX}/lib/libgraphblas.lib" "${GRAPHBLAS_PREFIX}/lib/graphblas.lib"
84+
mv "${GRAPHBLAS_PREFIX}/lib/libgraphblas.dll.a" "${GRAPHBLAS_PREFIX}/lib/graphblas.dll.a"
85+
# cp instead of mv because the GNU tools expect libgraphblas.dll and the MS tools expect graphblas.dll.
86+
cp "${GRAPHBLAS_PREFIX}/bin/libgraphblas.dll" "${GRAPHBLAS_PREFIX}/bin/graphblas.dll"
87+
fi

suitesparse_graphblas/tests/__init__.py

Whitespace-only changes.

suitesparse_graphblas/tests/test_package.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ def test_matrix_existence():
77

88

99
def test_version():
10-
assert suitesparse_graphblas.__version__ > "7.4.2.0"
10+
assert suitesparse_graphblas.__version__ >= "0.0.1"

0 commit comments

Comments
 (0)