diff --git a/.copier-answers.yml b/.copier-answers.yaml similarity index 86% rename from .copier-answers.yml rename to .copier-answers.yaml index f3bd003..e789630 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yaml @@ -1,7 +1,9 @@ # Changes here will be overwritten by Copier -_commit: 81e8acd +_commit: f812aaa _src_path: https://github.com/python-project-templates/base.git +add_docs: false add_extension: python +add_wiki: false email: t.paine154@gmail.com github: python-project-templates project_description: Hatch plugin for C++ builds diff --git a/.gitattributes b/.gitattributes index 092719d..8e05465 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,4 +3,11 @@ docs/* linguist-documentation *.ipynb linguist-documentation Makefile linguist-documentation +<<<<<<< before updating * text=auto eol=lf +======= +*.md text=auto eol=lf +*.py text=auto eol=lf +*.toml text=auto eol=lf +*.yaml text=auto eol=lf +>>>>>>> after updating diff --git a/.github/dependabot.yml b/.github/dependabot.yaml similarity index 66% rename from .github/dependabot.yml rename to .github/dependabot.yaml index 4d17b20..42cac77 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yaml @@ -14,11 +14,3 @@ updates: labels: - "lang: python" - "part: dependencies" - - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "monthly" - labels: - - "lang: javascript" - - "part: dependencies" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yaml similarity index 58% rename from .github/workflows/build.yml rename to .github/workflows/build.yaml index b5df3cb..6c3e975 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yaml @@ -29,18 +29,15 @@ jobs: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.9", "3.10", "3.11", "3.12"] + os: [ubuntu-latest] + python-version: ["3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - uses: actions-ext/python/setup@main with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - cache-dependency-path: 'pyproject.toml' + version: ${{ matrix.python-version }} - name: Install dependencies run: make develop @@ -56,27 +53,17 @@ jobs: - name: Test run: make coverage - if: matrix.os != 'windows-latest' - - - name: Test - run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - make coverage - shell: cmd - if: matrix.os == 'windows-latest' - + - name: Upload test results (Python) - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: test-results-${{ matrix.os }}-${{ matrix.python-version }} path: junit.xml - if: ${{ always() }} - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v2 with: files: '**/junit.xml' - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' - name: Upload coverage uses: codecov/codecov-action@v5 @@ -86,8 +73,8 @@ jobs: - name: Make dist run: make dist - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: dist-${{matrix.os}} path: dist - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.9' + if: ${{ matrix.python-version == '3.11' }} diff --git a/.github/workflows/copier.yml b/.github/workflows/copier.yaml similarity index 100% rename from .github/workflows/copier.yml rename to .github/workflows/copier.yaml diff --git a/.gitignore b/.gitignore index 5e96e1e..ed2e334 100644 --- a/.gitignore +++ b/.gitignore @@ -13,26 +13,26 @@ __pycache__/ # Distribution / packaging .Python -env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ -include/ lib/ lib64/ parts/ sdist/ var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg +MANIFEST # PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec @@ -43,18 +43,17 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache -python_junit.xml -junit.xml nosetests.xml coverage.xml -*,cover +junit.xml +*.cover +*.py,cover .hypothesis/ -.pytest_cache -.ruff_cache -js/playwright-report +.pytest_cache/ # Translations *.mo @@ -63,108 +62,98 @@ js/playwright-report # Django stuff: *.log local_settings.py +db.sqlite3 +db.sqlite3-journal -# Flask instance folder +# Flask stuff: instance/ +.webassets-cache # Scrapy stuff: .scrapy -# Sphinx documentation -docs/_build/ -docs/source - # PyBuilder target/ -# IPython Notebook -.ipynb_checkpoints -*.ipynb -.autoversion +# IPython +profile_default/ +ipython_config.py # pyenv .python-version -# celery beat schedule file +# pipenv +Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff celerybeat-schedule +celerybeat.pid -# dotenv -.env +# SageMath parsed files +*.sage.py -# virtualenv +# Environments +.env +.venv +env/ venv/ ENV/ +env.bak/ +venv.bak/ # Spyder project settings .spyderproject +.spyproject # Rope project settings .ropeproject -# ========================= -# Operating System Files -# ========================= - -# OSX -# ========================= +# mkdocs documentation +/site -.DS_Store -.AppleDouble -.LSOverride +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json -# Thumbnails -._* +# Pyre type checker +.pyre/ -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# Windows -# ========================= - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk - - -# NPM -# ---- -**/node_modules/ +# Documentation +/site +index.md +docs/_build/ +docs/src/_build/ +docs/api +docs/index.md +docs/html +docs/jupyter_execute +index.md + +# JS +js/coverage +js/dist +js/lib +js/node_modules +js/test-results +js/playwright-report +js/*.tgz +hatch_cpp/extension -# Coverage data -# ------------- -**/coverage/ +# Jupyter +.ipynb_checkpoints +.autoversion +Untitled*.ipynb +!hatch_cpp/extension/hatch_cpp.json +!hatch_cpp/extension/install.json +hatch_cpp/nbextension +hatch_cpp/labextension -# Notebook and lab extensions +# Mac +.DS_Store -nbprint/extension/* -nbprint/templates/nbprint/static/* -nbprint/voila/static/* -tmp.html -examples/output/ +# Rust +target diff --git a/LICENSE b/LICENSE index 261eeb9..95b5e23 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,11 @@ same "printed page" as the copyright notice for easier identification within third-party archives. +<<<<<<< before updating Copyright [yyyy] [name of copyright owner] +======= + Copyright 2025 the hatch-cpp authors +>>>>>>> after updating Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile index f1ad97c..e55cbef 100644 --- a/Makefile +++ b/Makefile @@ -4,31 +4,43 @@ .PHONY: develop build install develop: ## install dependencies and build library - python -m pip install -e .[develop] + uv pip install -e .[develop] + +requirements: ## install prerequisite python build requirements + python -m pip install --upgrade pip toml + python -m pip install `python -c 'import toml; c = toml.load("pyproject.toml"); print("\n".join(c["build-system"]["requires"]))'` + python -m pip install `python -c 'import toml; c = toml.load("pyproject.toml"); print(" ".join(c["project"]["optional-dependencies"]["develop"]))'` build: ## build the python library python -m build -n install: ## install library - python -m pip install . + uv pip install . ######### # LINTS # ######### -.PHONY: lint lints fix format +.PHONY: lint-py lint-docs fix-py fix-docs lint lints fix format -lint: ## run python linter with ruff +lint-py: ## lint python with ruff python -m ruff check hatch_cpp python -m ruff format --check hatch_cpp -# Alias -lints: lint +lint-docs: ## lint docs with mdformat and codespell + python -m mdformat --check README.md + python -m codespell_lib README.md -fix: ## fix python formatting with ruff +fix-py: ## autoformat python code with ruff python -m ruff check --fix hatch_cpp python -m ruff format hatch_cpp -# alias +fix-docs: ## autoformat docs with mdformat and codespell + python -m mdformat README.md + python -m codespell_lib --write README.md + +lint: lint-py lint-docs ## run all linters +lints: lint +fix: fix-py fix-docs ## run all autoformatters format: fix ################ @@ -88,7 +100,7 @@ dist-check: ## run python dist checker with twine dist: clean dist-build dist-check ## build all dists -publish: dist # publish python assets +publish: dist ## publish python assets ######### # CLEAN # diff --git a/README.md b/README.md index 1f26618..31715dd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Hatch plugin for C++ builds -[![Build Status](https://github.com/python-project-templates/hatch-cpp/actions/workflows/build.yml/badge.svg?branch=main&event=push)](https://github.com/python-project-templates/hatch-cpp/actions/workflows/build.yml) +[![Build Status](https://github.com/python-project-templates/hatch-cpp/actions/workflows/build.yaml/badge.svg?branch=main&event=push)](https://github.com/python-project-templates/hatch-cpp/actions/workflows/build.yaml) [![codecov](https://codecov.io/gh/python-project-templates/hatch-cpp/branch/main/graph/badge.svg)](https://codecov.io/gh/python-project-templates/hatch-cpp) [![License](https://img.shields.io/github/license/python-project-templates/hatch-cpp)](https://github.com/python-project-templates/hatch-cpp) [![PyPI](https://img.shields.io/pypi/v/hatch-cpp.svg)](https://pypi.python.org/pypi/hatch-cpp) @@ -19,17 +19,81 @@ libraries = [ ``` For more complete systems, see: + - [scikit-build-core](https://github.com/scikit-build/scikit-build-core) - [setuptools](https://setuptools.pypa.io/en/latest/userguide/ext_modules.html) -## Environment Variables -| Name | Default | Description | -|:-----|:--------|:------------| -|`CC`| | | -|`CXX`| | | -|`LD`| | | -|`HATCH_CPP_PLATFORM`| | | -|`HATCH_CPP_DISABLE_CCACHE`| | | +## Configuration + +Configuration is driven from the `[tool.hatch.build.hooks.hatch-cpp]` hatch hook configuration field in a `pyproject.toml`. +It is designed to closely match existing Python/C/C++ packaging tools. + +```toml +verbose = true +libraries = { Library Args } +cmake = { CMake Args } +platform = { Platform, either "linux", "darwin", or "win32" } +``` + +See the [test cases](./hatch_cpp/tests/) for more concrete examples. + +`hatch-cpp` is driven by [pydantic](https://docs.pydantic.dev/latest/) models for configuration and execution of the build. +These models can themselves be overridden by setting `build-config-class` / `build-plan-class`. + +### Library Arguments + +```toml +name = "mylib" +sources = [ + "path/to/file.cpp", +] +language = "c++" + +binding = "cpython" # or "pybind11", "nanobind", "generic" +std = "" # Passed to -std= or /std: + +include_dirs = ["paths/to/add/to/-I"] +library_dirs = ["paths/to/add/to/-L"] +libraries = ["-llibraries_to_link"] + +extra_compile_args = ["--extra-compile-args"] +extra_link_args = ["--extra-link-args"] +extra_objects = ["extra_objects"] + +define_macros = ["-Ddefines_to_use"] +undef_macros = ["-Uundefines_to_use"] + +py_limited_api = "cp39" # limited API to use +``` + +### CMake Arguments + +`hatch-cpp` has some convenience integration with CMake. +Though this is not designed to be as full-featured as e.g. `scikit-build`, it should be satisfactory for many small projects. + +```toml +root = "path/to/cmake/root" +build = "path/to/cmake/build/folder" +install = "path/to/cmake/install/folder" + +cmake_arg_prefix = "MYPROJECT_" +cmake_args = {} # any other cmake args to pass +cmake_env_args = {} # env-specific cmake args to pass + +include_flags = {} # include flags to pass -D +``` + +### Environment Variables + +`hatch-cpp` will respect standard environment variables for compiler control. + +| Name | Default | Description | +| :------------------------- | :------ | :-------------------- | +| `CC` | | C Compiler override | +| `CXX` | | C++ Compiler override | +| `LD` | | Linker override | +| `HATCH_CPP_PLATFORM` | | Platform to build | +| `HATCH_CPP_DISABLE_CCACHE` | | Disable CCache usage | > [!NOTE] > This library was generated using [copier](https://copier.readthedocs.io/en/stable/) from the [Base Python Project Template repository](https://github.com/python-project-templates/base). diff --git a/hatch_cpp/__init__.py b/hatch_cpp/__init__.py index d3dfbab..2f17aa3 100644 --- a/hatch_cpp/__init__.py +++ b/hatch_cpp/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.7" +__version__ = "0.1.8" from .hooks import hatch_register_build_hook from .plugin import HatchCppBuildHook diff --git a/hatch_cpp/structs.py b/hatch_cpp/structs.py index 75df2d8..5884988 100644 --- a/hatch_cpp/structs.py +++ b/hatch_cpp/structs.py @@ -102,8 +102,15 @@ def default() -> HatchCppPlatform: toolchain = "clang" elif "cl" in CC and "cl" in CXX: toolchain = "msvc" + # Fallback to platform defaults + elif platform == "linux": + toolchain = "gcc" + elif platform == "darwin": + toolchain = "clang" + elif platform == "win32": + toolchain = "msvc" else: - raise Exception(f"Unrecognized toolchain: {CC}, {CXX}") + toolchain = "gcc" # Customizations if which("ccache") and not environ.get("HATCH_CPP_DISABLE_CCACHE"): @@ -117,6 +124,12 @@ def default() -> HatchCppPlatform: # LD = which("ld.lld") return HatchCppPlatform(cc=CC, cxx=CXX, ld=LD, platform=platform, toolchain=toolchain) + @staticmethod + def platform_for_toolchain(toolchain: CompilerToolchain) -> HatchCppPlatform: + platform = HatchCppPlatform.default() + platform.toolchain = toolchain + return platform + def get_compile_flags(self, library: HatchCppLibrary, build_type: BuildType = "release") -> str: flags = "" @@ -241,11 +254,27 @@ class HatchCppBuildConfig(BaseModel): cmake: Optional[HatchCppCmakeConfiguration] = Field(default=None) platform: Optional[HatchCppPlatform] = Field(default_factory=HatchCppPlatform.default) - @model_validator(mode="after") - def check_toolchain_matches_args(self): - if self.cmake and self.libraries: + @model_validator(mode="wrap") + @classmethod + def validate_model(cls, data, handler): + if "toolchain" in data: + data["platform"] = HatchCppPlatform.platform_for_toolchain(data["toolchain"]) + data.pop("toolchain") + elif "platform" not in data: + data["platform"] = HatchCppPlatform.default() + if "cc" in data: + data["platform"].cc = data["cc"] + data.pop("cc") + if "cxx" in data: + data["platform"].cxx = data["cxx"] + data.pop("cxx") + if "ld" in data: + data["platform"].ld = data["ld"] + data.pop("ld") + model = handler(data) + if model.cmake and model.libraries: raise ValueError("Must not provide libraries when using cmake toolchain.") - return self + return model class HatchCppBuildPlan(HatchCppBuildConfig): diff --git a/hatch_cpp/tests/test_project_cmake/project/include/project/basic.hpp b/hatch_cpp/tests/test_project_cmake/project/include/project/basic.hpp new file mode 100644 index 0000000..65cb62e --- /dev/null +++ b/hatch_cpp/tests/test_project_cmake/project/include/project/basic.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "Python.h" + +PyObject* hello(PyObject*, PyObject*); + +static PyMethodDef extension_methods[] = { + {"hello", (PyCFunction)hello, METH_NOARGS}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyModuleDef extension_module = { + PyModuleDef_HEAD_INIT, "extension", "extension", -1, extension_methods}; + +PyMODINIT_FUNC PyInit_extension(void) { + Py_Initialize(); + return PyModule_Create(&extension_module); +} diff --git a/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.cpp b/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.cpp new file mode 100644 index 0000000..db4432a --- /dev/null +++ b/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.cpp @@ -0,0 +1,5 @@ +#include "project/basic.hpp" + +PyObject* hello(PyObject*, PyObject*) { + return PyUnicode_FromString("A string"); +} diff --git a/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.hpp b/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.hpp new file mode 100644 index 0000000..65cb62e --- /dev/null +++ b/hatch_cpp/tests/test_project_override_toolchain/cpp/project/basic.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "Python.h" + +PyObject* hello(PyObject*, PyObject*); + +static PyMethodDef extension_methods[] = { + {"hello", (PyCFunction)hello, METH_NOARGS}, + {nullptr, nullptr, 0, nullptr} +}; + +static PyModuleDef extension_module = { + PyModuleDef_HEAD_INIT, "extension", "extension", -1, extension_methods}; + +PyMODINIT_FUNC PyInit_extension(void) { + Py_Initialize(); + return PyModule_Create(&extension_module); +} diff --git a/hatch_cpp/tests/test_project_override_toolchain/project/__init__.py b/hatch_cpp/tests/test_project_override_toolchain/project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hatch_cpp/tests/test_project_override_toolchain/pyproject.toml b/hatch_cpp/tests/test_project_override_toolchain/pyproject.toml new file mode 100644 index 0000000..b5c40ca --- /dev/null +++ b/hatch_cpp/tests/test_project_override_toolchain/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = ["hatchling>=1.20"] +build-backend = "hatchling.build" + +[project] +name = "hatch-cpp-test-project-toolchain" +description = "Toolchain override test project for hatch-cpp" +version = "0.1.0" +requires-python = ">=3.9" +dependencies = [ + "hatchling>=1.20", + "hatch-cpp", +] + +[tool.hatch.build] +artifacts = [ + "project/*.dll", + "project/*.dylib", + "project/*.so", +] + +[tool.hatch.build.sources] +src = "/" + +[tool.hatch.build.targets.sdist] +packages = ["project"] + +[tool.hatch.build.targets.wheel] +packages = ["project"] + +[tool.hatch.build.hooks.hatch-cpp] +verbose = true +libraries = [ + {name = "project/extension", sources = ["cpp/project/basic.cpp"], include-dirs = ["cpp"]} +] +toolchain = "gcc" +cc = "clang" +cxx = "clang++" diff --git a/hatch_cpp/tests/test_projects.py b/hatch_cpp/tests/test_projects.py index 7553c04..dd4c6fc 100644 --- a/hatch_cpp/tests/test_projects.py +++ b/hatch_cpp/tests/test_projects.py @@ -13,6 +13,8 @@ class TestProject: [ "test_project_basic", "test_project_override_classes", + "test_project_override_classes", + "test_project_override_toolchain", "test_project_pybind", "test_project_nanobind", "test_project_limited_api", @@ -29,8 +31,7 @@ def test_basic(self, project): # compile check_call( [ - "hatchling", - "build", + "hatch-build", "--hooks-only", ], cwd=f"hatch_cpp/tests/{project}", diff --git a/hatch_cpp/tests/test_structs.py b/hatch_cpp/tests/test_structs.py index fc36d9a..263b917 100644 --- a/hatch_cpp/tests/test_structs.py +++ b/hatch_cpp/tests/test_structs.py @@ -46,3 +46,11 @@ def test_cmake_args(self): assert f"-DHATCH_CPP_TEST_PROJECT_BASIC_PYTHON_VERSION=3.{version_info.minor}" in hatch_build_plan.commands[0] if hatch_build_plan.platform.platform == "darwin": assert "-DCMAKE_OSX_DEPLOYMENT_TARGET=11" in hatch_build_plan.commands[0] + + def test_platform_toolchain_override(self): + txt = (Path(__file__).parent / "test_project_override_toolchain" / "pyproject.toml").read_text() + toml = loads(txt) + hatch_build_config = HatchCppBuildConfig(name=toml["project"]["name"], **toml["tool"]["hatch"]["build"]["hooks"]["hatch-cpp"]) + assert "clang" in hatch_build_config.platform.cc + assert "clang++" in hatch_build_config.platform.cxx + assert hatch_build_config.platform.toolchain == "gcc" diff --git a/pyproject.toml b/pyproject.toml index cdbf5d7..70d89ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [{name = "the hatch-cpp authors", email = "t.paine154@gmail.com"}] description = "Hatch plugin for C++ builds" readme = "README.md" license = { text = "Apache-2.0" } -version = "0.1.7" +version = "0.1.8" requires-python = ">=3.9" keywords = [ "hatch", @@ -20,6 +20,7 @@ keywords = [ ] classifiers = [ "Development Status :: 3 - Alpha", + "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -28,7 +29,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dependencies = [ @@ -41,11 +43,19 @@ develop = [ "build", "bump-my-version", "check-manifest", - "ruff>=0.3,<0.9", + "codespell>=2.4,<2.5", + "hatchling", + "hatch-build", + "mdformat>=0.7.22,<0.8", + "mdformat-tables>=1", + "pytest", + "pytest-cov", + "ruff", "twine", + "uv", "wheel", # test - "nanobind", + "nanobind<2.10.0", # https://github.com/wjakob/nanobind/commit/abd27e3b5565bc95f5091321f0f863fce8b5b95b "pybind11", "pytest", "pytest-cov", @@ -63,9 +73,10 @@ Repository = "https://github.com/python-project-templates/hatch-cpp" Homepage = "https://github.com/python-project-templates/hatch-cpp" [tool.bumpversion] -current_version = "0.1.7" +current_version = "0.1.8" commit = true tag = false +commit_args = "-s" [[tool.bumpversion.files]] filename = "hatch_cpp/__init__.py" @@ -79,9 +90,8 @@ replace = 'version = "{new_version}"' [tool.check-manifest] ignore = [ - ".copier-answers.yml", + ".copier-answers.yaml", "Makefile", - "setup.py", "docs/**/*", ] @@ -97,7 +107,7 @@ exclude_also = [ "@(abc\\.)?abstractmethod", ] ignore_errors = true -fail_under = 70 +fail_under = 50 [tool.hatch.build] artifacts = [] @@ -113,7 +123,6 @@ packages = ["hatch_cpp"] [tool.pytest.ini_options] addopts = ["-vvv", "--junitxml=junit.xml"] -asyncio_mode = "strict" testpaths = "hatch_cpp/tests" [tool.ruff]