diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index f2fda55..19b3ebf 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -4,36 +4,149 @@ 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 + + - 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/python-wheels-manylinux-build@v0.4.2-manylinux2014_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 + + - 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. It's listed here to explicitly list this requirement. + run: | + brew install coreutils + brew install libomp + + - name: Build Wheels + env: + # very verbose + CIBW_BUILD_VERBOSITY: 3 + + # Build SuiteSparse + CIBW_BEFORE_ALL: bash suitesparse.sh ${{ github.ref }} +# CIBW_BEFORE_ALL: bash suitesparse.sh refs/tags/7.4.3.0 + + # Ask suitesparse.sh to build libraries in MSVC style on Windows. + CIBW_ENVIRONMENT_WINDOWS: CMAKE_GNUtoMS=ON + + # macOS libomp setup. + # FindOpenMP doesn't find brew's libomp, so set the necessary configs manually. + # The OpenMP_* variables need to be passed to CMake as -D defines, that is done by suitesparse.sh + CIBW_ENVIRONMENT_MACOS: LDFLAGS="-L$(brew --prefix libomp)/lib" OpenMP_C_FLAGS="-Xclang -fopenmp -I$(brew --prefix libomp)/include" OpenMP_C_LIB_NAMES="libomp" OpenMP_libomp_LIBRARY="omp" + + # Uncomment to only build CPython wheels + # CIBW_BUILD: "cp*" + + # macOS: build x86_64 and arm64 + CIBW_ARCHS_MACOS: "x86_64 arm64" + + # No 32-bit builds + CIBW_SKIP: "*-win32 *_i686" + + # 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. + CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel" + CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair --add-path \"C:\\Program Files (x86)\\bin\" --no-mangle \"libgomp-1.dll;libgcc_s_seh-1.dll\" -w {dest_dir} {wheel}" + + # Test dependencies + CIBW_TEST_REQUIRES: "pytest" + + # 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 + with: + path: wheelhouse/*.whl + + + 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/ diff --git a/suitesparse.sh b/suitesparse.sh old mode 100644 new mode 100755 index 77410fc..119b96e --- a/suitesparse.sh +++ b/suitesparse.sh @@ -1,14 +1,68 @@ +#!/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 "${OpenMP_C_FLAGS}" ]; then + # macOS OpenMP flags. + # wheels.yml sets them as env vars, but CMake needs them as -D defs. + cmake_params+=(-DOpenMP_C_FLAGS="${OpenMP_C_FLAGS}") + cmake_params+=(-DOpenMP_C_LIB_NAMES="${OpenMP_C_LIB_NAMES}") + cmake_params+=(-DOpenMP_libomp_LIBRARY="${OpenMP_libomp_LIBRARY}") +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 + 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). +#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 + +cmake .. -DCMAKE_BUILD_TYPE=Release -G 'Unix Makefiles' "${cmake_params[@]}" +make -j$NPROC make install + +if [ -n "${CMAKE_GNUtoMS}" ]; then + # 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 "C:/Program Files (x86)/lib/libgraphblas.lib" "C:/Program Files (x86)/lib/graphblas.lib" + mv "C:/Program Files (x86)/lib/libgraphblas.dll.a" "C:/Program Files (x86)/lib/graphblas.dll.a" + cp "C:/Program Files (x86)/bin/libgraphblas.dll" "C:/Program Files (x86)/bin/graphblas.dll" +fi diff --git a/suitesparse_graphblas/tests/__init__.py b/suitesparse_graphblas/tests/__init__.py deleted file mode 100644 index e69de29..0000000