From c59cdd1c9a5b9be2e6d2fc75308501814e7c4dc3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 07:19:12 +0000 Subject: [PATCH 1/8] build: require setuptools>=77 for the SPDX license expression `license = "Apache-2.0"` is a PEP 639 SPDX expression, only understood by setuptools >= 77, while the build requirement allowed >= 64: a build without isolation (`python -m build --no-isolation`, as the manual release procedure used to do) with an older setuptools fails with a configuration error on `project.license`. AI-Level: 8 (bots planned, human approved) AI-Model: claude-sonnet-5-5 Co-Authored-By: Claude Sonnet 5.5 --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 64124533..8efb3617 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [build-system] -requires = ["setuptools>=64", "setuptools-scm>8"] +# setuptools>=77 is needed for the SPDX `license` expression (PEP 639) +requires = ["setuptools>=77", "setuptools-scm>8"] build-backend = "setuptools.build_meta" [project] From bc07a3292596736e9fcd0d699a8f2624f291666c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 07:19:12 +0000 Subject: [PATCH 2/8] build: drop the unused setuptools-scm build requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setuptools-scm isn't configured (no `[tool.setuptools_scm]`, the version is single-sourced from `dicogis/__about__.py`) but, installed in the build environment, it still acts as a file finder: the sdist embedded every git-tracked file (documentation images, tests, CI configuration…) and weighed 11 MB instead of 0.5 MB, and the wheel shipped the `.ts` translation sources that `package-data` deliberately leaves out. AI-Level: 8 (bots planned, human approved) AI-Model: claude-sonnet-5-5 Co-Authored-By: Claude Sonnet 5.5 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8efb3617..d42c161e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] # setuptools>=77 is needed for the SPDX `license` expression (PEP 639) -requires = ["setuptools>=77", "setuptools-scm>8"] +requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [project] From e12eead703392bbe36a0e81364bc974c157d5626 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 07:19:12 +0000 Subject: [PATCH 3/8] test(packaging): check package-data patterns and the setuptools floor Fails when a `.ui` or `.qm` file isn't covered by `[tool.setuptools.package-data]`, when a pattern matches nothing, or when the build requirements can't handle the SPDX license expression. AI-Level: 8 (bots planned, human approved) AI-Model: claude-sonnet-5-5 Co-Authored-By: Claude Sonnet 5.5 --- tests/test_packaging.py | 100 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/test_packaging.py diff --git a/tests/test_packaging.py b/tests/test_packaging.py new file mode 100644 index 00000000..b4ab2924 --- /dev/null +++ b/tests/test_packaging.py @@ -0,0 +1,100 @@ +#! python3 # noqa E265 + +""" +Usage from the repo root folder: + +.. code-block:: bash + # for whole tests + python -m unittest tests.test_packaging + # for specific test + python -m unittest tests.test_packaging.TestPackaging.test_ui_and_translation_files_are_packaged +""" + +# standard library +import tomllib +import unittest +from pathlib import Path + +# 3rd party +from packaging.requirements import Requirement + + +# ############################################################################ +# ########## Globals ############# +# ################################ + +PROJECT_ROOT = Path(__file__).parent.parent +PACKAGE_DIR = PROJECT_ROOT / "dicogis" + +# Qt files loaded at runtime: if one is missing from the wheel, the GUI breaks +RUNTIME_DATA_SUFFIXES = {".ui", ".qm"} + + +# ############################################################################ +# ########## Classes ############# +# ################################ + + +class TestPackaging(unittest.TestCase): + """Test the packaging configuration declared in pyproject.toml.""" + + @classmethod + def setUpClass(cls) -> None: + """Read pyproject.toml once.""" + with (PROJECT_ROOT / "pyproject.toml").open("rb") as pyproject_file: + cls.pyproject = tomllib.load(pyproject_file) + + cls.package_data_patterns: list[str] = cls.pyproject["tool"]["setuptools"][ + "package-data" + ]["dicogis"] + + def test_package_data_patterns_match_files(self) -> None: + """A pattern matching nothing is a typo or a moved folder: files would be + silently left out of the wheel. + """ + for pattern in self.package_data_patterns: + with self.subTest(pattern=pattern): + self.assertTrue(any(PACKAGE_DIR.glob(pattern))) + + def test_ui_and_translation_files_are_packaged(self) -> None: + """Every .ui and .qm file must be covered by a package-data pattern, e.g. a + new dialog in a new subfolder. + """ + packaged = { + path + for pattern in self.package_data_patterns + for path in PACKAGE_DIR.glob(pattern) + } + runtime_data = { + path + for path in PACKAGE_DIR.rglob("*") + if path.suffix in RUNTIME_DATA_SUFFIXES + } + + missing = sorted( + str(path.relative_to(PROJECT_ROOT)) for path in runtime_data - packaged + ) + self.assertEqual(missing, [], "Not declared in [tool.setuptools.package-data]") + + def test_build_requires_setuptools_supports_spdx_license(self) -> None: + """An SPDX `license` expression (PEP 639) is only understood by setuptools + >= 77: with a lower floor, builds without isolation fail. + """ + if not isinstance(self.pyproject["project"]["license"], str): + self.skipTest("The license is not declared as an SPDX expression.") + + requirements = [ + Requirement(requirement) + for requirement in self.pyproject["build-system"]["requires"] + ] + setuptools = next(req for req in requirements if req.name == "setuptools") + + self.assertNotIn("76.1.0", setuptools.specifier) + self.assertIn("77.0.0", setuptools.specifier) + + +# ############################################################################ +# ####### Stand-alone run ######## +# ################################ +if __name__ == "__main__": + unittest.main() From 5a4fd158cc6dcdbb735880bfce15f6f66ee3bbb4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 07:19:12 +0000 Subject: [PATCH 4/8] ci: publish to PyPI on tags with Trusted Publishing New `pypi_publisher.yml` workflow: builds the sdist and the wheel, runs `twine check --strict`, refuses a tag that doesn't match the package version, then publishes from the `pypi` environment through OIDC (no API token). Run manually, or from a pull request touching the packaging files, it builds and checks without publishing. AI-Level: 8 (bots planned, human approved) AI-Model: claude-sonnet-5-5 Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/pypi_publisher.yml | 115 +++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/workflows/pypi_publisher.yml diff --git a/.github/workflows/pypi_publisher.yml b/.github/workflows/pypi_publisher.yml new file mode 100644 index 00000000..8bac1375 --- /dev/null +++ b/.github/workflows/pypi_publisher.yml @@ -0,0 +1,115 @@ +name: "Build 📦 and publish to PyPI 🐍" + +# Triggers +on: + push: + branches: + - master + paths: + - ".github/workflows/pypi_publisher.yml" + - "MANIFEST.in" + - "pyproject.toml" + tags: + - "*" + + pull_request: + branches: + - master + paths: + - ".github/workflows/pypi_publisher.yml" + - "MANIFEST.in" + - "pyproject.toml" + + # dry run: builds and checks the distributions, publishes only from a tag + workflow_dispatch: + +# Globals +env: + PYTHON_VERSION: "3.14" + +# Jobs definition +jobs: + build: + name: "📦 Build distributions" + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Get source code + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install build tools + run: python -m pip install -U build twine + + - name: Build source and wheel distributions + run: python -m build + + - name: Check distributions metadata + run: python -m twine check --strict dist/* + + # PyPI never accepts the same version twice: refuse to publish a tag + # which doesn't match the version declared in dicogis/__about__.py + - name: Check that the git tag matches the package version + if: startsWith(github.ref, 'refs/tags/') + env: + TAG: ${{ github.ref_name }} + run: | + python - <<'EOF' + import os + import sys + from pathlib import Path + + from packaging.utils import parse_wheel_filename + from packaging.version import Version + + tag_version = Version(os.environ["TAG"]) + (wheel,) = Path("dist").glob("*.whl") + built_version = parse_wheel_filename(wheel.name)[1] + if tag_version != built_version: + sys.exit( + f"Tag {tag_version} does not match the built version " + f"{built_version}: fix dicogis/__about__.py or the tag." + ) + print(f"Tag and built version match: {built_version}") + EOF + + - name: Save distributions as artifact + uses: actions/upload-artifact@v7 + with: + name: python-distributions + path: dist/ + if-no-files-found: error + + publish-pypi: + name: "🚀 Publish to PyPI" + runs-on: ubuntu-latest + needs: build + + if: startsWith(github.ref, 'refs/tags/') + + environment: + name: pypi + url: https://pypi.org/p/dicogis + + permissions: + # PyPI Trusted Publishing (OIDC): no API token stored in the repository + id-token: write + + steps: + - name: Download distributions + uses: actions/download-artifact@v8 + with: + name: python-distributions + path: dist/ + + - name: Publish distributions to PyPI + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 From 96274ec66251b23e14ba8ffe230ba41973a66c33 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 07:19:12 +0000 Subject: [PATCH 5/8] docs(releasing): document the PyPI publication Describe the automated publication and the one-time pending publisher setup, fix the manual fallback (no editable install nor `--no-isolation` needed, add the `twine check` step) and note the packaging rules in CLAUDE.md. AI-Level: 8 (bots planned, human approved) AI-Model: claude-sonnet-5-5 Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 11 +++++++++++ docs/development/releasing.md | 29 +++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 24f112d7..5cfd90ce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -283,6 +283,17 @@ by `builder/win_version_info.py`. CI's single-sourced from `dicogis/__about__.py::__version__` (`pyproject.toml` reads it via `[tool.setuptools.dynamic]`). +The package itself is published to PyPI by `.github/workflows/pypi_publisher.yml` +on every tag (Trusted Publishing, `pypi` GitHub environment, no API token): it +builds the sdist and the wheel, runs `twine check --strict` and refuses a tag +that doesn't match `__version__`. `tests/test_packaging.py` fails when a `.ui` +or `.qm` file isn't covered by `[tool.setuptools.package-data]`: add the pattern +there when introducing a new UI subfolder or kind of data file. Don't add +`setuptools-scm` to `build-system.requires`: it is not configured here, but as +a build-time file finder it would make the sdist ship every git-tracked file +(docs, images…) and the wheel the `.ts` translation sources. The release +procedure is in `docs/development/releasing.md`. + A root-level `Dockerfile` (multi-stage, based on the official `ghcr.io/osgeo/gdal:ubuntu-small-*` image so GDAL's Python bindings are already present and version-matched — no `gdal` extra to compile) packages diff --git a/docs/development/releasing.md b/docs/development/releasing.md index 2df9b269..101d8c71 100644 --- a/docs/development/releasing.md +++ b/docs/development/releasing.md @@ -5,6 +5,23 @@ 1. Apply a git tag with the relevant version: `git tag -a 0.3.0 {git commit hash} -m "New awesome feature"` 1. Push commit and tag to main branch: `git push --tags` +Pushing the tag triggers the CI/CD workflows. Among them, `pypi_publisher.yml` builds the source and wheel distributions, runs `twine check --strict` and checks that the tag matches the version declared in `__about__.py` (compared as [PEP 440](https://peps.python.org/pep-0440/) versions: `4.0.0-beta9` and `4.0.0b9` are the same version), then publishes them to [PyPI](https://pypi.org/project/dicogis/). + +The workflow can also be started manually from the _Actions_ tab to build and check the distributions without publishing anything. + +## PyPI publication + +The publication relies on [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OpenID Connect): no API token is stored in the repository. + +One-time setup, before the first release: + +1. On PyPI, in the account's [publishing settings](https://pypi.org/manage/account/publishing/), add a _pending publisher_ with the project name `dicogis`, the owner `Guts`, the repository `DicoGIS`, the workflow `pypi_publisher.yml` and the environment `pypi` +1. On GitHub, in the repository settings, create an environment named `pypi` (optionally with required reviewers, to confirm each publication) + +The first successful publication turns the pending publisher into a regular one. + +> A version can be published only once on PyPI: even deleted, a file can't be uploaded again under the same name, so moving the tag and re-running the workflow doesn't fix a faulty release. [Yank it](https://pypi.org/help/#yanked) and publish a new version. + ## Unpublish a tag ```sh @@ -22,20 +39,20 @@ If the CI/CD fails for any reason, here comes the manual procedure: 1. Install required packages: ```sh - python -m pip install -U build twine wheel + python -m pip install -U build twine ``` -1. Install package in editable mode: +1. Clean previous builds and build package artifacts (source and wheel distributions, in `dist/`): ```sh - python -m pip install -e . + rm -rf dist/ + python -m build ``` -1. Clean previous builds and build package artifacts: +1. Check built artifacts: ```sh - rm -rf dist/ - python -m build --no-isolation --sdist --wheel --outdir dist/ . + twine check --strict dist/* ``` 1. Upload built artifacts to PyPi: From 759e1d2c17397d68994a635611b32ef1b11b81a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 12:39:13 +0000 Subject: [PATCH 6/8] ci(release): build the Python package and attach it to the GitHub release Add a `build-python-package` job to the build and release workflow, as done in QDT: it builds the sdist and the wheel, runs `twine check --strict` and, on a tag, refuses a tag that doesn't match the package version. The distributions are attached to the GitHub release along with the executables, and rebuilt on pull requests touching `pyproject.toml` or `MANIFEST.in`. AI-Level: 8 (bots planned, human approved) AI-Model: claude-sonnet-5-5 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01HATwPxs2xpwnubZPvpGzBh --- .github/workflows/builder_releaser.yml | 72 ++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/.github/workflows/builder_releaser.yml b/.github/workflows/builder_releaser.yml index 4e44e72c..b2f0bf14 100644 --- a/.github/workflows/builder_releaser.yml +++ b/.github/workflows/builder_releaser.yml @@ -9,6 +9,8 @@ on: - ".github/workflows/builder_releaser.yml" - "builder/**/*" - "dicogis/**/*.py" + - "MANIFEST.in" + - "pyproject.toml" tags: - "*" @@ -18,6 +20,8 @@ on: paths: - ".github/workflows/builder_releaser.yml" - "builder/" + - "MANIFEST.in" + - "pyproject.toml" # Globals env: @@ -29,6 +33,66 @@ env: # Jobs definition jobs: + build-python-package: + name: "🐍 Python package" + runs-on: ubuntu-24.04 + + permissions: + contents: read + + steps: + - name: Get source code + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install build tools + run: python -m pip install -U build twine + + - name: Build source and wheel distributions + run: python -m build + + - name: Check distributions metadata + run: python -m twine check --strict dist/* + + # PyPI never accepts the same version twice: refuse to publish a tag + # which doesn't match the version declared in dicogis/__about__.py + - name: Check that the git tag matches the package version + if: startsWith(github.ref, 'refs/tags/') + env: + TAG: ${{ github.ref_name }} + run: | + python - <<'EOF' + import os + import sys + from pathlib import Path + + from packaging.utils import parse_wheel_filename + from packaging.version import Version + + tag_version = Version(os.environ["TAG"]) + (wheel,) = Path("dist").glob("*.whl") + built_version = parse_wheel_filename(wheel.name)[1] + if tag_version != built_version: + sys.exit( + f"Tag {tag_version} does not match the built version " + f"{built_version}: fix dicogis/__about__.py or the tag." + ) + print(f"Tag and built version match: {built_version}") + EOF + + - name: Save distributions as artifact + uses: actions/upload-artifact@v7 + with: + name: python_package + path: dist/* + if-no-files-found: error + build-ubuntu: name: "🐧 Ubuntu LTS" runs-on: ubuntu-24.04 @@ -214,6 +278,7 @@ jobs: name: "🚀 GitHub Release" runs-on: ubuntu-latest needs: + - build-python-package - build-ubuntu - build-windows permissions: @@ -223,6 +288,12 @@ jobs: if: startsWith(github.ref, 'refs/tags/') steps: + - name: Download built Python package (sdist and wheel) + uses: actions/download-artifact@v8 + with: + name: python_package + path: builds/python/ + - name: Download built package for Linux (Ubuntu) - GUI uses: actions/download-artifact@v8 with: @@ -253,6 +324,7 @@ jobs: discussion_category_name: announcements fail_on_unmatched_files: true files: | + builds/python/* builds/ubuntu/cli/*.bin builds/ubuntu/gui/*.bin builds/windows/cli/*.exe From a67ea3b7562751bf7fb03e9a36f70be19229b605 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 12:39:36 +0000 Subject: [PATCH 7/8] ci(release): publish to PyPI from the build and release workflow Add the `release-pypi` job, as done in QDT: on a tag, it uploads the distributions built by `build-python-package` from the `pypi` environment through Trusted Publishing (no API token). It replaces the standalone `pypi_publisher.yml` workflow, removed here, so that the executables, the GitHub release and the PyPI package come from a single workflow. AI-Level: 8 (bots planned, human approved) AI-Model: claude-sonnet-5-5 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01HATwPxs2xpwnubZPvpGzBh --- .github/workflows/builder_releaser.yml | 26 ++++++ .github/workflows/pypi_publisher.yml | 115 ------------------------- 2 files changed, 26 insertions(+), 115 deletions(-) delete mode 100644 .github/workflows/pypi_publisher.yml diff --git a/.github/workflows/builder_releaser.yml b/.github/workflows/builder_releaser.yml index b2f0bf14..3548c3ac 100644 --- a/.github/workflows/builder_releaser.yml +++ b/.github/workflows/builder_releaser.yml @@ -330,3 +330,29 @@ jobs: builds/windows/cli/*.exe builds/windows/gui/*.exe generate_release_notes: true + + release-pypi: + name: "🐍 Release on PyPI" + runs-on: ubuntu-latest + needs: + - build-python-package + + if: startsWith(github.ref, 'refs/tags/') + + environment: + name: pypi + url: https://pypi.org/p/dicogis + + permissions: + # PyPI Trusted Publishing (OIDC): no API token stored in the repository + id-token: write + + steps: + - name: Download built Python package (sdist and wheel) + uses: actions/download-artifact@v8 + with: + name: python_package + path: dist/ + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 diff --git a/.github/workflows/pypi_publisher.yml b/.github/workflows/pypi_publisher.yml deleted file mode 100644 index 8bac1375..00000000 --- a/.github/workflows/pypi_publisher.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: "Build 📦 and publish to PyPI 🐍" - -# Triggers -on: - push: - branches: - - master - paths: - - ".github/workflows/pypi_publisher.yml" - - "MANIFEST.in" - - "pyproject.toml" - tags: - - "*" - - pull_request: - branches: - - master - paths: - - ".github/workflows/pypi_publisher.yml" - - "MANIFEST.in" - - "pyproject.toml" - - # dry run: builds and checks the distributions, publishes only from a tag - workflow_dispatch: - -# Globals -env: - PYTHON_VERSION: "3.14" - -# Jobs definition -jobs: - build: - name: "📦 Build distributions" - runs-on: ubuntu-latest - - permissions: - contents: read - - steps: - - name: Get source code - uses: actions/checkout@v7 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@v7 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Install build tools - run: python -m pip install -U build twine - - - name: Build source and wheel distributions - run: python -m build - - - name: Check distributions metadata - run: python -m twine check --strict dist/* - - # PyPI never accepts the same version twice: refuse to publish a tag - # which doesn't match the version declared in dicogis/__about__.py - - name: Check that the git tag matches the package version - if: startsWith(github.ref, 'refs/tags/') - env: - TAG: ${{ github.ref_name }} - run: | - python - <<'EOF' - import os - import sys - from pathlib import Path - - from packaging.utils import parse_wheel_filename - from packaging.version import Version - - tag_version = Version(os.environ["TAG"]) - (wheel,) = Path("dist").glob("*.whl") - built_version = parse_wheel_filename(wheel.name)[1] - if tag_version != built_version: - sys.exit( - f"Tag {tag_version} does not match the built version " - f"{built_version}: fix dicogis/__about__.py or the tag." - ) - print(f"Tag and built version match: {built_version}") - EOF - - - name: Save distributions as artifact - uses: actions/upload-artifact@v7 - with: - name: python-distributions - path: dist/ - if-no-files-found: error - - publish-pypi: - name: "🚀 Publish to PyPI" - runs-on: ubuntu-latest - needs: build - - if: startsWith(github.ref, 'refs/tags/') - - environment: - name: pypi - url: https://pypi.org/p/dicogis - - permissions: - # PyPI Trusted Publishing (OIDC): no API token stored in the repository - id-token: write - - steps: - - name: Download distributions - uses: actions/download-artifact@v8 - with: - name: python-distributions - path: dist/ - - - name: Publish distributions to PyPI - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 From 7dddb36abc1ba5ac1bf0fe7bb008f54459e004bb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 12:40:02 +0000 Subject: [PATCH 8/8] docs(releasing): describe the PyPI publication as part of the release workflow The package is now built, attached to the GitHub release and published to PyPI by `builder_releaser.yml`: update the release procedure, the workflow name to give to the PyPI pending publisher, and the packaging notes in CLAUDE.md. AI-Level: 8 (bots planned, human approved) AI-Model: claude-sonnet-5-5 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01HATwPxs2xpwnubZPvpGzBh --- CLAUDE.md | 14 ++++++++------ docs/development/releasing.md | 8 +++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5cfd90ce..932b6c60 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -283,12 +283,14 @@ by `builder/win_version_info.py`. CI's single-sourced from `dicogis/__about__.py::__version__` (`pyproject.toml` reads it via `[tool.setuptools.dynamic]`). -The package itself is published to PyPI by `.github/workflows/pypi_publisher.yml` -on every tag (Trusted Publishing, `pypi` GitHub environment, no API token): it -builds the sdist and the wheel, runs `twine check --strict` and refuses a tag -that doesn't match `__version__`. `tests/test_packaging.py` fails when a `.ui` -or `.qm` file isn't covered by `[tool.setuptools.package-data]`: add the pattern -there when introducing a new UI subfolder or kind of data file. Don't add +The same workflow publishes the package to PyPI: its `build-python-package` job +builds the sdist and the wheel, runs `twine check --strict` and, on a tag, +refuses a tag that doesn't match `__version__`; its `release-pypi` job (Trusted +Publishing, `pypi` GitHub environment, no API token) then uploads them, and the +`release` job attaches them to the GitHub release. `tests/test_packaging.py` +fails when a `.ui` or `.qm` file isn't covered by +`[tool.setuptools.package-data]`: add the pattern there when introducing a new +UI subfolder or kind of data file. Don't add `setuptools-scm` to `build-system.requires`: it is not configured here, but as a build-time file finder it would make the sdist ship every git-tracked file (docs, images…) and the wheel the `.ts` translation sources. The release diff --git a/docs/development/releasing.md b/docs/development/releasing.md index 101d8c71..fb5ec272 100644 --- a/docs/development/releasing.md +++ b/docs/development/releasing.md @@ -5,9 +5,11 @@ 1. Apply a git tag with the relevant version: `git tag -a 0.3.0 {git commit hash} -m "New awesome feature"` 1. Push commit and tag to main branch: `git push --tags` -Pushing the tag triggers the CI/CD workflows. Among them, `pypi_publisher.yml` builds the source and wheel distributions, runs `twine check --strict` and checks that the tag matches the version declared in `__about__.py` (compared as [PEP 440](https://peps.python.org/pep-0440/) versions: `4.0.0-beta9` and `4.0.0b9` are the same version), then publishes them to [PyPI](https://pypi.org/project/dicogis/). +Pushing the tag triggers the `builder_releaser.yml` workflow. It builds the executables and the Python package (source and wheel distributions), creates the GitHub release with all of them attached, then publishes the Python package to [PyPI](https://pypi.org/project/dicogis/). -The workflow can also be started manually from the _Actions_ tab to build and check the distributions without publishing anything. +Before building, the workflow runs `twine check --strict` on the distributions and checks that the tag matches the version declared in `__about__.py` (compared as [PEP 440](https://peps.python.org/pep-0440/) versions: `4.0.0-beta9` and `4.0.0b9` are the same version). + +On a pull request touching `pyproject.toml`, `MANIFEST.in` or the workflow itself, the Python package is built and checked without publishing anything. ## PyPI publication @@ -15,7 +17,7 @@ The publication relies on [PyPI Trusted Publishing](https://docs.pypi.org/truste One-time setup, before the first release: -1. On PyPI, in the account's [publishing settings](https://pypi.org/manage/account/publishing/), add a _pending publisher_ with the project name `dicogis`, the owner `Guts`, the repository `DicoGIS`, the workflow `pypi_publisher.yml` and the environment `pypi` +1. On PyPI, in the account's [publishing settings](https://pypi.org/manage/account/publishing/), add a _pending publisher_ with the project name `dicogis`, the owner `Guts`, the repository `DicoGIS`, the workflow `builder_releaser.yml` and the environment `pypi` 1. On GitHub, in the repository settings, create an environment named `pypi` (optionally with required reviewers, to confirm each publication) The first successful publication turns the pending publisher into a regular one.