From 2fc2125ed6db503e12d18c83f09b43078ba4032a Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 13:42:56 +0100
Subject: [PATCH 01/35] Add pre-commit hook and improve with better linting

---
 cookiecutter.json                             |  3 +-
 docker/ci-runner/Dockerfile                   |  4 +--
 {{cookiecutter.project_slug}}/.dockerignore   |  4 +++
 {{cookiecutter.project_slug}}/.gitignore      | 10 +++++--
 {{cookiecutter.project_slug}}/.gitlab-ci.yml  | 27 +++++------------
 .../.pre-commit-config.yaml                   | 22 ++++++++++++++
 .../bin/install-hooks/pre-commit.sh           |  8 +++++
 .../docker/ci-runner/Dockerfile               |  2 +-
 .../{{cookiecutter.project_slug}}/Dockerfile  | 18 ++++++++++++
 {{cookiecutter.project_slug}}/docs/cleanup.py | 23 +++++++++++++++
 {{cookiecutter.project_slug}}/docs/cleanup.sh | 14 ---------
 {{cookiecutter.project_slug}}/pyproject.toml  | 29 +++++++++----------
 .../tests/requirements.txt                    |  6 ----
 .../tests/test_cool_module.py                 |  3 ++
 14 files changed, 112 insertions(+), 61 deletions(-)
 create mode 100644 {{cookiecutter.project_slug}}/.dockerignore
 create mode 100644 {{cookiecutter.project_slug}}/.pre-commit-config.yaml
 create mode 100755 {{cookiecutter.project_slug}}/bin/install-hooks/pre-commit.sh
 create mode 100644 {{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
 create mode 100644 {{cookiecutter.project_slug}}/docs/cleanup.py
 delete mode 100644 {{cookiecutter.project_slug}}/docs/cleanup.sh

diff --git a/cookiecutter.json b/cookiecutter.json
index 1f29d35..8165b97 100644
--- a/cookiecutter.json
+++ b/cookiecutter.json
@@ -1,6 +1,7 @@
 {
     "project_name": "My Awesome App",
     "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}",
-    "project_url": "https://git.astron.nl/{{cookiecutter.project_slug}}",
+    "project_package": "{{ cookiecutter.project_name.lower()|replace(' ', '-')|replace('_', '-')|replace('.', '-')|trim() }}",
+    "project_url": "https://git.astron.nl/{{ cookiecutter.project_slug }}",
     "description": "An example package for CI/CD working group"
 }
diff --git a/docker/ci-runner/Dockerfile b/docker/ci-runner/Dockerfile
index 8b96217..497b89a 100644
--- a/docker/ci-runner/Dockerfile
+++ b/docker/ci-runner/Dockerfile
@@ -1,5 +1,5 @@
-FROM python:3.11
+FROM python:3.12
 
 RUN python -m pip install --upgrade pip
-RUN pip install --upgrade cookiecutter tox twine cibuildwheel==2.13.1 cookiecutter
+RUN python -m pip install --upgrade cookiecutter tox twine cibuildwheel==2.13.1 cookiecutter
 RUN curl -sSL https://get.docker.com/ | sh
diff --git a/{{cookiecutter.project_slug}}/.dockerignore b/{{cookiecutter.project_slug}}/.dockerignore
new file mode 100644
index 0000000..141f90d
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/.dockerignore
@@ -0,0 +1,4 @@
+.tox
+build
+*.egg-info
+.venv
diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore
index 5513c1b..885470b 100644
--- a/{{cookiecutter.project_slug}}/.gitignore
+++ b/{{cookiecutter.project_slug}}/.gitignore
@@ -1,13 +1,19 @@
 dist/*
 *.egg-info
 *.pyc
+.tox
+.venv
 
 .coverage
 coverage.xml
 htmlcov/*
+build
+dist
 
-# Tox
-.tox
+# Documentation
+docs/source/source_documentation
+!docs/source/source_documentation/index.rst
+docs/build
 
 # setuptools-scm
 src/{{cookiecutter.project_slug}}/_version.py
diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index 4294826..5071e25 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -14,6 +14,7 @@ stages:
   - lint
   - test
   - package
+  - images
   - integration
   - publish # publish instead of deploy
 
@@ -35,22 +36,10 @@ trigger_prepare:
     strategy: depend
     include: .prepare.gitlab-ci.yml
 
-run_black:
+run_lint:
   stage: lint
   script:
-    - tox -e black
-  allow_failure: true
-
-run_flake8:
-  stage: lint
-  script:
-    - tox -e pep8
-  allow_failure: true
-
-run_pylint:
-  stage: lint
-  script:
-    - tox -e pylint
+    - tox -e lint
   allow_failure: true
 
 sast:
@@ -74,7 +63,7 @@ secret_detection:
   before_script:
     - python --version # For debugging
     - python -m pip install --upgrade pip
-    - pip install --upgrade tox twine
+    - python -m pip install --upgrade tox twine
 
 # Run all unit tests for Python versions except the base image
 run_unit_tests:
@@ -156,8 +145,7 @@ publish_on_test_pypi:
     - package_files
   when: manual
   rules:
-    - if: $CI_COMMIT_TAG
-  allow_failure: true
+    - if: '$CI_COMMIT_TAG && $CI_COMMIT_REF_PROTECTED == "true"'
   script:
     - echo "run twine for test pypi"
     # - |
@@ -175,8 +163,7 @@ publish_on_pypi:
     - package_files
   when: manual
   rules:
-    - if: $CI_COMMIT_TAG
-  allow_failure: true
+    - if: '$CI_COMMIT_TAG && $CI_COMMIT_REF_PROTECTED == "true"'
   script:
     - echo "run twine for pypi"
     # - |
@@ -195,7 +182,7 @@ publish_to_readthedocs:
     - package_docs
   when: manual
   rules:
-    - if: $CI_COMMIT_TAG
+    - if: '$CI_COMMIT_TAG && $CI_COMMIT_REF_PROTECTED == "true"'
   script:
     - echo "scp docs/* ???"
     - exit 1
diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
new file mode 100644
index 0000000..ce46e64
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
@@ -0,0 +1,22 @@
+default_stages: [ pre-commit, pre-push ]
+default_language_version:
+  python: python3
+exclude: '^docs/.*\.py$'
+
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.5.0
+    hooks:
+      - id: end-of-file-fixer
+      - id: trailing-whitespace
+      - id: check-yaml
+      - id: check-toml
+      - id: detect-private-key
+  - repo: local
+    hooks:
+      - id: tox-lint
+        name: tox-lint (local)
+        entry: tox
+        language: python
+        types: [file, python]
+        args: ["-e",  "lint", "--"]
diff --git a/{{cookiecutter.project_slug}}/bin/install-hooks/pre-commit.sh b/{{cookiecutter.project_slug}}/bin/install-hooks/pre-commit.sh
new file mode 100755
index 0000000..792a3aa
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/bin/install-hooks/pre-commit.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+if [ ! -f "setup.sh" ]; then
+  echo "pre-commit.sh must be executed with repository root as working directory!"
+  exit 1
+fi
+
+pre-commit install --hook-type pre-push
diff --git a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
index 48d4586..2deb674 100644
--- a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
+++ b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
@@ -1,5 +1,5 @@
 FROM python:3.13
 
 RUN python -m pip install --upgrade pip
-RUN pip install --upgrade tox twine cibuildwheel==2.13.1
+RUN python -m pip install --upgrade tox twine cibuildwheel==2.13.1
 RUN curl -sSL https://get.docker.com/ | sh
diff --git a/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile b/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
new file mode 100644
index 0000000..1f7eb27
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
@@ -0,0 +1,18 @@
+ARG BUILD_ENV=no_copy
+
+FROM python:3.13 AS build_no_copy
+ADD ../../requirements.txt .
+COPY ../.. /work
+RUN rm -r /work/dist | true
+RUN python -m pip install --user tox
+WORKDIR /work
+RUN python -m tox -e build
+
+FROM python:3.13 AS build_copy
+COPY dist /work/dist
+
+FROM build_${BUILD_ENV} AS build
+
+FROM python:3.13-slim
+COPY --from=build /work/dist /dist
+RUN python -m pip install /dist/*.whl
diff --git a/{{cookiecutter.project_slug}}/docs/cleanup.py b/{{cookiecutter.project_slug}}/docs/cleanup.py
new file mode 100644
index 0000000..3a4508d
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/docs/cleanup.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+
+#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
+import os
+
+file_dir = os.path.dirname(os.path.realpath(__file__))
+
+clean_dir = os.path.join(file_dir, "source", "source_documentation")
+print(f"Cleaning.. {clean_dir}/*")
+
+if not os.path.exists(clean_dir):
+    exit()
+
+for file_name in os.listdir(clean_dir):
+    file = os.path.join(clean_dir, file_name)
+    
+    if file_name == "index.rst":
+        continue
+
+    print(f"Removing.. {file}")
+    os.remove(file)
diff --git a/{{cookiecutter.project_slug}}/docs/cleanup.sh b/{{cookiecutter.project_slug}}/docs/cleanup.sh
deleted file mode 100644
index aac4cef..0000000
--- a/{{cookiecutter.project_slug}}/docs/cleanup.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/bin/sh
-
-FILE_DIR=$(dirname -- "$(readlink -f -- "${0}")")
-
-echo "Cleaning.. ${FILE_DIR}/source/source_documentation/*"
-
-for f in "${FILE_DIR}"/source/source_documentation/*
-do
-
-  case $f in
-    */index.rst) true;;
-    *) echo "Removing.. ${f}"; rm "${f}";;
-  esac
-done
diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index db601f1..3eefe30 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -46,20 +46,19 @@ archs = ["x86_64", "i686"]
 [tool.cibuildwheel.windows]
 archs = ["AMD64", "x86"]
 
