diff --git a/.github/workflows/pypi_release.yml b/.github/workflows/pypi_release.yml new file mode 100644 index 0000000000..e4b7241b93 --- /dev/null +++ b/.github/workflows/pypi_release.yml @@ -0,0 +1,63 @@ +name: Build and Release Python Bindings to PyPI + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + default: '' + required: false + description: Ref to build + +jobs: + build-sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - name: Checkout the repo  + uses: actions/checkout@v3 + with: + ref: ${{ github.event.inputs.tag }} + + - name: Set up Python 🐍 + uses: actions/setup-python@v3 + with: + python-version: '3.10' + + - name: Install system dependencies  + run: | + sudo apt-get install -y build-essential cmake libboost-all-dev libjemalloc-dev libtbb-dev libblosc-dev zlib1g-dev + + - name: Install build dependencies  + run: | + python -m pip install build + + - name: Build the sdist 📦 + working-directory: ./openvdb/openvdb/python + run: | + python -m build --sdist + + - name: Upload artifact ⇑ + uses: actions/upload-artifact@v3 + with: + name: dist + path: ./openvdb/openvdb/python/dist/ + + publish: + name: Publish Python packages on PyPI + needs: [build-sdist, build-wheels] + runs-on: ubuntu-latest + steps: + - name: Download artifacts ⇓ + uses: actions/download-artifact@v3 + with: + name: dist + path: dist + + - name: Publish to PyPI 🎉 + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: dist diff --git a/openvdb/openvdb/python/.gitignore b/openvdb/openvdb/python/.gitignore new file mode 100644 index 0000000000..1b59adf6fe --- /dev/null +++ b/openvdb/openvdb/python/.gitignore @@ -0,0 +1,7 @@ +CMakeCache.txt +CMakeFiles/ +Makefile +cmake_install.cmake +*.egg-info/ +*.so +_skbuild/ diff --git a/openvdb/openvdb/python/CMakeLists.txt b/openvdb/openvdb/python/CMakeLists.txt index 0a949cc520..7acb6c321c 100644 --- a/openvdb/openvdb/python/CMakeLists.txt +++ b/openvdb/openvdb/python/CMakeLists.txt @@ -114,6 +114,14 @@ if(USE_NUMPY) list(APPEND OPENVDB_PYTHON_REQUIRED_COMPONENTS NumPy) endif() +# scikit-build doesn't yet support FindPython, but this workaround was +# supplied by one of the scikit-build devs. See +# https://github.com/pypa/cibuildwheel/issues/727 for details. +if(SKBUILD) + set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}") + set(Python_NumPy_INCLUDE_DIR "${NUMPY_INCLUDE_DIR}") +endif() + # Make sure find_package(Python) is only ever invoked once with all required components find_package(Python COMPONENTS ${OPENVDB_PYTHON_REQUIRED_COMPONENTS}) @@ -283,17 +291,20 @@ if(USE_AX) target_compile_definitions(pyopenvdb PUBLIC "-DPY_OPENVDB_USE_AX") endif() -set(PYTHON_PUBLIC_INCLUDE_NAMES - pyopenvdb.h -) - install(TARGETS pyopenvdb DESTINATION ${PYOPENVDB_INSTALL_DIRECTORY} ) -install(FILES ${PYTHON_PUBLIC_INCLUDE_NAMES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/openvdb/python) +# Disable copying this file over during the build; it will be copied by skbuild to the correct +# site-package directory automatically +if(NOT SKBUILD) + set(PYTHON_PUBLIC_INCLUDE_NAMES + pyopenvdb.h + ) + install(FILES ${PYTHON_PUBLIC_INCLUDE_NAMES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/openvdb/python) +endif() # pytest if(OPENVDB_BUILD_PYTHON_UNITTESTS) diff --git a/openvdb/openvdb/python/README.md b/openvdb/openvdb/python/README.md new file mode 100644 index 0000000000..a215a7ef6f --- /dev/null +++ b/openvdb/openvdb/python/README.md @@ -0,0 +1,75 @@ +# pyopenvdb + +This project contains python bindings for OpenVDB. + +## Dependencies + +| Name | Version | +| --- | --- | +| cmake | 3.15 | +| boost | 1.70 | +| numpy | 1.14 | +| openvdb | >9.0.0 | + +## Installing from source + +Installation using `pip` is preferred because it manages the package version +sensibly, and also automatically handles finding the correct python +installation. + +### Using pip + +The python bindings for OpenVDB can be installed using `pip install .`. In this +case, the build build toolchain executes in the following order: + +1. `setup.py` is executed, calling `scikit-build`'s [`setup` + function](https://scikit-build.readthedocs.io/en/latest/usage.html#setup-options). + `scikit-build` acts as [glue between `setuptools` and + `cmake`](https://scikit-build.readthedocs.io/en/latest/). +2. `setuptools_scm` generates a version string for the python package. If + building on a git tag, the python version will be identical to the tag. See + [this page](https://github.com/pypa/setuptools_scm#default-versioning-scheme) + for more information about the versioning scheme. +3. `scikit-build` passes a sensible default set of flags, defined in `setup.py`, + to `cmake`, which generates build files the project (ninja or GNU make) + before using them to build the project. +4. `pip` copies the resulting shared object into your python installation's + `site-packages` directory. C headers for the python bindings are installed to + your python installation's `sys.prefix`, under + `include/pythonX.Y/pyopenvdb/`. + +### Using cmake + +Build files for the C++ code are generated by `cmake`, so the bindings can be +built by invoking + +```bash +mkdir build +cd build +cmake .. +cmake --build . +cmake --install . +``` + +This will generate build files, build a shared object library, and copy it into +your python installation's `site-packages` directory, where it can be imported +directly. If you wish to target a particular version of python, this can be done +by setting [the appropriate cmake +flags](https://cmake.org/cmake/help/latest/module/FindPython.html). +`CMakeLists.txt` contains a full listing of build options for more +customization; by default the `pyopenvdb` package on PyPI is built with all the +optional functionality enabled. + +## Releasing to PyPI + +Using GitHub actions, new `pyopenvdb` versions are published to PyPI +automatically when a new OpenVDB release is made. Releases can also published to +PyPI by triggering the "Build and Release Python Bindings to PyPI" job, and +specifying a branch/ref; if no ref is specified, the most recent commit on the +given branch is used. Publishing to PyPI requires an API token to be specified +as a secret (`PYPI_API_TOKEN`) on the repository. + +## Contributors + +Thanks to Alex Braun (@theNewFlesh) for contributing the original python +packaging for pyopenvdb. diff --git a/openvdb/openvdb/python/pyproject.toml b/openvdb/openvdb/python/pyproject.toml new file mode 100644 index 0000000000..873ba8b769 --- /dev/null +++ b/openvdb/openvdb/python/pyproject.toml @@ -0,0 +1,9 @@ +[build-system] +requires = [ + "setuptools>=42", + "setuptools_scm[toml]>=6.2", + "wheel", + "scikit-build", + "cmake", + "numpy" +] diff --git a/openvdb/openvdb/python/setup.py b/openvdb/openvdb/python/setup.py new file mode 100644 index 0000000000..1e767bd3dd --- /dev/null +++ b/openvdb/openvdb/python/setup.py @@ -0,0 +1,40 @@ +import sys +import pathlib +import numpy as np +from skbuild import setup + + +with open(pathlib.Path(__file__).parent / "README.md") as f: + long_description = f.read() + + +# Explicitly set NUMPY_INCLUDE_DIR because cmake fails to find it otherwise. +# Same is true for the boost python libs. +setup( + name="pyopenvdb", + use_scm_version={ + "root": '../../..', + "relative_to": __file__, + }, + author="OpenVDB", + author_email="openvdb-dev@lists.aswf.io", + description=( + "Python bindings for OpenVDB: sparse volume data structure and tools." + ), + download_url="https://pypi.python.org/pypi/pyopenvdb", + headers=['pyopenvdb.h'], + include_package_data=True, + license="Mozilla Public License 2.0", + long_description=long_description, + long_description_content_type='text/markdown', + url="https://github.com/AcademySoftwareFoundation/openvdb", + cmake_args=[ + '-DUSE_NUMPY=ON', + '-DOPENVDB_PYTHON_WRAP_ALL_GRID_TYPES=ON', + '-DOPENVDB_BUILD_CORE=ON', + f'-DNUMPY_INCLUDE_DIR={np.get_include()}', + f'-DBoost_PYTHON_VERSION={sys.version_info[0]}.{sys.version_info[1]}', + ], + cmake_install_dir='.', + cmake_source_dir='.', +)