diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..887a2c18 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# SCM syntax highlighting & preventing 3-way merges +pixi.lock merge=binary linguist-language=YAML linguist-generated=true diff --git a/.gitignore b/.gitignore index 7045bc61..ea448d57 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,11 @@ build/ .nfs* .coverage htmlcov* -cherab.egg-info/ \ No newline at end of file +cherab.egg-info/ + +# pixi environments +.pixi/* +!.pixi/config.toml + +# Ignore lock files until we start using them +pixi.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 513c1bc1..bc9d8b42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Bug fixes: * Fix the import statement for `netcdf_file` in `calcam.py` for compatibility with the upcoming `scipy` v2.0.0. (#510) New: +* Add an optional Pixi workspace for package builds and isolated development environments, with tasks for testing, documentation, formatting, and static analysis, and include the corresponding developer guide in the Sphinx documentation. (#489) * Add GaussianQuadrature2D integrator. (#475) * Support Raysect 0.9. (#486) * Test against Python 3.9, 3.10, 3.11, 3.12, 3.13 and latest released Numpy. Drop Python 3.7, 3.8 and older Numpy from tests. (#486) diff --git a/dev/pixi.md b/dev/pixi.md new file mode 100644 index 00000000..6b19fa1a --- /dev/null +++ b/dev/pixi.md @@ -0,0 +1,181 @@ +# Pixi developer guide + +This document describes the development environments and tasks configured in +[`pixi.toml`](https://github.com/cherab/core/blob/development/pixi.toml). Pixi +manages the development dependencies in isolated environments and provides a +common interface for running tests, building the documentation, and checking +the source tree. + +Pixi installs or updates the selected environment automatically when a command +is run. The workspace currently supports Linux x86-64, macOS x86-64, and macOS +Arm64. + +## ๐Ÿ”Ž Discovering tasks + +List every task in the workspace with: + +```console +pixi task list +``` + +To show only the tasks available in a particular environment, pass its name: + +```console +pixi task list -e test +``` + +Use `pixi run --help` for general command help. When a task is available in +more than one environment, use `-e ` to select the environment +explicitly. + +## ๐Ÿงฉ Environments + +| Environment | Purpose | +| --- | --- | +| `default` | Basic development tools; Cherab is not installed | +| `test` | Run the complete test suite using the latest supported Python; currently equivalent to `test-pylatest` | +| `test-pylatest` | Run the complete test suite using the latest supported Python | +| `test-pyoldest` | Run the complete test suite using the oldest supported Python | +| `test-opencl` | Run the OpenCL SART tests with Cherab's `opencl` extra | +| `docs` | Build the documentation | +| `lint` | Run formatting and static-analysis tools without installing Cherab | + +See the [`pyoldest` and `pylatest` features and environment definitions in +`pixi.toml`](https://github.com/cherab/core/blob/development/pixi.toml#:~:text=Python%20Version%20Features) +for the Python versions used by each environment. + +## ๐Ÿ› ๏ธ Basic development tasks + +Start an IPython session in the default environment: + +```console +pixi run ipython +``` + +Remove generated C/Cython libraries and HTML files from the `cherab/` source +tree: + +```console +pixi run clean +``` + +The `clean` task deletes files matching `*.c`, `*.so`, `*.pyd`, `*.dll`, and +`*.html` below `cherab/`. + +## ๐Ÿงช Testing + +Run the complete test suite with the latest supported Python: + +```console +pixi run -e test test +``` + +The `test` environment currently uses the same solve group as +`test-pylatest`. The explicit alias can also be used: + +```console +pixi run -e test-pylatest test +``` + +Run the suite with the oldest supported Python: + +```console +pixi run -e test-pyoldest test +``` + +Run the OpenCL SART tests: + +```console +pixi run -e test-opencl test-opencl +``` + +The regular test task runs `python -m unittest discover cherab -v`. The OpenCL +task runs `cherab.tools.tests.test_sart_opencl` only. + +## ๐Ÿ“š Documentation + +Build the HTML documentation: + +```console +pixi run -e docs doc-build +``` + +`html` is the default Sphinx builder. A different builder can be supplied as +the final argument; for example, check external and internal links with: + +```console +pixi run -e docs doc-build linkcheck +``` + +Build output is written below `docs/build/`. Remove all documentation +build output with: + +```console +pixi run doc-clean +``` + +After building the HTML documentation, serve it locally on port 8000 with: + +```console +pixi run doc-serve +``` + +Then open in a browser. To use a different port, pass +it as the final argument: + +```console +pixi run doc-serve 8080 +``` + +## ๐Ÿงน Formatting and static analysis + +The `lint` environment keeps code-quality tools separate from the environments +that build and install Cherab. Because the task names below are unique to this +environment, Pixi selects it automatically; `-e lint` is not required. + +| Task | Action | +| --- | --- | +| `lefthook` | Run Lefthook | +| `hooks` | Install the Git hooks managed by Lefthook | +| `pre-commit` | Run the Lefthook `pre-commit` group | +| `ruff-check` | Run `ruff check` | +| `ruff-format` | Run `ruff format` | +| `toml-format` | Run `tombi format` | +| `dprint` | Run `dprint fmt` | +| `typos` | Find and fix spelling errors | +| `actionlint` | Run Actionlint | +| `blacken-docs` | Format Python examples in documentation | +| `validate-pyproject` | Validate `pyproject.toml` | +| `cython-lint` | Run Cython-Lint | +| `lint` | Run the Lefthook `pre-commit` group on all files | + +For example: + +```console +pixi run ruff-check +pixi run toml-format +pixi run cython-lint +pixi run validate-pyproject +``` + +The `hooks`, `lefthook`, `pre-commit`, and aggregate `lint` tasks invoke +Lefthook. + +> [!WARNING] +> A Lefthook configuration file has not been added to the repository yet, so +> these tasks are not currently available. + +Install the Git hooks and run all configured checks with: + +```console +pixi run hooks +pixi run lint +``` + +Running `pixi run hooks` installs the Git hooks once. After installation, the +configured pre-commit checks are triggered automatically for every commit. To +remove the installed hooks and stop the automatic checks, run: + +```console +pixi run lefthook uninstall +``` diff --git a/docs/source/conf.py b/docs/source/conf.py index b2f22c70..c268831e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -40,6 +40,7 @@ 'sphinx.ext.autodoc', 'sphinx.ext.mathjax', 'sphinx_tabs.tabs', + 'myst_parser', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/development/pixi.rst b/docs/source/development/pixi.rst new file mode 100644 index 00000000..0be6bd02 --- /dev/null +++ b/docs/source/development/pixi.rst @@ -0,0 +1,2 @@ +.. include:: ../../../dev/pixi.md + :parser: myst_parser.sphinx_ diff --git a/docs/source/index.rst b/docs/source/index.rst index 3e3e90ce..72f6cda6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -28,6 +28,14 @@ become stable until we have finished moving the source code to github. tools/tools +.. toctree:: + :maxdepth: 2 + :caption: Development + :name: development + + development/pixi + + .. toctree:: :maxdepth: 2 :caption: Demonstrations @@ -41,4 +49,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` - diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 00000000..64d3c551 --- /dev/null +++ b/pixi.toml @@ -0,0 +1,167 @@ +[workspace] +channels = ["conda-forge"] +platforms = ["linux-64", "osx-arm64", "osx-64"] +preview = ["pixi-build"] + +[workspace.build-variants] +python = [ + "3.9.*", + "3.10.*", + "3.11.*", + "3.12.*", + "3.13.*", + "3.14.*", +] + +# ------------------------------- +# === Packaging Configuration === +# ------------------------------- +[package] +name = "cherab" +version = "dynamic" + +[package.build.backend] +name = "pixi-build-python" +version = "*" + +[package.build.config] +compilers = ["c"] + +[package.host-dependencies] +python = "*" +setuptools = "*" +cython = ">=3.1" +numpy = "*" +raysect = "0.9.*" + +[package.run-dependencies] +scipy = "*" +matplotlib-base = "*" + +[package.extra-dependencies.opencl] +pyopencl = "*" +pocl = "*" + +# --------------------------------- +# === Development Configuration === +# --------------------------------- +[dependencies] +ipython = "*" + +[dev] +cherab = { path = "." } + +[tasks] +clean = { + cmd = "find cherab -type f \\( -name '*.c' -o -name '*.so' -o -name '*.pyd' -o -name '*.dll' -o -name '*.html' \\) -delete", + description = "๐Ÿ”ฅ Remove in-place build artifacts and temporary files (*.c, *.so, *.pyd, *.dll, *.html)", +} + +# The documentation-related tasks below do not require the source package. +doc-clean = { + cmd = "rm -rf build", + cwd = "docs", + description = "๐Ÿ”ฅ Clean the docs build directory", +} +doc-serve = { + cmd = [ + "python", + "-m", + "http.server", + "{{ port }}", + "--directory", + "build/html", + ], + cwd = "docs", + args = [ + { arg = "port", default = "8000" }, + ], + description = "๐Ÿš€ Start a local server for the docs", +} + +# === Testing feature === +[feature.test.dependencies] +cherab = { path = "." } + +[feature.test.tasks] +test = { + cmd = "python -m unittest discover cherab -v", + description = "๐Ÿงช Run the tests", +} + +[feature.test-opencl.dependencies] +cherab = { path = ".", extras = ["opencl"] } + +[feature.test-opencl.tasks] +test-opencl = { + cmd = "python -m unittest cherab.tools.tests.test_sart_opencl -v", + description = "๐Ÿงช Run the OpenCL tests", +} + +# === Documentation feature === +[feature.docs.dependencies] +cherab = { path = "." } +sphinx = "*" +sphinx_rtd_theme = "<1" # TODO: change to >=1.0 when our docs layout is compatible with the new theme +sphinx-tabs = "*" +myst-parser = "*" + +[feature.docs.tasks] +doc-build = { + cmd = [ + "sphinx-build", + "-b", + "{{ target }}", + "source", + "build/{{ target }}", + ], + cwd = "docs", + args = [ + { arg = "target", default = "html" }, + ], + description = "๐Ÿ“ Build the docs" +} + +# === Linting feature === +[feature.lint.dependencies] +dprint = "*" +lefthook = "*" +ruff = "*" +typos = "*" +actionlint = "*" +shellcheck = "*" +validate-pyproject = "*" +cython-lint = "*" +blacken-docs = "*" +tombi = "*" + +[feature.lint.tasks] +lefthook = { cmd = "lefthook", description = "๐Ÿ”— Run lefthook" } +hooks = { cmd = "lefthook install", description = "๐Ÿ”— Install pre-commit hooks" } +pre-commit = { cmd = "lefthook run pre-commit", description = "๐Ÿ”— Run pre-commit checks" } +ruff-check = { cmd = "ruff check", description = "Lint with ruff" } +ruff-format = { cmd = "ruff format", description = "Format with ruff" } +toml-format = { cmd = "tombi format", description = "Format TOML files" } +dprint = { cmd = "dprint fmt", description = "Format with dprint" } +typos = { cmd = "typos --write-changes --force-exclude", description = "Fix typos" } +actionlint = { cmd = "actionlint", description = "Lint actions with actionlint" } +blacken-docs = { cmd = "blacken-docs", description = "Format Python markdown blocks with Black" } +validate-pyproject = { cmd = "validate-pyproject pyproject.toml", description = "Validate pyproject.toml" } +cython-lint = { cmd = "cython-lint", description = "Lint Cython files" } +lint = { cmd = "lefthook run pre-commit --all-files --force", description = "๐Ÿงน Run all linters" } + +# === Python Version Features === +[feature.pyoldest.dependencies] +python = "3.9.*" + +[feature.pylatest.dependencies] +python = "3.14.*" + +[environments] +default = { features = ["pylatest"], solve-group = "pylatest" } +test = { features = ["test"], solve-group = "pylatest" } +docs = { features = ["pyoldest", "docs"], solve-group = "pyoldest" } # TODO: change to pylatest when bumping RTD theme to >=1.0 +test-pylatest = { features = ["pylatest", "test"], solve-group = "pylatest" } # alias of test +test-pyoldest = { features = ["pyoldest", "test"], solve-group = "pyoldest" } +test-opencl = { features = ["test-opencl"], solve-group = "pyoldest" } +lint = { features = ["lint"], no-default-feature = true }