Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Python packaging #1720

Merged
merged 45 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
03b7de3
pip package for gt4py to depend on
Jul 14, 2022
2e13579
move pip package into subdir
Jul 14, 2022
43a1b10
Update pylibgt/setup.py
havogt Jul 22, 2022
51b7fd4
Update pylibgt/setup.py
havogt Jul 22, 2022
f81f6bb
remove print statements
Aug 2, 2022
6c3f99b
add test and CI
Aug 3, 2022
be6f794
fix piptest CI
Aug 3, 2022
37f68ff
set python version for pylib CI to 3.10
Aug 3, 2022
6d00ba2
use python instead of python3 in nox-ci
Aug 3, 2022
06333b8
add python setup step to nox-ci
Aug 3, 2022
2cea7bf
add workdir to nox-ci
Aug 3, 2022
e37f977
add tests
Aug 3, 2022
7662b29
add test for wheel build
Nov 11, 2022
41fa89a
fix headers for all py files
Nov 11, 2022
5fe6a99
fix licence header also for tests
Nov 11, 2022
35ef68f
add get_include_dir method and test
Nov 11, 2022
c4bb261
remove duplicate setup keys from cfg
Nov 11, 2022
8a9a674
renamed to .python_package
Nov 11, 2022
e2f1047
rename test module
Nov 11, 2022
0628d7f
update CI with new python package dir
Nov 14, 2022
2e1b28e
reusable build_wheel session with caching
Nov 15, 2022
ccaf5f8
rename python package github action
Nov 18, 2022
8e8be70
Update .python_package/py_src/gridtools/__init__.py
havogt Nov 21, 2022
d2289e9
clean up noxfile
Nov 22, 2022
c77d8ee
update minimum compatible python version to 3.4
Nov 22, 2022
23cc4eb
remove MANIFEST.in and `include_package_data=True`
Nov 22, 2022
1085e5b
add cmake build/install to nox instead of setup.py
Nov 23, 2022
c6155bf
change distribution name
Nov 23, 2022
7ddd678
update dist name in noxfile
Nov 24, 2022
6e2fec7
try testing wheel on matrix of python versions
Nov 24, 2022
e04a3b4
add dependency between build and test jobs
Nov 24, 2022
d17034e
set cwd of wheel upload step
Nov 24, 2022
393649a
change artifact path instead of pwd
Nov 24, 2022
006c084
download wheel to the right path
Nov 24, 2022
1483de8
cleanup and add package metadata
Nov 25, 2022
01eab36
do not rely on external `make` being present
Nov 25, 2022
2a96235
add description to metadata
Nov 25, 2022
be0095f
fix description metadata
Nov 25, 2022
5ac7494
add README, pkg name -> gridtools_cpp, setup.cfg -> setup.cfg.in
Nov 25, 2022
72c54b7
remove cmake install requirement
Nov 25, 2022
863c647
update usage section in README
Nov 25, 2022
eb9f344
update tests with new package name
Nov 25, 2022
353dc1d
update package name in preparation step
Nov 25, 2022
38b1596
change README.md
Dec 1, 2022
001b1ff
Merge branch 'master' into make-pip-installable
Dec 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"]
DropD marked this conversation as resolved.
Show resolved Hide resolved
compiler: [gcc-10]
DropD marked this conversation as resolved.
Show resolved Hide resolved
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 }}
123 changes: 123 additions & 0 deletions .python_package/noxfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# 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

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


def prepare(session: nox.Session):
session.install("cmake>=2.18.1")
session.install("ninja")
build_path = session.cache_dir.joinpath("build").absolute()
build_path.mkdir(exist_ok=True)
install_path = pathlib.Path(".").absolute() / "src" / "gridtools" / "data"
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"
setup_path = pathlib.Path(".") / "setup.cfg"
config = configparser.ConfigParser()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably you're already aware, but configparser removes all comments and formatting from the original file. It's not very important in this case, though, but it is not nice. The only alternative I know which preserves format is ConfigUpdate (https://pypi.org/project/ConfigUpdater/) but is a third-party project

config.read(str(setup_path))
config["metadata"]["version"] = version_path.read_text()
with setup_path.open("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()
workdir = pathlib.Path(".").absolute()
session.install("build[virtualenv]")
Copy link
Contributor

@egparedes egparedes Nov 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment, maybe it would be good to add all packages used or installed here (build, cmake, ...) to the list of build_system.requires in pyproject.toml to make the dependencies explicit.

Copy link
Contributor Author

@DropD DropD Nov 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add them for documentation purposes, even they are not build requirements anymore. They are pre-build requirements. In the PEP-517 conformant isolated build environment, they don't have to be present.

A compromise might be to add them commented-out, I guess?

with session.chdir(session.cache_dir):
session.run(
"python",
"-m",
"build",
"--no-isolation",
"--wheel",
"-o",
str(dist_path),
str(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):
top_dir = pathlib.Path(".")
data_dir = top_dir / "src" / "gridtools" / "data"
session.log(f"rm -r {data_dir}")
shutil.rmtree(data_dir, True)
session.log("rm -r src/*.egg-info")
for egg_tree in top_dir.joinpath("src").glob("*.egg-info"):
shutil.rmtree(egg_tree, True)
session.log("rm -r dist")
shutil.rmtree(top_dir / "dist", True)
session.log("rm -r build")
shutil.rmtree(top_dir / "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
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
DropD marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to add a comment here to document the fact that the License file is being copied in the building process... (but configparser would delete the comment later 😓 )

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 = 2.2.1a2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, I would like to add a comment here to document the fact that the version is actually updated from the main gridtools version file... (but configparser would delete the comment later 😓 )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can solve these by generating setup.cfg from setup.cfg.in, which would have the comments. Only question is whether setup.cfg should still be versioned (since it has tool config as well).


classifiers =
Development Status :: 5 - Production/Stable
License :: OSI Approved :: BSD License
Intended Audience :: Science/Research
Operating System :: OS Independent
Programming Language :: Python :: 3 :: Only
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, sort the classifiers alphabetically. Optionally, you might want to add the same topic classifiers used in gt4py:

    Topic :: Scientific/Engineering :: Atmospheric Science
    Topic :: Scientific/Engineering :: Mathematics
    Topic :: Scientific/Engineering :: Physics


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

[options.package_data]
* =
*.hpp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also optional, we could add the license file to the package sources, since the python package is some kind or source distribution

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is already included in the distribution. Can be found after install in the usual location .../site-packages/gridtools_cpp-XXX.dist-info/LICENSE.

*.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/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# GridTools
DropD marked this conversation as resolved.
Show resolved Hide resolved
#
# 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


def test_cmake_dir_contains_gridtools_cmake():
main_config_file = gridtools.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.get_include_dir()
assert include_path.exists()
assert len(list(include_path.rglob("*.hpp"))) > 0