diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 8cce8eab9..4331975cd 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -61,8 +61,7 @@ target_include_directories(_mir PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_BINA target_link_libraries(_mir PRIVATE mir) target_compile_definitions(_mir PRIVATE NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) -# _mir.so installs at site-packages root (not inside mir/) — matches setup.py Extension("_mir") -install(TARGETS _mir DESTINATION ${MIR_PYTHON_INSTALL_DIR} COMPONENT python) +install(TARGETS _mir DESTINATION ${MIR_PYTHON_INSTALL_DIR}/mir COMPONENT python) install(FILES mir/src/mir/__init__.py mir/src/mir/griddef.py diff --git a/python/mir/build_chain.sh b/python/mir/build_chain.sh deleted file mode 100755 index 6dd7e838c..000000000 --- a/python/mir/build_chain.sh +++ /dev/null @@ -1,60 +0,0 @@ -# (C) Copyright 1996- 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. - -# building & publishing of a wheel with mir-python only, with requirements for mirlib & eckitlib etc wheels -# assumed to be executed *inside* wheelmaker docker image, or at least: -# - have access to the setup_utils python package -# - uv installed -# - manylinux-compatible compilation stack -# - env var PYVERSION, eg 3.11 - -# TODO unify with eckit/python/eckit/build_chain.sh, by moving to ci-utils/wheelmaker - -set -euo pipefail - -# prepare python -rm -rf .venv -uv venv --python python$PYVERSION .venv -source .venv/bin/activate -uv pip install --upgrade -r ./requirements.txt twine build - -TEST_PYPI=${TEST_PYPI:-no} -if [ "$TEST_PYPI" = "yes" ] ; then - EXTRA_PIP="--no-cache --index-url https://test.pypi.org/simple/" - TARGET="--repository testpypi" -else - EXTRA_PIP="" - TARGET="" -fi - -# mir-python prereqs -# TODO get these from pyproject... -if [ -n "$INSTALL_LOCALLY" ] ; then - # TODO reuse the dep resolver from setup utils - uv pip install --no-cache $INSTALL_LOCALLY/eckitlib* $INSTALL_LOCALLY/eccodeslib* $INSTALL_LOCALLY/atlaslib* $INSTALL_LOCALLY/mirlib* -else - uv pip install --prerelease=allow $EXTRA_PIP mirlib -fi -PRF=".venv/lib/python$PYVERSION/site-packages" -if [ "$(uname)" == "Darwin" ] ; then L="lib" ; else L="lib64" ; fi -export MIR_LIB_DIR="$PRF/eckitlib/$L:$PRF/eccodeslib/$L:$PRF/mirlib/$L" -export MIR_INCLUDE_DIRS="$PRF/eckitlib/include:$PRF/eccodeslib/include:$PRF/mirlib/include" - -# build -rm -rf build dist -PYTHONPATH=/buildscripts python -m build --no-isolation --wheel . - -# test -uv pip install ./dist/* -# pytest tests/ # TODO re-enable after fixed -twine check dist/*whl - -# upload -# NOTE we don't upload because of execution via ci-utils/wheelmaker/buildscripts/multirelease.sh, which uploads on its own -# twine upload --verbose --skip-existing dist/*whl diff --git a/python/mir/pyproject.toml b/python/mir/pyproject.toml index 64f77a01e..fc2c37bcd 100644 --- a/python/mir/pyproject.toml +++ b/python/mir/pyproject.toml @@ -32,8 +32,9 @@ classifiers = [ "Operating System :: MacOS", "Topic :: Scientific/Engineering", ] -dependencies = ["eckit", "findlibs", "numpy", "pyyaml", "scipy"] -dynamic = ["version"] +# NOTE don't list `dependencies` in this file, use setup.py instead +dynamic = ["version", "dependencies"] + [project.scripts] mir-weight-matrix-convert = "mir.tools.weight_matrix_convert:main" @@ -44,3 +45,4 @@ version = {file = "VERSION"} [tool.setuptools.packages.find] where = ["src"] +exclude = ["_mir*"] diff --git a/python/mir/setup.py b/python/mir/setup.py new file mode 100644 index 000000000..639de7dea --- /dev/null +++ b/python/mir/setup.py @@ -0,0 +1,38 @@ +import os +from setuptools import setup +import platform +from wheel.bdist_wheel import bdist_wheel +import sys + +with open('VERSION', 'r') as fVersion: + version = fVersion.readlines()[0].strip() +install_requires = [ + f"mirlib=={version}", + f"eckit=={version}", + "findlibs", + "numpy>=2.0,<3.0", # NOTE may need tighter range in case of ABI issues. Dont forget to keep in sync with pre-compile.sh (or cmake files if numpy install refactored) + "scipy", + "pyyaml", +] + +# NOTE see ci-utils/wheelmaker/buildscripts/setup_utils, we need to get the right abi compat tag +class bdist_wheel_ext(bdist_wheel): + def get_tag(self): + python, abi, plat = bdist_wheel.get_tag(self) + return python, abi, f"manylinux_2_28_{platform.machine()}" + + +ext_kwargs = { + "darwin": {}, + "linux": {"cmdclass": {"bdist_wheel": bdist_wheel_ext}}, +} + +setup( + version=version, + package_data={ + "": ["*.so"], + }, + has_ext_modules=lambda: True, + install_requires=install_requires, + **ext_kwargs[sys.platform], +) diff --git a/python/mir/src/mir/__init__.py b/python/mir/src/mir/__init__.py index fda7c7598..3c0f6126a 100644 --- a/python/mir/src/mir/__init__.py +++ b/python/mir/src/mir/__init__.py @@ -8,16 +8,12 @@ # does it submit to any jurisdiction. -from ctypes import CDLL - -# init section -- ensure libmir.so is loaded, utilizing findlibs instead of relying on rpath import findlibs -m = findlibs.find("mir") -CDLL(m) +findlibs.load("mir") -from _mir import * from eckit.geo import Grid +from mir._mir import * -__version__ = version() +__lib_version__ = version() __git_sha1__ = git_sha1() diff --git a/python/mirlib/buildconfig b/python/mirlib/buildconfig index 010e0421e..82006b58b 100644 --- a/python/mirlib/buildconfig +++ b/python/mirlib/buildconfig @@ -11,6 +11,8 @@ # TODO we duplicate information -- pyproject.toml's `name` and `packages` are derivable from $NAME and must stay consistent NAME="mir" -CMAKE_PARAMS="-Deckit_ROOT=/tmp/mir/prereqs/eckitlib -Deccodes_ROOT=/tmp/mir/prereqs/eccodeslib -Datlas_ROOT=/tmp/mir/prereqs/atlaslib-ecmwf -DENABLE_ECKIT_GEO=1" +_HERE="$(dirname -- "$(readlink -f -- "${BASH_SOURCE[0]}")")" +PYTHON_INSTALL_DIR=$(cd -- "$_HERE/../mir/src" && pwd) # dont use readlink -m here, not macos portable +CMAKE_PARAMS="-Deckit_ROOT=/tmp/mir/prereqs/eckitlib -Deccodes_ROOT=/tmp/mir/prereqs/eccodeslib -Datlas_ROOT=/tmp/mir/prereqs/atlaslib-ecmwf -DENABLE_ECKIT_GEO=1 -DENABLE_PYTHON=1 -DMIR_PYTHON_INSTALL_DIR=$PYTHON_INSTALL_DIR -DPython3_FIND_VIRTUALENV=ONLY" PYPROJECT_DIR="python/mirlib" DEPENDENCIES='["eccodeslib", "eckitlib", "atlaslib-ecmwf"]' diff --git a/python/mirlib/pre-compile.sh b/python/mirlib/pre-compile.sh new file mode 100755 index 000000000..60df41e73 --- /dev/null +++ b/python/mirlib/pre-compile.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# python bindings are built via cmake, as part of the overall compile.sh action +# this requires numpy present -- but currently the cmake files only check +# numpy presence, dont pip install it on its own. Hence we hack the install here + +uv pip install 'numpy>=2.0,<3.0' cython