diff --git a/README.md b/README.md index 67b5b07d..089ff339 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,11 @@ for a Python package. [Flit](https://flit.pypa.io), [meson-python](https://meson-python.readthedocs.io/en/latest/index.html), [Setuptools](https://setuptools.pypa.io/en/latest/), - [PDM](https://pdm.fming.dev/) or - [Hatch](https://hatch.pypa.io) - [Maturin](https://pypi.org/project/maturin/0.8.2/) - [scikit-build-core](https://scikit-build-core.readthedocs.io/en/latest/) + [PDM](https://pdm.fming.dev/), + [Hatch](https://hatch.pypa.io), + [Maturin](https://pypi.org/project/maturin/0.8.2/), + [scikit-build-core](https://scikit-build-core.readthedocs.io/en/latest/) or + [setuptools + pybind11](https://pybind11.readthedocs.io/en/stable/) based on your preference. - The structure of the project can use the *src layout* or *flat layout*. The “src layout” moving the code that is intended to be diff --git a/docs/guide.md b/docs/guide.md index 475c949e..bc917ffe 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -334,7 +334,20 @@ packages. SciCookie support the following: build requirements. With its capabilities, it facilitates cross-platform builds using CMake and effortless integration with C/C++ libraries, making it a valuable asset for research software engineers. - + +- [**setuptools + pybind11**](https://pybind11.readthedocs.io/en/stable/): It's build system designed + for C++ library that simplifies the creation of Python bindings for C++ code, + enabling easy integration of C++ functions and classes into Python scripts. It + acts as a bridge between the two languages, allowing C++ algorithms and functionality + to be directly called from Python as if they were native Python modules. Pybind11's + user-friendly syntax reduces boilerplate code, making binding generation more + straightforward, while standard C++ build systems like CMake or Make facilitate the + compilation of projects using pybind11. Its efficiency, ease of use, and strong community + support have made it a popular choice for projects requiring seamless interoperability + between C++ and Python, ranging from scientific computing to game development and + automation. Staying up-to-date with the latest pybind11 documentation ensures the + best practices are followed. + The idea behind the options in SciCookie is that you can choose from some of the most popular system compilers to suit your needs and preferences for developing Python packages. If you think we should add more options, you can submit your diff --git a/src/scicookie/cookiecutter.json b/src/scicookie/cookiecutter.json index 30064ef0..6bccf5e0 100644 --- a/src/scicookie/cookiecutter.json +++ b/src/scicookie/cookiecutter.json @@ -52,7 +52,8 @@ "pdm", "hatch", "maturin", - "scikit-build-core" + "scikit-build-core", + "pybind11" ], "use_bandit": "no", "use_black": "yes", diff --git a/src/scicookie/hooks/post_gen_project.py b/src/scicookie/hooks/post_gen_project.py index 334e01ab..dd164684 100644 --- a/src/scicookie/hooks/post_gen_project.py +++ b/src/scicookie/hooks/post_gen_project.py @@ -69,6 +69,8 @@ BUILD_SYSTEM = "maturin" {% elif cookiecutter.build_system == "scikit-build-core" -%} BUILD_SYSTEM = "scikit-build-core" +{% elif cookiecutter.build_system == "pybind11" -%} +BUILD_SYSTEM = "pybind11" {%- else %} BUILD_SYSTEM = None {%- endif %} @@ -217,6 +219,19 @@ def clean_up_build_system(): build_system_dir / "skcdemo.cpp", PROJECT_DIRECTORY / 'skcdemo.cpp' ) + elif BUILD_SYSTEM == "pybind11": + shutil.move( + build_system_dir / "pybind11-pyproject.toml", + PROJECT_DIRECTORY / 'pyproject.toml' + ) + shutil.move( + build_system_dir / "CMakeLists.txt", + PROJECT_DIRECTORY / 'CMakeLists.txt' + ) + shutil.move( + build_system_dir / "setup.py", + PROJECT_DIRECTORY / 'setup.py' + ) else: shutil.move( build_system_dir / "base-pyproject.toml", @@ -291,6 +306,14 @@ def add_binding_source_files(): else: os.makedir(src_system_dir) shutil.move(build_system_dir / "lib.rs", src_system_dir) + elif BUILD_SYSTEM == "pybind11" : + build_system_dir = PROJECT_DIRECTORY / "build-system" + src_system_dir = PROJECT_DIRECTORY/ "src" + if USE_SRC_LAYOUT : + shutil.move(build_system_dir / "main.cpp", "src") + else: + os.makedir(src_system_dir) + shutil.move(build_system_dir / "main.cpp", src_system_dir) else: pass diff --git a/src/scicookie/profiles/base.yaml b/src/scicookie/profiles/base.yaml index ca180e89..5e06442d 100644 --- a/src/scicookie/profiles/base.yaml +++ b/src/scicookie/profiles/base.yaml @@ -89,6 +89,7 @@ build_system: - hatch - maturin - scikit-build-core + - pybind11 enabled: false command_line_interface: diff --git a/src/scicookie/{{cookiecutter.project_slug}}/Makefile b/src/scicookie/{{cookiecutter.project_slug}}/Makefile index adc9c2d9..3e230a4c 100644 --- a/src/scicookie/{{cookiecutter.project_slug}}/Makefile +++ b/src/scicookie/{{cookiecutter.project_slug}}/Makefile @@ -115,6 +115,8 @@ build: hatch build {%- elif cookiecutter.build_system == "maturin" %} maturin build +{%- elif cookiecutter.build_system == "pybind11" %} + python -m build {%- endif %} .PHONY:release-ci diff --git a/src/scicookie/{{cookiecutter.project_slug}}/build-system/CMakeLists.txt b/src/scicookie/{{cookiecutter.project_slug}}/build-system/CMakeLists.txt index aa7a3da9..aa6dc7aa 100644 --- a/src/scicookie/{{cookiecutter.project_slug}}/build-system/CMakeLists.txt +++ b/src/scicookie/{{cookiecutter.project_slug}}/build-system/CMakeLists.txt @@ -2,8 +2,20 @@ cmake_minimum_required(VERSION 3.15...3.26) project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION} LANGUAGES CXX) +{% if cookiecutter.build_system == "scikit-build-core" -%} + find_package(pybind11 CONFIG REQUIRED) pybind11_add_module(skcdemo MODULE skcdemo.cpp) install(TARGETS skcdemo DESTINATION .) + +{% elif cookiecutter.build_system == "pybind11" -%} + +find_package(pybind11 CONFIG REQUIRED) + +pybind11_add_module(_core MODULE src/{{cookiecutter.project_slug}}/main.cpp) + +install(TARGETS _core DESTINATION src/{{cookiecutter.project_slug}}/main.cpp ) + +{%- endif %} diff --git a/src/scicookie/{{cookiecutter.project_slug}}/build-system/main.cpp b/src/scicookie/{{cookiecutter.project_slug}}/build-system/main.cpp new file mode 100644 index 00000000..2b8ed183 --- /dev/null +++ b/src/scicookie/{{cookiecutter.project_slug}}/build-system/main.cpp @@ -0,0 +1,28 @@ +#include + +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_example + .. 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/src/scicookie/{{cookiecutter.project_slug}}/build-system/pybind11-pyproject.toml b/src/scicookie/{{cookiecutter.project_slug}}/build-system/pybind11-pyproject.toml new file mode 100644 index 00000000..e6c5c05e --- /dev/null +++ b/src/scicookie/{{cookiecutter.project_slug}}/build-system/pybind11-pyproject.toml @@ -0,0 +1,124 @@ +[build-system] +requires = ["setuptools>=65", "wheel", "pybind11~=2.11.1","build>=0.10.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "{{ cookiecutter.project_slug }}" +authors = [ + { name = "{{ cookiecutter.author_full_name }}", email = "{{ cookiecutter.author_email }}" }, +] +description = "{{ cookiecutter.project_short_description }}" +readme = "README.md" +classifiers = [ + "Development Status :: 1 - Planning", + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", +{%- if cookiecutter.project_license == "MIT" %} + "License :: OSI Approved :: MIT License", +{%- elif cookiecutter.project_license == "BSD 3 Clause" %} + "License :: OSI Approved :: BSD License", +{%- elif cookiecutter.project_license == "Apache Software License 2.0" %} + "License :: OSI Approved :: Apache Software License", +{%- elif cookiecutter.project_license == "GNU General Public License v3" %} + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", +{%- endif %} + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "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", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering", + "Typing :: Typed", +] +dynamic = ["version"] +requires-python = ">=3.8.1" +dependencies = [ +{# keep this line here #} + "build>=0.10.0", +{%- if cookiecutter.use_pytest == "yes" -%} + "pytest >= 7.3.2,<8", +{% if cookiecutter.use_coverage == "yes" -%} + "pytest-cov >= 4.1.0,<5", +{% endif %} +{%- endif -%}{#- end of use_pytest -#} +{%- if cookiecutter.use_hypothesis == "yes" -%} + "hypothesis >= 6.0,<7", +{% endif %} +{%- if cookiecutter.use_coverage == "yes" -%} + "coverage >= 7.2.7,<8", +{% endif %} +{%- if cookiecutter.use_blue == "yes" -%} + "blue >= 0.9.1,<1", +{% endif %} +{%- if cookiecutter.use_black == "yes" -%} + "black >= 23.3.0,<24", +{% endif %} +{%- if cookiecutter.use_isort == "yes" -%} + "isort >= 5.12.0,<6", +{% endif %} +{%- if cookiecutter.use_pre_commit -%} + "pre-commit >= 3.3.2,<4", +{% endif %} +{%- if cookiecutter.use_flake8 == "yes" -%} + "flake8 >= 4.0.1, < 7", +{% endif %} +{%- if cookiecutter.use_ruff == "yes" -%} + "ruff >= 0.0.272,<1", +{% endif %} +{%- if cookiecutter.use_mypy == "yes" -%} + "mypy >= 1.3.0,<2", +{% endif %} +{%- if cookiecutter.use_bandit == "yes" -%} + "bandit >= 1.7.5,<2", +{% endif %} +{%- if cookiecutter.use_pydocstyle == "yes" -%} + "pydocstyle >= 6.3.0,<7", +{% endif %} +{%- if cookiecutter.use_vulture == "yes" -%} + "vulture >= 2.7,<3", +{% endif %} +{%- if cookiecutter.use_mccabe == "yes" -%} + "mccabe >= 0.6.1,<1", +{% endif %} +{%- if cookiecutter.use_containers in ['Docker', 'Podman'] -%} +# if you want to use docker-compose from your system, remove compose-go here + "compose-go >= 2.18.1,<3", +{% endif %} + "ipython < 8", + "ipykernel >=6.0.0", +{%- if cookiecutter.documentation_engine == 'mkdocs' -%} + "Jinja2 >=3.1.2,<4", + "mkdocs >=1.4.3,<2", + "mkdocs-exclude >= 1.0.2,<2", + "mkdocs-jupyter >= 0.24.1,<1", + "mkdocs-literate-nav >= 0.6.0,<1", + "mkdocs-macros-plugin >= 0.7.0, < 1", + "mkdocs-material >= 9.1.15,<10", + "mkdocstrings >= 0.21.2,< 1", + "mkdocstrings-python >= 1.1.2,<2", +{% elif cookiecutter.documentation_engine == 'sphinx' -%} + "Sphinx >= 6.2.1,<7", + "sphinx-rtd-theme >= 1.2.2,<2", + "importlib-metadata >= 6.5.1,<7", + "myst-parser >= 0.19.2,<1", + "nbsphinx >= 0.9.2,<1", + "pandoc >= 2.3,<3", +{% elif cookiecutter.documentation_engine == 'jupyter-book' -%} + "jupyter-book >= 0.15.1,<1", + "myst-parser >= 0.18.1,<1", +{% endif %} +] + +[project.urls] +Homepage = "{{ cookiecutter.project_url }}" +"Bug Tracker" = "{{ cookiecutter.project_url }}/issues" +Discussions = "{{ cookiecutter.project_url }}/discussions" +Changelog = "{{ cookiecutter.project_url }}/releases" + + +{% include "build-system/base-pyproject.toml" %} +{#- keep this line at the end of the file -#} diff --git a/src/scicookie/{{cookiecutter.project_slug}}/build-system/setup.py b/src/scicookie/{{cookiecutter.project_slug}}/build-system/setup.py new file mode 100644 index 00000000..eeb9e823 --- /dev/null +++ b/src/scicookie/{{cookiecutter.project_slug}}/build-system/setup.py @@ -0,0 +1,21 @@ +from setuptools import setup # isort:skip + +# Available at setup time due to pyproject.toml +from pybind11.setup_helpers import Pybind11Extension # isort:skip + +# Note: +# Sort input source files if you glob sources to ensure bit-for-bit +# reproducible builds (https://github.com/pybind/python_example/pull/53) + +ext_modules = [ + Pybind11Extension( + "{{ cookiecutter.project_slug }}._core", + ["src/main.cpp"], + cxx_std=11, + ), +] + + +setup( + ext_modules=ext_modules, +) diff --git a/src/scicookie/{{cookiecutter.project_slug}}/conda/dev.yaml b/src/scicookie/{{cookiecutter.project_slug}}/conda/dev.yaml index 20739cad..83d65268 100644 --- a/src/scicookie/{{cookiecutter.project_slug}}/conda/dev.yaml +++ b/src/scicookie/{{cookiecutter.project_slug}}/conda/dev.yaml @@ -23,7 +23,10 @@ dependencies: - rust {%- elif cookiecutter.build_system == "scikit-build-core" %} - scikit-build-core - - cmake + - cmake +{%- elif cookiecutter.build_system == "pybind11" %} + - pybind11 + - cmake {%- endif %} - nodejs # used by semantic-release {%- if cookiecutter.use_shellcheck == "yes" %} diff --git a/src/scicookie/{{cookiecutter.project_slug}}/docs/contributing.md b/src/scicookie/{{cookiecutter.project_slug}}/docs/contributing.md index de1d374a..6f37ced5 100644 --- a/src/scicookie/{{cookiecutter.project_slug}}/docs/contributing.md +++ b/src/scicookie/{{cookiecutter.project_slug}}/docs/contributing.md @@ -59,6 +59,10 @@ In addition, you should know that to build our package we use {%- elif cookiecutter.build_system == "scikit-build-core" -%} In addition, you should know that to build our package we use [scikit-build-core](https://scikit-build-core.readthedocs.io/en/latest/): It's a Python packaging tool and build system an improved build system generator for CPython C extensions. It provides better support for additional compilers, build systems, cross compilation, and locating dependencies and their associated build requirements.This tool improves package management in the scientific Python ecosystem, enabling cross-platform builds with CMake, and seamless integration with C/C++ libraries for research software engineers. +{%- elif cookiecutter.build_system == "pybind11" -%} +In addition, you should know that to build our package we use +[setuptools + pybind11](https://pybind11.readthedocs.io/en/stable/): It's a Python packaging tool for C++ build system that simplifies creating Python bindings for C++ code, allowing easy integration of C++ functions and classes into Python scripts. Acting as a bridge between the two languages, it enables direct calls to C++ functionality from Python as if it were a native Python module. Its user-friendly syntax reduces boilerplate code, while standard C++ build systems like CMake or Make aid in project compilation. Pybind11's efficiency and strong community support make it a popular choice for projects requiring seamless interoperability between C++ and Python, from scientific computing to game development. + {%- endif %} Contributions are welcome, and they are greatly appreciated! Every little bit diff --git a/tests/smoke/base.sh b/tests/smoke/base.sh index f9fd5ab9..ac8147f1 100755 --- a/tests/smoke/base.sh +++ b/tests/smoke/base.sh @@ -55,6 +55,10 @@ elif command -v maturin &> /dev/null; then pip install . elif [ "$(pip list|grep -c scikit_build_core)" -ne "0" ]; then pip install . +elif [ "$(pip list|grep -c pybind11)" -ne "0" ]; then + # Assuming you are inside the root of the CMake source directory + pip install . + else # use setuptools pip install . diff --git a/tests/smoke/build-systems.sh b/tests/smoke/build-systems.sh index 655c9325..9206404c 100755 --- a/tests/smoke/build-systems.sh +++ b/tests/smoke/build-systems.sh @@ -10,3 +10,4 @@ SMOKE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" . ${SMOKE_DIR}/base.sh "build_system=hatch" . ${SMOKE_DIR}/base.sh "build_system=maturin" . ${SMOKE_DIR}/base.sh "build_system=scikit-build-core" +. ${SMOKE_DIR}/base.sh "build_system=pybind11"