diff --git a/.github/workflows/TestWithSinglePython.yml b/.github/workflows/TestWithSinglePython.yml index d38a539..e5dbf8e 100644 --- a/.github/workflows/TestWithSinglePython.yml +++ b/.github/workflows/TestWithSinglePython.yml @@ -48,3 +48,12 @@ jobs: with: token: ${{secrets.QLTY_COVERAGE_TOKEN}} files: cov.info + - name: Build wheel + run: | + python -m pip install build + python -m build --wheel + - name: Smoke test wheel import + run: | + python -m venv /tmp/smoke + /tmp/smoke/bin/pip install dist/*.whl + /tmp/smoke/bin/python -c "import debrisframe" diff --git a/.github/workflows/buildAndUploadPyPi.yml b/.github/workflows/buildAndUploadPyPi.yml new file mode 100644 index 0000000..365c4d8 --- /dev/null +++ b/.github/workflows/buildAndUploadPyPi.yml @@ -0,0 +1,90 @@ +# Build DebrisFrame and publish it to TestPyPI and PyPI. +# +# Release-first flow (matches AvaFrame): +# 1. Create a GitHub release for the new version tag (X.Y.Z). This creates the +# tag and triggers Zenodo, which archives the release and mints the DOI. +# 2. Dispatch this workflow from the Actions tab, selecting that release tag in +# the "Use workflow from" dropdown. +# +# hatch-vcs derives the version from the git tag, so the workflow must run against +# the tag; otherwise the build gets a local version segment (e.g. +g) that +# PyPI rejects. The guard step below enforces this. + +name: Build and upload to PyPI + +on: + workflow_dispatch: + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Verify the workflow runs on a release tag + if: github.ref_type != 'tag' + run: | + echo "::error::Dispatch this workflow against a release tag (X.Y.Z), not '${{ github.ref_name }}'." + exit 1 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install build tools + run: | + python -m pip install --upgrade pip + pip install build twine + + - name: Build sdist and wheel + run: python -m build + + - name: Check distribution metadata + run: twine check dist/* + + - name: Smoke test wheel install + run: | + python -m venv /tmp/smoke + /tmp/smoke/bin/pip install dist/*.whl + /tmp/smoke/bin/python -c "import debrisframe" + + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/* + + publish_testpypi: + name: Publish to TestPyPI + needs: build + runs-on: ubuntu-latest + environment: testpypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v8 + with: + name: dist + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish_pypi: + name: Publish to PyPI + needs: publish_testpypi + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v8 + with: + name: dist + path: dist + + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/README.md b/README.md index cc7c9ba..3d0f680 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # DebrisFrame -How to install DebrisFrame can be found [here](https://docs.debrisframe.org/en/latest/installation.html#). +[![PyPI](https://img.shields.io/pypi/v/debrisframe.svg)](https://pypi.org/project/debrisframe/) + +Install from PyPI: + +``` +pip install debrisframe +``` + +How to install DebrisFrame from source can be found [here](https://docs.debrisframe.org/en/latest/installation.html#). [![Maintainability](https://qlty.sh/badges/0bf94b24-fee3-4fb1-9520-9e09adfe7195/maintainability.svg)](https://qlty.sh/gh/OpenNHM/projects/DebrisFrame) diff --git a/docs/source/index.rst b/docs/source/index.rst index a9bba23..0afb473 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -20,6 +20,7 @@ General * :doc:`introduction` * :doc:`installation` * :doc:`workflow` + * :doc:`release` .. toctree:: :maxdepth: 4 @@ -29,6 +30,7 @@ General introduction.rst installation.rst workflow.rst + release.rst Computational modules diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 1a3357c..f492252 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -1,5 +1,17 @@ Install DebrisFrame --------------------- + +From PyPI +^^^^^^^^^ + +:: + + pip install debrisframe + +This installs AvaFrame (``avaframe>=2.2b1``) as a dependency. + +From source +^^^^^^^^^^^ Running DebrisFrame means running AvaFrame's com1DFA with parameters for debris flows (:py:mod:`c1TIF`). Therefore, follow AvaFrame's `installation instructions `_ first. diff --git a/docs/source/release.rst b/docs/source/release.rst new file mode 100644 index 0000000..b3370c1 --- /dev/null +++ b/docs/source/release.rst @@ -0,0 +1,37 @@ +Release procedure +----------------- + +DebrisFrame is versioned from git tags via ``hatch-vcs`` and published to PyPI by the +``Build and upload to PyPI`` GitHub Actions workflow. Releases follow a release-first flow: +the GitHub release is created first (triggering Zenodo), then the PyPI workflow is run +against that release tag. + +Prerequisites (one-time) +^^^^^^^^^^^^^^^^^^^^^^^^ + +- PyPI and TestPyPI trusted publishers configured for the repository: + + - owner: ``OpenNHM`` + - repository: ``DebrisFrame`` + - workflow filename: ``buildAndUploadPyPi.yml`` + - environment: ``pypi`` (PyPI) and ``testpypi`` (TestPyPI) + +- GitHub environments ``pypi`` and ``testpypi`` created in the repository settings. +- The repository linked to Zenodo, with ``.zenodo.json`` present on ``master``. + +Create a release +^^^^^^^^^^^^^^^^ + +1. Make sure all packaging changes are merged into ``master``. +2. Create a new GitHub release and set its tag to the new version, e.g. ``X.Y.Z`` (GitHub + creates the tag if it does not exist). Publishing the release makes Zenodo archive it + and mint the DOI. +3. Run the ``Build and upload to PyPI`` workflow from the Actions tab + (``workflow_dispatch``). In the "Use workflow from" dropdown, select the release tag + ``X.Y.Z`` so the build runs on the tagged commit. +4. The workflow builds the sdist and wheel, checks the metadata, publishes to TestPyPI, + and then to PyPI. + +The version is taken from the git tag. If the workflow does not run on a version tag, +hatch-vcs adds a local version segment (e.g. ``+g``) that PyPI rejects; the workflow +fails early in that case. diff --git a/pyproject.toml b/pyproject.toml index 034df57..76f4377 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,18 +7,17 @@ name = "debrisframe" description = "The Open Debris flow Framework" readme = "README.md" dynamic = ["version"] -license = { text = "EUPL" } +license = "EUPL-1.2" authors = [ { name = "DebrisFrame Contributors", email = "felix@avaframe.org" } ] -urls = { Homepage = "http://debrisframe.org" } +keywords = ["debris flow", "natural hazards", "simulation", "open source"] classifiers = [ # How mature is this project? Common values are # 3 - Alpha 4 - Beta 5 - Production/Stable "Development Status :: 3 - Alpha", # Indicate who your project is intended for "Intended Audience :: Science/Research", - "License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -27,8 +26,14 @@ classifiers = [ ] requires-python = ">=3.9,<3.14" dependencies = [ + "avaframe>=2.2b1", ] - + +[project.urls] +Homepage = "http://opennhm.org" +Repository = "https://github.com/OpenNHM/DebrisFrame" +Issues = "https://github.com/OpenNHM/DebrisFrame/issues" + [tool.flake8] max-line-length = 109 select = ['B950'] @@ -44,6 +49,13 @@ source = "vcs" version_scheme = "no-guess-dev" local_scheme = "node-and-date" +[tool.hatch.build.targets.wheel] +packages = ["debrisframe"] +exclude = ["debrisframe/tests", "debrisframe/data"] + +[tool.hatch.build.targets.sdist] +exclude = ["debrisframe/tests", "debrisframe/data"] + [tool.pixi.workspace] channels = ["conda-forge/label/qgis_ltr", "https://prefix.dev/conda-forge"] platforms = ["linux-64", "win-64", "osx-64"] @@ -67,6 +79,12 @@ pixi-pycharm = "*" debrisframe = { path = "./", editable = true } avaframe = { path = "../AvaFrame", editable = true } +# The local editable AvaFrame reports a pre-release dev version (e.g. 2.1+57.g...), +# which cannot satisfy debrisframe's published avaframe>=2.2b1 pin. Relax the +# constraint for local development; the published metadata keeps the pin. +[tool.pixi.feature.dev.pypi-options.dependency-overrides] +avaframe = ">=2.1" + # Environments [tool.pixi.environments] default = { features = ["dev"], solve-group = "default" } diff --git a/setup.py b/setup.py deleted file mode 100644 index f4ef80c..0000000 --- a/setup.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -"""Setup file for the avaframe package. - -important commands: -python setup.py sdist -python setup.py build_ext --inplace -python setup.py bdist_wheel -twine uploade dist/* - -To create a release (with github): -- update the version number below -- push to github -- create a release there -- run releaseWithMany.. in the actions tab - -""" - -# from setuptools import setup, find_packages # Always prefer setuptools -from setuptools import Extension, setup, find_packages -from pathlib import Path -import sys - -sys.path.append(str(Path(__file__).parent)) - -setup( - # Find packages automatically - packages=find_packages(exclude=["docs"]), - # Include package data - include_package_data=True, - # Run build_ext - # options=setup_options, - # Executable scripts - entry_points={}, - zip_safe=False, - # ext_modules=extensions, -)