diff --git a/.bumpversion.toml b/.bumpversion.toml index 5413b265..dee63272 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -23,4 +23,7 @@ post_commit_hooks = [] filename = "CMakeLists.txt" [[tool.bumpversion.files]] -filename = "src/serialbox-python/setup.py" +filename = "pyproject.toml" + +[[tool.bumpversion.files]] +filename = "src/serialbox-python/serialbox/__init__.py" diff --git a/.github/workflows/pip_package.yml b/.github/workflows/pip_package.yml index 4556f87c..e3a7a771 100644 --- a/.github/workflows/pip_package.yml +++ b/.github/workflows/pip_package.yml @@ -40,7 +40,7 @@ jobs: run: pip install -r src/serialbox-python/requirements.txt - name: build - run: pip install src/serialbox-python + run: pip install . - name: run tests run: pytest -v test/serialbox-python/serialbox >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/pypi_deploy.yml b/.github/workflows/pypi_deploy.yml new file mode 100644 index 00000000..80589666 --- /dev/null +++ b/.github/workflows/pypi_deploy.yml @@ -0,0 +1,123 @@ +name: Deploy Python Distribution + +on: [push, pull_request] + +jobs: + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install boost + run: | + sudo apt-get update + sudo apt-get install libboost-dev + - name: Install pypa/build + run: | + python -m pip install build --user + - name: Build source tarball + run: | + python -m build --sdist --outdir dist/ + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: sdist + path: ./dist/** + + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: + - ubuntu-latest + - macos-13 # intel + - macos-14 # apple silicon + steps: + - uses: actions/checkout@v4 + - name: Build wheels + uses: pypa/cibuildwheel@v2.22.0 + env: + CIBW_SKIP: pp* *musllinux* cp36-* cp37-* + CIBW_BEFORE_BUILD_LINUX: yum -y install boost-devel + CIBW_ARCHS_LINUX: "x86_64" + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_BEFORE_BUILD_MACOS: brew install boost # should be ok as we only use headers + MACOSX_DEPLOYMENT_TARGET: 10.15 + with: + output-dir: dist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./dist/*.whl + + test_wheels: + name: Test wheel on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + needs: [build_wheels] + strategy: + matrix: + os: + - ubuntu-latest + - macos-13 # intel + - macos-14 # apple silicon + steps: + - name: Download wheel + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - name: Test wheel + run: | + python -m venv .venv + . .venv/bin/activate + python -m pip install --find-links ./dist serialbox4py + python -c "from serialbox import *" + + # publish-pypi: + # name: Publish Python distribution to pypi.org + # runs-on: ubuntu-latest + # needs: [build_wheels, build_sdist] + # if: ${{ github.event_name == 'workflow-dispatch' }} + # environment: + # name: pypi + # url: https://pypi.org/project/serialbox4py/ + # permissions: + # id-token: write + # steps: + # - name: Download wheel + # uses: actions/download-artifact@v4 + # with: + # path: dist + # merge-multiple: true + # - name: Publish distribution to PyPI + # uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # repository-url: https://pypi.org/legacy/ + + publish-test-pypi: + name: Publish Python distribution to test.pypi.org + runs-on: ubuntu-latest + needs: [build_wheels, build_sdist] + if: ${{ github.event_name == 'workflow_dispatch' }} # TODO: once working, enable line below + # if: ${{ github.event_name == 'release' }} # triggered by releasing on github, test first before manually triggering the deployment to PyPI (see release documentation) + environment: + name: testpypi + url: https://test.pypi.org/project/serialbox4py/ + permissions: + id-token: write + steps: + - name: Download wheel + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - name: Publish distribution to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/CMakeLists.txt b/CMakeLists.txt index d652fd39..6cdac7d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,9 @@ set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/install" CACHE PATH "CMake cmake_policy(SET CMP0048 NEW) cmake_minimum_required(VERSION 3.12.0) +# note: version should not be changed manually, use bump-my-version instead: +# install: pip install bump-my-version +# example: bump-my-version bump patch project(Serialbox LANGUAGES C CXX VERSION 2.6.2) set(CMAKE_CXX_EXTENSIONS OFF) @@ -142,8 +145,15 @@ if(SERIALBOX_BUILD_SHARED) # The RPATH to be used when installing, but only if it's not a system directory list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) if("${isSystemDir}" STREQUAL "-1") - SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") endif("${isSystemDir}" STREQUAL "-1") + if(SKBUILD_PROJECT_NAME) + if(APPLE) + set(CMAKE_INSTALL_RPATH "@loader_path") + else() + set(CMAKE_INSTALL_RPATH "$ORIGIN") + endif() + endif() endif() if(NOT(SERIALBOX_LOGGING)) diff --git a/cmake/modules/SerialboxInstallTargets.cmake b/cmake/modules/SerialboxInstallTargets.cmake index 397ae745..79bd0eaa 100644 --- a/cmake/modules/SerialboxInstallTargets.cmake +++ b/cmake/modules/SerialboxInstallTargets.cmake @@ -21,11 +21,19 @@ function( serialbox_install_targets ) foreach( target ${target_list} ) if( TARGET ${target} ) - install(TARGETS ${target} - EXPORT SerialboxTargets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - ) + if(SKBUILD_PROJECT_NAME) + install(TARGETS ${target} + EXPORT SerialboxTargets + LIBRARY DESTINATION . + ARCHIVE DESTINATION . + ) + else() + install(TARGETS ${target} + EXPORT SerialboxTargets + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) + endif() export( TARGETS ${target} APPEND FILE ${PROJECT_BINARY_DIR}/SerialboxTargets.cmake NAMESPACE Serialbox:: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d09343a6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,67 @@ +[build-system] +build-backend = 'scikit_build_core.build' +requires = [ + 'cmake>=3.25', + 'numpy >= 1.23', + 'scikit-build-core>=0.10.7', + 'wheel>=0.45.1', +] + +[project] +dependencies = [ + 'numpy>=1.23', + 'packaging>=20.0' +] + +description = 'Serialbox - Serialization Library for C, C++, Fortran and Python' +name = 'serialbox4py' +# note: version should not be changed manually, use bump-my-version instead: +# install: pip install bump-my-version +# example: bump-my-version bump patch +version = '2.6.2' +license = {text = "BSD-3 License"} +readme = {file = 'README.md', content-type = 'text/markdown'} +authors = [{email = 'gridtools@cscs.ch'}, {name = 'ETH Zurich'}] +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: POSIX', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: Implementation :: CPython', + 'Topic :: File Formats', + 'Topic :: Scientific/Engineering :: Atmospheric Science', + 'Topic :: Scientific/Engineering :: Physics', + 'Topic :: Software Development :: Testing', + 'Topic :: System :: Archiving' +] + +[project.urls] +repository = 'https://github.com/GridTools/serialbox' + +[project.optional-dependencies] +test = ['pytest', 'pytest-cache'] + +[tool.scikit-build] +minimum-version = '0.5' +cmake.minimum-version = '3.25' +cmake.verbose = true +cmake.source-dir = "." +cmake.build-type = "Release" +cmake.args = [ + "-DSERIALBOX_ENABLE_FORTRAN=false", + "-DSERIALBOX_ENABLE_SDB=false", + "-DSERIALBOX_ASYNC_API=false" +] +wheel.expand-macos-universal-tags = true +wheel.install-dir = "serialbox" +wheel.packages = [] +wheel.license-files = [] diff --git a/src/serialbox-python/CMakeLists.txt b/src/serialbox-python/CMakeLists.txt index b8b2f3cf..b458bf40 100644 --- a/src/serialbox-python/CMakeLists.txt +++ b/src/serialbox-python/CMakeLists.txt @@ -52,11 +52,18 @@ if(SERIALBOX_ENABLE_PYTHON) add_dependencies(SerialboxCShared SerialboxPython) # Install python source - install(DIRECTORY ${PROJECT_SOURCE_DIR}/src/serialbox-python/serialbox/ + if(SKBUILD_PROJECT_NAME) + install(DIRECTORY ${PROJECT_SOURCE_DIR}/src/serialbox-python/serialbox/ + DESTINATION . + FILES_MATCHING PATTERN "*.py" + PATTERN "*__pycache__*" EXCLUDE) + else() + install(DIRECTORY ${PROJECT_SOURCE_DIR}/src/serialbox-python/serialbox/ DESTINATION python/serialbox FILES_MATCHING PATTERN "*.py" PATTERN "*__pycache__*" EXCLUDE) - + endif() + install(CODE " EXECUTE_PROCESS(COMMAND ln -sf diff --git a/src/serialbox-python/MANIFEST.in b/src/serialbox-python/MANIFEST.in deleted file mode 100644 index e9f2109a..00000000 --- a/src/serialbox-python/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include *.dylib \ No newline at end of file diff --git a/src/serialbox-python/pyproject.toml b/src/serialbox-python/pyproject.toml deleted file mode 100644 index 113d2a1b..00000000 --- a/src/serialbox-python/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools", "setuptools-scm[toml]", "wheel", "cmake"] -build-backend = "setuptools.build_meta" diff --git a/src/serialbox-python/serialbox/__init__.py b/src/serialbox-python/serialbox/__init__.py index 94eee68b..37ad62dc 100644 --- a/src/serialbox-python/serialbox/__init__.py +++ b/src/serialbox-python/serialbox/__init__.py @@ -4,7 +4,7 @@ ## ## S E R I A L B O X ## -## This file is distributed under terms of BSD license. +## This file is distributed under terms of BSD license. ## See LICENSE.txt for more information. ## ##===------------------------------------------------------------------------------------------===## @@ -15,8 +15,13 @@ """'Serialbox Python Interface'""" -__versioninfo__ = (2, 2, 0) -__version__ = '.'.join(str(v) for v in __versioninfo__) +from packaging import version as pkg_version + +# note: version should not be changed manually, use bump-my-version instead: +# install: pip install bump-my-version +# example: bump-my-version bump patch +__version__ = "2.6.2" +__versioninfo__ = pkg_version.parse(__version__) # # Check python version @@ -31,6 +36,7 @@ # try: import numpy + del numpy except ImportError: raise Exception("Serialbox requires numpy") @@ -49,6 +55,17 @@ from .archive import Archive from .slice import Slice -__all__ = ['Config', 'TypeID', 'SerialboxError', 'Logging', 'Serializer', 'Savepoint', - 'SavepointCollection', 'MetainfoMap', 'FieldMetainfo', 'OpenModeKind', 'Archive', - 'Slice'] +__all__ = [ + "Config", + "TypeID", + "SerialboxError", + "Logging", + "Serializer", + "Savepoint", + "SavepointCollection", + "MetainfoMap", + "FieldMetainfo", + "OpenModeKind", + "Archive", + "Slice", +] diff --git a/src/serialbox-python/setup.py b/src/serialbox-python/setup.py deleted file mode 100644 index 4550252d..00000000 --- a/src/serialbox-python/setup.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- -# NOTE: Serialbox does not support Windows. -# Windows instructions in this file are inherited from the template -# https://github.com/pybind/cmake_example/blob/master/setup.py. -import re -import subprocess -import sys - -import os -from setuptools import setup, Extension, find_packages -from setuptools.command.build_ext import build_ext - -DIR = os.path.abspath(os.path.dirname(__file__)) - -# Convert distutils Windows platform specifiers to CMake -A arguments -PLAT_TO_CMAKE = { - "win32": "Win32", - "win-amd64": "x64", - "win-arm32": "ARM", - "win-arm64": "ARM64", -} - - -# A CMakeExtension needs a sourcedir instead of a file list. -# The name must be the _single_ output extension from the CMake build. -# If you need multiple extensions, see scikit-build. -class CMakeExtension(Extension): - def __init__(self, name, sourcedir=""): - Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) - - -class CMakeBuild(build_ext): - def build_extension(self, ext): - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - origin = self._get_origin() - # required for auto-detection & inclusion of auxiliary "native" libs - if not extdir.endswith(os.path.sep): - extdir += os.path.sep - - debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug - cfg = "Debug" if debug else "Release" - - # CMake lets you override the generator - we need to check this. - # Can be set with Conda-Build, for example. - cmake_generator = os.environ.get("CMAKE_GENERATOR", "") - - # Set Python3_EXECUTABLE instead if you use PYBIND11_FINDPYTHON - # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code - # from Python. - cmake_args = [ - "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}".format(extdir), - "-DPython3_EXECUTABLE={}".format(sys.executable), - "-DCMAKE_BUILD_TYPE={}".format(cfg), # not used on MSVC, but no harm - "-DSERIALBOX_ENABLE_FORTRAN=false", - "-DCMAKE_BUILD_RPATH={}".format(origin), - "-DSERIALBOX_ENABLE_SDB=false", - "-DSERIALBOX_ASYNC_API=false", - ] - build_args = [] - # Adding CMake arguments set as environment variable - # (needed e.g. to build for ARM OSx on conda-forge) - if "CMAKE_ARGS" in os.environ: - cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] - - # In this example, we pass in the version to C++. You might not need to. - cmake_args += [ - "-DEXAMPLE_VERSION_INFO={}".format(self.distribution.get_version()) - ] - if sys.platform == "darwin": - from distutils import sysconfig - - vars = sysconfig.get_config_vars() - vars["SO"] = ".dylib" - vars["EXT_SUFFIX"] = ".dylib" - - if self.compiler.compiler_type != "msvc": - # Using Ninja-build since it a) is available as a wheel and b) - # multithreads automatically. MSVC would require all variables be - # exported for Ninja to pick it up, which is a little tricky to do. - # Users can override the generator with CMAKE_GENERATOR in CMake - # 3.15+. - if not cmake_generator: - try: - import ninja # noqa: F401 - - cmake_args += ["-GNinja"] - except ImportError: - pass - - else: - # Single config generators are handled "normally" - single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) - - # CMake allows an arch-in-generator style for backward compatibility - contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) - - # Specify the arch if using MSVC generator, but only if it doesn't - # contain a backward-compatibility arch spec already in the - # generator name. - if not single_config and not contains_arch: - cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] - - # Multi-config generators have a different way to specify configs - if not single_config: - cmake_args += [ - "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir) - ] - build_args += ["--config", cfg] - - if sys.platform.startswith("darwin"): - # Cross-compile support for macOS - respect ARCHFLAGS if set - archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) - if archs: - cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] - - # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level - # across all generators. - if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: - # self.parallel is a Python 3 only way to set parallel jobs by hand - # using -j in the build_ext call, not supported by pip or PyPA-build. - if hasattr(self, "parallel") and self.parallel: - # CMake 3.12+ only. - build_args += ["-j{}".format(self.parallel)] - - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) - - subprocess.check_call( - ["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp - ) - subprocess.check_call( - ["cmake", "--build", "."] + build_args, cwd=self.build_temp - ) - - def _get_origin(self): - origin = "" - if sys.platform == "linux": - origin = "${ORIGIN}" - elif sys.platform == "darwin": - origin = "@loader_path" - return origin - - -# The information here can also be placed in setup.cfg - better separation of -# logic and declaration, and simpler if you include description/version in a file. -setup( - name="serialbox", - version="2.6.2", - author="Serialbox Developers", - packages=find_packages(), - install_requires=["numpy"], - ext_modules=[ - CMakeExtension("libSerialboxC", sourcedir=os.path.join(DIR, "../../")) - ], - cmdclass={"build_ext": CMakeBuild}, - zip_safe=False, -)