-[tool.pylint]
-ignore = "_version.py"
 
-# This does nothing until the issue below is resolved:
-# https://github.com/PyCQA/flake8/issues/234
-[tool.flake8]
-max-line-length = 88
-extend-ignore = ["E203"]
-exclude = ["_version.py"]
+[tool.ruff]
+exclude = [
+    ".venv",
+    ".git",
+    ".tox",
+    "dist",
+    "docs",
+    "*lib/python*",
+    "*egg",
+    "_version.py"
+]
+
+[tool.ruff.lint]
+ignore = ["E203"]
 
-[tool.black]
-line-length = 88
-extend-exclude = '''
-(
-    _version.py
-)
-'''
diff --git a/{{cookiecutter.project_slug}}/tests/requirements.txt b/{{cookiecutter.project_slug}}/tests/requirements.txt
index 82f1ee8..b507faf 100644
--- a/{{cookiecutter.project_slug}}/tests/requirements.txt
+++ b/{{cookiecutter.project_slug}}/tests/requirements.txt
@@ -1,8 +1,2 @@
-autopep8 >= 1.7.0 # MIT
-black >= 22.0.0 # MIT
-build >= 0.8.0 # MIT
-flake8 >= 5.0.0 # MIT
-pyproject-flake8 >= 5.0.4 # Unlicense
-pylint >= 2.15.0 # GPLv2
 pytest >= 7.0.0 # MIT
 pytest-cov >= 3.0.0 # MIT
diff --git a/{{cookiecutter.project_slug}}/tests/test_cool_module.py b/{{cookiecutter.project_slug}}/tests/test_cool_module.py
index 19e5cd2..2cacc8e 100644
--- a/{{cookiecutter.project_slug}}/tests/test_cool_module.py
+++ b/{{cookiecutter.project_slug}}/tests/test_cool_module.py
@@ -1,3 +1,6 @@
+#  Copyright (C) 2025 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: Apache-2.0
+
 """Testing of the Cool Module"""
 from unittest import TestCase
 
-- 
GitLab


From 9716b9ea8dabdfffa2fa64d5a7ae27028155b777 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 13:53:27 +0100
Subject: [PATCH 02/35] fixes

