diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..39c26b6952a750c5e6ec7e5abd257a97e390ffde --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "{{cookiecutter.project_slug}}/cmake/cmake-extra-utils"] + path = {{cookiecutter.project_slug}}/cmake/cmake-extra-utils + url = https://github.com/LecrisUT/CMakeExtraUtils.git diff --git a/README.md b/README.md index 4ca5706d6538a159d9333c68b054baae61542c01..ffd874a4b6c8872e16339bd38092390235fcb2dd 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,34 @@ -# Example Python Package - - - - -An example repository of an CI/CD pipeline for building, testing and publishing a python package. - -If you find some missing functionality with regards to CI/CD, testing, linting or something else, feel free to make a merge request with the proposed changes. +# Example Python Binary Extension Wheel Package + + + + +An example repository of an CI/CD pipeline for building, testing and publishing +a python package that uses binary extensions compiled as wheels. + +If you find some missing functionality with regards to CI/CD, testing, linting +or something else, feel free to make a merge request with the proposed changes. + +### Features: + +- Compile C / C++11 and bind with Python + - Easily define any C / C++ dependency to be included using `before-all = "apt / yum xxx"` + https://cibuildwheel.readthedocs.io/en/stable/options/#examples +- Automatic versioning using semantic version tags (`git tag vX.X.X`) + - Versions from `setuptools-scm` in Python are bound and forwarded to the + C / C++ extension using the `DynamicVersion` git submodule. +- Run any task performed by CI/CD locally such as: + - linting (`tox -e black`, `tox -e pylint`, `tox -e pep8`) + - automatically format code (`tox -e format`) + - unit tests (`tox -e py37`) + - code coverage (`tox -e coverage`) + - building & packaging (`tox -e build-local`, `tox -e build-ci-linux`) +- Automatically publish new releases to the gitlab package repository +- CI/CD uses [docker base image](docker/ci-runner/Dockerfile) to speed up pipeline ## How to apply this template -This templates uses `cookiecutter` which can be +This templates uses `cookiecutter` which can be installed easily: ```bash pip install --user cookiecutter @@ -18,7 +37,7 @@ pip install --user cookiecutter Then you can create an instance of your template with: ```bash -cookiecutter https://git.astron.nl/templates/python-package.git +cookiecutter https://git.astron.nl/templates/python-binary-wheel-package.git # Next follow a set of prompts (such as the name and description of the package) ``` diff --git a/cookiecutter.json b/cookiecutter.json index b61933c29cb84cb9615e7cfcad595d2083900fb5..4536dbab8d034a904465539e2a99ed12a3401786 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,5 +1,6 @@ { "project_name": "My Awesome App", "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}", + "project_url": "git.astron.nl/{{cookiecutter.project_slug}}", "description": "An example package for CI/CD working group" } diff --git a/{{cookiecutter.project_slug}}/.git_archival.txt b/{{cookiecutter.project_slug}}/.git_archival.txt new file mode 100644 index 0000000000000000000000000000000000000000..8fb235d7045be0330d94bcb3abd2ac43badaa197 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ +ref-names: $Format:%D$ diff --git a/{{cookiecutter.project_slug}}/.gitattributes b/{{cookiecutter.project_slug}}/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..00a7b00c94e08b86c765d47689b6523148c46eec --- /dev/null +++ b/{{cookiecutter.project_slug}}/.gitattributes @@ -0,0 +1 @@ +.git_archival.txt export-subst diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore index 3de3f10df4bb74f8d30e7ed9ebf98e44387b96f4..045812e7a497afb32efaa702e180ad6604473f5b 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -6,8 +6,11 @@ dist/* coverage.xml htmlcov/* -# Setuptools SCM -{{cookiecutter.project_slug}}/_version.py +# Tox +.tox + +# setuptools-scm +src/python_binary_wheel/_version.py # IDE configuration .vscode diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ce8b8df75115ebd9a9fca2c573e62155c9316b22 --- /dev/null +++ b/{{cookiecutter.project_slug}}/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.15...3.26) + +# Include submodule to dynamically determine version +set( + CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-extra-utils/cmake" +) + +# Include script +include(DynamicVersion) +dynamic_version( + ${SKBUILD_PROJECT_NAME} + PROJECT_PREFIX PROJECT_VERSION_ + PROJECT_SOURCE $ENV{DYNAMIC_VERSION_SOURCE} +) + +# Get project name from scikit-build-core and version DynamicVersion +project(${SKBUILD_PROJECT_NAME} VERSION ${PROJECT_VERSION}) + +# Get required packages +find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) +find_package(pybind11 CONFIG REQUIRED) + +# Build our Pythonextension +python_add_library(_core MODULE src/main.cpp WITH_SOABI) +target_link_libraries(_core PRIVATE pybind11::headers) + +# Emit Python determined version as definition +target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION}) + +# Install the library into the build directory +install(TARGETS _core DESTINATION python_binary_wheel) diff --git a/{{cookiecutter.project_slug}}/MANIFEST.in b/{{cookiecutter.project_slug}}/MANIFEST.in index c4a3399ac9b36be786467e020c84f95e09db0606..dbb5e5ffd4d3b24b8cc8d5597bf55dfc4f83706a 100644 --- a/{{cookiecutter.project_slug}}/MANIFEST.in +++ b/{{cookiecutter.project_slug}}/MANIFEST.in @@ -1,5 +1,8 @@ include LICENSE include README.md +include CMakeLists.txt + recursive-include docs * +recursive-include docker * recursive-exclude tests * diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 4a0ae9083c5f75f291e3e193972b9e5aabe5651e..428513b663ccf3d32b9df1cd29c04a98134cfed5 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -1,11 +1,11 @@ # {{cookiecutter.project_name}} <!-- TODO: replace --> - - + + <!--  --> -An example repository of an CI/CD pipeline for building, testing and publishing a python package. +{{cookiecutter.description}} ## Installation ``` diff --git a/{{cookiecutter.project_slug}}/build-requirements.txt b/{{cookiecutter.project_slug}}/build-requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..357a5ca22a4257b5aadfc73ae9ea9a3f97d12ef7 --- /dev/null +++ b/{{cookiecutter.project_slug}}/build-requirements.txt @@ -0,0 +1,3 @@ +cibuildwheel >= 2.12.3 # BSD +tox >= 4.0 # ? +build >= 0.8.0 # MIT diff --git a/{{cookiecutter.project_slug}}/cmake/cmake-extra-utils b/{{cookiecutter.project_slug}}/cmake/cmake-extra-utils new file mode 160000 index 0000000000000000000000000000000000000000..26450da9dceea86a809fe1a38c095a0c988a6159 --- /dev/null +++ b/{{cookiecutter.project_slug}}/cmake/cmake-extra-utils @@ -0,0 +1 @@ +Subproject commit 26450da9dceea86a809fe1a38c095a0c988a6159 diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 183007c3eb92a098dc3cc38fee60fc47e5f5884e..e137d163f7f92d96c9822557592ab51742955623 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -1,13 +1,48 @@ [build-system] requires = [ - "setuptools>=45", - "setuptools_scm[toml]>=6.2", - "wheel" + "scikit-build-core>=0.3.3", + "pybind11", + "setuptools_scm[toml]>=6.2" ] -build-backend = "setuptools.build_meta" +build-backend = "scikit_build_core.build" + +[project] +name = "{{cookiecutter.project_slug}}" +dynamic = ["version"] +description="{{cookiecutter.description}}" +readme = "README.md" +requires-python = ">=3.7" +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] + +[tool.scikit-build] +experimental=true +wheel.expand-macos-universal-tags = true +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +sdist.include = ["src/{{cookiecutter.project_slug}}/_version.py"] [tool.setuptools_scm] -write_to = "{{cookiecutter.project_slug}}/_version.py" +write_to = "src/{{cookiecutter.project_slug}}/_version.py" + +[tool.cibuildwheel] +skip = "pp*" +build-verbosity = 1 + +[tool.cibuildwheel.macos] +archs = ["x86_64", "universal2", "arm64"] + +[tool.cibuildwheel.linux] +archs = ["x86_64", "i686"] + +[tool.cibuildwheel.windows] +archs = ["AMD64", "x86"] [tool.pylint] ignore = "_version.py" diff --git a/{{cookiecutter.project_slug}}/setup.cfg b/{{cookiecutter.project_slug}}/setup.cfg deleted file mode 100644 index 1741808bc860dd1e2294dcd1ec8e0d7e6ed8fd2d..0000000000000000000000000000000000000000 --- a/{{cookiecutter.project_slug}}/setup.cfg +++ /dev/null @@ -1,38 +0,0 @@ -[metadata] -name = {{cookiecutter.project_slug}} -description = An example package for CI/CD working group -long_description = file: README.md -long_description_content_type = text/markdown -url = https://git.astron.nl/templates/python-package -license = Apache License 2.0 -classifiers = - Development Status :: 3 - Alpha - Environment :: Web Environment - Intended Audience :: Developers - Intended Audience :: Science/Research - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - 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 - Topic :: Internet :: WWW/HTTP - Topic :: Internet :: WWW/HTTP :: Dynamic Content - Topic :: Scientific/Engineering - Topic :: Scientific/Engineering :: Astronomy - -[options] -include_package_data = true -packages = find: -python_requires = >=3.7 -install_requires = - importlib-metadata>=0.12, <5.0;python_version<"3.8" - numpy - -[flake8] -max-line-length = 88 -extend-ignore = E203 diff --git a/{{cookiecutter.project_slug}}/setup.py b/{{cookiecutter.project_slug}}/setup.py deleted file mode 100644 index b908cbe55cb344569d32de1dfc10ca7323828dc5..0000000000000000000000000000000000000000 --- a/{{cookiecutter.project_slug}}/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -import setuptools - -setuptools.setup() diff --git a/{{cookiecutter.project_slug}}/src/main.cpp b/{{cookiecutter.project_slug}}/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7873bc3950cb57684b7994da98a57fad240590b5 --- /dev/null +++ b/{{cookiecutter.project_slug}}/src/main.cpp @@ -0,0 +1,37 @@ +#include <pybind11/pybind11.h> + +#define STRINGIFY(x) #x +#define MACRO_STRINGIFY(x) STRINGIFY(x) + +int add(int i, int j) { + return i + j; +} + +namespace py = pybind11; + +PYBIND11_MODULE(_core, m) { + m.doc() = R"pbdoc( + Pybind11 example plugin + ----------------------- + + .. currentmodule:: python_binary_wheel + + .. autosummary:: + :toctree: _generate + + add + subtract + )pbdoc"; + + m.def("add", &add, R"pbdoc( + Add two numbers + + Some other explanation about the add function. + )pbdoc"); + + m.def("subtract", [](int i, int j) { return i - j; }, R"pbdoc( + Subtract two numbers + + Some other explanation about the subtract function. + )pbdoc"); +} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py rename to {{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/__init__.py diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cool_module.py b/{{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/cool_module.py similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cool_module.py rename to {{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/cool_module.py diff --git a/{{cookiecutter.project_slug}}/tox.ini b/{{cookiecutter.project_slug}}/tox.ini index 389403d52064a498216d8eb281afa0e60af09d6a..3edee4680d148b3d780c2a9186b4fd2749f98d17 100644 --- a/{{cookiecutter.project_slug}}/tox.ini +++ b/{{cookiecutter.project_slug}}/tox.ini @@ -1,12 +1,11 @@ [tox] # Generative environment list to test all supported Python versions -envlist = py3{7,8,9,10},black,pep8,pylint +envlist = py3{8,9,10,11},black,pep8,pylint minversion = 3.18.0 [testenv] -usedevelop = True -package = wheel -wheel_build_env = .pkg +package = sdist # 'Source' package required for binary extension +use_develop = False # use_develop implies 'editable' package, not possible setenv = LANGUAGE=en_US @@ -40,7 +39,11 @@ commands = format: {envpython} -m autopep8 -v -aa --in-place --recursive tests format: {envpython} -m black -v . - -[testenv:build] -usedevelop = False -commands = {envpython} -m build +[testenv:{build-local,build-ci-linux}] +deps = + -r{toxinidir}/tests/requirements.txt + -r{toxinidir}/build-requirements.txt +skip_install = True +commands = + build-local: {envpython} -m build + build-ci-linux: {envpython} -m cibuildwheel --platform linux --output-dir dist