Skip to content

Commit

Permalink
Merge pull request GraphBLAS#1 from jim22k/cibw
Browse files Browse the repository at this point in the history
Cibw
  • Loading branch information
jim22k authored Mar 22, 2023
2 parents 49f1b70 + f15f08c commit 0d8c242
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 34 deletions.
189 changes: 162 additions & 27 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,171 @@ on:
release:
types: [created]

# Enable Run Workflow button in GitHub UI
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
wheels:
build_sdist:
name: Build SDist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Build SDist
run: pipx run build --sdist

- name: Check metadata
run: pipx run twine check dist/*

- uses: actions/upload-artifact@v3
with:
path: dist/*.tar.gz


build_wheels:
name: Wheels on ${{ matrix.platform_id }} - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash -l {0}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
# Loosely based on scikit-learn's config:
# https://github.com/scikit-learn/scikit-learn/blob/main/.github/workflows/wheels.yml
include:
- os: windows-latest
python-version: "3.8"
platform_id: win_amd64

- os: ubuntu-latest
python-version: "3.8"
platform_id: manylinux_x86_64
manylinux_image: manylinux2014
- os: ubuntu-latest
python-version: "3.8"
platform_id: manylinux_aarch64
manylinux_image: manylinux2014

# Use x86 macOS runner to build both x86 and ARM. GitHub does not offer M1/M2 yet (only self-hosted).
- os: macos-latest
python-version: "3.8"
platform_id: macosx_x86_64

steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Upgrade pip
run: |
python -m pip install --upgrade pip
- name: Build manylinux Python wheels
uses: RalfG/[email protected]_x86_64
with:
python-versions: 'cp38-cp38 cp39-cp39'
build-requirements: 'cffi numpy>=1.19,<1.20 cython'
pre-build-command: ${{ format('sh suitesparse.sh {0}', github.ref) }}
- name: Publish wheels to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
pip install twine
twine upload dist/*-manylinux*.whl
- uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install tools (macOS)
if: contains(matrix.os, 'macos')
# Install coreutils which includes `nproc` used by `make -j` in suitesparse.sh
#
# GitHub actions comes with libomp already installed, but for its native arch only. Must build universal one
# manually so that both x86 and arm builds can be built.
run: |
brew install coreutils
brew install libomp
sh add_arm_to_libomp_dylib.sh
- name: Build Wheels
env:
# very verbose
CIBW_BUILD_VERBOSITY: 3

# Build SuiteSparse
# CIBW_BEFORE_ALL: bash suitesparse.sh ${{ github.ref }}
# TODO
CIBW_BEFORE_ALL: bash suitesparse.sh refs/tags/7.4.3.0

# CMAKE_GNUtoMS=ON asks suitesparse.sh to build libraries in MSVC style on Windows.
CIBW_ENVIRONMENT_WINDOWS: CMAKE_GNUtoMS=ON GRAPHBLAS_PREFIX="C:/GraphBLAS"

# macOS libomp requires special configs. BREW_LIBOMP=1 asks suitesparse.sh to include them.
CIBW_ENVIRONMENT_MACOS: BREW_LIBOMP="1"

# Uncomment to only build CPython wheels
# TODO
CIBW_BUILD: "cp*"

# macOS: build x86_64 and arm64
CIBW_ARCHS_MACOS: "x86_64 arm64"

# No 32-bit builds
# TODO
CIBW_SKIP: "*-win32 *_i686 *musl*"

# Use delvewheel on Windows.
# This copies graphblas.dll into the wheel. "repair" in cibuildwheel parlance includes copying any shared
# libraries from the build host into the wheel to make the wheel self-contained.
# Cibuildwheel includes tools for this for Linux and macOS, and they recommend delvewheel for Windows.
# Note: Currently using a workaround: --no-mangle instead of stripping graphblas.dll
# see https://github.com/adang1345/delvewheel/issues/33
CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel"
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}"

# make cibuildwheel install test dependencies from pyproject.toml
CIBW_TEST_EXTRAS: "test"

# run tests
CIBW_TEST_COMMAND: "pytest {project}/suitesparse_graphblas/tests"

# GitHub Actions macOS Intel runner cannot run ARM tests.
CIBW_TEST_SKIP: "*-macosx_arm64"

run: |
python -m pip install cibuildwheel
python -m cibuildwheel --output-dir wheelhouse .
shell: bash

- uses: actions/upload-artifact@v3
id: uploadAttempt1
continue-on-error: true
with:
path: wheelhouse/*.whl
if-no-files-found: error

# Retry upload if first attempt failed. This happens somewhat randomly and for irregular reasons.
# Logic is a duplicate of previous step.
- uses: actions/upload-artifact@v3
id: uploadAttempt2
if: steps.uploadAttempt1.outcome == 'failure'
continue-on-error: false
with:
path: wheelhouse/*.whl
if-no-files-found: error

upload_all:
name: Upload to PyPI
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
# if: github.event_name == 'release' && github.event.action == 'published'

steps:
- uses: actions/setup-python@v4
with:
python-version: "3.x"

- uses: actions/download-artifact@v3
with:
name: artifact
path: dist

- uses: pypa/gh-action-pypi-publish@release/v1
with:
# PyPI does not allow replacing a file. Without this flag the entire action fails if even a single duplicate exists.
skip_existing: true
verbose: true
# Real PyPI:
# password: ${{ secrets.PYPI_TOKEN }}

# Test PyPI:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
16 changes: 16 additions & 0 deletions add_arm_to_libomp_dylib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/sh

#mkdir x86lib
mkdir armlib

# download and unzip both x86 and arm libomp tarballs
#brew fetch --force --bottle-tag=x86_64_monterey libomp
brew fetch --force --bottle-tag=arm64_big_sur libomp

# untar
#tar -xzf $(brew --cache --bottle-tag=x86_64_monterey libomp) --strip-components 2 -C x86lib
tar -xzf $(brew --cache --bottle-tag=arm64_big_sur libomp) --strip-components 2 -C armlib

# merge
lipo armlib/lib/libomp.dylib $(brew --prefix libomp)/lib/libomp.dylib -output libomp.dylib -create
cp -f libomp.dylib $(brew --prefix libomp)/lib
6 changes: 6 additions & 0 deletions build_graphblas_cffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
include_dirs.append(os.path.join(sys.prefix, "Library", "include"))
library_dirs.append(os.path.join(sys.prefix, "Library", "lib"))

# wheels.yml configures suitesparse.sh to install GraphBLAS here.
prefix = "C:\\GraphBLAS"
include_dirs.append(os.path.join(prefix, "include"))
library_dirs.append(os.path.join(prefix, "lib"))
library_dirs.append(os.path.join(prefix, "bin"))

ffibuilder.set_source(
"suitesparse_graphblas._graphblas",
(ss_g / "source.c").read_text(),
Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ requires = [
"setuptools >=64",
"setuptools-git-versioning",
"wheel",
"cffi",
"cffi>=1.11",
"cython",
"oldest-supported-numpy",
]

[project]
name = "suitesparse-graphblas"
dynamic = ["version"]
#dynamic = ["version"]
# TODO
version = "0.0.3"
description = "SuiteSparse:GraphBLAS Python bindings."
readme = "README.md"
requires-python = ">=3.8"
Expand Down Expand Up @@ -45,7 +47,7 @@ classifiers = [
]
dependencies = [
# These are super-old; can/should we update them?
"cffi>=1.0.0",
"cffi>=1.11",
"numpy>=1.19",
]
[project.urls]
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@
if use_cython:
ext_modules = cythonize(ext_modules, include_path=include_dirs)

ext_modules.append(build_graphblas_cffi.get_extension(extra_compile_args=extra_compile_args))
if build_graphblas_cffi.is_win:
ext_modules.append(build_graphblas_cffi.get_extension(extra_compile_args=extra_compile_args))

setup(
ext_modules=ext_modules,
cffi_modules=None if build_graphblas_cffi.is_win else ["build_graphblas_cffi.py:ffibuilder"],
)
77 changes: 75 additions & 2 deletions suitesparse.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,14 +1,87 @@
#!/bin/bash

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

NPROC="$(nproc)"
if [ -z "${NPROC}" ]; then
# Default for platforms that don't have nproc. Mostly Windows.
NPROC="2"
fi

cmake_params=()
if [ -n "${BREW_LIBOMP}" ]; then
# macOS OpenMP flags.
# FindOpenMP doesn't find brew's libomp, so set the necessary configs manually.
cmake_params+=(-DOpenMP_C_FLAGS="-Xclang -fopenmp -I$(brew --prefix libomp)/include")
cmake_params+=(-DOpenMP_C_LIB_NAMES="libomp")
cmake_params+=(-DOpenMP_libomp_LIBRARY="omp")
export LDFLAGS="-L$(brew --prefix libomp)/lib"

# build both x86 and ARM
export CFLAGS="-arch x86_64 -arch arm64"
fi

if [ -n "${CMAKE_GNUtoMS}" ]; then
# Windows needs .lib libraries, not .a
cmake_params+=(-DCMAKE_GNUtoMS=ON)
# Windows expects 'graphblas.lib', not 'libgraphblas.lib'
cmake_params+=(-DCMAKE_SHARED_LIBRARY_PREFIX=)
cmake_params+=(-DCMAKE_STATIC_LIBRARY_PREFIX=)
fi

if [ -n "${GRAPHBLAS_PREFIX}" ]; then
echo "GRAPHBLAS_PREFIX=${GRAPHBLAS_PREFIX}"
cmake_params+=(-DCMAKE_INSTALL_PREFIX="${GRAPHBLAS_PREFIX}")
fi

curl -L https://github.com/DrTimothyAldenDavis/GraphBLAS/archive/refs/tags/v${VERSION}.tar.gz | tar xzf -
cd GraphBLAS-${VERSION}/build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)

# Disable optimizing some rarely-used types for significantly faster builds and significantly smaller wheel size.
# Also the build with all types enabled sometimes stalls on GitHub Actions. Probably due to exceeded resource limits.
# These can still be used, they'll just have reduced performance (AFAIK similar to UDTs).
# TODO
echo "#define GxB_NO_BOOL 1" >> ../Source/GB_control.h #
echo "#define GxB_NO_FP32 1" >> ../Source/GB_control.h #
echo "#define GxB_NO_FP64 1" >> ../Source/GB_control.h #
echo "#define GxB_NO_FC32 1" >> ../Source/GB_control.h #
echo "#define GxB_NO_FC64 1" >> ../Source/GB_control.h #
echo "#define GxB_NO_INT16 1" >> ../Source/GB_control.h
echo "#define GxB_NO_INT32 1" >> ../Source/GB_control.h
echo "#define GxB_NO_INT64 1" >> ../Source/GB_control.h #
echo "#define GxB_NO_INT8 1" >> ../Source/GB_control.h
echo "#define GxB_NO_UINT16 1" >> ../Source/GB_control.h
echo "#define GxB_NO_UINT32 1" >> ../Source/GB_control.h
echo "#define GxB_NO_UINT64 1" >> ../Source/GB_control.h
echo "#define GxB_NO_UINT8 1" >> ../Source/GB_control.h

# Disable all Source/Generated2 kernels. For workflow development only.
#cmake_params+=(-DCMAKE_CUDA_DEV=1)

cmake .. -DCMAKE_BUILD_TYPE=Release -G 'Unix Makefiles' "${cmake_params[@]}"
make -j$NPROC
make install

if [ -n "${CMAKE_GNUtoMS}" ]; then
if [ -z "${GRAPHBLAS_PREFIX}" ]; then
# Windows default
GRAPHBLAS_PREFIX="C:/Program Files (x86)"
fi

# Windows:
# CMAKE_STATIC_LIBRARY_PREFIX is sometimes ignored, possibly when the MinGW toolchain is selected.
# Drop the 'lib' prefix manually.
echo "manually removing lib prefix"
mv "${GRAPHBLAS_PREFIX}/lib/libgraphblas.lib" "${GRAPHBLAS_PREFIX}/lib/graphblas.lib"
mv "${GRAPHBLAS_PREFIX}/lib/libgraphblas.dll.a" "${GRAPHBLAS_PREFIX}/lib/graphblas.dll.a"
# cp instead of mv because the GNU tools expect libgraphblas.dll and the MS tools expect graphblas.dll.
cp "${GRAPHBLAS_PREFIX}/bin/libgraphblas.dll" "${GRAPHBLAS_PREFIX}/bin/graphblas.dll"
fi
Empty file.
2 changes: 1 addition & 1 deletion suitesparse_graphblas/tests/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ def test_matrix_existence():


def test_version():
assert suitesparse_graphblas.__version__ > "7.4.2.0"
assert suitesparse_graphblas.__version__ >= "0.0.1"

0 comments on commit 0d8c242

Please sign in to comment.