From d8c97fcde7f675ab88894a24eee7916d18d3558d Mon Sep 17 00:00:00 2001 From: Vojta Tuma Date: Thu, 28 Nov 2024 11:54:24 +0100 Subject: [PATCH] prototype of so-only wheel and cffi interface --- .github/workflows/build-wheel-linux.yml | 91 +++++++++++++++++++++++ .gitignore | 4 + python_interface/README.md | 7 ++ python_interface/pyproject.toml | 8 ++ python_interface/src/pymetkit/__init__.py | 18 +++++ python_wrapper/buildconfig | 14 ++++ python_wrapper/setup.cfg | 5 ++ python_wrapper/setup.py | 2 + 8 files changed, 149 insertions(+) create mode 100644 .github/workflows/build-wheel-linux.yml create mode 100644 python_interface/README.md create mode 100644 python_interface/pyproject.toml create mode 100644 python_interface/src/pymetkit/__init__.py create mode 100644 python_wrapper/buildconfig create mode 100644 python_wrapper/setup.cfg create mode 100644 python_wrapper/setup.py diff --git a/.github/workflows/build-wheel-linux.yml b/.github/workflows/build-wheel-linux.yml new file mode 100644 index 00000000..0dea81b0 --- /dev/null +++ b/.github/workflows/build-wheel-linux.yml @@ -0,0 +1,91 @@ +# (C) Copyright 2024- ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + + +name: Build Python Wheel for Linux + +on: + # Trigger the workflow manually + workflow_dispatch: ~ + + # Allow to be called from another workflow + workflow_call: ~ + + # TODO automation trigger + +jobs: + + build: + + runs-on: [self-hosted, Linux, platform-builder-Rocky-8.6] + # TODO which manylinux do we want to build for? 2014? 2_28? 2_34? Matrix? + container: wheelmaker_2_28:0.1 + + name: Build manylinux_2_28 + + steps: + # TODO which project do we build odc wheel from -- this, or some bundle? Or can we obtain the compiled eckit artifact somehow? + - run: git clone --branch develop --depth=1 https://github.com/ecmwf/eckit.git /src/eckit + - run: /buildscripts/compile.sh ./eckit/python_wrapper/buildconfig + - uses: actions/checkout@v2 + - run: /buildscripts/compile.sh ./metkit/python_wrapper/buildconfig + + ################################################################ + - run: /buildscripts/wheel-linux.sh ./metkit/python_wrapper/buildconfig 3.11 + - uses: actions/upload-artifact@v4 + name: Upload wheel 3.11 + with: + name: wheel-manylinux2_28-3.11 + path: /build/wheel/*.whl + + # TODO other python versions, once the above is correct. + # NOTE if Matrix, then break into (compile & upload) ; (wheel & upload)[matix] steps + + test: + + needs: build + strategy: + fail-fast: false + matrix: + python-version: ["3.11"] # ["3.8", "3.9", "3.10", "3.11", "3.12"] # TODO enable + + name: Test with ${{ matrix.python-version }} + runs-on: [self-hosted, Linux, platform-builder-Rocky-8.6] + container: wheelmaker_2_28:0.1 + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v4 + with: + name: wheel-manylinux2_28-${{ matrix.python-version }} + - run: /buildscripts/test-wheel.sh ${{ matrix.python-version }} + +# TODO enable and test +# deploy: +# +# if: ${{ github.ref_type == 'tag' || github.event_name == 'release' }} +# needs: [test, build] +# strategy: +# fail-fast: false +# matrix: +# python-version: ["3.11"] # ["3.8", "3.9", "3.10", "3.11", "3.12"] # TODO enable +# +# name: Deploy wheel ${{ matrix.python-version }} +# runs-on: [self-hosted, Linux, platform-builder-Rocky-8.6] +# container: wheelmaker_2_28:0.1 +# steps: +# - run: mkdir artifact-${{ matrix.python-version }} +# - uses: actions/checkout@v2 +# - uses: actions/download-artifact@v4 +# with: +# name: wheel-manylinux2_28-${{ matrix.python-version }} +# path: artifact-${{ matrix.python-version }} +# - run: | +# /buildsripts/upload-twine.sh ${{ matrix.python-version }} +# env: +# TWINE_USERNAME: __token__ +# TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore index e4cea70b..350c3457 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ doc/latex .*un~ *.ccls-cache share +__pycache__/ +*.pyc +*.egg-info/ + diff --git a/python_interface/README.md b/python_interface/README.md new file mode 100644 index 00000000..e349a5da --- /dev/null +++ b/python_interface/README.md @@ -0,0 +1,7 @@ +This is just a placeholder for proper python interface of metkit -- for now we just showcase `findlibs`-based finding of `.so`, and loading via cffi. + +To demonstrate functionality, install `eckit` and `metkit` binary wheels in your venv, and then you can `pip install -e . && python -c 'import metkit; metkit.version()`. + +To be done: +1. proper cffi setup +2. wheel building (note this is **not** a binary wheel! That happens in `../python_wrapper`, and contains no python code) diff --git a/python_interface/pyproject.toml b/python_interface/pyproject.toml new file mode 100644 index 00000000..c1aa4aff --- /dev/null +++ b/python_interface/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = ["setuptools >= 61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "pymetkit" +version = "0.0.0" +dependencies = ["cffi", "findlibs"] diff --git a/python_interface/src/pymetkit/__init__.py b/python_interface/src/pymetkit/__init__.py new file mode 100644 index 00000000..47439340 --- /dev/null +++ b/python_interface/src/pymetkit/__init__.py @@ -0,0 +1,18 @@ +import cffi +import findlibs + +_lib = None + +# TODO expose the full functionality + +def _get_lib(): + global _lib + if _lib is None: + ffi = cffi.FFI() + ffi.cdef("unsigned int metkit_version_int();") + loc = findlibs.find("metkit", "metkitlibs") + _lib = ffi.dlopen(loc) + return _lib + +def metkit_version_int() -> int: + return _get_lib().metkit_version_int() diff --git a/python_wrapper/buildconfig b/python_wrapper/buildconfig new file mode 100644 index 00000000..e4fe1183 --- /dev/null +++ b/python_wrapper/buildconfig @@ -0,0 +1,14 @@ +# (C) Copyright 2024- ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + +# to be source'd by wheelmaker's compile.sh *and* wheel-linux.sh +# NOTE replace the whole thing with pyproject.toml? Less powerful, and quaint to use for sourcing ecbuild invocation + +NAME="metkit" +CMAKE_PARAMS="-DECKIT_PATH=/target/eckit" +PYPROJECT_DIR="python_wrapper" diff --git a/python_wrapper/setup.cfg b/python_wrapper/setup.cfg new file mode 100644 index 00000000..7d273367 --- /dev/null +++ b/python_wrapper/setup.cfg @@ -0,0 +1,5 @@ +[metadata] +description = "metkit" +long_description = file: README.md +long_description_content_type = text/markdown +author = file: AUTHORS diff --git a/python_wrapper/setup.py b/python_wrapper/setup.py new file mode 100644 index 00000000..c64ad6d7 --- /dev/null +++ b/python_wrapper/setup.py @@ -0,0 +1,2 @@ +from setup_utils import plain_setup +plain_setup()