From 4a6bfbd9cf50a9f2b4a0d5008f79bde524fcf862 Mon Sep 17 00:00:00 2001 From: Ankit Kumar <72691866+ayeankit@users.noreply.github.com> Date: Fri, 28 Jul 2023 02:56:11 +0530 Subject: [PATCH] feat(template): Added maturin as an option for build-system (#152) * Activate option in TUI to maturin * Adding documentation about maturin * Adding maturin as a build-system: * Creating a maturin-pyproject.toml * Editing post_gen_project.py * Creating a smoke test (build-system.sh) * Editing cookicutter.json * Added maturin in Readme.md --- README.md | 1 + docs/guide.md | 10 +- src/scicookie/cookiecutter.json | 3 +- src/scicookie/hooks/post_gen_project.py | 27 +++- src/scicookie/profiles/base.yaml | 1 + .../{{cookiecutter.project_slug}}/Makefile | 2 + .../build-system/Cargo.toml | 19 +++ .../build-system/lib.rs | 24 ++++ .../build-system/maturin-pyproject.toml | 136 ++++++++++++++++++ .../conda/dev.yaml | 3 + .../docs/contributing.md | 6 +- tests/smoke/base.sh | 3 +- tests/smoke/build-system.sh | 1 + 13 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 src/scicookie/{{cookiecutter.project_slug}}/build-system/Cargo.toml create mode 100644 src/scicookie/{{cookiecutter.project_slug}}/build-system/lib.rs create mode 100644 src/scicookie/{{cookiecutter.project_slug}}/build-system/maturin-pyproject.toml diff --git a/README.md b/README.md index f5afffa8..31526b16 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ for a Python package. [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/) 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 41b2001d..c930a624 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -231,7 +231,15 @@ packages. SciCookie support the following: can be confident that they will always produce the same results. It also helps you manage your Python environments, so you can be sure that your projects have the correct dependencies. - +- [**Maturin**](https://pypi.org/project/maturin/0.8.2/):It's build system designed + to create Python bindings from Rust projects. It allows Rust code to be seamlessly + integrated into Python applications, providing efficient builds and cross-platform + support for various Python versions. Maturin automates the generation of Python + modules that directly access Rust functions, harnessing Rust's high performance + and low-level capabilities within Python. Its user-friendly interface and + compatibility with setuptools and Cargo make it an easy-to-use tool, offering + developers a simple solution to combine the strengths of Python and Rust within + a unified project. 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 cf8c0d35..1e7a88ad 100644 --- a/src/scicookie/cookiecutter.json +++ b/src/scicookie/cookiecutter.json @@ -49,7 +49,8 @@ "mesonpy", "setuptools", "pdm", - "hatch" + "hatch", + "maturin" ], "use_bandit": "yes", "use_black": "no", diff --git a/src/scicookie/hooks/post_gen_project.py b/src/scicookie/hooks/post_gen_project.py index ff2f39c8..70c137c7 100644 --- a/src/scicookie/hooks/post_gen_project.py +++ b/src/scicookie/hooks/post_gen_project.py @@ -62,6 +62,8 @@ BUILD_SYSTEM = "pdm" {% elif cookiecutter.build_system == "hatch" -%} BUILD_SYSTEM = "hatch" +{% elif cookiecutter.build_system == "maturin" -%} +BUILD_SYSTEM = "maturin" {%- else %} BUILD_SYSTEM = None {%- endif %} @@ -186,7 +188,16 @@ def clean_up_build_system(): shutil.move( build_system_dir / "hatch-pyproject.toml", PROJECT_DIRECTORY / 'pyproject.toml' - ) + ) + elif BUILD_SYSTEM == "maturin": + shutil.move( + build_system_dir / "maturin-pyproject.toml", + PROJECT_DIRECTORY / 'pyproject.toml' + ) + shutil.move( + build_system_dir / "Cargo.toml", + PROJECT_DIRECTORY / 'Cargo.toml' + ) else: shutil.move( build_system_dir / "base-pyproject.toml", @@ -241,12 +252,24 @@ def prepare_git(): print("=" * 80) +def add_binding_source_files(): + if BUILD_SYSTEM == "maturin": + build_system_dir = PROJECT_DIRECTORY / "build-system" + src_system_dir = PROJECT_DIRECTORY/ "src" + if USE_SRC_LAYOUT : + shutil.move(build_system_dir / "lib.rs", "src") + else: + os.makedir(src_system_dir) + shutil.move(build_system_dir / "lib.rs", src_system_dir) + else: + pass + def post_gen(): validation() # keep this one first, because it changes the package folder clean_up_project_layout() - + add_binding_source_files() clean_up_cli() clean_up_code_of_conduct() clean_up_conda() diff --git a/src/scicookie/profiles/base.yaml b/src/scicookie/profiles/base.yaml index 682fae6e..a6d1439d 100644 --- a/src/scicookie/profiles/base.yaml +++ b/src/scicookie/profiles/base.yaml @@ -87,6 +87,7 @@ build_system: - setuptools - pdm - hatch + - maturin enabled: false command_line_interface: diff --git a/src/scicookie/{{cookiecutter.project_slug}}/Makefile b/src/scicookie/{{cookiecutter.project_slug}}/Makefile index 470da97a..adc9c2d9 100644 --- a/src/scicookie/{{cookiecutter.project_slug}}/Makefile +++ b/src/scicookie/{{cookiecutter.project_slug}}/Makefile @@ -113,6 +113,8 @@ build: pdm build {%- elif cookiecutter.build_system == "hatch" %} hatch build +{%- elif cookiecutter.build_system == "maturin" %} + maturin build {%- endif %} .PHONY:release-ci diff --git a/src/scicookie/{{cookiecutter.project_slug}}/build-system/Cargo.toml b/src/scicookie/{{cookiecutter.project_slug}}/build-system/Cargo.toml new file mode 100644 index 00000000..bd932c8e --- /dev/null +++ b/src/scicookie/{{cookiecutter.project_slug}}/build-system/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "{{ cookiecutter.project_slug }}" +version = "0.1.0" +authors = ["{{ cookiecutter.author_full_name }} <{{ cookiecutter.author_email }}>"] +edition = "2021" + +[lib] +name = "_core" +# "cdylib" is necessary to produce a shared library for Python to import from. +crate-type = ["cdylib"] + +[dependencies] +rand = "0.8.4" + +[dependencies.pyo3] +version = "0.19.1" +# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so) +# "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8 +features = ["extension-module", "abi3-py38"] diff --git a/src/scicookie/{{cookiecutter.project_slug}}/build-system/lib.rs b/src/scicookie/{{cookiecutter.project_slug}}/build-system/lib.rs new file mode 100644 index 00000000..1aed67ef --- /dev/null +++ b/src/scicookie/{{cookiecutter.project_slug}}/build-system/lib.rs @@ -0,0 +1,24 @@ +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +#[pyfunction] +fn add(x: i64, y: i64) -> i64 { + x + y +} + +#[pyfunction] +fn subtract(x: i64, y: i64) -> i64 { + x - y +} + +/// A Python module implemented in Rust. The name of this function must match +/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to +/// import the module. +#[pymodule] +fn _core(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(add, m)?)?; + m.add_function(wrap_pyfunction!(subtract, m)?)?; + m.add("__version__", env!("CARGO_PKG_VERSION"))?; + + Ok(()) +} diff --git a/src/scicookie/{{cookiecutter.project_slug}}/build-system/maturin-pyproject.toml b/src/scicookie/{{cookiecutter.project_slug}}/build-system/maturin-pyproject.toml new file mode 100644 index 00000000..02d93bb8 --- /dev/null +++ b/src/scicookie/{{cookiecutter.project_slug}}/build-system/maturin-pyproject.toml @@ -0,0 +1,136 @@ +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + + +[project] +name = "{{ cookiecutter.project_slug }}" +description = "{{ cookiecutter.project_short_description }}" +authors = [ + { name = "{{ cookiecutter.author_full_name }}", email = "{{ cookiecutter.author_email }}" }, +] +{% if cookiecutter.project_layout == "src" -%} +packages = [ + {include = "{{ cookiecutter.package_slug }}", from="src"}, +] +{% else -%} +packages = [ + {include = "{{ cookiecutter.package_slug }}"}, +] +{% endif -%} +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" +dependencies = [ +{# keep this line here #} +{%- if cookiecutter.use_pytest == "yes" -%} + "pytest>=7.3.2", +{% if cookiecutter.use_coverage == "yes" -%} + "pytest-cov>=4.1.0", +{% endif %} +{%- endif -%}{#- end of use_pytest -#} +{%- if cookiecutter.use_hypothesis == "yes" -%} + "hypothesis>=6.0", +{% endif %} +{%- if cookiecutter.use_coverage == "yes" -%} + "coverage>=7.2.7", +{% endif %} +{%- if cookiecutter.use_blue == "yes" -%} + "blue>=0.9.1", +{% endif %} +{%- if cookiecutter.use_black == "yes" -%} + "black>=23.3.0", +{% endif %} +{%- if cookiecutter.use_isort == "yes" -%} + "isort>=5.12.0", +{% endif %} +{%- if cookiecutter.use_pre_commit -%} + "pre-commit>=3.3.2", +{% endif %} +{%- if cookiecutter.use_flake8 == "yes" -%} + "flake8>=4.0.1, <7", +{% endif %} +{%- if cookiecutter.use_ruff == "yes" -%} + "ruff>=0.0.272", +{% endif %} +{%- if cookiecutter.use_mypy == "yes" -%} + "mypy>=1.3.0", +{% endif %} +{%- if cookiecutter.use_bandit == "yes" -%} + "bandit>=1.7.5", +{% endif %} +{%- if cookiecutter.use_pydocstyle == "yes" -%} + "pydocstyle>=6.3.0", +{% endif %} +{%- if cookiecutter.use_vulture == "yes" -%} + "vulture>=2.7", +{% endif %} +{%- if cookiecutter.use_mccabe == "yes" -%} + "mccabe>=0.6.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", +{% endif %} + "ipython<8", + "ipykernel>=6.0.0", +{%- if cookiecutter.documentation_engine == 'mkdocs' -%} + "Jinja2>=3.1.2", + "mkdocs>=1.4.3", + "mkdocs-exclude>=1.0.2", + "mkdocs-jupyter>=0.24.1", + "mkdocs-literate-nav>=0.6.0", + "mkdocs-macros-plugin>=0.7.0, <1", + "mkdocs-material>=9.1.15", + "mkdocstrings>=0.21.2", + "mkdocstrings-python>=1.1.2", +{% elif cookiecutter.documentation_engine == 'sphinx' -%} + "Sphinx>=6.2.1", + "sphinx-rtd-theme>=1.2.2", + "importlib-metadata>=6.5.1", + "myst-parser>=0.19.2", + "nbsphinx>=0.9.2", + "pandoc>=2.3", +{% elif cookiecutter.documentation_engine == 'jupyter-book' -%} + "jupyter-book>=0.15.1", + "myst-parser>=0.18.1", +{% endif %} +] + +[project.urls] +Homepage = "{{ cookiecutter.project_url }}" +"Bug Tracker" = "{{ cookiecutter.project_url }}/issues" +Discussions = "{{ cookiecutter.project_url }}/discussions" +Changelog = "{{ cookiecutter.project_url }}/releases" + +[tool.maturin] +manifest-path = "Cargo.toml" + + +{% include "build-system/base-pyproject.toml" %} +{#- keep this line at the end of the file -#} diff --git a/src/scicookie/{{cookiecutter.project_slug}}/conda/dev.yaml b/src/scicookie/{{cookiecutter.project_slug}}/conda/dev.yaml index fe7cadf1..cf313282 100644 --- a/src/scicookie/{{cookiecutter.project_slug}}/conda/dev.yaml +++ b/src/scicookie/{{cookiecutter.project_slug}}/conda/dev.yaml @@ -17,6 +17,9 @@ dependencies: - pdm {%- elif cookiecutter.build_system == "hatch" %} - hatch +{%- elif cookiecutter.build_system == "maturin" %} + - maturin + - rust {%- endif %} - nodejs # used by semantic-release - shellcheck diff --git a/src/scicookie/{{cookiecutter.project_slug}}/docs/contributing.md b/src/scicookie/{{cookiecutter.project_slug}}/docs/contributing.md index a9348d05..03b75854 100644 --- a/src/scicookie/{{cookiecutter.project_slug}}/docs/contributing.md +++ b/src/scicookie/{{cookiecutter.project_slug}}/docs/contributing.md @@ -50,8 +50,12 @@ It provides an efficient and fast way to manage project dependencies, as well as build and distribute code. It is fast to install, has built-in virtualenv, offers support for different package sources, and provides an easy way to distribute code. +{%- elif cookiecutter.build_system == "hatch" -%} +In addition, you should know that to build our package we use [Hatch](https://hatch.pypa.io): It's a Python Package that is compatible build backend used by Hatch, a modern, extensible Python project manager. It provides a standardized build system with reproducible builds by default, robust environment management with support for custom scripts, easy publishing to PyPI or other indexes, version management, and configurable project generation with sane defaults. Hatchling might support multiple programming languages and offer language-specific options for building projects in different languages. It could also provide customization and extensibility options, allowing you to incorporate plugins or scripts for tailored build processes. - +{%- elif cookiecutter.build_system == "maturin" -%} +In addition, you should know that to build our package we use +[Maturin](https://pypi.org/project/maturin/0.8.2/):It's a Python packaging tool and build system for creating Python bindings from Rust projects. It enables seamless integration of Rust code into Python applications, offering efficient builds, cross-platform support, and compatibility with different Python versions. Maturin automates the process of generating Python modules that directly call Rust functions, leveraging Rust's performance and low-level capabilities in Python. With its easy-to-use interface and integration with setuptools and Cargo, Maturin provides a straightforward solution for developers seeking to combine the strengths of Python and Rust in a single project. {%- 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 f80aa5e2..1da58d21 100755 --- a/tests/smoke/base.sh +++ b/tests/smoke/base.sh @@ -51,7 +51,8 @@ elif command -v pdm &> /dev/null; then pdm install elif command -v hatch &> /dev/null; then COMMAND_PREFIX="hatch run" - +elif command -v maturin &> /dev/null; then + pip install . else # use setuptools pip install --editable . diff --git a/tests/smoke/build-system.sh b/tests/smoke/build-system.sh index 48b78208..fee7677c 100755 --- a/tests/smoke/build-system.sh +++ b/tests/smoke/build-system.sh @@ -8,3 +8,4 @@ SMOKE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" . ${SMOKE_DIR}/base.sh "build_system=setuptools" . ${SMOKE_DIR}/base.sh "build_system=pdm" . ${SMOKE_DIR}/base.sh "build_system=hatch" +. ${SMOKE_DIR}/base.sh "build_system=maturin"