Skip to content

Commit

Permalink
Support Python packaging (GridTools#1720)
Browse files Browse the repository at this point in the history
  • Loading branch information
DropD authored and havogt committed Dec 12, 2022
1 parent d88bb2c commit 38a5033
Show file tree
Hide file tree
Showing 7 changed files with 352 additions and 0 deletions.
65 changes: 65 additions & 0 deletions .github/workflows/python-package-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Python Package Tests

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build-wheel:
runs-on: ubuntu-latest
container: ghcr.io/gridtools/gridtools-base:${{ matrix.compiler }}
strategy:
matrix:
python-version: ["3.10"]
compiler: [gcc-10]
build_type: [release]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: install nox
run: |
python -m pip install --upgrade pip setuptools
python -m pip install nox
- name: run tests
working-directory: ./.python_package
run: nox -s test_src
- name: build and test wheel
working-directory: ./.python_package
run: nox -s build_wheel test_wheel_with_python-${{ matrix.python-version }}
- name: archive wheel
uses: actions/upload-artifact@v3
with:
name: gridtools-cpp-wheel
path: .python_package/.nox/.cache/dist/gridtools_cpp-*.whl

test-wheel:
needs: build-wheel
runs-on: ubuntu-latest
container: ghcr.io/gridtools/gridtools-base:${{ matrix.compiler }}
strategy:
matrix:
python-version: ["3.8", "3.9", "3.11"]
compiler: [gcc-10]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: install nox
run: |
python -m pip install --upgrade pip setuptools
python -m pip install nox
- name: download wheel
uses: actions/download-artifact@v3
with:
name: gridtools-cpp-wheel
path: .python_package/.nox/.cache/dist
- name: test wheel
working-directory: ./.python_package
run: nox -s test_wheel_with_python-${{ matrix.python-version }}
73 changes: 73 additions & 0 deletions .python_package/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Python package for GridTools headers and CMake files

## Usage

Use the following when compiling C++ code with GridTools programmatically from Python.
Either by calling a compiler directly or by generating a CMake project and calling CMake on it.

```python
import gridtools_cpp
include_dir = gridtools_cpp.get_include_dir() # header files can be found here
cmake_dir = gridtools_cpp.get_cmake_dir() # cmake files can be found here
```

## Development

In order to be able to work on this package, it is necessary to run a preparation step.
This will generate the `setup.cfg` file from `setup.cfg.in` and install the gridtools header distribution into the package data.
It will read the version number from the top-level `version.txt` and copy the `LICENSE` file to where packaging tools can find it.

All of this requires `nox`, the preparation step runs in an isolated environment and installs additional requirements `cmake` and `ninja` at runtime.

```bash
pip install nox
nox -s prepare
```

To delete all generated files run

```bash
nox -s clean clean_cache
```

where `clean_cache` deletes chached files from Nox sessions like CMake builds and testing wheels (found in `.nox/.cache`), and `clean` deletes visible artifacts like `dist/`, `build/`, `.egg-info/`.
`setup.cfg` will not be deleted for convenience, to make sure tools keep functioning as expected.

### Installing

As always it is recommended to carry out the following steps in a virtual environment:

```bash
nox -s build -- --wheel .
pip install dist/gridtools_cpp-2.2.0-py3-none-any.whl
```

### Testing

Using nox, the tests will be carried out in isolated python environments for you:

```bash
nox -s test_src
```

To test the wheel distribution specifically:

```bash
nox -s build_wheel test_wheel_with_python-3.10 # replace 3.10 with the Python version you are running
```

### Advanced testing (all supported versions)

The following requires you to have Python interpreters for Python 3.8, 3.9, 3.10 and 3.11 in your system path.

```bash
nox
```

### Building for distribution

Uses (`build`)[https://pypa-build.readthedocs.io/en/latest/], follow the link for available options.

```bash
nox -s build -- <build options>
```
126 changes: 126 additions & 0 deletions .python_package/noxfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# GridTools
#
# Copyright (c) 2014-2021, ETH Zurich
# All rights reserved.
#
# Please, refer to the LICENSE file in the root directory.
# SPDX-License-Identifier: BSD-3-Clause

import configparser
import pathlib
import shutil

import nox


_RELATIVE_DATA_DIR = pathlib.Path("src/gridtools_cpp/data")


nox.options.sessions = ["test_src", "test_wheel"]


@nox.session
def prepare(session: nox.Session):
session.install("cmake>=3.18.1")
session.install("ninja")
build_path = session.cache_dir.joinpath("build").absolute()
build_path.mkdir(exist_ok=True)
install_path = pathlib.Path(".").absolute() / _RELATIVE_DATA_DIR
source_path = pathlib.Path("..").absolute()
with session.chdir(build_path):
session.run(
"cmake",
"-DBUILD_TESTING=OFF",
"-DGT_INSTALL_EXAMPLES:BOOL=OFF",
f"-DCMAKE_INSTALL_PREFIX={install_path}",
"-GNinja",
str(source_path),
)
session.run("cmake", "--install", ".")
session.log("installed gridttols sources")
version_path = source_path / "version.txt"
config = configparser.ConfigParser()
config.read("setup.cfg.in")
config["metadata"]["version"] = version_path.read_text().strip()
with open("setup.cfg", mode="w") as setup_fp:
config.write(setup_fp)
session.log("updated version metadata")
shutil.copy(source_path / "LICENSE", ".")
session.log("copied license file")


def get_wheel(session: nox.Session) -> pathlib.Path:
return list(session.cache_dir.joinpath("dist").glob("gridtools_cpp-*.whl"))[0]


@nox.session
def build_wheel(session: nox.Session):
prepare(session)
dist_path = session.cache_dir.joinpath("dist").absolute()
nox_workdir = pathlib.Path(".").absolute()
session.install("build[virtualenv]")
with session.chdir(session.cache_dir):
session.run(
"python",
"-m",
"build",
"--no-isolation",
"--wheel",
"-o",
str(dist_path),
str(nox_workdir),
)
session.log(f"built wheel in {dist_path}")
session.log("\n".join(str(path) for path in dist_path.iterdir()))


@nox.session
def test_src(session: nox.Session):
prepare(session)
session.install(".")
session.install("pytest")
session.run("pytest", "tests", *session.posargs)


@nox.session
def test_wheel(session: nox.Session):
session.notify("build_wheel")
session.notify("test_wheel_with_python-3.8")
session.notify("test_wheel_with_python-3.9")
session.notify("test_wheel_with_python-3.10")
session.notify("test_wheel_with_python-3.11")


@nox.session(python=["3.8", "3.9", "3.10", "3.11"])
def test_wheel_with_python(session: nox.Session):
wheel_path = get_wheel(session)
session.install("pytest")
session.install(str(wheel_path))
session.run("pytest", "tests", *session.posargs)


@nox.session
def clean_cache(session: nox.Session):
for subtree in session.cache_dir.iterdir():
shutil.rmtree(subtree, True)


@nox.session
def build(session: nox.Session):
prepare(session)
session.install("build[virtualenv]")
session.run("python", "-m", "build", "--no-isolation", *session.posargs)


@nox.session
def clean(session: nox.Session):
data_dir = _RELATIVE_DATA_DIR
session.log(f"rm -r {data_dir}")
shutil.rmtree(data_dir, True)
session.log("rm -r src/*.egg-info")
for egg_tree in pathlib.Path("src").glob("*.egg-info"):
shutil.rmtree(egg_tree, True)
session.log("rm -r dist")
shutil.rmtree("dist", True)
session.log("rm -r build")
shutil.rmtree("build", True)
3 changes: 3 additions & 0 deletions .python_package/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"
44 changes: 44 additions & 0 deletions .python_package/setup.cfg.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[metadata]
name = gridtools-cpp
author = ETH Zurich
author_email = gridtools@cscs.ch
license_files = LICENSE # this file is automatically copied from the top level
long_description = file: README.md
long_description_content_type = text/markdown; charset=UTF-8
url = https://gridtools.github.io
project_urls =
Source Code = https://github.com/GridTools/gridtools
platforms = any
version = # leave empty, automatically generated
classifiers =
Development Status :: 5 - Production/Stable
Intended Audience :: Science/Research
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Programming Language :: Python :: 3 :: Only
Topic :: Scientific/Engineering :: Atmospheric Science
Topic :: Scientific/Engineering :: Mathematics
Topic :: Scientific/Engineering :: Physics

[options]
packages = find_namespace:
package_dir =
= src
python_requires = >=3.8

[options.package_data]
* =
*.hpp
*.h
*.cmake

[options.packages.find]
where = src
exclude = tests

[tools:isort]
line_length = 100

[flake8]
max-line-length = 100

20 changes: 20 additions & 0 deletions .python_package/src/gridtools_cpp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# GridTools
#
# Copyright (c) 2014-2021, ETH Zurich
# All rights reserved.
#
# Please, refer to the LICENSE file in the root directory.
# SPDX-License-Identifier: BSD-3-Clause

import pathlib


_file = pathlib.Path(__file__)


def get_cmake_dir() -> pathlib.Path:
return _file.parent / "data" / "lib" / "cmake" / "GridTools"


def get_include_dir() -> pathlib.Path:
return _file.parent / "data" / "include"
21 changes: 21 additions & 0 deletions .python_package/tests/test_dirs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# GridTools
#
# Copyright (c) 2014-2021, ETH Zurich
# All rights reserved.
#
# Please, refer to the LICENSE file in the root directory.
# SPDX-License-Identifier: BSD-3-Clause

import gridtools_cpp


def test_cmake_dir_contains_gridtools_cmake():
main_config_file = gridtools_cpp.get_cmake_dir() / "GridToolsConfig.cmake"
assert main_config_file.exists()
assert main_config_file.read_text()


def test_include_dir_contains_headers():
include_path = gridtools_cpp.get_include_dir()
assert include_path.exists()
assert len(list(include_path.rglob("*.hpp"))) > 0

0 comments on commit 38a5033

Please sign in to comment.