---
 {{cookiecutter.project_slug}}/pyproject.toml | 65 ++++++++++++++++++++
 {{cookiecutter.project_slug}}/tox.ini        | 65 --------------------
 2 files changed, 65 insertions(+), 65 deletions(-)
 delete mode 100644 {{cookiecutter.project_slug}}/tox.ini

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 3eefe30..8018bc0 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -62,3 +62,68 @@ exclude = [
 [tool.ruff.lint]
 ignore = ["E203"]
 
+
+[tool.tox]
+# Generative environment list to test all supported Python versions
+requires = ["tox>=4.21"]
+env_list = ["fix", "coverage", "lint", "format", "py{13, 12, 11, 10}"]
+requires = ["tox-ignore-env-name-mismatch >= 0.2.0"]
+
+[tool.tox.env_run_base]
+package = "editable"
+deps = [
+    "-r{toxinidir}/requirements.txt",
+    "-r{toxinidir}/tests/requirements.txt"]
+set_env = { LANGUAGE = "en_US", LC_ALL = "en_US.UTF-8", PYTHONWARNINGS = "default::DeprecationWarning", DYNAMIC_VERSION_SOURCE= "{toxinidir}" }
+commands = [["python", "--version"], ["python", "-m", "pytest", "tests/{posargs}"]]
+
+[tool.tox.env.fix]
+description = "format the code base to adhere to our styles, and complain about what we cannot do automatically"
+skip_install = true
+deps = ["pre-commit-uv>=4.1.1"]
+commands = [["pre-commit", "run", "--all-files", "--show-diff-on-failure"]]
+
+[tool.tox.env.coverage]
+commands = [
+    ["python", "--version"],
+    ["python", "-m", "pytest", "--cov-report", "term", "--cov-report", "xml", "--cov-report", "html", "--cov={{cookiecutter.project_slug}}", "tests/{posargs}"]]
+
+# Command prefixes to reuse the same virtualenv for all linting jobs.
+[tool.tox.env.lint]
+deps = [
+    "ruff",
+    "-r{toxinidir}/tests/requirements.txt"]
+commands = [
+    ["python", "-m", "ruff", "--version"],
+    ["python", "-m", "ruff", "check", { replace = "posargs", default = ["{{cookiecutter.project_slug}}", "tests"], extend = true }]
+]
+
+[tool.tox.env.format]
+deps = [
+    "ruff",
+    "-r{toxinidir}/tests/requirements.txt"]
+commands = [
+    ["python", "-m", "ruff", "format", "-v", { replace = "posargs", default = ["{{cookiecutter.project_slug}}", "tests"], extend = true }]
+]
+
+[tool.tox.env.docs]
+deps = [
+    "-r{toxinidir}/requirements.txt",
+    "-r{toxinidir}/docs/requirements.txt"]
+# unset LC_ALL / LANGUAGE from testenv, would fail sphinx otherwise
+set_env = ""
+changedir = "{tox_root}"
+commands = [
+    ["python", "docs/cleanup.py"],
+    ["sphinx-build", "-b", "html", "docs/source", "docs/build/html"]
+]
+
+[tool.tox.env.build-local]
+package = "wheel"
+deps = ["-r{toxinidir}/build-requirements.txt"]
+commands = [["python", "-m", "build"]]
+
+[tool.tox.env.build-ci-linux]
+package = "wheel"
+deps = ["-r{toxinidir}/build-requirements.txt"]
+commands = [["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]]
diff --git a/{{cookiecutter.project_slug}}/tox.ini b/{{cookiecutter.project_slug}}/tox.ini
deleted file mode 100644
index 37d15ba..0000000
--- a/{{cookiecutter.project_slug}}/tox.ini
+++ /dev/null
@@ -1,65 +0,0 @@
-[tox]
-# Generative environment list to test all supported Python versions
-envlist = py3{9,10,11,12,13},black,pep8,pylint
-min_version = 4.3.3
-requires =
-    tox-ignore-env-name-mismatch >= 0.2.0
-
-[testenv]
-package = sdist  # 'Source' package required for binary extension
-use_develop = False  # use_develop implies 'editable' package, not possible
-
-setenv =
-    PYTHONWARNINGS=default::DeprecationWarning
-    DYNAMIC_VERSION_SOURCE={toxinidir}
-deps =
-    -r{toxinidir}/requirements.txt
-    -r{toxinidir}/tests/requirements.txt
-commands =
-    {envpython} --version
-    {envpython} -m pytest tests/{posargs}
-
-[testenv:coverage]
-commands =
-    {envpython} --version
-    {envpython} -m pytest --cov-report term --cov-report xml --cov-report html --cov={{cookiecutter.project_slug}} tests/{posargs}
-
-# Use generative name and command prefixes to reuse the same virtualenv
-# for all linting jobs.
-[testenv:{pep8,black,pylint,format}]
-usedevelop = False
-envdir = {toxworkdir}/linting
-commands =
-    pep8: {envpython} -m flake8 --version
-    pep8: {envpython} -m flake8 --exclude=_version.py --max-line-length 88 --extend-ignore=E203 src tests
-    black: {envpython} -m black --version
-    black: {envpython} -m black --check --diff src tests
-    pylint: {envpython} -m pylint --version
-    pylint: {envpython} -m pylint src tests
-    format: {envpython} -m autopep8 -v --max-line-length 88 -aa --in-place --recursive src
-    format: {envpython} -m autopep8 -v --max-line-length 88 -aa --in-place --recursive tests
-    format: {envpython} -m black -v src tests
-
-[testenv:docs]
-allowlist_externals =
-    sh
-; unset LC_ALL / LANGUAGE from testenv, would fail sphinx otherwise
-setenv =
-    PYTHONWARNINGS=default::DeprecationWarning
-    DYNAMIC_VERSION_SOURCE={toxinidir}
-deps =
-    -r{toxinidir}/requirements.txt
-    -r{toxinidir}/docs/requirements.txt
-changedir = {toxinidir}
-commands =
-    sh docs/cleanup.sh
-    sphinx-build -b html docs/source docs/build/html
-
-[testenv:{build-local,build-ci-linux}]
-deps =
-    -r{toxinidir}/tests/requirements.txt
-    -r{toxinidir}/build-requirements.txt
-skip_install = True
-commands =
-    build-local: {envpython} -m build
-    build-ci-linux: {envpython} -m cibuildwheel --platform linux --output-dir dist
-- 
GitLab


From 6e7b87d63e1399afa510d8fb8cd41f74fcfaae6e Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 13:59:31 +0100
Subject: [PATCH 03/35] fixes

---
 {{cookiecutter.project_slug}}/pyproject.toml | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 8018bc0..8191c8b 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -65,9 +65,8 @@ ignore = ["E203"]
 
 [tool.tox]
 # Generative environment list to test all supported Python versions
-requires = ["tox>=4.21"]
+requires = ["tox>=4.21", "tox-ignore-env-name-mismatch >= 0.2.0"]
 env_list = ["fix", "coverage", "lint", "format", "py{13, 12, 11, 10}"]
-requires = ["tox-ignore-env-name-mismatch >= 0.2.0"]
 
 [tool.tox.env_run_base]
 package = "editable"
-- 
GitLab


From ca769d04e8967dbf5ec30eecb36b6bb4a93035e0 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 14:03:19 +0100
Subject: [PATCH 04/35] fix

---
 {{cookiecutter.project_slug}}/pyproject.toml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 8191c8b..3fc4fdf 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -94,7 +94,7 @@ deps = [
     "-r{toxinidir}/tests/requirements.txt"]
 commands = [
     ["python", "-m", "ruff", "--version"],
-    ["python", "-m", "ruff", "check", { replace = "posargs", default = ["{{cookiecutter.project_slug}}", "tests"], extend = true }]
+    ["python", "-m", "ruff", "check", { replace = "posargs", default = ["src", "tests"], extend = true }]
 ]
 
 [tool.tox.env.format]
@@ -102,7 +102,7 @@ deps = [
     "ruff",
     "-r{toxinidir}/tests/requirements.txt"]
 commands = [
-    ["python", "-m", "ruff", "format", "-v", { replace = "posargs", default = ["{{cookiecutter.project_slug}}", "tests"], extend = true }]
+    ["python", "-m", "ruff", "format", "-v", { replace = "posargs", default = ["src", "tests"], extend = true }]
 ]
 
 [tool.tox.env.docs]
-- 
GitLab


From cd467f0bc21db4bc03dd2f394478f27e79a311b9 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 14:33:41 +0100
Subject: [PATCH 05/35] fix

---
 {{cookiecutter.project_slug}}/.gitlab-ci.yml              | 2 +-
 {{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index 5071e25..a08ac42 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -100,7 +100,7 @@ package_files:
       - dist/*
   script:
     - source scripts/setup-docker-host.sh
-    - cibuildwheel --platform linux --output-dir dist
+    - tox -e build-ci-linux
 
 package_docs:
   stage: package
diff --git a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
index 2deb674..5af9fd4 100644
--- a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
+++ b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
@@ -1,5 +1,4 @@
 FROM python:3.13
 
-RUN python -m pip install --upgrade pip
-RUN python -m pip install --upgrade tox twine cibuildwheel==2.13.1
+RUN python -m pip install --upgrade cibuildwheel pip tox twine
 RUN curl -sSL https://get.docker.com/ | sh
-- 
GitLab


From 46787a73b81861611845f8fa585526caf6bcd63a Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 14:44:59 +0100
Subject: [PATCH 06/35] update

---
 .gitlab-ci.yml        |  86 +++++++++++++----------------
 project.gitlab-ci.yml | 122 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 159 insertions(+), 49 deletions(-)
 create mode 100644 project.gitlab-ci.yml

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1650b82..20272ac 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,8 +1,7 @@
-include:
-- local: "{{cookiecutter.project_slug}}/.gitlab-ci.yml"
-
-variables:
-  GIT_SUBMODULE_STRATEGY: recursive
+stages:
+  - prepare
+  - build
+  - test
 
 trigger_prepare:
   stage: prepare
@@ -10,53 +9,42 @@ trigger_prepare:
     strategy: depend
     include: "{{cookiecutter.project_slug}}/.prepare.gitlab-ci.yml"
 
-default:
+# Generate template instance in my_awesome_app directory
+build-template:
+  stage: build
+  image: $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG
   # Bootstrap Cookiecutter template to test provided ci pipeline template
-  before_script:
+  script:
     - python --version # For debugging
-    - git config --global user.name "unit test"
-    - git config --global user.email "info@astron.nl"
     - cookiecutter --no-input --overwrite-if-exists --output-dir . .
     - cd my_awesome_app
-
-# Override semgrep-sast before script
-sast:
-  before_script:
-    - python --version # For debugging
-
-# Override unit test before script
-.run_unit_test_version_base:
-  before_script:
-    - pip install cookiecutter
-    - !reference [default, before_script]
-    - python --version # For debugging
-    - python -m pip install --upgrade pip
-    - pip install --upgrade tox twine
-
-# Override artifact directories
-package_docs:
-  stage: package
-  artifacts:
-    expire_in: 1w
-    paths:
-      - my_awesome_app/docs/build/*
-
-# override package files before script
-package_files:
-  before_script:
-    - pip install cookiecutter
-    - !reference [default, before_script]
+    - git init
+    - git config user.email "ci-runner@example.com"
+    - git config user.name "CI Runner"
+    - source ./setup.sh
+    - ls -lah
+    - tox --version
+    - pip install --upgrade tox
+    - tox -e fix
+    - rm -r .venv
+    - rm -r .tox
+  # cannot use needs, for artifacts on child pipeline so must regenerate template!
   artifacts:
-    expire_in: 1w
     paths:
-      - my_awesome_app/dist/*
-
-# Override artifact directories
-run_unit_tests_coverage:
-  artifacts:
-    reports:
-      coverage_report:
-        coverage_format: cobertura
-        path: my_awesome_app/coverage.xml
-    paths:
-      - my_awesome_app/htmlcov/*
+      - my_awesome_app/*
+      - project.gitlab-ci.yml
+
+# Spawn pipeline using the gitlab-ci.yml from generated template instance
+# use project.gitlab.ci.yml for necessary job overrides from this template instance
+# (due to changes in directories and paths etc)
+project-pipeline:
+  stage: test
+  trigger:
+    strategy: depend
+    include:
+      - artifact: my_awesome_app/.gitlab-ci.yml
+        job: build-template
+      - artifact: project.gitlab-ci.yml
+        job: build-template
+  variables:
+    PARENT_PIPELINE_ID: $CI_PIPELINE_ID
diff --git a/project.gitlab-ci.yml b/project.gitlab-ci.yml
new file mode 100644
index 0000000..663b7b5
--- /dev/null
+++ b/project.gitlab-ci.yml
@@ -0,0 +1,122 @@
+
+# This file overrides all the jobs defined in {{cookiecutter.project_slug}}/.gitlab.ci-yml
+# this is to ensure they depend on the template installation artifact of the root
+# .gitlab-ci.yml from job `build-template`
+# The generated gitlab-ci.yml from this `build-template` job is used for the actual
+# trigger include to prevent including jobs that still contain template arguments
+
+
+trigger_prepare:
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "parent_pipeline"
+      when: never
+
+default:
+  # Bootstrap Cookiecutter template to test provided ci pipeline template
+  before_script:
+    - cd my_awesome_app
+
+run_lint:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+
+sast:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+
+dependency_scanning:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+
+secret_detection:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+
+.run_unit_test_version_base:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+
+# Run all unit tests for Python versions except the base image
+run_unit_tests:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+
+# Run code coverage on the base image thus also performing unit tests
+run_unit_tests_coverage:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+  artifacts:
+    reports:
+      coverage_report:
+        coverage_format: cobertura
+        path: my_awesome_app/coverage.xml
+    paths:
+      - my_awesome_app/htmlcov/*
+
+package_files:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+  artifacts:
+    expire_in: 1w
+    paths:
+      - my_awesome_app/dist/*
+
+package_docs:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+  artifacts:
+    expire_in: 1w
+    paths:
+      - my_awesome_app/docs/build/*
+
+#docker_build:
+#  needs:
+#    - pipeline: $PARENT_PIPELINE_ID
+#      job: build-template
+#    - package_files
+#  before_script:
+#    - cd my_awesome_app
+
+run_integration_tests:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+    - package_files
+
+publish_on_gitlab:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+    - package_files
+
+publish_on_test_pypi:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+    - package_files
+
+publish_on_pypi:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+    - package_files
+
+publish_to_readthedocs:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+    - package_docs
+
+release_job:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
-- 
GitLab


From 45571645920f9cfb16312d7b6042d25b548b67d8 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 14:58:16 +0100
Subject: [PATCH 07/35] test

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 20272ac..003be8f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,9 +18,9 @@ build-template:
     - python --version # For debugging
     - cookiecutter --no-input --overwrite-if-exists --output-dir . .
     - cd my_awesome_app
-    - git init
     - git config user.email "ci-runner@example.com"
     - git config user.name "CI Runner"
+    - git init
     - source ./setup.sh
     - ls -lah
     - tox --version
-- 
GitLab


From c6da061ef8433ff37ff1f7bdb3fba252644bb91b Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 15:04:52 +0100
Subject: [PATCH 08/35] update

---
 .gitlab-ci.yml              | 5 +++--
 docker/ci-runner/Dockerfile | 5 ++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 003be8f..0388ead 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,10 +16,11 @@ build-template:
   # Bootstrap Cookiecutter template to test provided ci pipeline template
   script:
     - python --version # For debugging
+    - git config --global user.email "ci-runner@example.com"
+    - git config --global user.name "CI Runner"
+    - git config --global init.defaultBranch main
     - cookiecutter --no-input --overwrite-if-exists --output-dir . .
     - cd my_awesome_app
-    - git config user.email "ci-runner@example.com"
-    - git config user.name "CI Runner"
     - git init
     - source ./setup.sh
     - ls -lah
diff --git a/docker/ci-runner/Dockerfile b/docker/ci-runner/Dockerfile
index 497b89a..4d06f79 100644
--- a/docker/ci-runner/Dockerfile
+++ b/docker/ci-runner/Dockerfile
@@ -1,5 +1,4 @@
-FROM python:3.12
+FROM python:3.13
 
 RUN python -m pip install --upgrade pip
-RUN python -m pip install --upgrade cookiecutter tox twine cibuildwheel==2.13.1 cookiecutter
-RUN curl -sSL https://get.docker.com/ | sh
+RUN python -m pip install --upgrade cookiecutter tox twine
-- 
GitLab


From 840430c484ede42a4344c313d5836d0da7336d39 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 15:08:45 +0100
Subject: [PATCH 09/35] add setup.sh

---
 {{cookiecutter.project_slug}}/setup.sh | 30 ++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100755 {{cookiecutter.project_slug}}/setup.sh

diff --git a/{{cookiecutter.project_slug}}/setup.sh b/{{cookiecutter.project_slug}}/setup.sh
new file mode 100755
index 0000000..4548910
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/setup.sh
@@ -0,0 +1,30 @@
+#! /usr/bin/env bash
+#
+# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This file's directory is used to determine the station control directory
+# location.
+if [ -z ${BASH_SOURCE} ]; then
+  BASH_SOURCE=${(%):-%x}
+fi
+
+ABSOLUTE_PATH=$(realpath $(dirname ${BASH_SOURCE}))
+
+# Create a virtual environment directory if it doesn't exist
+VENV_DIR="${ABSOLUTE_PATH}/.venv"
+if [ ! -d "$VENV_DIR" ]; then
+    echo "Creating virtual environment..."
+    python3 -m venv "$VENV_DIR"
+fi
+
+# Activate the virtual environment
+source "$VENV_DIR/bin/activate"
+python -m pip install pre-commit
+python -m pip install "tox>=4.21.0"
+
+# Install git pre-commit pre-push hook
+if [ ! -f "${ABSOLUTE_PATH}/.git/hooks/pre-push.legacy" ]; then
+  source "${ABSOLUTE_PATH}/bin/install-hooks/pre-commit.sh"
+fi
-- 
GitLab


From 7993b014134e0e2cc6749309258638b9faefdc02 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 15:25:54 +0100
Subject: [PATCH 10/35] fix

---
 {{cookiecutter.project_slug}}/.gitlab-ci.yml | 1 -
 {{cookiecutter.project_slug}}/pyproject.toml | 5 ++++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index a08ac42..41e7261 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -99,7 +99,6 @@ package_files:
     paths:
       - dist/*
   script:
-    - source scripts/setup-docker-host.sh
     - tox -e build-ci-linux
 
 package_docs:
diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 3fc4fdf..2c4fa1b 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -125,4 +125,7 @@ commands = [["python", "-m", "build"]]
 [tool.tox.env.build-ci-linux]
 package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
-commands = [["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]]
+commands = [
+    ["source", "scripts/setup-docker-host.sh"],
+    ["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]
+]
-- 
GitLab


From 48a16050c2e428a0b7c581b2537bb255562e0817 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Tue, 11 Mar 2025 15:59:05 +0100
Subject: [PATCH 11/35] fix

---
 {{cookiecutter.project_slug}}/.gitlab-ci.yml | 1 +
 {{cookiecutter.project_slug}}/pyproject.toml | 6 ++----
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index 41e7261..a08ac42 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -99,6 +99,7 @@ package_files:
     paths:
       - dist/*
   script:
+    - source scripts/setup-docker-host.sh
     - tox -e build-ci-linux
 
 package_docs:
diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 2c4fa1b..257b56d 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -125,7 +125,5 @@ commands = [["python", "-m", "build"]]
 [tool.tox.env.build-ci-linux]
 package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
-commands = [
-    ["source", "scripts/setup-docker-host.sh"],
-    ["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]
-]
+allowlist_externals = ["docker"]
+commands = [["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]]
-- 
GitLab


From 48dc72ac250eebfc3f92818e1022b1d7894ef3ff Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Thu, 13 Mar 2025 10:02:45 +0100
Subject: [PATCH 12/35] fix

---
 docker/ci-runner/Dockerfile | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/docker/ci-runner/Dockerfile b/docker/ci-runner/Dockerfile
index 4d06f79..ded7c6d 100644
--- a/docker/ci-runner/Dockerfile
+++ b/docker/ci-runner/Dockerfile
@@ -1,4 +1,5 @@
 FROM python:3.13
 
-RUN python -m pip install --upgrade pip
-RUN python -m pip install --upgrade cookiecutter tox twine
+RUN python -m pip install --upgrade cibuildwheel pip tox twine
+RUN curl -sSL https://get.docker.com/ | sh
+
-- 
GitLab


From 76eed8bc5ba4f314760e1ba5a65e8174c9d4e915 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Thu, 13 Mar 2025 10:06:08 +0100
Subject: [PATCH 13/35] fix

---
 docker/ci-runner/Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docker/ci-runner/Dockerfile b/docker/ci-runner/Dockerfile
index ded7c6d..70893df 100644
--- a/docker/ci-runner/Dockerfile
+++ b/docker/ci-runner/Dockerfile
@@ -1,5 +1,5 @@
 FROM python:3.13
 
-RUN python -m pip install --upgrade cibuildwheel pip tox twine
+RUN python -m pip install --upgrade cibuildwheel cookiecutter pip tox twine
 RUN curl -sSL https://get.docker.com/ | sh
 
-- 
GitLab


From 6860bd7ab8730e1c1a77a08a8caff7ad5593132e Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Thu, 13 Mar 2025 10:23:17 +0100
Subject: [PATCH 14/35] fix

---
 {{cookiecutter.project_slug}}/pyproject.toml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 257b56d..5eb59e8 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -126,4 +126,5 @@ commands = [["python", "-m", "build"]]
 package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
 allowlist_externals = ["docker"]
+pass_ent = ["DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]
 commands = [["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]]
-- 
GitLab


From 64dfb80af7c928e60e49f377633a91af41e0b487 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Thu, 13 Mar 2025 10:36:22 +0100
Subject: [PATCH 15/35] fix

---
 {{cookiecutter.project_slug}}/pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 5eb59e8..85db592 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -126,5 +126,5 @@ commands = [["python", "-m", "build"]]
 package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
 allowlist_externals = ["docker"]
-pass_ent = ["DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]
+pass_env = ["DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]
 commands = [["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]]
-- 
GitLab


From 1a5e491c6c2faff8f037ab324998f6be8b9e22d9 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Thu, 13 Mar 2025 10:45:52 +0100
Subject: [PATCH 16/35] fix

---
 {{cookiecutter.project_slug}}/pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 85db592..c72b34b 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -125,6 +125,6 @@ commands = [["python", "-m", "build"]]
 [tool.tox.env.build-ci-linux]
 package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
-allowlist_externals = ["docker"]
+allowlist_externals = ["docker", "which"]
 pass_env = ["DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]
 commands = [["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]]
-- 
GitLab


From aba3915e2d61e77423ce6609507a26d6e70d1f0b Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Thu, 13 Mar 2025 11:00:24 +0100
Subject: [PATCH 17/35] remove python 3.9

---
 {{cookiecutter.project_slug}}/pyproject.toml | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index c72b34b..bdfbae8 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -11,13 +11,12 @@ name = "{{cookiecutter.project_slug}}"
 dynamic = ["version"]
 description="{{cookiecutter.description}}"
 readme = "README.md"
-requires-python = ">=3.9"
+requires-python = ">=3.10"
 classifiers = [
   "Development Status :: 4 - Beta",
   "License :: OSI Approved :: Apache Software License",
   "Operating System :: OS Independent",
   "Programming Language :: Python :: 3 :: Only",
-  "Programming Language :: Python :: 3.9",
   "Programming Language :: Python :: 3.10",
   "Programming Language :: Python :: 3.11",
   "Programming Language :: Python :: 3.12",
-- 
GitLab


From e8d9c673da07223be900658f395d97528cbb8c2a Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Fri, 14 Mar 2025 11:42:04 +0100
Subject: [PATCH 18/35] fix

---
 {{cookiecutter.project_slug}}/pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index bdfbae8..9387c01 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -124,6 +124,6 @@ commands = [["python", "-m", "build"]]
 [tool.tox.env.build-ci-linux]
 package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
-allowlist_externals = ["docker", "which"]
+allowlist_externals = ["docker", "which", "{work_dir}/.tox/bin/python"]
 pass_env = ["DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]
 commands = [["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]]
-- 
GitLab


From 1f3eeaac5ca305d93ebcf5b145e08b84f6ce331a Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Fri, 14 Mar 2025 11:52:45 +0100
Subject: [PATCH 19/35] fix

---
 {{cookiecutter.project_slug}}/CMakeLists.txt | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt
index ce8b8df..3b8b18d 100644
--- a/{{cookiecutter.project_slug}}/CMakeLists.txt
+++ b/{{cookiecutter.project_slug}}/CMakeLists.txt
@@ -6,6 +6,9 @@ set(
     "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-extra-utils/cmake"
 )
 
+set(LARGEFILE_SOURCE)
+set(FILE_OFFSET_BITS 64)
+
 # Include script
 include(DynamicVersion)
 dynamic_version(
-- 
GitLab


From 007d754de4bd10e80fe7db280bb36750165fe5c7 Mon Sep 17 00:00:00 2001
From: Hannes Feldt <feldt@astron.nl>
Date: Fri, 14 Mar 2025 12:18:24 +0100
Subject: [PATCH 20/35] test

---
 docker/ci-runner/Dockerfile                               | 2 +-
 {{cookiecutter.project_slug}}/build-requirements.txt      | 2 +-
 {{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/docker/ci-runner/Dockerfile b/docker/ci-runner/Dockerfile
index 70893df..2076807 100644
--- a/docker/ci-runner/Dockerfile
+++ b/docker/ci-runner/Dockerfile
@@ -1,5 +1,5 @@
 FROM python:3.13
 
-RUN python -m pip install --upgrade cibuildwheel cookiecutter pip tox twine
+RUN python -m pip install --upgrade cibuildwheel==2.23.0 cookiecutter pip tox twine
 RUN curl -sSL https://get.docker.com/ | sh
 
diff --git a/{{cookiecutter.project_slug}}/build-requirements.txt b/{{cookiecutter.project_slug}}/build-requirements.txt
index 357a5ca..d2d6bde 100644
--- a/{{cookiecutter.project_slug}}/build-requirements.txt
+++ b/{{cookiecutter.project_slug}}/build-requirements.txt
@@ -1,3 +1,3 @@
-cibuildwheel >= 2.12.3 # BSD
+cibuildwheel==2.23.0 # BSD
 tox >= 4.0 # ?
 build >= 0.8.0 # MIT
diff --git a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
index 5af9fd4..c984a99 100644
--- a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
+++ b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
@@ -1,4 +1,4 @@
 FROM python:3.13
 
-RUN python -m pip install --upgrade cibuildwheel pip tox twine
+RUN python -m pip install --upgrade cibuildwheel==2.23.0 pip tox twine
 RUN curl -sSL https://get.docker.com/ | sh
-- 
GitLab


From e10b3ba60ddbafa6cf4f8beb8ccc39f0965344e3 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 14:14:33 +0100
Subject: [PATCH 21/35] Test

---
 {{cookiecutter.project_slug}}/.gitlab-ci.yml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index a08ac42..26a54cd 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -94,12 +94,17 @@ package_files:
   stage: package
   tags:
     - dind
+  variables:
+    DOCKER_TLS_CERTDIR: "/certs"
+    DOCKER_TLS_VERIFY: 1
   artifacts:
     expire_in: 1w
     paths:
       - dist/*
   script:
+    - env
     - source scripts/setup-docker-host.sh
+    - env
     - tox -e build-ci-linux
 
 package_docs:
-- 
GitLab


From bd814bbb5a4269d9ab74231dac4bad94a55adb88 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 14:23:37 +0100
Subject: [PATCH 22/35] test

---
 {{cookiecutter.project_slug}}/.gitlab-ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index 26a54cd..8208900 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -96,15 +96,15 @@ package_files:
     - dind
   variables:
     DOCKER_TLS_CERTDIR: "/certs"
-    DOCKER_TLS_VERIFY: 1
   artifacts:
     expire_in: 1w
     paths:
       - dist/*
   script:
-    - env
+    - unset DOCKER_TLS_VERIFY
+    - env | grep DOCKER
     - source scripts/setup-docker-host.sh
-    - env
+    - env | grep DOCKER
     - tox -e build-ci-linux
 
 package_docs:
-- 
GitLab


From dd0a7864525ef1d0b8c8efe6c61c6cafdce0aeeb Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 14:46:01 +0100
Subject: [PATCH 23/35] lives biggest mystery

---
 {{cookiecutter.project_slug}}/.gitlab-ci.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index 8208900..2f1c504 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -105,6 +105,9 @@ package_files:
     - env | grep DOCKER
     - source scripts/setup-docker-host.sh
     - env | grep DOCKER
+    - echo $PATH
+    - which git
+    - which python
     - tox -e build-ci-linux
 
 package_docs:
-- 
GitLab


From e97db211673127bae4d6867097073ec8966369f6 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 15:08:03 +0100
Subject: [PATCH 24/35] Override manylinux choice because ci runner makes bad
 decisons

---
 {{cookiecutter.project_slug}}/pyproject.toml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 9387c01..b22e727 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -35,6 +35,14 @@ write_to = "src/{{cookiecutter.project_slug}}/_version.py"
 [tool.cibuildwheel]
 skip = "pp*"
 build-verbosity = 1
+manylinux-x86_64-image = "manylinux2014"
+manylinux-i686-image = "manylinux2014"
+
+# Before Python 3.10, manylinux2010 is the most compatible
+[[tool.cibuildwheel.overrides]]
+select = "cp3?-*"
+manylinux-x86_64-image = "manylinux2010"
+manylinux-i686-image = "manylinux2010"
 
 [tool.cibuildwheel.macos]
 archs = ["x86_64", "universal2", "arm64"]
-- 
GitLab


From 90e883a81fc6c28aa776ab80d9b1418959ca591e Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 15:22:49 +0100
Subject: [PATCH 25/35] Remove build

---
 {{cookiecutter.project_slug}}/pyproject.toml | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index b22e727..0c8f98e 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -134,4 +134,7 @@ package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
 allowlist_externals = ["docker", "which", "{work_dir}/.tox/bin/python"]
 pass_env = ["DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]
-commands = [["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]]
+commands = [
+    ["rm", "-rf", "build"],
+    ["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]
+]
-- 
GitLab


From 2577a22481176174d408cc9f9afb2cb82bdfab42 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 15:31:24 +0100
Subject: [PATCH 26/35] Rm the things

---
 {{cookiecutter.project_slug}}/pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 0c8f98e..a990c2a 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -132,7 +132,7 @@ commands = [["python", "-m", "build"]]
 [tool.tox.env.build-ci-linux]
 package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
-allowlist_externals = ["docker", "which", "{work_dir}/.tox/bin/python"]
+allowlist_externals = ["docker", "which", "{work_dir}/.tox/bin/python", "rm"]
 pass_env = ["DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]
 commands = [
     ["rm", "-rf", "build"],
-- 
GitLab


From 7c73e2c1097c5c0c9424e98fb5f5989daffcea67 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 15:44:33 +0100
Subject: [PATCH 27/35] Skip 32bit builds

---
 {{cookiecutter.project_slug}}/pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index a990c2a..acc4388 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -33,7 +33,7 @@ sdist.include = ["src/{{cookiecutter.project_slug}}/_version.py"]
 write_to = "src/{{cookiecutter.project_slug}}/_version.py"
 
 [tool.cibuildwheel]
-skip = "pp*"
+skip = "*-win32 *-manylinux_i686 pp*"
 build-verbosity = 1
 manylinux-x86_64-image = "manylinux2014"
 manylinux-i686-image = "manylinux2014"
-- 
GitLab


From 5e9cc6bd5b0a391c9c5d9aae230a1cd18fa61996 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 16:20:17 +0100
Subject: [PATCH 28/35] Remove kebab

---
 {{cookiecutter.project_slug}}/.gitlab-ci.yml |  8 --------
 {{cookiecutter.project_slug}}/pyproject.toml | 11 +----------
 2 files changed, 1 insertion(+), 18 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index 2f1c504..a08ac42 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -94,20 +94,12 @@ package_files:
   stage: package
   tags:
     - dind
-  variables:
-    DOCKER_TLS_CERTDIR: "/certs"
   artifacts:
     expire_in: 1w
     paths:
       - dist/*
   script:
-    - unset DOCKER_TLS_VERIFY
-    - env | grep DOCKER
     - source scripts/setup-docker-host.sh
-    - env | grep DOCKER
-    - echo $PATH
-    - which git
-    - which python
     - tox -e build-ci-linux
 
 package_docs:
diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index acc4388..418fa71 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -35,14 +35,6 @@ write_to = "src/{{cookiecutter.project_slug}}/_version.py"
 [tool.cibuildwheel]
 skip = "*-win32 *-manylinux_i686 pp*"
 build-verbosity = 1
-manylinux-x86_64-image = "manylinux2014"
-manylinux-i686-image = "manylinux2014"
-
-# Before Python 3.10, manylinux2010 is the most compatible
-[[tool.cibuildwheel.overrides]]
-select = "cp3?-*"
-manylinux-x86_64-image = "manylinux2010"
-manylinux-i686-image = "manylinux2010"
 
 [tool.cibuildwheel.macos]
 archs = ["x86_64", "universal2", "arm64"]
@@ -132,9 +124,8 @@ commands = [["python", "-m", "build"]]
 [tool.tox.env.build-ci-linux]
 package = "wheel"
 deps = ["-r{toxinidir}/build-requirements.txt"]
-allowlist_externals = ["docker", "which", "{work_dir}/.tox/bin/python", "rm"]
+allowlist_externals = ["docker"]
 pass_env = ["DOCKER_HOST", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH"]
 commands = [
-    ["rm", "-rf", "build"],
     ["python", "-m", "cibuildwheel", "--platform", "linux", "--output-dir", "dist"]
 ]
-- 
GitLab


From cccbd30945125ff646ffbe1bd36d495e537ec4f0 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 14 Mar 2025 16:37:44 +0100
Subject: [PATCH 29/35] Track branch coverage

---
 {{cookiecutter.project_slug}}/pyproject.toml | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 418fa71..9240bc2 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -33,7 +33,7 @@ sdist.include = ["src/{{cookiecutter.project_slug}}/_version.py"]
 write_to = "src/{{cookiecutter.project_slug}}/_version.py"
 
 [tool.cibuildwheel]
-skip = "*-win32 *-manylinux_i686 pp*"
+skip = "*-manylinux_i686 pp*"  # manylinux_i686 does not work on our runners currently
 build-verbosity = 1
 
 [tool.cibuildwheel.macos]
@@ -46,6 +46,10 @@ archs = ["x86_64", "i686"]
 archs = ["AMD64", "x86"]
 
 
+[tool.coverage.run]
+branch = true
+
+
 [tool.ruff]
 exclude = [
     ".venv",
-- 
GitLab


From 9b3ba167555c1e8726e8683c4124208fece0b13b Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Thu, 17 Apr 2025 13:07:23 +0200
Subject: [PATCH 30/35] Adress review comments

---
 {{cookiecutter.project_slug}}/.pre-commit-config.yaml       | 1 +
 {{cookiecutter.project_slug}}/CMakeLists.txt                | 3 ---
 {{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile   | 3 ++-
 .../docker/{{cookiecutter.project_slug}}/Dockerfile         | 5 +++++
 {{cookiecutter.project_slug}}/docs/cleanup.py               | 6 +++++-
 {{cookiecutter.project_slug}}/setup.sh                      | 4 +++-
 6 files changed, 16 insertions(+), 6 deletions(-)

diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
index ce46e64..c6570e2 100644
--- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
+++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
@@ -1,3 +1,4 @@
+# https://pre-commit.com/
 default_stages: [ pre-commit, pre-push ]
 default_language_version:
   python: python3
diff --git a/{{cookiecutter.project_slug}}/CMakeLists.txt b/{{cookiecutter.project_slug}}/CMakeLists.txt
index 3b8b18d..ce8b8df 100644
--- a/{{cookiecutter.project_slug}}/CMakeLists.txt
+++ b/{{cookiecutter.project_slug}}/CMakeLists.txt
@@ -6,9 +6,6 @@ set(
     "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-extra-utils/cmake"
 )
 
-set(LARGEFILE_SOURCE)
-set(FILE_OFFSET_BITS 64)
-
 # Include script
 include(DynamicVersion)
 dynamic_version(
diff --git a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
index c984a99..d61c399 100644
--- a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
+++ b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
@@ -1,4 +1,5 @@
 FROM python:3.13
 
-RUN python -m pip install --upgrade cibuildwheel==2.23.0 pip tox twine
+RUN python -m pip install --upgrade pip
+RUN python -m pip install --upgrade cibuildwheel==2.23.0 tox twine
 RUN curl -sSL https://get.docker.com/ | sh
diff --git a/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile b/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
index 1f7eb27..d64db49 100644
--- a/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
+++ b/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
@@ -1,5 +1,6 @@
 ARG BUILD_ENV=no_copy
 
+# Build without copying from host inside docker
 FROM python:3.13 AS build_no_copy
 ADD ../../requirements.txt .
 COPY ../.. /work
@@ -8,11 +9,15 @@ RUN python -m pip install --user tox
 WORKDIR /work
 RUN python -m tox -e build
 
+# Copy build package from host `dist` directory
 FROM python:3.13 AS build_copy
 COPY dist /work/dist
 
+# Dynamically set `build` from `build_no_copy` or `build_copy` depending on
+# `BUILD_ENV` argument
 FROM build_${BUILD_ENV} AS build
 
+# Copy build stage files into final python slim image and install
 FROM python:3.13-slim
 COPY --from=build /work/dist /dist
 RUN python -m pip install /dist/*.whl
diff --git a/{{cookiecutter.project_slug}}/docs/cleanup.py b/{{cookiecutter.project_slug}}/docs/cleanup.py
index 3a4508d..b2e219c 100644
--- a/{{cookiecutter.project_slug}}/docs/cleanup.py
+++ b/{{cookiecutter.project_slug}}/docs/cleanup.py
@@ -3,6 +3,10 @@
 #  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
 #  SPDX-License-Identifier: Apache-2.0
 
+"""
+Remove generated source documentation files except for index.rst
+"""
+
 import os
 
 file_dir = os.path.dirname(os.path.realpath(__file__))
@@ -15,7 +19,7 @@ if not os.path.exists(clean_dir):
 
 for file_name in os.listdir(clean_dir):
     file = os.path.join(clean_dir, file_name)
-    
+
     if file_name == "index.rst":
         continue
 
diff --git a/{{cookiecutter.project_slug}}/setup.sh b/{{cookiecutter.project_slug}}/setup.sh
index 4548910..3eb7429 100755
--- a/{{cookiecutter.project_slug}}/setup.sh
+++ b/{{cookiecutter.project_slug}}/setup.sh
@@ -5,7 +5,9 @@
 #
 
 # This file's directory is used to determine the station control directory
-# location.
+# location. We substitute BASH_SOURCE if it doesn't get set, this particularly
+# happens when sourcing using the `docker` docker image due to its primitive
+# shell.
 if [ -z ${BASH_SOURCE} ]; then
   BASH_SOURCE=${(%):-%x}
 fi
-- 
GitLab


From 9af9706cadedb0624a999e0f81dc59ab0f299830 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Thu, 17 Apr 2025 13:07:23 +0200
Subject: [PATCH 31/35] Adress review comments

---
 docker/ci-runner/Dockerfile | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/docker/ci-runner/Dockerfile b/docker/ci-runner/Dockerfile
index 2076807..d016805 100644
--- a/docker/ci-runner/Dockerfile
+++ b/docker/ci-runner/Dockerfile
@@ -1,5 +1,6 @@
 FROM python:3.13
 
-RUN python -m pip install --upgrade cibuildwheel==2.23.0 cookiecutter pip tox twine
+RUN python -m pip install --upgrade pip
+RUN python -m pip install --upgrade cibuildwheel==2.23.0 cookiecutter tox twine
 RUN curl -sSL https://get.docker.com/ | sh
 
-- 
GitLab


From a3565db3c625631b15896a213062c711b9363319 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 25 Apr 2025 11:18:01 +0200
Subject: [PATCH 32/35] Document

---
 docker/ci-runner/Dockerfile                   |  4 +-
 project.gitlab-ci.yml                         | 14 +++----
 {{cookiecutter.project_slug}}/.gitlab-ci.yml  | 38 +++++++++++--------
 .../.pre-commit-config.yaml                   |  5 ++-
 {{cookiecutter.project_slug}}/README.md       |  2 +
 .../bin/install-hooks/pre-commit.sh           |  8 ----
 .../build-requirements.txt                    |  2 +-
 .../docker/ci-runner/Dockerfile               |  4 +-
 .../{{cookiecutter.project_slug}}/Dockerfile  |  9 ++++-
 {{cookiecutter.project_slug}}/docs/cleanup.py |  6 +++
 {{cookiecutter.project_slug}}/setup.sh        | 16 ++++----
 11 files changed, 65 insertions(+), 43 deletions(-)
 delete mode 100755 {{cookiecutter.project_slug}}/bin/install-hooks/pre-commit.sh

diff --git a/docker/ci-runner/Dockerfile b/docker/ci-runner/Dockerfile
index d016805..aa79583 100644
--- a/docker/ci-runner/Dockerfile
+++ b/docker/ci-runner/Dockerfile
@@ -1,6 +1,8 @@
+# This dockerfile is used throughout steps of the CI/CD pipeline
+# It contains dependencies to execute the template and run tests
 FROM python:3.13
 
 RUN python -m pip install --upgrade pip
-RUN python -m pip install --upgrade cibuildwheel==2.23.0 cookiecutter tox twine
+RUN python -m pip install --upgrade cibuildwheel>=2.23.0 cookiecutter tox twine
 RUN curl -sSL https://get.docker.com/ | sh
 
diff --git a/project.gitlab-ci.yml b/project.gitlab-ci.yml
index 663b7b5..740497a 100644
--- a/project.gitlab-ci.yml
+++ b/project.gitlab-ci.yml
@@ -78,13 +78,13 @@ package_docs:
     paths:
       - my_awesome_app/docs/build/*
 
-#docker_build:
-#  needs:
-#    - pipeline: $PARENT_PIPELINE_ID
-#      job: build-template
-#    - package_files
-#  before_script:
-#    - cd my_awesome_app
+docker_build:
+  needs:
+    - pipeline: $PARENT_PIPELINE_ID
+      job: build-template
+    - package_files
+  before_script:
+    - cd my_awesome_app
 
 run_integration_tests:
   needs:
diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
index a08ac42..d08a621 100644
--- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml
+++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml
@@ -2,8 +2,6 @@ default:
   image:
     name: $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG
     pull_policy: always
-  before_script:
-    - python --version # For debugging
   cache:
     paths:
       - .cache/pip
@@ -22,14 +20,13 @@ stages:
 variables:
   PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
 
-
+# Job templates from Gitlab that scan for security issues or leaked secrets
 include:
   - template: Security/SAST.gitlab-ci.yml
   - template: Security/Dependency-Scanning.gitlab-ci.yml
   - template: Security/Secret-Detection.gitlab-ci.yml
 
-
-# Prepare image to run ci on
+# Prepare image to run ci on, image will be used in most jobs
 trigger_prepare:
   stage: prepare
   trigger:
@@ -48,16 +45,6 @@ sast:
       pmd-apex, sobelow, spotbugs
   stage: test
 
-dependency_scanning:
-  # override default before_script, job won't have Python available
-  before_script:
-    - uname
-
-secret_detection:
-  # override default before_script, job won't have Python available
-  before_script:
-    - uname
-
 # Basic setup for all Python versions for which we don't have a base image
 .run_unit_test_version_base:
   before_script:
@@ -66,6 +53,7 @@ secret_detection:
     - python -m pip install --upgrade tox twine
 
 # Run all unit tests for Python versions except the base image
+# The base image is used for the highest Python version
 run_unit_tests:
   extends: .run_unit_test_version_base
   stage: test
@@ -90,6 +78,8 @@ run_unit_tests_coverage:
     paths:
       - htmlcov/*
 
+# Binary packages will be build for many architectures
+# using scikitbuild-core and cibuildwheel
 package_files:
   stage: package
   tags:
@@ -102,6 +92,7 @@ package_files:
     - source scripts/setup-docker-host.sh
     - tox -e build-ci-linux
 
+# Build the sphinx documentation
 package_docs:
   stage: package
   artifacts:
@@ -111,6 +102,22 @@ package_docs:
   script:
     - tox -e docs
 
+# Build a tiny docker image that contains the minimal dependencies to run this project
+docker_build:
+  stage: images
+  image: docker:latest
+  needs:
+    - package_files
+  tags:
+    - dind
+  before_script: []
+  script:
+    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+    - docker build -f docker/{{cookiecutter.project_slug}}/Dockerfile . --build-arg BUILD_ENV=copy --tag $CI_REGISTRY_IMAGE/{{cookiecutter.project_slug}}:$CI_COMMIT_REF_SLUG
+    # enable this push line once you have configured docker registry cleanup policy
+    # - docker push $CI_REGISTRY_IMAGE/{{cookiecutter.project_slug}}:$CI_COMMIT_REF_SLUG
+
+
 run_integration_tests:
   stage: integration
   allow_failure: true
@@ -187,6 +194,7 @@ publish_to_readthedocs:
     - echo "scp docs/* ???"
     - exit 1
 
+# automatically create a release on Gitlab for every tagged commit
 release_job:
   stage: publish
   image: registry.gitlab.com/gitlab-org/release-cli:latest
diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
index c6570e2..c7c1b72 100644
--- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
+++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml
@@ -1,4 +1,7 @@
-# https://pre-commit.com/
+# run several checks prior to creating a commit or pushing to a remote
+# These can be skipped (but you shouldn't generally as the pipeline will fail)
+# using `git push / commit --no-check`
+# for more information see: https://pre-commit.com/
 default_stages: [ pre-commit, pre-push ]
 default_language_version:
   python: python3
diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md
index 4e6e8c7..f74c6e0 100644
--- a/{{cookiecutter.project_slug}}/README.md
+++ b/{{cookiecutter.project_slug}}/README.md
@@ -38,5 +38,7 @@ To automatically apply most suggested linting changes execute:
 
 ```tox -e format```
 
+The configuration for linting and tox can be found in `pyproject.toml`
+
 ## License
 This project is licensed under the Apache License Version 2.0
diff --git a/{{cookiecutter.project_slug}}/bin/install-hooks/pre-commit.sh b/{{cookiecutter.project_slug}}/bin/install-hooks/pre-commit.sh
deleted file mode 100755
index 792a3aa..0000000
--- a/{{cookiecutter.project_slug}}/bin/install-hooks/pre-commit.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-if [ ! -f "setup.sh" ]; then
-  echo "pre-commit.sh must be executed with repository root as working directory!"
-  exit 1
-fi
-
-pre-commit install --hook-type pre-push
diff --git a/{{cookiecutter.project_slug}}/build-requirements.txt b/{{cookiecutter.project_slug}}/build-requirements.txt
index d2d6bde..502b5f3 100644
--- a/{{cookiecutter.project_slug}}/build-requirements.txt
+++ b/{{cookiecutter.project_slug}}/build-requirements.txt
@@ -1,3 +1,3 @@
-cibuildwheel==2.23.0 # BSD
+cibuildwheel>=2.23.0 # BSD
 tox >= 4.0 # ?
 build >= 0.8.0 # MIT
diff --git a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
index d61c399..4494c2b 100644
--- a/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
+++ b/{{cookiecutter.project_slug}}/docker/ci-runner/Dockerfile
@@ -1,5 +1,7 @@
+# This dockerfile is used throughout steps of the CI/CD pipeline
+# It contains dependencies to run tests
 FROM python:3.13
 
 RUN python -m pip install --upgrade pip
-RUN python -m pip install --upgrade cibuildwheel==2.23.0 tox twine
+RUN python -m pip install --upgrade cibuildwheel>=2.23.0 tox twine
 RUN curl -sSL https://get.docker.com/ | sh
diff --git a/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile b/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
index d64db49..9e9cb23 100644
--- a/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
+++ b/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
@@ -1,13 +1,18 @@
+# This dockerfile creates a minimal docker image that can run the project.
+# It can copy the result of `tox -e build` from the host to the container
+# or build from source inside the container.
+# Set `docker build --build-arg BUILD_ENV=copy` to copy from the host, rebuild otherwise
+# contrarily, to the ci image it does not contain any dependencies used for testing.
 ARG BUILD_ENV=no_copy
 
 # Build without copying from host inside docker
 FROM python:3.13 AS build_no_copy
 ADD ../../requirements.txt .
 COPY ../.. /work
-RUN rm -r /work/dist | true
+RUN rm -rf /work/dist
 RUN python -m pip install --user tox
 WORKDIR /work
-RUN python -m tox -e build
+RUN python -m tox -e build-local
 
 # Copy build package from host `dist` directory
 FROM python:3.13 AS build_copy
diff --git a/{{cookiecutter.project_slug}}/docs/cleanup.py b/{{cookiecutter.project_slug}}/docs/cleanup.py
index b2e219c..c888890 100644
--- a/{{cookiecutter.project_slug}}/docs/cleanup.py
+++ b/{{cookiecutter.project_slug}}/docs/cleanup.py
@@ -5,6 +5,12 @@
 
 """
 Remove generated source documentation files except for index.rst
+
+If a source file is created, documentation is generated and the source file is later
+removed. The documentation for this source file will persists.
+
+This file ensures generated source documentation files, which are automatically
+generated from source, are removed between every build.
 """
 
 import os
diff --git a/{{cookiecutter.project_slug}}/setup.sh b/{{cookiecutter.project_slug}}/setup.sh
index 3eb7429..b1a3a35 100755
--- a/{{cookiecutter.project_slug}}/setup.sh
+++ b/{{cookiecutter.project_slug}}/setup.sh
@@ -4,14 +4,16 @@
 # SPDX-License-Identifier: Apache-2.0
 #
 
-# This file's directory is used to determine the station control directory
-# location. We substitute BASH_SOURCE if it doesn't get set, this particularly
-# happens when sourcing using the `docker` docker image due to its primitive
-# shell.
+# Substitute BASH_SOURCE if unset this is required for simple shells
+# such as the one found in docker or alpine docker images.
+# `#! /usr/bin/env bash` does not actually ensure the sourcing is executed
+# using BASH
 if [ -z ${BASH_SOURCE} ]; then
   BASH_SOURCE=${(%):-%x}
 fi
 
+# From BASH_SOURCE determine the absolute path, this is needed because $0 is
+# unset when sourcing in some shells.
 ABSOLUTE_PATH=$(realpath $(dirname ${BASH_SOURCE}))
 
 # Create a virtual environment directory if it doesn't exist
@@ -26,7 +28,7 @@ source "$VENV_DIR/bin/activate"
 python -m pip install pre-commit
 python -m pip install "tox>=4.21.0"
 
-# Install git pre-commit pre-push hook
-if [ ! -f "${ABSOLUTE_PATH}/.git/hooks/pre-push.legacy" ]; then
-  source "${ABSOLUTE_PATH}/bin/install-hooks/pre-commit.sh"
+# Install git pre-commit pre-push hook if not already installed
+if [ ! -f "${ABSOLUTE_PATH}/.git/hooks/pre-push" ]; then
+  pre-commit install --hook-type pre-push
 fi
-- 
GitLab


From 3af76de09dfee37fabffdaf64e8e5360139654b9 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Mon, 28 Apr 2025 14:49:28 +0200
Subject: [PATCH 33/35] Dynamically select the binary wheel to install

---
 .../docker/{{cookiecutter.project_slug}}/Dockerfile             | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile b/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
index 9e9cb23..f50ac74 100644
--- a/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
+++ b/{{cookiecutter.project_slug}}/docker/{{cookiecutter.project_slug}}/Dockerfile
@@ -25,4 +25,4 @@ FROM build_${BUILD_ENV} AS build
 # Copy build stage files into final python slim image and install
 FROM python:3.13-slim
 COPY --from=build /work/dist /dist
-RUN python -m pip install /dist/*.whl
+RUN python -m pip install /dist/*cp313*many*$(uname -m).whl
-- 
GitLab


From bc1fed8f5d9108159414a73a2fabbb87713f3b11 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Fri, 13 Jun 2025 13:06:27 +0200
Subject: [PATCH 34/35] Add the promised documentation

---
 {{cookiecutter.project_slug}}/README.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md
index f74c6e0..323185a 100644
--- a/{{cookiecutter.project_slug}}/README.md
+++ b/{{cookiecutter.project_slug}}/README.md
@@ -39,6 +39,10 @@ To automatically apply most suggested linting changes execute:
 ```tox -e format```
 
 The configuration for linting and tox can be found in `pyproject.toml`
+For further references on tox or pyproject.toml visit:
+
+1. https://tox.wiki/en/latest/config.html#pyproject-toml-ini
+2. https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
 
 ## License
 This project is licensed under the Apache License Version 2.0
-- 
GitLab


From 02fa248643d969f2eaf015c3d3986ecedc6aa9a1 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Mon, 16 Jun 2025 10:41:59 +0200
Subject: [PATCH 35/35] Match ruff rule expansion across Python templates

---
 {{cookiecutter.project_slug}}/pyproject.toml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml
index 9240bc2..f6dd9c3 100644
--- a/{{cookiecutter.project_slug}}/pyproject.toml
+++ b/{{cookiecutter.project_slug}}/pyproject.toml
@@ -63,6 +63,10 @@ exclude = [
 ]
 
 [tool.ruff.lint]
+# Expand basic rules with name and style checks roughly equivalent to flake8 + black + pylint
+# Some additional functional errors such as `await` on none async or wrong return types / args.
+# More info https://docs.astral.sh/ruff/rules/
+select = ["N", "A", "E", "F", "B", "W", "Q", "PL", "ASYNC", "S", "G", "RET", "ARG"]
 ignore = ["E203"]
 
 
-- 
GitLab