From 93d92dfedf09d2949c10f4b950da1bfc7d302161 Mon Sep 17 00:00:00 2001 From: Ben Mather Date: Tue, 5 Dec 2023 21:26:21 +1100 Subject: [PATCH] Use meson to build stripy (#109) * first attempt at a meson build * added all fortran modules to meson.build * update pyproject.toml with meson build-backend * link fortranobject.c to build f sources * missing tag - probably does nothing * meson default is to install sources as a module (not submodule) * make pyproject.toml a bit more verbose * getting closer using subdir * don't want f2py to generate module files since these are already generated in the .pyf headers * no _ssrfpack_f2pywrappers.f * build PYPI wheels using meson * build with python 3.12 * update CI * update manylinux build * try getting libquadmath as a dependency * try installing using a fortran environment on windows * increment gcc to version 12 * ooops - typo * try again... * try another test using micromamba * try now * try now.. * remove micromamba - more trouble than its worth * maybe gfortran v11 will fix windows? * try intel compiler * update windows fortran * wheelhouse actions * project requires meson instead of meson-python * revert to mesonpy * update wheelhouse platforms * update wheelhouse to use fortran and mesa images * meson build * add python dependencies * add python meson dependency * try initialising pip install from cibuildwheel env * update pip index-url * try setting up a build machine with ninja * move build env to another yaml * move build env to a separate directory * typo * restrict to python 3.7 or greater * run pytest * run pytest in correct folder * not working on windows... * install ninja * remove ninja installation on linux * try restricting to 64-bit builds * -lquadmath * replace pkg_resources with importlib.resources * do not build pypy wheels * restrict env variable to windows * install gcc on mac * maybe these platform specific arguments are no good * revert platform in meson.build * revert -lquadmath in meson.build * try reinstalling gcc on mac * remove scipy dependency * upload to PYPI on release * just test on ubuntu with different python versions --------- Co-authored-by: Ben Mather --- .github/workflows/CI.yml | 299 +------------------ .github/workflows/pypi.yml | 77 +++++ .github/workflows/setup-build-env/action.yml | 24 ++ meson.build | 108 +++++++ pyproject.toml | 58 +++- setup.py | 2 +- src/stripack.pyf | 2 +- stripy/documentation.py | 33 +- stripy/tests/test_0_imports.py | 5 - 9 files changed, 296 insertions(+), 312 deletions(-) create mode 100644 .github/workflows/pypi.yml create mode 100644 .github/workflows/setup-build-env/action.yml create mode 100644 meson.build diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a798eba3..f1c9a3ab 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,4 @@ -name: CI +name: Run tests on: push: @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v2 @@ -37,298 +37,3 @@ jobs: run: | pip install pytest pytest -v - - test_macos: - runs-on: macos-latest - strategy: - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - - steps: - - uses: actions/checkout@v2 - - uses: awvwgk/setup-fortran@main - id: setup-fortran - with: - compiler: gcc - version: 11 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - pip install numpy - pip install scipy - pip install Cython - -# - name: Provide gfortran -# run: | -# ln -s /usr/local/bin/gfortran-10 /usr/local/bin/gfortran - - - name: Install - run: | - pip install . - - - name: Run tests - run: | - pip install pytest - pytest -v - - test_windows: - runs-on: windows-latest - strategy: - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - pip install numpy - pip install scipy - pip install Cython - - - name: Install - run: | - pip install . - - - name: Run tests - run: | - pip install pytest - pytest -v - - - conda_build_ubuntu: - name: Conda Build (Python ${{matrix.python-version}}) - needs: [test_ubuntu] - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - steps: - - uses: actions/checkout@v2 - - uses: conda-incubator/setup-miniconda@v2 - with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - - - name: Config Conda - shell: bash -l {0} - run: | - conda install --channel conda-forge conda-build anaconda-client conda-verify - conda config --add channels conda-forge - conda config --add channels underworldcode - conda config --set anaconda_upload no - - - name: Config Conda For Upload - if: github.event_name == 'release' - shell: bash -l {0} - run: conda config --set anaconda_upload yes - - - name: Upload new Packages - if: github.event_name == 'release' - shell: bash -l {0} - run: | - anaconda login --hostname github-actions-${{ matrix.os }}-$RANDOM --username ${{ secrets.ANACONDA_USERNAME }} --password ${{ secrets.ANACONDA_PASSWORD }} - conda-build --channel conda-forge --user geo-down-under conda - anaconda logout - - conda_build_macos: - name: Conda Build (Python ${{matrix.python-version}}) - needs: [test_macos] - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - steps: - - uses: actions/checkout@v2 - - uses: conda-incubator/setup-miniconda@v2 - with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - - - name: Config Conda - shell: bash -l {0} - run: | - conda install --channel conda-forge conda-build anaconda-client conda-verify - conda config --add channels conda-forge - conda config --add channels underworldcode - conda config --set anaconda_upload no - - - name: Config Conda For Upload - if: github.event_name == 'release' - shell: bash -l {0} - run: conda config --set anaconda_upload yes - - - name: Upload new Packages - if: github.event_name == 'release' - shell: bash -l {0} - run: | - anaconda login --hostname github-actions-${{ matrix.os }}-$RANDOM --username ${{ secrets.ANACONDA_USERNAME }} --password ${{ secrets.ANACONDA_PASSWORD }} - conda-build --channel conda-forge --user geo-down-under conda - anaconda logout - - conda_build_windows: - name: Conda Build (Python ${{matrix.python-version}} ${{ matrix.os }}) - needs: [test_windows] - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - steps: - - uses: actions/checkout@v2 - - uses: conda-incubator/setup-miniconda@v2 - with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - - - name: Config Conda - shell: bash -l {0} - run: | - conda install --channel conda-forge conda-build anaconda-client conda-verify - conda config --add channels conda-forge - conda config --add channels underworldcode - conda config --set anaconda_upload no - - - name: Config Conda For Upload - if: github.event_name == 'release' - shell: bash -l {0} - run: conda config --set anaconda_upload yes - - - name: Upload new Packages - if: github.event_name == 'release' - shell: bash -l {0} - run: | - anaconda login --hostname github-actions-${{ matrix.os }}-$RANDOM --username ${{ secrets.ANACONDA_USERNAME }} --password ${{ secrets.ANACONDA_PASSWORD }} - conda-build --channel conda-forge --user geo-down-under conda - anaconda logout - - pypi_windows: - runs-on: windows_latest - needs: test_windows - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - strategy: - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - pip install numpy - pip install scipy - pip install Cython - - - name: Provide gfortran - run: | - ln -s /usr/local/bin/gfortran-10 /usr/local/bin/gfortran - - - name: Package for Pypi - run: | - python setup.py sdist bdist_wheel - - - name: Upload to Pypi - run: | - python -m twine upload dist/* -r pypi --skip-existing - - pypi_macos: - runs-on: macos-latest - needs: test_macos - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - strategy: - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] - - steps: - - uses: actions/checkout@v2 - - uses: awvwgk/setup-fortran@main - id: setup-fortran - with: - compiler: gcc - version: 11 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - pip install numpy - pip install scipy - pip install Cython - -# - name: Provide gfortran -# run: | -# ln -s /usr/local/bin/gfortran-10 /usr/local/bin/gfortran - - - name: Package for Pypi - run: | - python setup.py sdist bdist_wheel - - - name: Upload to Pypi - run: | - python -m twine upload dist/* -r pypi --skip-existing - - - manylinux: - name: Build manylinux package - needs: test_ubuntu - runs-on: ubuntu-latest - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools twine wheel - pip install numpy - pip install scipy - pip install Cython - - name: Build manylinux Python wheels - uses: RalfG/python-wheels-manylinux-build@v0.4.2-manylinux2014_x86_64 - with: - python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311' - pip-wheel-args: '--no-deps' - build-requirements: 'cython numpy' - - name: Publish wheels to PyPI - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: | - twine upload *-manylinux*.whl --skip-existing diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 00000000..07349573 --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,77 @@ +name: Build wheels + +on: [push] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.2 + env: + # Disable building PyPy wheels on all platforms + CIBW_SKIP: 'pp*' + CIBW_ENVIRONMENT_WINDOWS: "CFLAGS='-lquadmath'" + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.7" + CIBW_ARCHS: 'auto64' + CIBW_BEFORE_BUILD_WINDOWS: "choco install ninja" + CIBW_BEFORE_BUILD_MACOS: "brew install ninja gcc && brew reinstall gcc" + # CIBW_BEFORE_BUILD_LINUX: "apt-get install -y ninja-build" + CIBW_BEFORE_BUILD: "pip install numpy meson-python ninja setuptools build" + CIBW_TEST_REQUIRES: pytest + CIBW_TEST_COMMAND: "pytest {project}/stripy/tests" + # ... + # with: + # package-dir: . + # output-dir: wheelhouse + # config-file: "{package}/pyproject.toml" + + - uses: actions/upload-artifact@v3 + with: + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v3 + with: + path: dist/*.tar.gz + + upload_pypi: + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + if: github.event_name == 'release' && github.event.action == 'published' + # or, alternatively, upload to PyPI on every tag starting with 'v' (remove on: release above to use this) + # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v3 + with: + # unpacks default artifact into dist/ + # if `name: artifact` is omitted, the action will create extra parent dir + name: artifact + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} + # To test: repository-url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/.github/workflows/setup-build-env/action.yml b/.github/workflows/setup-build-env/action.yml new file mode 100644 index 00000000..6f1f4b4a --- /dev/null +++ b/.github/workflows/setup-build-env/action.yml @@ -0,0 +1,24 @@ +name: Setup build environment +description: "Install ninja and a fortran compiler" + +# It's assumed that `actions/setup-python` is run before this one. + +runs: + using: "composite" + steps: + - uses: awvwgk/setup-fortran@v1 + + - run: pip install --upgrade pip + shell: bash + + - if: runner.os == 'Linux' + run: sudo apt-get install -y ninja-build + shell: bash + + - if: runner.os == 'macOS' + run: brew install ninja + shell: bash + + - if: runner.os == 'Windows' + run: choco install ninja + shell: bash \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..bad6db72 --- /dev/null +++ b/meson.build @@ -0,0 +1,108 @@ +project('stripy', 'c') + +name = 'stripy' + +add_languages('fortran') + +platform = host_machine.system() +if platform == 'windows' + add_project_link_arguments('-static', language: ['fortran', 'c']) +elif platform == 'darwin' + add_project_link_arguments('-Wl,-rpath, "@loader_path"', language: ['fortran', 'c']) +else + add_project_link_arguments('-Wl,-rpath,"$ORIGIN"', language: ['fortran', 'c']) +endif + +py_mod = import('python') +py = py_mod.find_installation(pure: false) +py_dep = py.dependency() + +incdir_numpy = run_command(py, + ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'], + check : true +).stdout().strip() + +incdir_f2py = run_command(py, + ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'], + check : true +).stdout().strip() + +inc_np = include_directories(incdir_numpy, incdir_f2py) + +install_subdir(name, install_dir: py.get_install_dir() / name, strip_directory: true) + +tripack_source = custom_target('_tripackmodule.c', + input : ['src/tripack.pyf'], # .f so no F90 wrappers + output : ['_tripackmodule.c', '_tripack-f2pywrappers.f'], + command : [py, '-m', 'numpy.f2py', '@INPUT@'] +) + +stripack_source = custom_target('_stripackmodule.c', + input : ['src/stripack.pyf'], # .f so no F90 wrappers + output : ['_stripackmodule.c', '_stripack-f2pywrappers.f'], + command : [py, '-m', 'numpy.f2py', '@INPUT@'] +) + +srfpack_source = custom_target('_srfpackmodule.c', + input : ['src/srfpack.pyf'], # .f so no F90 wrappers + output : ['_srfpackmodule.c'], + command : [py, '-m', 'numpy.f2py', '@INPUT@'] +) + +ssrfpack_source = custom_target('_ssrfpackmodule.c', + input : ['src/ssrfpack.pyf'], # .f so no F90 wrappers + output : ['_ssrfpackmodule.c', '_ssrfpack-f2pywrappers.f'], + command : [py, '-m', 'numpy.f2py', '@INPUT@'] +) + +fortran_source = custom_target('_fortranmodule.c', + input : ['src/stripyf.pyf'], # .f so no F90 wrappers + output : ['_fortranmodule.c', '_fortran-f2pywrappers.f'], + command : [py, '-m', 'numpy.f2py', '@INPUT@'] +) + + +py.extension_module('_tripack', + ['src/tripack.f90', tripack_source], + incdir_f2py / 'fortranobject.c', + subdir: 'stripy', + include_directories: inc_np, + dependencies: py_dep, + install: true, +) + +py.extension_module('_stripack', + ['src/stripack.f90', stripack_source], + incdir_f2py / 'fortranobject.c', + subdir: 'stripy', + include_directories: inc_np, + dependencies: py_dep, + install: true, +) + +py.extension_module('_srfpack', + ['src/srfpack.f', srfpack_source], + incdir_f2py / 'fortranobject.c', + subdir: 'stripy', + include_directories: inc_np, + dependencies: py_dep, + install: true, +) + +py.extension_module('_ssrfpack', + ['src/ssrfpack.f', ssrfpack_source], + incdir_f2py / 'fortranobject.c', + subdir: 'stripy', + include_directories: inc_np, + dependencies: py_dep, + install: true, +) + +py.extension_module('_fortran', + ['src/stripyf.f90', fortran_source], + incdir_f2py / 'fortranobject.c', + subdir: 'stripy', + include_directories: inc_np, + dependencies: py_dep, + install: true, +) diff --git a/pyproject.toml b/pyproject.toml index 280647a8..3e71d5eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,57 @@ [build-system] -build-backend = "setuptools.build_meta" -requires = ["setuptools", "numpy", "pip"] +build-backend = "mesonpy" +requires = [ + "numpy", + "pip", + "meson-python>=0.15.0", + "setuptools>=61.0", + "build", +] + +[project] +name = "stripy" +version = "2.3" +description = "Python interface to TRIPACK and STRIPACK fortran code for triangulation/interpolation in Cartesian coordinates and on a sphere" +readme = "README.md" +authors = [ + {name = "Louis Moresi", email = "louis.moresi@anu.edu.au"}, + {name = "Ben Mather", email = "ben.mather@sydney.edu.au"}, +] +maintainers = [ + {name = "Louis Moresi", email = "louis.moresi@anu.edu.au"}, + {name = "Ben Mather", email = "ben.mather@sydney.edu.au"}, +] +license = { file = "COPYING.LESSER" } +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "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", +] +keywords = ["triangulation", "sphere", "interpolation", "mesh"] +dependencies = [ + "numpy>=1.16.0", +] + +[tool.setuptools] +packages = [ + "stripy", + "stripy._tripack", + "stripy._stripack", + "stripy._srfpack", + "stripy._ssrfpack", +] + +# [tool.setuptools.dynamic] +# version = {attr = "stripy.__version__"} + +[project.optional-dependencies] +dev = ["pip-tools", "pytest", "scipy>=1.0.0"] + +[project.urls] +Homepage = "https://github.com/underworldcode/stripy" diff --git a/setup.py b/setup.py index 28764078..e098e806 100755 --- a/setup.py +++ b/setup.py @@ -108,7 +108,7 @@ def _minimal_ext_cmd(cmd): long_description = long_description, long_description_content_type='text/markdown', ext_modules = [ext1, ext2, ext3, ext4, ext5], - install_requires = ['numpy>=1.16.0', 'scipy>=1.0.0'], + install_requires = ['numpy>=1.16.0'], packages = ['stripy'], package_data = {'stripy': ['Notebooks/*ipynb', # Worked Examples is not currently used 'Notebooks/CartesianTriangulations/*ipynb', diff --git a/src/stripack.pyf b/src/stripack.pyf index 054e3fbf..8f544361 100644 --- a/src/stripack.pyf +++ b/src/stripack.pyf @@ -409,7 +409,7 @@ python module _stripack ! in ! real(kind=8) :: pw ! real(kind=8) dimension(3) :: pg ! end subroutine wval - subroutine interp_n(npts,nptso,order,olats,olons,x,y,z,data,lst,lptr,lend,odata,edata,ierr) + subroutine interp_n(npts,nptso,order,olats,olons,x,y,z,data,lst,lptr,lend,odata,edata,ierr) ! in :_stripack:stripack.f90 integer( kind = 4 ), depend(x), intent(hide) :: npts=len(x) integer( kind = 4 ), depend(olats), intent(hide) :: nptso=len(olats) integer( kind = 4 ), intent(in) :: order diff --git a/stripy/documentation.py b/stripy/documentation.py index 9987a979..abb6c161 100644 --- a/stripy/documentation.py +++ b/stripy/documentation.py @@ -16,8 +16,20 @@ along with Stripy. If not, see . """ -import pkg_resources as _pkg_resources -from distutils import dir_util as _dir_util +import os as _os +import shutil as _shutil +from importlib import resources as _importlib_resources +from platform import python_version as _python_version + + +def _copytree(src, dst, symlinks=False, ignore=None): + for item in _os.listdir(src): + s = _os.path.join(src, item) + d = _os.path.join(dst, item) + if _os.path.isdir(s): + _shutil.copytree(s, d, symlinks, ignore) + else: + _shutil.copy2(s, d) def install_documentation(path="./Stripy-Notebooks"): @@ -43,9 +55,18 @@ def install_documentation(path="./Stripy-Notebooks"): """ ## Question - overwrite or not ? shutils fails if directory exists. - - Notebooks_Path = _pkg_resources.resource_filename('stripy', 'Notebooks') - - ct = _dir_util.copy_tree(Notebooks_Path, path,preserve_mode=1, preserve_times=1, preserve_symlinks=1, update=0, verbose=1, dry_run=0) + version = _python_version().split('.') + python_more_than_v39 = int(version[0]) >=3 and int(version[1]) >= 9 + + if python_more_than_v39: + ref = _importlib_resources.files('stripy') / 'Notebooks' + with _importlib_resources.as_file(ref) as path: + _copytree(Notebooks_Path, path) + + else: + import pkg_resources as _pkg_resources + Notebooks_Path = _pkg_resources.resource_filename('stripy', 'Notebooks') + _copytree(Notebooks_Path, path) + # ct = _dir_util.copy_tree(Notebooks_Path, path, preserve_mode=1, preserve_times=1, preserve_symlinks=1, update=0, verbose=1, dry_run=0) return diff --git a/stripy/tests/test_0_imports.py b/stripy/tests/test_0_imports.py index b4aac675..224b4f00 100644 --- a/stripy/tests/test_0_imports.py +++ b/stripy/tests/test_0_imports.py @@ -6,11 +6,6 @@ def test_numpy_import(): import numpy return -def test_scipy_import(): - import scipy - print("\t\t You have scipy version {}".format(scipy.__version__)) - - def test_stripy_modules(): import stripy from stripy import documentation