Compare commits

..

112 Commits

Author SHA1 Message Date
Patrick Lehmann
2658aeb896 v7.1.0 2025-12-20 16:56:30 +01:00
Patrick Lehmann
37a73ff495 Changed codecov/test-results-action@v1 to codecov/codecov-action@v5. 2025-12-20 12:43:43 +01:00
Patrick Lehmann
7d9dc6d312 v7.0.1 2025-12-19 23:57:41 +01:00
Patrick Lehmann
2e502e165a Bumped version to v7.0.1 2025-12-19 23:40:47 +01:00
Patrick Lehmann
e5894f4654 Updated maintainer notice. 2025-12-19 23:34:30 +01:00
Patrick Lehmann
f733694766 Removed BuildTheDocs, CoverageCollection and NightlyRelease templates. 2025-12-19 23:33:59 +01:00
Patrick Lehmann
b49cd82b47 v7.0.0 2025-12-17 00:21:12 +01:00
Patrick Lehmann
69f7689c69 Removed releaser. 2025-12-17 00:18:25 +01:00
Patrick Lehmann
9459e295d1 Create an asset table. 2025-12-16 23:39:21 +01:00
Patrick Lehmann
e5a874819f Deploy GitHub pages using an Action instead of a special branch. 2025-12-14 23:49:25 +01:00
Patrick Lehmann
18379306db Don't install GCC in MSYS2. 2025-12-14 23:23:03 +01:00
Patrick Lehmann
1bef5347ae Compute path to requirements.txt. 2025-12-14 19:57:42 +01:00
Patrick Lehmann
d3e7e4f6ed Reorganized unit testing requirements. 2025-12-14 18:42:50 +01:00
Patrick Lehmann
8354c4a084 Read requirements for static typing from 'tests/typing/'. 2025-12-14 18:39:14 +01:00
Patrick Lehmann
323fa17773 Allow explicit relative path in requirements file. 2025-12-14 16:25:54 +01:00
Patrick Lehmann
92a168c8c8 Fixed linebreak in printf. 2025-12-14 01:24:13 +01:00
Patrick Lehmann
388f55721c Trying to find a workarounf for mypy's dependency to librt. 2025-12-13 23:52:16 +01:00
Patrick Lehmann
7c249a1ae0 Test macos-15-intel. 2025-12-13 23:51:44 +01:00
Patrick Lehmann
8198b215a7 Changed macos-13 (x86-64) to macos-15-intel (x86-64). 2025-12-13 22:48:00 +01:00
Patrick Lehmann
800976853f Updated actions/download-artifact@v6 to actions/download-artifact@v7. 2025-12-13 22:44:33 +01:00
Patrick Lehmann
1593383254 Updated actions/upload-artifact@v5 to actions/upload-artifact@v6. 2025-12-13 22:44:13 +01:00
Patrick Lehmann
780b6f466c Bumped version of actions/checkout to @v6. 2025-11-20 21:18:57 +01:00
Patrick Lehmann
c2282e4d63 v6.7.0 2025-11-13 15:30:01 +01:00
Patrick Lehmann
546bf3db8a Fixed wrongly quoted variable in jq pattern. 2025-11-13 13:39:51 +01:00
Patrick Lehmann
b04ceae7bb Extract version from version file. 2025-10-28 11:54:20 +01:00
Patrick Lehmann
25c007b491 v6.6.0 2025-10-27 07:45:58 +01:00
Patrick Lehmann
1e3e3011e4 Bumped Python dependencies. 2025-10-27 07:36:38 +01:00
Patrick Lehmann
68c8c8b7cc Added pip option '--break-system-packages'. 2025-10-27 07:36:14 +01:00
Patrick Lehmann
fbf1108ec2 Bumped versions of upload-artifact to @v5 and download-artifact to @v6. 2025-10-27 07:35:31 +01:00
Patrick Lehmann
a78656a0bb Fixed used colors for selected Python versions and removed Python 3.8. 2025-10-19 23:25:13 +02:00
Patrick Lehmann
ec73d6bc41 v6.5.0 2025-10-19 01:22:16 +02:00
Patrick Lehmann
10c10d9566 Updated documentation accordingly. 2025-10-19 01:04:46 +02:00
Patrick Lehmann
29b1e2d8eb Fixed artifact name checking. 2025-10-19 00:41:50 +02:00
Patrick Lehmann
0edc7c4ca7 Set Python alpha version to 3.15 2025-10-19 00:24:32 +02:00
Patrick Lehmann
77ed5bb343 Check job matrix. 2025-10-19 00:20:07 +02:00
Patrick Lehmann
6432741888 Reduced code duplications when checking job-matrix and artifact names by using local Actions. 2025-10-18 23:33:24 +02:00
Patrick Lehmann
7e6bb82ae8 Removed Python 3.9 from preselected list of Python versions and added Python 3.14. 2025-10-18 22:20:06 +02:00
Patrick Lehmann
cf7a98730e Activated bandit and pylint. [skip ci] 2025-10-05 15:37:39 +02:00
Patrick Lehmann
fb36154250 v6.4.0 2025-10-05 15:29:38 +02:00
Patrick Lehmann
fc08112235 Added parameters to enable bandit and pylint checks. 2025-10-05 15:14:39 +02:00
Patrick Lehmann
953d0698c9 Fixed wheel package installations on Windows. 2025-10-04 23:48:00 +02:00
Patrick Lehmann
05e5d1f86c Improved pyproject.toml reading if settings don't exist. 2025-10-04 21:30:24 +02:00
Patrick Lehmann
2eebeec719 v6.3.0 2025-10-01 15:16:07 +02:00
Patrick Lehmann
5b97eaf241 Gather a list of submodule names, pathes etc. 2025-10-01 12:55:33 +02:00
Patrick Lehmann
1e694005ed Fixed structure version datatype in inventory JSON. 2025-10-01 03:11:48 +02:00
Patrick Lehmann
46a2764e73 Fixed timestamp format in inventory JSON. 2025-10-01 00:53:09 +02:00
Patrick Lehmann
626d64ef6a Updated classification outputs. 2025-10-01 00:28:54 +02:00
Patrick Lehmann
fe4c9139c1 Print Git reference classification. 2025-09-30 23:47:28 +02:00
Patrick Lehmann
ae8a961e93 v6.2.0 2025-09-30 22:53:57 +02:00
Patrick Lehmann
e4b5ea3895 Check if commit is on default branch. 2025-09-30 21:34:33 +02:00
Patrick Lehmann
b61f479180 Fix computation of latest version from GH CLI. 2025-09-30 09:02:50 +02:00
Patrick Lehmann
9e6bbd52a6 v6.1.0 2025-09-25 00:26:08 +02:00
Patrick Lehmann
438207a68d Added latest released version to the inventory.json. 2025-09-25 00:21:48 +02:00
Patrick Lehmann
c08a164b9e v6.0.0 2025-09-24 15:25:58 +02:00
Patrick Lehmann
2e4f6f3e7c Documented InstallPackage. 2025-09-24 15:19:59 +02:00
Patrick Lehmann
a8e4c60424 Improved links. 2025-09-24 15:17:57 +02:00
Patrick Lehmann
c0547188f9 Changed documentation to @r6. 2025-09-24 09:53:51 +02:00
Patrick Lehmann
53a32fbf35 Also use JSON objects for SphinxDocumentation. 2025-09-24 00:17:43 +02:00
Patrick Lehmann
91736df13e Try to package myPackage or myFramework.Extension with same setup-routine. 2025-09-23 22:59:02 +02:00
Patrick Lehmann
d190b1a3b1 Update documentation according to latest changes - part 2. 2025-09-23 20:26:31 +02:00
Patrick Lehmann
055863ee5f Test Bandit reporting. 2025-09-23 20:24:35 +02:00
Patrick Lehmann
22bbe48d4b Check if artifacts have no basename (prefix). 2025-09-23 18:09:53 +02:00
Patrick Lehmann
4d260bfdf5 Publish Bandit report only if issues are found (avoid empty report). 2025-09-23 18:06:51 +02:00
Patrick Lehmann
f684e67bca Update documentation according to latest changes. 2025-09-23 17:00:35 +02:00
Patrick Lehmann
8ecefcec59 Added deprecation warnings. 2025-09-22 08:18:42 +02:00
Patrick Lehmann
b247eb4a53 Documentation fine-tuning. 2025-09-22 00:58:18 +02:00
Patrick Lehmann
fb67dd0fdb Handle namespace.* packages. 2025-09-21 00:18:32 +02:00
Patrick Lehmann
bfe857a4af Added Bandir, Radon metrics and PyLint checking. 2025-09-18 00:08:07 +02:00
Patrick Lehmann
1041e7b5c7 Reduced number of parameters by passing JSON objects. 2025-09-17 07:17:48 +02:00
Patrick Lehmann
e2e8b39c41 Rework StaticTypeCheck. 2025-09-15 22:16:24 +02:00
Patrick Lehmann
ae6f532e52 Improved landing page. 2025-09-13 22:27:52 +02:00
Patrick Lehmann
2fe6be15c7 Corrections after consistency, spelling and grammar check by Gemini. 2025-09-13 22:03:05 +02:00
Patrick Lehmann
15c9a23136 Improve mypy execution and less dependencies and manual/hard-coded Bash commands. 2025-09-13 21:06:37 +02:00
Patrick Lehmann
f79a63bf8e Improving StaticTypeCheck. 2025-09-13 18:22:03 +02:00
Patrick Lehmann
0c8f81d52e Allow LaTeXDocumentation jobs to fail. 2025-09-12 21:51:43 +02:00
Patrick Lehmann
250ad53b7b Documented StaticTypeCheck. 2025-09-12 00:38:01 +02:00
Patrick Lehmann
d1d591efb8 Reworked PublishToGitHubPages. 2025-09-10 08:23:17 +02:00
Patrick Lehmann
57f88b158f Fine tuning documentation pages. 2025-09-10 07:49:11 +02:00
Patrick Lehmann
f37f087334 Documented PublishCoverageResults. 2025-09-08 23:56:28 +02:00
Patrick Lehmann
dd30d0bc18 Reduced duplications. 2025-09-08 22:24:07 +02:00
Patrick Lehmann
35dd52d018 Documented CheckDocumentation. 2025-09-08 07:50:57 +02:00
Patrick Lehmann
a3816bfd59 Set releases always to latest. 2025-09-08 07:26:44 +02:00
Patrick Lehmann
e6b2d0a876 Continued PublishReleaseNotes. 2025-09-08 00:14:16 +02:00
Patrick Lehmann
52712f8491 Bumped dependencies. 2025-09-07 08:26:01 +02:00
Patrick Lehmann
d005debf69 Documented IntermediateCleanup and ArtifactCleanup. 2025-09-07 08:19:12 +02:00
Patrick Lehmann
ee869dead5 Documented PublishReleaseNotes. 2025-09-05 01:19:17 +02:00
Patrick Lehmann
1c99a4a914 Documented TagReleaseCommit. 2025-09-04 23:46:46 +02:00
Patrick Lehmann
d62ba6c06b Documented InstallPackage. 2025-09-02 07:45:26 +02:00
Patrick Lehmann
e5b29528b7 Reworked PublishTestResults. 2025-09-02 07:31:24 +02:00
Patrick Lehmann
e559fb8219 Reworked PublishOnPyPI. 2025-09-01 23:04:45 +02:00
Patrick Lehmann
d5ad74fa89 Documented Package. 2025-09-01 22:22:05 +02:00
Patrick Lehmann
e5706693e2 Restructuring example code. 2025-09-01 01:17:01 +02:00
Patrick Lehmann
729e406294 Reworked UnitTesting. 2025-09-01 00:36:52 +02:00
Patrick Lehmann
bb03bdcc11 Improved SphinxDocumentation and LaTeXDocumentation. 2025-08-31 12:07:05 +02:00
Patrick Lehmann
0adc72d26a Support cross-repository pull request runs from forks. 2025-08-29 22:15:12 +02:00
Patrick Lehmann
277df7ea7f Partially documented SphinxDocumentation and LaTeXDocumentation. 2025-08-29 22:15:12 +02:00
Patrick Lehmann
817c84af2e Adding and testing ubuntu-arm and windows-arm support. 2025-08-29 22:15:12 +02:00
Patrick Lehmann
99c3752847 Reworked Parameters. 2025-08-29 22:15:12 +02:00
Patrick Lehmann
13e42615b2 Documented ExtractConfiguration. 2025-08-29 22:15:12 +02:00
Patrick Lehmann
6d75b849f5 Updated CompletePipeline. 2025-08-29 22:15:12 +02:00
Patrick Lehmann
4f49964e57 Documented PrepareJob. 2025-08-29 22:15:12 +02:00
Patrick Lehmann
b4d45277d1 Testing available runner images for Free/OpenSource Plan. 2025-08-29 22:15:12 +02:00
Patrick Lehmann
0f66b32418 Documented CompletePipeline. 2025-08-29 22:15:11 +02:00
Patrick Lehmann
4badbda8e7 Restructured chapters. 2025-08-29 22:15:11 +02:00
Patrick Lehmann
3141de852a Updated workflow references to @r5. 2025-08-29 22:15:11 +02:00
Patrick Lehmann
a2db5ec238 Document all recently added workflow templates. 2025-08-29 22:15:11 +02:00
Patrick Lehmann
c78cb4062f Fixed usage of Windows image. 2025-08-29 22:15:11 +02:00
Patrick Lehmann
2c0e88f39a Check artifact uploading. 2025-08-29 22:15:11 +02:00
Patrick Lehmann
e962cd6953 Fix potential artifact upload for XML and JSON coverage artifacts. 2025-08-29 22:15:11 +02:00
Patrick Lehmann
bde1b15783 Bumped Windows Server image from 2022 to 2025. 2025-08-29 22:15:11 +02:00
Patrick Lehmann
d6342484cd v5.4.0 2025-08-29 22:14:18 +02:00
Patrick Lehmann
ba3d82668e Allow configuration of JUnit dialects. 2025-08-29 21:57:36 +02:00
144 changed files with 10487 additions and 4676 deletions

View File

@@ -0,0 +1,75 @@
name: Check artifact names
branding:
icon: check-square
color: green
description: Check generated artifact names.
author: Patrick Lehmann (@Paebbels)
inputs:
prefix:
description:
type: string
required: true
generated-names:
description:
type: string
required: true
runs:
using: composite
steps:
- name: Install dependencies
shell: bash
run: pip install --disable-pip-version-check --break-system-packages pyTooling
- name: Check artifact names
id: check
shell: python
run: |
from json import loads as json_loads
from sys import exit
from pyTooling.Common import zipdicts
actualArtifactNames = json_loads("""${{ inputs.generated-names }}""".replace("'", '"'))
expectedName = "${{ inputs.prefix }}"
expectedArtifacts = {
"unittesting_xml": f"{expectedName}-UnitTestReportSummary-XML",
"unittesting_html": f"{expectedName}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{expectedName}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{expectedName}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{expectedName}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{expectedName}-CodeCoverage-SQLite",
"codecoverage_xml": f"{expectedName}-CodeCoverage-XML",
"codecoverage_json": f"{expectedName}-CodeCoverage-JSON",
"codecoverage_html": f"{expectedName}-CodeCoverage-HTML",
"statictyping_cobertura": f"{expectedName}-StaticTyping-Cobertura-XML",
"statictyping_junit": f"{expectedName}-StaticTyping-JUnit-XML",
"statictyping_html": f"{expectedName}-StaticTyping-HTML",
"package_all": f"{expectedName}-Packages",
"documentation_html": f"{expectedName}-Documentation-HTML",
"documentation_latex": f"{expectedName}-Documentation-LaTeX",
"documentation_pdf": f"{expectedName}-Documentation-PDF",
}
errors = 0
if len(actualArtifactNames) != len(expectedArtifacts):
print(f"❌ Number of 'artifact_names' does not match: {len(actualArtifactNames)} != {len(expectedArtifacts)}.")
errors += 1
else:
print("✅ Number of 'artifact_names' as expected.")
print("Checking artifact names ...")
for key, actual, expected in zipdicts(actualArtifactNames, expectedArtifacts):
if actual != expected:
print(f" ❌ Artifact name '{key}' does not match: {actual} != {expected}.")
errors += 1
else:
print(f" ☑ Artifact name as expected: {key} ⇢ {actual}.")
if errors == 0:
print("✅ All checks PASSED.")
else:
print(f"❌ Counted {errors} errors.")
exit(errors)

View File

@@ -0,0 +1,92 @@
name: Check job matrix
branding:
icon: check-square
color: green
description: Check generated job matrix.
author: Patrick Lehmann (@Paebbels)
inputs:
expected-default-version:
description:
type: string
required: true
expected-python-versions:
description:
type: string
required: true
expected-systems:
description:
type: string
required: true
expected-exclude-jobs:
description:
type: string
required: true
expected-include-jobs:
description:
type: string
required: true
generated-default-version:
description:
type: string
required: true
generated-jobmatrix:
description:
type: string
required: true
runs:
using: composite
steps:
- name: Check parameters
id: check
shell: python
run: |
from json import loads as json_loads
from sys import exit
actualPythonVersion = """${{ inputs.generated-default-version }}"""
actualPythonJobs = json_loads("""${{ inputs.generated-jobmatrix }}""".replace("'", '"'))
expectedPythonVersion = """${{ inputs.expected-default-version }}"""
expectedPythons = json_loads("""${{ inputs.expected-python-versions }}""".replace("'", '"'))
expectedSystems = json_loads("""${{ inputs.expected-systems }}""".replace("'", '"'))
excludedJobs = json_loads("""${{ inputs.expected-exclude-jobs }}""".replace("'", '"'))
includeJobs = json_loads("""${{ inputs.expected-include-jobs }}""".replace("'", '"'))
expectedJobs = sorted([f"{system}:{python}" for system in expectedSystems for python in expectedPythons if f"{system}:{python}" not in excludedJobs] + includeJobs)
errors = 0
if actualPythonVersion != expectedPythonVersion:
print(f"'python_version' does not match: '{actualPythonVersion}' != '{expectedPythonVersion}'.")
errors += 1
if len(actualPythonJobs) != len(expectedJobs):
print(f"❌ Number of 'python_jobs' does not match: {len(actualPythonJobs)} != {len(expectedJobs)}.")
print("Actual jobs:")
for job in actualPythonJobs:
if job['system'] == "msys2":
print(f" {job['runtime'].lower()}:{job['python']}")
else:
print(f" {job['system']}:{job['python']}")
print("Expected jobs:")
for job in expectedJobs:
print(f" {job}")
errors += 1
else:
print("✅ Number of 'python_jobs' as expected.")
print("Checking job combinations ...")
actualJobs = sorted([f"{job['system'] if job['system'] != 'msys2' else job['runtime'].lower()}:{job['python']}" for job in actualPythonJobs])
for actual, expected in zip(actualJobs, expectedJobs):
if actual != expected:
print(f" ❌ Job does not match: {actual} != {expected}.")
errors += 1
else:
print(f" ☑ Job as expected: {actual}.")
if errors == 0:
print("✅ All checks PASSED.")
else:
print(f"❌ Counted {errors} errors.")
exit(errors)

View File

@@ -86,14 +86,15 @@ jobs:
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: 📥 Download artifacts '${{ inputs.wheel }}' from 'Package' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
with:
name: ${{ inputs.wheel }}
path: install
# TODO: extract step to an Action so package lists are shared with UnitTesting (and GHDL?)
- name: Compute pacman/pacboy packages
id: pacboy
if: matrix.system == 'msys2'
@@ -199,7 +200,7 @@ jobs:
${{ inputs.pacboy }}
- name: 🐍 Setup Python ${{ matrix.python }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
if: matrix.system != 'msys2'
with:
python-version: ${{ matrix.python }}
@@ -214,18 +215,23 @@ jobs:
if: matrix.system == 'msys2'
run: |
if [ -n '${{ inputs.mingw_requirements }}' ]; then
python -m pip install --disable-pip-version-check ${{ inputs.mingw_requirements }}
python -m pip install --disable-pip-version-check --break-system-packages ${{ inputs.mingw_requirements }}
else
python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
python -m pip install --disable-pip-version-check --break-system-packages ${{ inputs.requirements }}
fi
- name: 🔧 Install wheel from artifact
- name: 🔧 Install wheel from artifact (Ubuntu/macOS)
if: ( matrix.system != 'windows' && matrix.system != 'windows-arm' )
run: |
ls -l install
python -m pip install --disable-pip-version-check -U install/*.whl
- name: 🔧 Install wheel from artifact (Windows)
if: ( matrix.system == 'windows' || matrix.system == 'windows-arm' )
run: |
python -m pip install -v --disable-pip-version-check (Get-Item .\install\*.whl).FullName
- name: ✅ Run application tests (Ubuntu/macOS)
if: matrix.system != 'windows'
if: ( matrix.system != 'windows' && matrix.system != 'windows-arm' )
run: |
export ENVIRONMENT_NAME="${{ matrix.envname }}"
@@ -240,7 +246,7 @@ jobs:
fi
- name: ✅ Run application tests (Windows)
if: matrix.system == 'windows'
if: ( matrix.system == 'windows' || matrix.system == 'windows-arm' )
run: |
$env:ENVIRONMENT_NAME = "${{ matrix.envname }}"
@@ -256,7 +262,7 @@ jobs:
- name: 📤 Upload 'TestReportSummary.xml' artifact
if: inputs.apptest_xml_artifact != ''
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: ${{ inputs.apptest_xml_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
working-directory: report/unit

View File

@@ -1,72 +0,0 @@
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# Unai Martinez-Corral #
# #
# ==================================================================================================================== #
# Copyright 2020-2025 The pyTooling Authors #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
name: Documentation
on:
workflow_call:
inputs:
artifact:
description: 'Name of the documentation artifact.'
required: false
default: ''
type: string
jobs:
BuildTheDocs:
name: 📓 Run BuildTheDocs
runs-on: ubuntu-24.04
steps:
- name: '❗ Deprecation message'
run: printf "::warning title=%s::%s\n" "Deprecated" "'BuildTheDocs.yml' is not maintained anymore. Please switch to 'SphinxDocumentation.yml', 'LaTeXDocumentation.yml' and 'ExtractConfiguration.yml'."
- name: ⏬ Checkout repository
uses: actions/checkout@v5
- name: 🛳️ Build documentation
uses: buildthedocs/btd@v0
with:
skip-deploy: true
- name: 📤 Upload 'documentation' artifacts
uses: pyTooling/upload-artifact@v4
if: inputs.artifact != ''
with:
name: ${{ inputs.artifact }}
working-directory: doc/_build/html
path: '*'
retention-days: 1
- name: '📓 Publish site to GitHub Pages'
if: inputs.artifact == '' && github.event_name != 'pull_request'
run: |
cp --recursive -T doc/_build/html public
cd public
touch .nojekyll
git init
cp ../.git/config ./.git/config
git add .
git config --local user.email "BuildTheDocs@GitHubActions"
git config --local user.name "GitHub Actions"
git commit -a -m "update ${{ github.sha }}"
git push -u origin +HEAD:gh-pages

201
.github/workflows/CheckCodeQuality.yml vendored Normal file
View File

@@ -0,0 +1,201 @@
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# #
# ==================================================================================================================== #
# Copyright 2025-2025 The pyTooling Authors #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
name: Code Quality Checking
on:
workflow_call:
inputs:
ubuntu_image_version:
description: 'Ubuntu image version.'
required: false
default: '24.04'
type: string
python_version:
description: 'Python version.'
required: false
default: '3.14'
type: string
package_directory:
description: 'The package''s directory'
required: true
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: '-r requirements.txt'
type: string
bandit:
description: 'Run bandit checks.'
required: false
default: 'true'
type: string
radon:
description: 'Run radon checks.'
required: false
default: 'true'
type: string
pylint:
description: 'Run pylint checks.'
required: false
default: 'true'
type: string
artifact:
description: 'Name of the package artifact.'
required: true
type: string
jobs:
Bandit:
name: 🚨 Security Scanning (Bandit)
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
if: inputs.bandit == 'true'
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v6
with:
lfs: true
submodules: true
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
- name: ⚙ Install dependencies for running bandit
run: python -m pip install --disable-pip-version-check bandit
- name: 👮 Bandit
id: bandit
if: inputs.artifact != ''
run: |
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_LIGHT_BLUE=$'\x1b[94m'
ANSI_NOCOLOR=$'\x1b[0m'
bandit_directory=report/bandit
bandit_fullpath=report/bandit/report.xml
tee "${GITHUB_OUTPUT}" <<EOF
bandit_directory=${bandit_directory}
bandit_fullpath=${bandit_fullpath}
EOF
mkdir -p ${bandit_directory}
printf "\nRun bandit ...\n"
bandit -c pyproject.toml -r ${{ inputs.package_directory }} -f xml -o ${bandit_fullpath}
if [[ $? -eq 0 ]]; then
printf "Bandit result: ${ANSI_LIGHT_GREEN}[PASSED]${ANSI_NOCOLOR}\n"
printf "bandit_passed=true\n" >> "${GITHUB_OUTPUT}"
else
faults=$(grep -Poh '(?<=<testsuite\sname="bandit"\stests=")(\d+)(?=">)' ${bandit_fullpath})
printf "Bandit result: ${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf " ${ANSI_LIGHT_RED}Bandit found %s issues.${ANSI_NOCOLOR}\n" "${faults}"
printf "::error title=%s::%s\n" "🚨 Security Scanning (Bandit)" "Bandi found ${faults} issues."
printf "bandit_passed=false\n" >> "${GITHUB_OUTPUT}"
printf "::group::${ANSI_LIGHT_BLUE}JUnit XML report created by Bandit ...${ANSI_NOCOLOR}\n"
cat ${bandit_fullpath}
printf "\n::endgroup::\n"
fi
- name: 📊 Publish Bandit Results
uses: dorny/test-reporter@v2
if: steps.bandit.outputs.bandit_passed == 'false'
continue-on-error: true
with:
name: 'Bandit Results'
path: ${{ steps.bandit.outputs.bandit_fullpath }}
reporter: java-junit
Radon:
name: ☢️ Metrics and Complexity
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
if: inputs.radon == 'true'
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v6
with:
lfs: true
submodules: true
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
- name: ⚙ Install dependencies for running radon
run: python -m pip install --disable-pip-version-check radon
- name: Code Metrics
# if: inputs.artifact != ''
run: |
radon raw ${{ inputs.package_directory }} -s
- name: Code Complexity
# if: inputs.artifact != ''
run: |
radon cc ${{ inputs.package_directory }} --total-average
- name: Halstead Complexity Metrics
# if: inputs.artifact != ''
run: |
radon hal ${{ inputs.package_directory }}
- name: Maintainability Index
# if: inputs.artifact != ''
run: |
radon mi ${{ inputs.package_directory }} -s
PyLint:
name: 🩺 Linting
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
if: inputs.pylint == 'true'
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v6
with:
lfs: true
submodules: true
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
- name: ⚙ Install dependencies for running PyLint
run: |
python -m pip install --disable-pip-version-check pylint
python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
- name: 🩺 PyLint
# if: inputs.artifact != ''
run: |
pylint ${{ inputs.package_directory }}

View File

@@ -32,7 +32,7 @@ on:
python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
directory:
description: 'Source code directory to check.'
@@ -41,7 +41,7 @@ on:
fail_under:
description: 'Minimum required documentation coverage level'
required: false
default: 80
default: '80'
type: string
jobs:
@@ -50,14 +50,14 @@ jobs:
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
- name: 🔧 Install wheel,tomli and pip dependencies (native)
- name: 🔧 Install docstr_coverage and interrogate dependencies
run: |
python -m pip install --disable-pip-version-check -U docstr_coverage interrogate[png]
@@ -80,4 +80,3 @@ jobs:
if [[ $? -ne 0 ]]; then
printf "%s\n" "::error title=docstr-coverage::Insufficient documentation quality (goal: ${{ inputs.fail_under }})"
fi

View File

@@ -36,17 +36,17 @@ on:
unittest_python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
unittest_python_version_list:
description: 'Space separated list of Python versions to run tests with.'
required: false
default: '3.9 3.10 3.11 3.12 3.13'
default: '3.10 3.11 3.12 3.13 3.14'
type: string
unittest_system_list:
description: 'Space separated list of systems to run tests on.'
required: false
default: 'ubuntu windows macos macos-arm mingw64 ucrt64'
default: 'ubuntu ubuntu-arm windows windows-arm macos macos-arm mingw64 ucrt64'
type: string
unittest_include_list:
description: 'Space separated list of system:python items to be included into the list of test.'
@@ -56,27 +56,27 @@ on:
unittest_exclude_list:
description: 'Space separated list of system:python items to be excluded from the list of test.'
required: false
default: ''
default: 'windows-arm:3.9 windows-arm:3.10'
type: string
unittest_disable_list:
description: 'Space separated list of system:python items to be disabled from the list of test.'
required: false
default: ''
default: 'windows-arm:pypy-3.10 windows-arm:pypy-3.11'
type: string
apptest_python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
apptest_python_version_list:
description: 'Space separated list of Python versions to run tests with.'
required: false
default: ""
default: ''
type: string
apptest_system_list:
description: 'Space separated list of systems to run tests on.'
required: false
default: 'ubuntu windows macos macos-arm ucrt64'
default: 'ubuntu ubuntu-arm windows windows-arm macos macos-arm ucrt64'
type: string
apptest_include_list:
description: 'Space separated list of system:python items to be included into the list of test.'
@@ -86,18 +86,28 @@ on:
apptest_exclude_list:
description: 'Space separated list of system:python items to be excluded from the list of test.'
required: false
default: ''
default: 'windows-arm:3.9 windows-arm:3.10'
type: string
apptest_disable_list:
description: 'Space separated list of system:python items to be disabled from the list of test.'
required: false
default: ''
default: 'windows-arm:pypy-3.10 windows-arm:pypy-3.11'
type: string
bandit:
description: 'Run Static Application Security Testing (SAST) using Bandit.'
required: false
default: 'false'
type: string
pylint:
description: 'Run Python linting using pylint.'
required: false
default: 'false'
type: string
codecov:
description: 'Publish merged coverage and unittest reports to Codecov.'
required: false
default: 'false'
type: string
type: string
codacy:
description: 'Publish merged coverage report to Codacy.'
required: false
@@ -130,9 +140,6 @@ jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@main
with:
package_namespace: ${{ inputs.package_namespace }}
package_name: ${{ inputs.package_name }}
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
@@ -170,6 +177,58 @@ jobs:
exclude_list: ${{ inputs.unittest_exclude_list }}
disable_list: ${{ inputs.unittest_disable_list }}
VersionCheck:
name: ''
runs-on: 'ubuntu-24.04'
needs:
- Prepare
- UnitTestingParams
if: needs.Prepare.outputs.version != '' && needs.UnitTestingParams.outputs.package_version_file != ''
outputs:
code_version: ${{ steps.extract.outputs.code_version }}
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v6
with:
# The command 'git describe' (used for version) needs the history.
fetch-depth: 0
- name: 🔧 Install pyTooling dependencies (native)
run: |
python -m pip install --disable-pip-version-check -U pyTooling
- name: Extract version from Python source file
id: extract
if: endsWith(needs.UnitTestingParams.outputs.package_version_file, '.py')
shell: python
run: |
from pathlib import Path
from sys import exit
from pyTooling.Packaging import extractVersionInformation
expectedVersion = "${{ needs.Prepare.outputs.version }}".strip()
versionFile = Path("${{ needs.UnitTestingParams.outputs.package_version_file }}")
if not versionFile.exists():
print(f"::error title=CompletePipeline::Version file '{versionFile}' not found.")
exit(1)
versionInformation = extractVersionInformation(versionFile)
print(f"expected: {expectedVersion}")
print(f"from code: {versionInformation.Version}")
if expectedVersion != versionInformation.Version:
print(f"::error title=CompletePipeline::Expected version does not version in Python code.")
exit(2)
# Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT"))
print(f"GITHUB_OUTPUT: {github_output}")
with github_output.open("a+", encoding="utf-8") as f:
f.write(dedent(f"""\
code_version={versionInformation.Version}
"""))
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@main
needs:
@@ -177,13 +236,13 @@ jobs:
- UnitTestingParams
with:
jobs: ${{ needs.UnitTestingParams.outputs.python_jobs }}
requirements: "-r tests/unit/requirements.txt"
# pacboy: "msys/git python-lxml:p"
unittest_report_xml_directory: ${{ needs.ConfigParams.outputs.unittest_report_xml_directory }}
unittest_report_xml_filename: ${{ needs.ConfigParams.outputs.unittest_report_xml_filename }}
coverage_report_html_directory: ${{ needs.ConfigParams.outputs.coverage_report_html_directory }}
unittest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
coverage_sqlite_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}
# TODO: shouldn't this be configured by a parameter? Same as directories
unittest_report_xml: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
coverage_report_xml: ${{ needs.ConfigParams.outputs.coverage_report_xml }}
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
unittest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
coverage_sqlite_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}
StaticTypeCheck:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@main
@@ -191,27 +250,37 @@ jobs:
- ConfigParams
- UnitTestingParams
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
commands: |
${{ needs.ConfigParams.outputs.mypy_prepare_command }}
mypy --html-report report/typing -p ${{ needs.ConfigParams.outputs.package_fullname }}
html_report: 'report/typing'
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).statictyping_html }}
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
cobertura_report: ${{ needs.ConfigParams.outputs.typing_report_cobertura }}
junit_report: ${{ needs.ConfigParams.outputs.typing_report_junit }}
html_report: ${{ needs.ConfigParams.outputs.typing_report_html }}
cobertura_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).statictyping_cobertura }}
junit_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).statictyping_junit }}
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).statictyping_html }}
CodeQuality:
uses: pyTooling/Actions/.github/workflows/CheckCodeQuality.yml@main
needs:
- UnitTestingParams
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
package_directory: ${{ needs.UnitTestingParams.outputs.package_directory }}
bandit: ${{ inputs.bandit }}
pylint: ${{ inputs.pylint }}
artifact: CodeQuality
DocCoverage:
uses: pyTooling/Actions/.github/workflows/CheckDocumentation.yml@main
needs:
- ConfigParams
- UnitTestingParams
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
directory: ${{ needs.ConfigParams.outputs.package_directory }}
directory: ${{ needs.UnitTestingParams.outputs.package_directory }}
Package:
uses: pyTooling/Actions/.github/workflows/Package.yml@main
needs:
- UnitTestingParams
# - UnitTesting
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}
@@ -219,14 +288,13 @@ jobs:
Install:
uses: pyTooling/Actions/.github/workflows/InstallPackage.yml@main
needs:
- ConfigParams
- UnitTestingParams
- InstallParams
- Package
with:
jobs: ${{ needs.InstallParams.outputs.python_jobs }}
wheel: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}
package_name: ${{ needs.ConfigParams.outputs.package_fullname }}
package_name: ${{ needs.UnitTestingParams.outputs.package_fullname }}
# AppTesting:
# uses: pyTooling/Actions/.github/workflows/ApplicationTesting.yml@main
@@ -251,13 +319,12 @@ jobs:
# coverage_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_xml }}
# coverage_report_xml_directory: ${{ needs.ConfigParams.outputs.coverage_report_xml_directory }}
# coverage_report_xml_filename: ${{ needs.ConfigParams.outputs.coverage_report_xml_filename }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
coverage_report_json_directory: ${{ needs.ConfigParams.outputs.coverage_report_json_directory }}
coverage_report_json_filename: ${{ needs.ConfigParams.outputs.coverage_report_json_filename }}
coverage_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_html }}
coverage_report_html_directory: ${{ needs.ConfigParams.outputs.coverage_report_html_directory }}
codecov: ${{ inputs.codecov }}
codacy: ${{ inputs.codacy }}
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
coverage_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_html }}
codecov: ${{ inputs.codecov }}
codacy: ${{ inputs.codacy }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CODACY_TOKEN: ${{ secrets.CODACY_TOKEN }}
@@ -270,8 +337,8 @@ jobs:
- UnitTesting
if: success() || failure()
with:
testsuite-summary-name: ${{ needs.ConfigParams.outputs.package_fullname }}
merged_junit_filename: ${{ needs.ConfigParams.outputs.unittest_merged_report_xml_filename }}
testsuite-summary-name: ${{ needs.UnitTestingParams.outputs.package_fullname }}
merged_junit_filename: ${{ fromJson(needs.ConfigParams.outputs.unittest_merged_report_xml).filename }}
merged_junit_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
dorny: ${{ inputs.dorny }}
codecov: ${{ inputs.codecov }}
@@ -295,12 +362,12 @@ jobs:
# - VerifyDocs
if: success() || failure()
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
coverage_report_json_directory: ${{ needs.ConfigParams.outputs.coverage_report_json_directory }}
unittest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_html }}
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
unittest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_html }}
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
IntermediateCleanUp:
uses: pyTooling/Actions/.github/workflows/IntermediateCleanUp.yml@main
@@ -313,15 +380,16 @@ jobs:
sqlite_coverage_artifacts_prefix: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}-
xml_unittest_artifacts_prefix: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}-
# PDFDocumentation:
# uses: pyTooling/Actions/.github/workflows/LaTeXDocumentation.yml@main
# needs:
# - UnitTestingParams
# - Documentation
# with:
# document: pyEDAA.ProjectModel
# latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
# pdf_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_pdf }}
PDFDocumentation:
uses: pyTooling/Actions/.github/workflows/LaTeXDocumentation.yml@main
needs:
- UnitTestingParams
- Documentation
with:
document: ${{ needs.UnitTestingParams.outputs.package_fullname }}
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
pdf_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_pdf }}
can-fail: 'true'
PublishToGitHubPages:
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@main
@@ -341,12 +409,12 @@ jobs:
needs:
- Prepare
- UnitTesting
- Install
# - AppTesting
# - StaticTypeCheck
- Package
- Install
- PublishToGitHubPages
if: needs.Prepare.outputs.is_release_commit && github.event_name != 'schedule'
if: needs.Prepare.outputs.is_release_commit == 'true' && github.event_name != 'schedule'
permissions:
contents: write # required for create tag
actions: write # required for trigger workflow
@@ -360,10 +428,10 @@ jobs:
needs:
- Prepare
- UnitTesting
- Install
# - AppTesting
# - StaticTypeCheck
- Package
- Install
- PublishToGitHubPages
if: needs.Prepare.outputs.is_release_tag == 'true'
permissions:
@@ -383,10 +451,10 @@ jobs:
if: needs.Prepare.outputs.is_release_tag == 'true'
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
requirements: -r dist/requirements.txt
artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}
requirements: '-r dist/requirements.txt'
artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
ArtifactCleanUp:
uses: pyTooling/Actions/.github/workflows/ArtifactCleanUp.yml@main
@@ -400,6 +468,7 @@ jobs:
- PublishCoverageResults
- PublishToGitHubPages
# - PublishOnPyPI
- Install
- IntermediateCleanUp
if: inputs.cleanup == 'true'
with:

View File

@@ -1,187 +0,0 @@
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# Unai Martinez-Corral #
# #
# ==================================================================================================================== #
# Copyright 2020-2025 The pyTooling Authors #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
name: Coverage Collection
on:
workflow_call:
inputs:
ubuntu_image_version:
description: 'Ubuntu image version.'
required: false
default: '24.04'
type: string
python_version:
description: 'Python version.'
required: false
default: '3.11'
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: '-r tests/requirements.txt'
type: string
tests_directory:
description: 'Path to the directory containing tests (test working directory).'
required: false
default: 'tests'
type: string
unittest_directory:
description: 'Path to the directory containing unit tests (relative to tests_directory).'
required: false
default: 'unit'
type: string
coverage_config:
description: 'Path to the .coveragerc file. Use pyproject.toml by default.'
required: false
default: 'pyproject.toml'
type: string
artifact:
description: 'Name of the coverage artifact.'
required: true
type: string
secrets:
codacy_token:
description: 'Token to push result to codacy.'
required: true
jobs:
Coverage:
name: 📈 Collect Coverage Data using Python ${{ inputs.python_version }}
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
steps:
- name: '❗ Deprecation message'
run: printf "::warning title=%s::%s\n" "Deprecated" "'CoverageCollection.yml' is not maintained anymore. Please switch to 'UnitTesting.yml', 'PublishCoverageResults.yml' and 'PublishTestResults.yml'."
- name: ⏬ Checkout repository
uses: actions/checkout@v5
with:
lfs: true
submodules: true
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python_version }}
- name: 🗂 Install dependencies
run: |
python -m pip install --disable-pip-version-check tomli
python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
- name: 🔁 Extract configurations from pyproject.toml
id: getVariables
shell: python
run: |
from os import getenv
from pathlib import Path
from tomli import load as tomli_load
from textwrap import dedent
htmlDirectory = 'htmlcov'
xmlFile = './coverage.xml'
coverageRC = "${{ inputs.coverage_config }}".strip()
# Read output paths from 'pyproject.toml' file
if coverageRC == "pyproject.toml":
pyProjectFile = Path("pyproject.toml")
if pyProjectFile.exists():
with pyProjectFile.open("rb") as file:
pyProjectSettings = tomli_load(file)
htmlDirectory = pyProjectSettings["tool"]["coverage"]["html"]["directory"]
xmlFile = pyProjectSettings["tool"]["coverage"]["xml"]["output"]
else:
print(f"File '{pyProjectFile}' not found.")
print(f"::error title=FileNotFoundError::File '{pyProjectFile}' not found.")
exit(1)
# Read output paths from '.coveragerc' file
elif len(coverageRC) > 0:
coverageRCFile = Path(coverageRC)
if coverageRCFile.exists():
with coverageRCFile.open("rb") as file:
coverageRCSettings = tomli_load(file)
htmlDirectory = coverageRCSettings["html"]["directory"]
xmlFile = coverageRCSettings["xml"]["output"]
else:
print(f"File '{coverageRCFile}' not found.")
print(f"::error title=FileNotFoundError::File '{coverageRCFile}' not found.")
exit(1)
# Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT"))
print(f"GITHUB_OUTPUT: {github_output}")
with github_output.open("a+", encoding="utf-8") as f:
f.write(dedent(f"""\
coverage_report_html_directory={htmlDirectory}
coverage_report_xml={xmlFile}
"""))
print(f"DEBUG:\n html={htmlDirectory}\n xml={xmlFile}")
- name: Collect coverage
continue-on-error: true
run: |
export ENVIRONMENT_NAME="Linux (x86-64)"
export PYTHONPATH=$(pwd)
ABSDIR=$(pwd)
cd "${{ inputs.tests_directory || '.' }}"
[ -n '${{ inputs.coverage_config }}' ] && PYCOV_ARGS="--cov-config=${ABSDIR}/${{ inputs.coverage_config }}" || unset PYCOV_ARGS
printf "%s\n" "python -m pytest -rA --cov=${ABSDIR} ${PYCOV_ARGS} ${{ inputs.unittest_directory }} --color=yes"
python -m pytest -rA --cov=${ABSDIR} $PYCOV_ARGS ${{ inputs.unittest_directory }} --color=yes
- name: Convert to cobertura format
run: coverage xml --data-file=${{ inputs.tests_directory || '.' }}/.coverage
- name: Convert to HTML format
run: |
coverage html --data-file=${{ inputs.tests_directory || '.' }}/.coverage -d ${{ steps.getVariables.outputs.coverage_report_html_directory }}
rm ${{ steps.getVariables.outputs.coverage_report_html_directory }}/.gitignore
- name: 📤 Upload 'Coverage Report' artifact
continue-on-error: true
uses: pyTooling/upload-artifact@v4
with:
name: ${{ inputs.artifact }}
working-directory: ${{ steps.getVariables.outputs.coverage_report_html_directory }}
path: '*'
if-no-files-found: error
retention-days: 1
- name: 📊 Publish coverage at CodeCov
continue-on-error: true
uses: codecov/codecov-action@v5
with:
files: ${{ steps.getVariables.outputs.coverage_report_xml }}
flags: unittests
env_vars: PYTHON
- name: 📉 Publish coverage at Codacy
continue-on-error: true
uses: codacy/codacy-coverage-reporter-action@v1
with:
project-token: ${{ secrets.codacy_token }}
coverage-reports: ${{ steps.getVariables.outputs.coverage_report_xml }}

View File

@@ -32,16 +32,7 @@ on:
python_version:
description: 'Python version.'
required: false
default: '3.13'
type: string
package_namespace:
description: 'Name of the tool''s namespace.'
required: false
default: ''
type: string
package_name:
description: 'Name of the tool''s package.'
required: true
default: '3.14'
type: string
coverage_config:
description: 'Path to the .coveragerc file. Use pyproject.toml by default.'
@@ -50,83 +41,51 @@ on:
type: string
outputs:
package_fullname:
description: ""
value: ${{ jobs.Extract.outputs.package_fullname }}
package_directory:
description: ""
value: ${{ jobs.Extract.outputs.package_directory }}
mypy_prepare_command:
description: ""
value: ${{ jobs.Extract.outputs.mypy_prepare_command }}
unittest_report_xml_directory:
description: ""
value: ${{ jobs.Extract.outputs.unittest_report_xml_directory }}
unittest_report_xml_filename:
description: ""
value: ${{ jobs.Extract.outputs.unittest_report_xml_filename }}
unittest_report_xml:
description: ""
value: ${{ jobs.Extract.outputs.unittest_report_xml }}
unittest_merged_report_xml_directory:
description: ""
value: ${{ jobs.Extract.outputs.unittest_merged_report_xml_directory }}
unittest_merged_report_xml_filename:
description: ""
value: ${{ jobs.Extract.outputs.unittest_merged_report_xml_filename }}
unittest_merged_report_xml:
description: ""
value: ${{ jobs.Extract.outputs.unittest_merged_report_xml }}
coverage_report_html_directory:
coverage_report_html:
description: ""
value: ${{ jobs.Extract.outputs.coverage_report_html_directory }}
coverage_report_xml_directory:
description: ""
value: ${{ jobs.Extract.outputs.coverage_report_xml_directory }}
coverage_report_xml_filename:
description: ""
value: ${{ jobs.Extract.outputs.coverage_report_xml_filename }}
value: ${{ jobs.Extract.outputs.coverage_report_html }}
coverage_report_xml:
description: ""
value: ${{ jobs.Extract.outputs.coverage_report_xml }}
coverage_report_json_directory:
description: ""
value: ${{ jobs.Extract.outputs.coverage_report_json_directory }}
coverage_report_json_filename:
description: ""
value: ${{ jobs.Extract.outputs.coverage_report_json_filename }}
coverage_report_json:
description: ""
value: ${{ jobs.Extract.outputs.coverage_report_json }}
typing_report_cobertura:
description: ""
value: ${{ jobs.Extract.outputs.typing_report_cobertura }}
typing_report_junit:
description: ""
value: ${{ jobs.Extract.outputs.typing_report_junit }}
typing_report_html:
description: ""
value: ${{ jobs.Extract.outputs.typing_report_html }}
jobs:
Extract:
name: 📓 Extract configurations from pyproject.toml
name: 🔬 Extract configurations from pyproject.toml
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
outputs:
package_fullname: ${{ steps.getPackageName.outputs.package_fullname }}
package_directory: ${{ steps.getPackageName.outputs.package_directory }}
mypy_prepare_command: ${{ steps.getPackageName.outputs.mypy_prepare_command }}
unittest_report_xml_directory: ${{ steps.getVariables.outputs.unittest_report_xml_directory }}
unittest_report_xml_filename: ${{ steps.getVariables.outputs.unittest_report_xml_filename }}
unittest_report_xml: ${{ steps.getVariables.outputs.unittest_report_xml }}
unittest_merged_report_xml_directory: ${{ steps.getVariables.outputs.unittest_merged_report_xml_directory }}
unittest_merged_report_xml_filename: ${{ steps.getVariables.outputs.unittest_merged_report_xml_filename }}
unittest_merged_report_xml: ${{ steps.getVariables.outputs.unittest_merged_report_xml }}
coverage_report_html_directory: ${{ steps.getVariables.outputs.coverage_report_html_directory }}
coverage_report_xml_directory: ${{ steps.getVariables.outputs.coverage_report_xml_directory }}
coverage_report_xml_filename: ${{ steps.getVariables.outputs.coverage_report_xml_filename }}
coverage_report_xml: ${{ steps.getVariables.outputs.coverage_report_xml }}
coverage_report_json_directory: ${{ steps.getVariables.outputs.coverage_report_json_directory }}
coverage_report_json_filename: ${{ steps.getVariables.outputs.coverage_report_json_filename }}
coverage_report_json: ${{ steps.getVariables.outputs.coverage_report_json }}
unittest_report_xml: ${{ steps.getVariables.outputs.unittest_report_xml }}
unittest_merged_report_xml: ${{ steps.getVariables.outputs.unittest_merged_report_xml }}
coverage_report_html: ${{ steps.getVariables.outputs.coverage_report_html }}
coverage_report_xml: ${{ steps.getVariables.outputs.coverage_report_xml }}
coverage_report_json: ${{ steps.getVariables.outputs.coverage_report_json }}
typing_report_cobertura: ${{ steps.getVariables.outputs.typing_report_cobertura }}
typing_report_junit: ${{ steps.getVariables.outputs.typing_report_junit }}
typing_report_html: ${{ steps.getVariables.outputs.typing_report_html }}
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
@@ -134,58 +93,17 @@ jobs:
run: |
python -m pip install --disable-pip-version-check -U wheel tomli
- name: 🔁 Full package name and directory
id: getPackageName
shell: python
run: |
from os import getenv
from pathlib import Path
from textwrap import dedent
namespace = "${{ inputs.package_namespace }}".strip()
name = "${{ inputs.package_name }}".strip()
print(dedent(f"""\
INPUTS:
package_namespace: {namespace}
package_name: {name}
"""))
if namespace == "" or namespace == ".":
fullname = f"{name}"
directory = f"{name}"
mypy_prepare_command = ""
else:
fullname = f"{namespace}.{name}"
directory = f"{namespace}/{name}"
mypy_prepare_command = f"touch {namespace}/__init__.py"
print(dedent(f"""\
OUTPUTS:
package_fullname: {fullname}
package_directory: {directory}
mypy_prepare_command: {mypy_prepare_command}
"""))
github_output = Path(getenv("GITHUB_OUTPUT"))
print(f"GITHUB_OUTPUT: {github_output}")
with github_output.open("a+", encoding="utf-8") as f:
f.write(dedent(f"""\
package_fullname={fullname}
package_directory={directory}
mypy_prepare_command={mypy_prepare_command}
"""))
- name: 🔁 Extract configurations from pyproject.toml
id: getVariables
shell: python
run: |
from json import dumps as json_dumps
from os import getenv
from pathlib import Path
from sys import version
from textwrap import dedent
print(f"Python: {version}")
print(f"Python: {version} (of default installation)")
from tomli import load as tomli_load
@@ -194,6 +112,9 @@ jobs:
coverageXMLFile = Path("./coverage.xml")
coverageJSONFile = Path("./coverage.json")
coverageRC = "${{ inputs.coverage_config }}".strip()
typingCoberturaFile = Path("report/typing/cobertura.xml")
typingJUnitFile = Path("report/typing/StaticTypingSummary.xml")
typingHTMLDirectory = Path("report/typing/html")
# Read output paths from 'pyproject.toml' file
if coverageRC == "pyproject.toml":
@@ -202,11 +123,34 @@ jobs:
with pyProjectFile.open("rb") as file:
pyProjectSettings = tomli_load(file)
unittestXMLFile = Path(pyProjectSettings["tool"]["pytest"]["junit_xml"])
mergedUnittestXMLFile = Path(pyProjectSettings["tool"]["pyedaa-reports"]["junit_xml"])
coverageHTMLDirectory = Path(pyProjectSettings["tool"]["coverage"]["html"]["directory"])
coverageXMLFile = Path(pyProjectSettings["tool"]["coverage"]["xml"]["output"])
coverageJSONFile= Path(pyProjectSettings["tool"]["coverage"]["json"]["output"])
toolSection = pyProjectSettings["tool"]
if "pytest" in toolSection:
section = toolSection["pytest"]
if "junit_xml" in section:
unittestXMLFile = Path(section["junit_xml"])
if "pyedaa-reports" in toolSection:
section = toolSection["pyedaa-reports"]
if "junit_xml" in section:
mergedUnittestXMLFile = Path(section["junit_xml"])
if "coverage" in toolSection:
section = toolSection["coverage"]
if "html" in section:
coverageHTMLDirectory = Path(section["html"]["directory"])
if "xml" in section:
coverageXMLFile = Path(section["xml"]["output"])
if "json" in section:
coverageJSONFile= Path(section["json"]["output"])
if "mypy" in toolSection:
section = toolSection["mypy"]
if "cobertura_xml_report" in section:
typingCoberturaFile = Path(section["cobertura_xml_report"]) / "cobertura.xml"
if "junit_xml" in section:
typingJUnitFile = Path(section["junit_xml"])
if "html_report" in section:
typingHTMLDirectory = Path(section["html_report"])
else:
print(f"File '{pyProjectFile}' not found.")
print(f"::error title=FileNotFoundError::File '{pyProjectFile}' not found.")
@@ -214,6 +158,8 @@ jobs:
# Read output paths from '.coveragerc' file
elif len(coverageRC) > 0:
print(f"::warning title=Deprecated::Using '{coverageRCFile}' is deprecated. Please use 'pyproject.toml'.")
coverageRCFile = Path(coverageRC)
if coverageRCFile.exists():
with coverageRCFile.open("rb") as file:
@@ -227,24 +173,58 @@ jobs:
print(f"::error title=FileNotFoundError::File '{coverageRCFile}' not found.")
exit(1)
unittest_report_xml = {
"fullpath": unittestXMLFile.as_posix(),
"directory": unittestXMLFile.parent.as_posix(),
"filename": unittestXMLFile.name
}
unittest_merged_report_xml = {
"fullpath": mergedUnittestXMLFile.as_posix(),
"directory": mergedUnittestXMLFile.parent.as_posix(),
"filename": mergedUnittestXMLFile.name
}
coverage_report_html = {
"fullpath": coverageHTMLDirectory.as_posix(),
"directory": coverageHTMLDirectory.as_posix()
}
coverage_report_xml = {
"fullpath": coverageXMLFile.as_posix(),
"directory": coverageXMLFile.parent.as_posix(),
"filename": coverageXMLFile.name
}
coverage_report_json = {
"fullpath": coverageJSONFile.as_posix(),
"directory": coverageJSONFile.parent.as_posix(),
"filename": coverageJSONFile.name
}
typing_report_cobertura = {
"fullpath": typingCoberturaFile.as_posix(),
"directory": typingCoberturaFile.parent.as_posix(),
"filename": typingCoberturaFile.name
}
typing_report_junit = {
"fullpath": typingJUnitFile.as_posix(),
"directory": typingJUnitFile.parent.as_posix(),
"filename": typingJUnitFile.name
}
typing_report_html = {
"fullpath": typingHTMLDirectory.as_posix(),
"directory": typingHTMLDirectory.as_posix()
}
# Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT"))
print(f"GITHUB_OUTPUT: {github_output}")
with github_output.open("a+", encoding="utf-8") as f:
f.write(dedent(f"""\
unittest_report_xml_directory={unittestXMLFile.parent.as_posix()}
unittest_report_xml_filename={unittestXMLFile.name}
unittest_report_xml={unittestXMLFile.as_posix()}
unittest_merged_report_xml_directory={mergedUnittestXMLFile.parent.as_posix()}
unittest_merged_report_xml_filename={mergedUnittestXMLFile.name}
unittest_merged_report_xml={mergedUnittestXMLFile.as_posix()}
coverage_report_html_directory={coverageHTMLDirectory.as_posix()}
coverage_report_xml_directory={coverageXMLFile.parent.as_posix()}
coverage_report_xml_filename={coverageXMLFile.name}
coverage_report_xml={coverageXMLFile.as_posix()}
coverage_report_json_directory={coverageJSONFile.parent.as_posix()}
coverage_report_json_filename={coverageJSONFile.name}
coverage_report_json={coverageJSONFile.as_posix()}
unittest_report_xml={json_dumps(unittest_report_xml)}
unittest_merged_report_xml={json_dumps(unittest_merged_report_xml)}
coverage_report_html={json_dumps(coverage_report_html)}
coverage_report_xml={json_dumps(coverage_report_xml)}
coverage_report_json={json_dumps(coverage_report_json)}
typing_report_cobertura={json_dumps(typing_report_cobertura)}
typing_report_junit={json_dumps(typing_report_junit)}
typing_report_html={json_dumps(typing_report_html)}
"""))
print(dedent(f"""\
@@ -254,4 +234,47 @@ jobs:
coverage html: {coverageHTMLDirectory}
coverage xml: {coverageXMLFile}
coverage json: {coverageJSONFile}
typing cobertura: {typingCoberturaFile}
typing junit: {typingJUnitFile}
typing html: {typingHTMLDirectory}
"""))
- name: Debug JSON objects
run: |
printf "unittest_report_xml: JSON=%s\n" "${{ steps.getVariables.outputs.unittest_report_xml }}"
printf " fullpath: %s\n" "${{ fromJSON(steps.getVariables.outputs.unittest_report_xml).fullpath }}"
printf " directory: %s\n" "${{ fromJSON(steps.getVariables.outputs.unittest_report_xml).directory }}"
printf " filename: %s\n" "${{ fromJSON(steps.getVariables.outputs.unittest_report_xml).filename }}"
printf "unittest_merged_report_xml: JSON=%s\n" "${{ steps.getVariables.outputs.unittest_merged_report_xml }}"
printf " fullpath: %s\n" "${{ fromJSON(steps.getVariables.outputs.unittest_merged_report_xml).fullpath }}"
printf " directory: %s\n" "${{ fromJSON(steps.getVariables.outputs.unittest_merged_report_xml).directory }}"
printf " filename: %s\n" "${{ fromJSON(steps.getVariables.outputs.unittest_merged_report_xml).filename }}"
printf "coverage_report_html: JSON=%s\n" "${{ steps.getVariables.outputs.coverage_report_html }}"
printf " fullpath: %s\n" "${{ fromJSON(steps.getVariables.outputs.coverage_report_html).fullpath }}"
printf " directory: %s\n" "${{ fromJSON(steps.getVariables.outputs.coverage_report_html).directory }}"
printf "coverage_report_xml: JSON=%s\n" "${{ steps.getVariables.outputs.coverage_report_xml }}"
printf " fullpath: %s\n" "${{ fromJSON(steps.getVariables.outputs.coverage_report_xml).fullpath }}"
printf " directory: %s\n" "${{ fromJSON(steps.getVariables.outputs.coverage_report_xml).directory }}"
printf " filename: %s\n" "${{ fromJSON(steps.getVariables.outputs.coverage_report_xml).filename }}"
printf "coverage_report_json: JSON=%s\n" "${{ steps.getVariables.outputs.coverage_report_json }}"
printf " fullpath: %s\n" "${{ fromJSON(steps.getVariables.outputs.coverage_report_json).fullpath }}"
printf " directory: %s\n" "${{ fromJSON(steps.getVariables.outputs.coverage_report_json).directory }}"
printf " filename: %s\n" "${{ fromJSON(steps.getVariables.outputs.coverage_report_json).filename }}"
printf "typing_report_cobertura: JSON=%s\n" "${{ steps.getVariables.outputs.typing_report_cobertura }}"
printf " fullpath: %s\n" "${{ fromJSON(steps.getVariables.outputs.typing_report_cobertura).fullpath }}"
printf " directory: %s\n" "${{ fromJSON(steps.getVariables.outputs.typing_report_cobertura).directory }}"
printf " filename: %s\n" "${{ fromJSON(steps.getVariables.outputs.typing_report_cobertura).filename }}"
printf "typing_report_junit: JSON=%s\n" "${{ steps.getVariables.outputs.typing_report_junit }}"
printf " fullpath: %s\n" "${{ fromJSON(steps.getVariables.outputs.typing_report_junit).fullpath }}"
printf " directory: %s\n" "${{ fromJSON(steps.getVariables.outputs.typing_report_junit).directory }}"
printf " filename: %s\n" "${{ fromJSON(steps.getVariables.outputs.typing_report_junit).filename }}"
printf "typing_report_html: JSON=%s\n" "${{ steps.getVariables.outputs.typing_report_html }}"
printf " fullpath: %s\n" "${{ fromJSON(steps.getVariables.outputs.typing_report_html).fullpath }}"
printf " directory: %s\n" "${{ fromJSON(steps.getVariables.outputs.typing_report_html).directory }}"

View File

@@ -53,7 +53,7 @@ jobs:
steps:
- name: 📥 Download artifacts '${{ inputs.wheel }}' from 'Package' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
with:
name: ${{ inputs.wheel }}
path: install
@@ -73,7 +73,7 @@ jobs:
python-tomli:p
- name: 🐍 Setup Python ${{ matrix.python }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
if: matrix.system != 'msys2'
with:
python-version: ${{ matrix.python }}
@@ -84,17 +84,17 @@ jobs:
python -m pip install --disable-pip-version-check -U wheel
- name: 🔧 Install wheel from artifact (Ubuntu/macOS)
if: matrix.system != 'windows'
if: ( matrix.system != 'windows' && matrix.system != 'windows-arm' )
run: |
python -m pip install --disable-pip-version-check -U install/*.whl
- name: 🔧 Install wheel from artifact (Windows)
if: matrix.system == 'windows'
if: ( matrix.system == 'windows' || matrix.system == 'windows-arm' )
run: |
python -m pip install -v --disable-pip-version-check (Get-Item .\install\*.whl).FullName
- name: 📦 Run application tests (Ubuntu/macOS)
if: matrix.system != 'windows'
- name: 📦 Run Package Version Check (Ubuntu/macOS)
if: ( matrix.system != 'windows' && matrix.system != 'windows-arm' )
run: |
set +e
@@ -116,8 +116,8 @@ jobs:
exit 1
fi
- name: 📦 Run application tests (Windows)
if: matrix.system == 'windows'
- name: 📦 Run Package Version Check (Windows)
if: ( matrix.system == 'windows' || matrix.system == 'windows-arm' )
run: |
$result=$(python -c "from ${{ inputs.package_name }} import __version__; print(f""Package version: {__version__}"")")
Write-Host $result

View File

@@ -30,12 +30,14 @@ on:
default: '24.04'
type: string
sqlite_coverage_artifacts_prefix:
description: 'Prefix for SQLite coverage artifacts'
description: 'Prefix for SQLite coverage artifacts to be removed.'
required: false
default: ''
type: string
xml_unittest_artifacts_prefix:
description: 'Prefix for XML unittest artifacts'
description: 'Prefix for XML unittest artifacts to be removed.'
required: false
default: ''
type: string
jobs:

View File

@@ -29,50 +29,61 @@ on:
required: false
default: '24.04'
type: string
latex_artifact:
description: 'Name of the LaTeX documentation artifact.'
required: true
type: string
document:
description: 'LaTeX root document without *.tex extension.'
required: true
type: string
latex_artifact:
description: 'Name of the LaTeX documentation artifact.'
processor:
description: 'Name of the used LaTeX processor.'
required: false
default: ''
default: 'xelatex'
type: string
pdf_artifact:
description: 'Name of the PDF documentation artifact.'
required: false
default: ''
type: string
can-fail:
description: 'Translation from LaTeX to PDF may fail.'
required: false
default: 'false'
type: string
jobs:
PDFDocumentation:
name: 📓 Converting LaTeX Documentation to PDF
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
continue-on-error: ${{ inputs.can-fail == 'true' }}
steps:
- name: 📥 Download artifacts '${{ inputs.latex_artifact }}' from 'SphinxDocumentation' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
with:
name: ${{ inputs.latex_artifact }}
path: latex
- name: Debug
run: |
tree -pash .
# - name: Debug
# run: |
# tree -pash .
- name: Build LaTeX document using 'pytooling/miktex:sphinx'
uses: addnab/docker-run-action@v3
if: inputs.pdf_artifact != ''
with:
image: pytooling/miktex:sphinx
options: -v ${{ github.workspace }}/latex:/latex --workdir /latex
run: |
which pdflatex
pwd
ls -lAh
# which ${{ inputs.processor }}
# pwd
# ls -lAh
latexmk -xelatex ${{ inputs.document }}.tex
latexmk -${{ inputs.processor }} "${{ inputs.document }}.tex"
- name: 📤 Upload 'PDF Documentation' artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
if: inputs.pdf_artifact != ''
with:
name: ${{ inputs.pdf_artifact }}

View File

@@ -1,530 +0,0 @@
# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# #
# ==================================================================================================================== #
# Copyright 2020-2025 The pyTooling Authors #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
name: Nightly
on:
workflow_call:
inputs:
ubuntu_image:
description: 'Name of the Ubuntu image.'
required: false
default: 'ubuntu-24.04'
type: string
nightly_name:
description: 'Name of the nightly release.'
required: false
default: 'nightly'
type: string
nightly_title:
description: 'Title of the nightly release.'
required: false
default: ''
type: string
nightly_description:
description: 'Description of the nightly release.'
required: false
default: 'Release of artifacts from latest CI pipeline.'
type: string
draft:
description: 'Specify if this is a draft.'
required: false
default: false
type: boolean
prerelease:
description: 'Specify if this is a pre-release.'
required: false
default: false
type: boolean
latest:
description: 'Specify if this is the latest release.'
required: false
default: false
type: boolean
replacements:
description: 'Multi-line string containing search=replace patterns.'
required: false
default: ''
type: string
assets:
description: 'Multi-line string containing artifact:file:title asset descriptions.'
required: true
type: string
inventory-json:
type: string
required: false
default: ''
inventory-version:
type: string
required: false
default: ''
inventory-categories:
type: string
required: false
default: ''
tarball-name:
type: string
required: false
default: '__pyTooling_upload_artifact__.tar'
can-fail:
type: boolean
required: false
default: false
jobs:
Release:
name: 📝 Update 'Nightly Page' on GitHub
runs-on: ${{ inputs.ubuntu_image }}
continue-on-error: ${{ inputs.can-fail }}
permissions:
contents: write
actions: write
# attestations: write
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
with:
# The command 'git describe' (used for version) needs the history.
fetch-depth: 0
- name: 🔧 Install zstd
run: sudo apt-get install -y --no-install-recommends zstd
- name: 📑 Delete (old) Release Page
id: deleteReleasePage
run: |
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_LIGHT_YELLOW=$'\x1b[93m'
ANSI_NOCOLOR=$'\x1b[0m'
export GH_TOKEN=${{ github.token }}
printf "%s" "Deleting release '${{ inputs.nightly_name }}' ... "
message="$(gh release delete ${{ inputs.nightly_name }} --yes 2>&1)"
if [[ $? -eq 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
elif [[ "${message}" == "release not found" ]]; then
printf "%s\n" "${ANSI_LIGHT_YELLOW}[NOT FOUND]${ANSI_NOCOLOR}"
else
printf "%s\n" "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't delete release '${{ inputs.nightly_name }}' -> Error: '${message}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "InternalError" "Couldn't delete release '${{ inputs.nightly_name }}' -> Error: '${message}'."
exit 1
fi
- name: 📑 (Re)create (new) Release Page
id: createReleasePage
run: |
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_NOCOLOR=$'\x1b[0m'
export GH_TOKEN=${{ github.token }}
addDraft="--draft"
if [[ "${{ inputs.prerelease }}" == "true" ]]; then
addPreRelease="--prerelease"
fi
if [[ "${{ inputs.latest }}" == "false" ]]; then
addLatest="--latest=false"
fi
if [[ "${{ inputs.nightly_title }}" != "" ]]; then
addTitle=("--title" "${{ inputs.nightly_title }}")
fi
cat <<'EOF' > __NoTeS__.md
${{ inputs.nightly_description }}
EOF
if [[ -s __NoTeS__.md ]]; then
addNotes=("--notes-file" "__NoTeS__.md")
fi
# Apply replacements
while IFS=$'\r\n' read -r patternLine; do
# skip empty lines
[[ "$patternLine" == "" ]] && continue
pattern="${patternLine%%=*}"
replacement="${patternLine#*=}"
sed -i -e "s/%$pattern%/$replacement/g" "__NoTeS__.md"
done <<<'${{ inputs.replacements }}'
# Add footer line
cat <<EOF >> __NoTeS__.md
--------
Published from [${{ github.workflow }}](https://github.com/Paebbels/ghdl/actions/runs/${{ github.run_id }}) workflow triggered by @${{ github.actor }} on $(date '+%Y-%m-%d %H:%M:%S %Z').
EOF
printf "%s\n" "Creating release '${{ inputs.nightly_name }}' ... "
message="$(gh release create "${{ inputs.nightly_name }}" --verify-tag $addDraft $addPreRelease $addLatest "${addTitle[@]}" "${addNotes[@]}" 2>&1)"
if [[ $? -eq 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
else
printf "%s\n" "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't create release '${{ inputs.nightly_name }}' -> Error: '${message}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "InternalError" "Couldn't create release '${{ inputs.nightly_name }}' -> Error: '${message}'."
exit 1
fi
- name: 📥 Download artifacts and upload as assets
id: uploadAssets
run: |
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_LIGHT_YELLOW=$'\x1b[93m'
ANSI_LIGHT_BLUE=$'\x1b[94m'
ANSI_NOCOLOR=$'\x1b[0m'
export GH_TOKEN=${{ github.token }}
Replace() {
line="$1"
while IFS=$'\r\n' read -r patternLine; do
# skip empty lines
[[ "$patternLine" == "" ]] && continue
pattern="${patternLine%%=*}"
replacement="${patternLine#*=}"
line="${line//"%$pattern%"/"$replacement"}"
done <<<'${{ inputs.replacements }}'
printf "%s\n" "$line"
}
# Create JSON inventory
if [[ "${{ inputs.inventory-json }}" != "" ]]; then
VERSION="1.0"
# Split categories by ',' into a Bash array.
# See https://stackoverflow.com/a/45201229/3719459
if [[ "${{ inputs.inventory-categories }}" != "" ]]; then
readarray -td, inventoryCategories <<<"${{ inputs.inventory-categories }},"
unset 'inventoryCategories[-1]'
declare -p inventoryCategories
else
inventoryCategories=""
fi
jsonInventory=$(jq -c -n \
--arg version "${VERSION}" \
--arg date "$(date +"%Y-%m-%dT%H-%M-%S%:z")" \
--argjson jsonMeta "$(jq -c -n \
--arg tag "${{ inputs.nightly_name }}" \
--arg version "${{ inputs.inventory-version }}" \
--arg hash "${{ github.sha }}" \
--arg repo "${{ github.server_url }}/${{ github.repository }}" \
--arg release "${{ github.server_url }}/${{ github.repository }}/releases/download/${{ inputs.nightly_name }}" \
--argjson categories "$(jq -c -n \
'$ARGS.positional' \
--args "${inventoryCategories[@]}" \
)" \
'{"tag": $tag, "version": $version, "git-hash": $hash, "repository-url": $repo, "release-url": $release, "categories": $categories}' \
)" \
'{"version": 1.0, "timestamp": $date, "meta": $jsonMeta, "files": {}}'
)
fi
ERRORS=0
# A dictionary of 0/1 to avoid duplicate downloads
declare -A downloadedArtifacts
# A dictionary to check for duplicate asset files in release
declare -A assetFilenames
while IFS=$'\r\n' read -r assetLine; do
if [[ "${assetLine}" == "" || "${assetLine:0:1}" == "#" ]]; then
continue
fi
# split assetLine colon separated triple: artifact:asset:title
artifact="${assetLine%%:*}"
assetLine="${assetLine#*:}"
asset="${assetLine%%:*}"
assetLine="${assetLine#*:}"
if [[ "${{ inputs.inventory-json }}" == "" ]]; then
categories=""
title="${assetLine##*:}"
else
categories="${assetLine%%:*}"
title="${assetLine##*:}"
fi
# remove leading whitespace
asset="${asset#"${asset%%[![:space:]]*}"}"
categories="${categories#"${categories%%[![:space:]]*}"}"
title="${title#"${title%%[![:space:]]*}"}"
# apply replacements
asset="$(Replace "${asset}")"
title="$(Replace "${title}")"
printf "%s\n" "Publish asset '${asset}' from artifact '${artifact}' with title '${title}'"
printf " %s" "Checked asset for duplicates ... "
if [[ -n "${assetFilenames[$asset]}" ]]; then
printf "%s\n" "${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "DuplicateAsset" "Asset '${asset}' from artifact '${artifact}' was already uploaded to release '${{ inputs.nightly_name }}'."
ERRORS=$((ERRORS + 1))
continue
else
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
assetFilenames[$asset]=1
fi
# Download artifact by artifact name
if [[ -n "${downloadedArtifacts[$artifact]}" ]]; then
printf " %s\n" "downloading '${artifact}' ... ${ANSI_LIGHT_YELLOW}[SKIPPED]${ANSI_NOCOLOR}"
else
echo " downloading '${artifact}' ... "
printf " %s" "gh run download $GITHUB_RUN_ID --dir \"${artifact}\" --name \"${artifact}\" "
gh run download $GITHUB_RUN_ID --dir "${artifact}" --name "${artifact}"
if [[ $? -eq 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
else
printf "%s\n" "${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't download artifact '${artifact}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "ArtifactNotFound" "Couldn't download artifact '${artifact}'."
ERRORS=$((ERRORS + 1))
continue
fi
downloadedArtifacts[$artifact]=1
printf " %s" "Checking for embedded tarball ... "
if [[ -f "${artifact}/${{ inputs.tarball-name }}" ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[FOUND]${ANSI_NOCOLOR}"
pushd "${artifact}" > /dev/null
printf " %s" "Extracting embedded tarball ... "
tar -xf "${{ inputs.tarball-name }}"
if [[ $? -ne 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}"
else
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
fi
printf " %s" "Removing temporary tarball ... "
rm -f "${{ inputs.tarball-name }}"
if [[ $? -ne 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}"
else
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
fi
popd > /dev/null
else
printf "%s\n" "${ANSI_LIGHT_YELLOW}[SKIPPED]${ANSI_NOCOLOR}"
fi
fi
# Check if artifact should be compressed (zip, tgz) or if asset was part of the downloaded artifact.
printf " %s" "checking asset '${artifact}/${asset}' ... "
if [[ "${asset}" == !*.zip ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[ZIP]${ANSI_NOCOLOR}"
asset="${asset##*!}"
printf "::group:: %s\n" "Compressing artifact '${artifact}' to '${asset}' ..."
(
cd "${artifact}" && \
zip -r "../${asset}" *
)
retCode=$?
printf "::endgroup::\n"
if [[ $retCode -eq 0 ]]; then
printf " %s\n" "Compression ${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
uploadFile="${asset}"
else
printf " %s\n" "Compression ${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't compress '${artifact}' to zip file '${asset}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "CompressionError" "Couldn't compress '${artifact}' to zip file '${asset}'."
ERRORS=$((ERRORS + 1))
continue
fi
elif [[ "${asset}" == !*.tgz || "${asset}" == !*.tar.gz || "${asset}" == \$*.tgz || "${asset}" == \$*.tar.gz ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[TAR/GZ]${ANSI_NOCOLOR}"
if [[ "${asset:0:1}" == "\$" ]]; then
asset="${asset##*$}"
dirName="${asset%.*}"
printf " %s\n" "Compressing artifact '${artifact}' to '${asset}' ..."
tar -c --gzip --owner=0 --group=0 --file="${asset}" --directory="${artifact}" --transform "s|^\.|${dirName%.tar}|" .
retCode=$?
else
asset="${asset##*!}"
printf " %s\n" "Compressing artifact '${artifact}' to '${asset}' ..."
(
cd "${artifact}" && \
tar -c --gzip --owner=0 --group=0 --file="../${asset}" *
)
retCode=$?
fi
if [[ $retCode -eq 0 ]]; then
printf " %s\n" "Compression ${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
uploadFile="${asset}"
else
printf " %s\n" "Compression ${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't compress '${artifact}' to tgz file '${asset}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "CompressionError" "Couldn't compress '${artifact}' to tgz file '${asset}'."
ERRORS=$((ERRORS + 1))
continue
fi
elif [[ "${asset}" == !*.tzst || "${asset}" == !*.tar.zst || "${asset}" == \$*.tzst || "${asset}" == \$*.tar.zst ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[ZST]${ANSI_NOCOLOR}"
if [[ "${asset:0:1}" == "\$" ]]; then
asset="${asset##*$}"
dirName="${asset%.*}"
printf " %s\n" "Compressing artifact '${artifact}' to '${asset}' ..."
tar -c --zstd --owner=0 --group=0 --file="${asset}" --directory="${artifact}" --transform "s|^\.|${dirName%.tar}|" .
retCode=$?
else
asset="${asset##*!}"
printf " %s\n" "Compressing artifact '${artifact}' to '${asset}' ..."
(
cd "${artifact}" && \
tar -c --zstd --owner=0 --group=0 --file="../${asset}" *
)
retCode=$?
fi
if [[ $retCode -eq 0 ]]; then
printf " %s\n" "Compression ${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
uploadFile="${asset}"
else
printf " %s\n" "Compression ${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't compress '${artifact}' to zst file '${asset}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "CompressionError" "Couldn't compress '${artifact}' to zst file '${asset}'."
ERRORS=$((ERRORS + 1))
continue
fi
elif [[ -e "${artifact}/${asset}" ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
uploadFile="${artifact}/${asset}"
else
printf "%s\n" "${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't find asset '${asset}' in artifact '${artifact}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "FileNotFound" "Couldn't find asset '${asset}' in artifact '${artifact}'."
ERRORS=$((ERRORS + 1))
continue
fi
# Add asset to JSON inventory
if [[ "${{ inputs.inventory-json }}" != "" ]]; then
if [[ "${categories}" != "${title}" ]]; then
printf " %s\n" "adding file '${uploadFile#*/}' with '${categories//;/ → }' to JSON inventory ..."
category=""
jsonEntry=$(jq -c -n \
--arg title "${title}" \
--arg file "${uploadFile#*/}" \
'{"file": $file, "title": $title}' \
)
while [[ "${categories}" != "${category}" ]]; do
category="${categories##*,}"
categories="${categories%,*}"
jsonEntry=$(jq -c -n --arg cat "${category}" --argjson value "${jsonEntry}" '{$cat: $value}')
done
jsonInventory=$(jq -c -n \
--argjson inventory "${jsonInventory}" \
--argjson file "${jsonEntry}" \
'$inventory * {"files": $file}' \
)
else
printf " %s\n" "adding file '${uploadFile#*/}' to JSON inventory ... ${ANSI_LIGHT_YELLOW}[SKIPPED]${ANSI_NOCOLOR}"
fi
fi
# Upload asset to existing release page
printf " %s" "uploading asset '${asset}' from '${uploadFile}' with title '${title}' ... "
gh release upload ${{ inputs.nightly_name }} "${uploadFile}#${title}" --clobber
if [[ $? -eq 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
else
printf "%s\n" "${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't upload asset '${asset}' from '${uploadFile}' to release '${{ inputs.nightly_name }}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "UploadError" "Couldn't upload asset '${asset}' from '${uploadFile}' to release '${{ inputs.nightly_name }}'."
ERRORS=$((ERRORS + 1))
continue
fi
done <<<'${{ inputs.assets }}'
if [[ "${{ inputs.inventory-json }}" != "" ]]; then
inventoryTitle="Release Inventory (JSON)"
printf "%s\n" "Publish asset '${{ inputs.inventory-json }}' with title '${inventoryTitle}'"
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Writing JSON inventory to '${{ inputs.inventory-json }}' ...."
printf "%s\n" "$(jq -n --argjson inventory "${jsonInventory}" '$inventory')" > "${{ inputs.inventory-json }}"
cat "${{ inputs.inventory-json }}"
printf "::endgroup::\n"
# Upload inventory asset to existing release page
printf " %s" "uploading asset '${{ inputs.inventory-json }}' title '${inventoryTitle}' ... "
gh release upload ${{ inputs.nightly_name }} "${{ inputs.inventory-json }}#${inventoryTitle}" --clobber
if [[ $? -eq 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
else
printf "%s\n" "${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't upload asset '${{ inputs.inventory-json }}' to release '${{ inputs.nightly_name }}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "UploadError" "Couldn't upload asset '${{ inputs.inventory-json }}' to release '${{ inputs.nightly_name }}'."
ERRORS=$((ERRORS + 1))
continue
fi
fi
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Inspecting downloaded artifacts ..."
tree -pash -L 3 .
printf "::endgroup::\n"
if [[ $ERRORS -ne 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_RED}${ERRORS} errors detected in previous steps.${ANSI_NOCOLOR}"
exit 1
fi
- name: 📑 Remove draft state from Release Page
if: ${{ ! inputs.draft }}
run: |
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_NOCOLOR=$'\x1b[0m'
export GH_TOKEN=${{ github.token }}
# Remove draft-state from release page
printf "%s" "Remove draft-state from release '${title}' ... "
gh release edit --draft=false "${{ inputs.nightly_name }}"
if [[ $? -eq 0 ]]; then
printf "%s\n" "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}"
else
printf "%s\n" "${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}"
printf " %s\n" "${ANSI_LIGHT_RED}Couldn't remove draft-state from release '${{ inputs.nightly_name }}'.${ANSI_NOCOLOR}"
printf "::error title=%s::%s\n" "ReleasePage" "Couldn't remove draft-state from release '${{ inputs.nightly_name }}'."
fi

View File

@@ -33,7 +33,7 @@ on:
python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
requirements:
description: 'Python dependencies to be installed through pip; if empty, use pyproject.toml through build.'
@@ -46,20 +46,20 @@ on:
type: string
jobs:
Package:
name: 📦 Package in Source and Wheel Format
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
env:
artifact: ${{ inputs.artifact }}
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
lfs: true
submodules: true
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
@@ -79,15 +79,15 @@ jobs:
# build (not isolated)
- name: 🔧 [build] Install dependencies for packaging and release
- name: 🔧 [build - no-isolation] Install dependencies for packaging and release
if: inputs.requirements == 'no-isolation'
run: python -m pip install --disable-pip-version-check build
- name: 🔨 [build] Build Python package (source distribution)
- name: 🔨 [build - no-isolation] Build Python package (source distribution)
if: inputs.requirements == 'no-isolation'
run: python -m build --no-isolation --sdist
- name: 🔨 [build] Build Python package (binary distribution - wheel)
- name: 🔨 [build - no-isolation] Build Python package (binary distribution - wheel)
if: inputs.requirements == 'no-isolation'
run: python -m build --no-isolation --wheel
@@ -106,7 +106,7 @@ jobs:
run: python setup.py bdist_wheel
- name: 📤 Upload wheel artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: ${{ inputs.artifact }}
working-directory: dist

View File

@@ -45,20 +45,25 @@ on:
required: false
default: ''
type: string
version_file:
description: "Path to module containing the version ('__version__' variable)."
required: false
default: '__init__.py'
type: string
python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
python_version_list:
description: 'Space separated list of Python versions to run tests with.'
required: false
default: '3.9 3.10 3.11 3.12 3.13'
default: '3.10 3.11 3.12 3.13 3.14'
type: string
system_list:
description: 'Space separated list of systems to run tests on.'
required: false
default: 'ubuntu windows macos macos-arm mingw64 ucrt64'
default: 'ubuntu ubuntu-arm windows windows-arm macos macos-arm mingw64 ucrt64'
type: string
include_list:
description: 'Space separated list of system:python items to be included into the list of test.'
@@ -68,32 +73,42 @@ on:
exclude_list:
description: 'Space separated list of system:python items to be excluded from the list of test.'
required: false
default: ''
default: 'windows-arm:3.9 windows-arm:3.10'
type: string
disable_list:
description: 'Space separated list of system:python items to be disabled from the list of test.'
required: false
default: ''
default: 'windows-arm:pypy-3.10 windows-arm:pypy-3.11'
type: string
ubuntu_image:
description: 'The used GitHub Action image for Ubuntu based jobs.'
description: 'The used GitHub Action image for Ubuntu (x86-64) based jobs.'
required: false
default: 'ubuntu-24.04'
type: string
windows_image:
description: 'The used GitHub Action image for Windows based jobs.'
ubuntu_arm_image:
description: 'The used GitHub Action image for Ubuntu (aarch64) based jobs.'
required: false
default: 'windows-2022'
default: 'ubuntu-24.04-arm'
type: string
windows_image:
description: 'The used GitHub Action image for Windows Server (x86-64) based jobs.'
required: false
default: 'windows-2025'
type: string
windows_arm_image:
description: 'The used GitHub Action image for Windows (aarch64) based jobs.'
required: false
default: 'windows-11-arm'
type: string
macos_intel_image:
description: 'The used GitHub Action image for macOS (Intel x86-64) based jobs.'
required: false
default: 'macos-13'
default: 'macos-15-intel'
type: string
macos_arm_image:
description: 'The used GitHub Action image for macOS (ARM aarch64) based jobs.'
required: false
default: 'macos-14'
default: 'macos-15'
type: string
pipeline-delay:
description: 'Slow down this job, to delay the startup of the GitHub Action pipline.'
@@ -105,35 +120,157 @@ on:
python_version:
description: "Default Python version for other jobs."
value: ${{ jobs.Parameters.outputs.python_version }}
python_jobs:
description: "List of Python versions (and system combinations) to be used in the matrix of other jobs."
value: ${{ jobs.Parameters.outputs.python_jobs }}
package_fullname:
description: "The package's full name."
value: ${{ jobs.Parameters.outputs.package_fullname }}
package_directory:
description: "The package's directory."
value: ${{ jobs.Parameters.outputs.package_directory }}
package_version_file:
description: "Path to the package's module containing the version ('__version__' variable)."
value: ${{ jobs.Parameters.outputs.package_version_file }}
artifact_basename:
description: "Artifact base name."
value: ${{ jobs.Parameters.outputs.artifact_basename }}
artifact_names:
description: "Pre-defined artifact names for other jobs."
value: ${{ jobs.Parameters.outputs.artifact_names }}
params:
description: "Parameters to be used in other jobs."
value: ${{ jobs.Parameters.outputs.params }}
python_jobs:
description: "List of Python versions (and system combinations) to be used in the matrix of other jobs."
value: ${{ jobs.Parameters.outputs.python_jobs }}
jobs:
Parameters:
name: ✎ Generate pipeline parameters
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
outputs:
python_version: ${{ steps.params.outputs.python_version }}
python_jobs: ${{ steps.params.outputs.python_jobs }}
artifact_names: ${{ steps.params.outputs.artifact_names }}
params: ${{ steps.params.outputs.params }}
python_version: ${{ steps.variables.outputs.python_version }}
package_fullname: ${{ steps.variables.outputs.package_fullname }}
package_directory: ${{ steps.variables.outputs.package_directory }}
package_version_file: ${{ steps.variables.outputs.package_version_file }}
artifact_basename: ${{ steps.variables.outputs.artifact_basename }}
artifact_names: ${{ steps.artifacts.outputs.artifact_names }}
python_jobs: ${{ steps.jobs.outputs.python_jobs }}
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v6
with:
# The command 'git describe' (used for version) needs the history.
fetch-depth: 0
- name: Generate a startup delay of ${{ inputs.pipeline-delay }} seconds
id: delay
if: inputs.pipeline-delay >= 0
run: |
sleep ${{ inputs.pipeline-delay }}
- name: Generate 'params' and 'python_jobs'
id: params
- name: Generate 'python_version'
id: variables
shell: python
run: |
from os import getenv
from pathlib import Path
from sys import exit
from textwrap import dedent
python_version = "${{ inputs.python_version }}".strip()
package_namespace = "${{ inputs.package_namespace }}".strip()
package_name = "${{ inputs.package_name }}".strip()
version_file = "${{ inputs.version_file }}".strip()
name = "${{ inputs.name }}".strip()
if package_namespace == "":
package_fullname = package_name
package_directory = package_name
elif package_namespace[-2:] == ".*":
package_fullname = package_namespace[:-2]
package_directory = package_namespace[:-2]
else:
package_fullname = f"{package_namespace}.{package_name}"
package_directory = f"{package_namespace}/{package_name}"
packageDirectory = Path(package_directory)
packageVersionFile = packageDirectory / version_file
print(f"Check if package version file '{packageVersionFile}' exists ... ", end="")
if packageVersionFile.exists():
print("✅")
package_version_file = packageVersionFile.as_posix()
else:
print("❌")
package_version_file = ""
print(f"::warning title=Parameters::Version file '{packageVersionFile}' not found.")
artifact_basename = package_fullname if name == "" else name
if artifact_basename == "" or artifact_basename == ".":
print("::error title=Parameters::artifact_basename is empty.")
exit(1)
print("Variables:")
print(f" python_version: {python_version}")
print(f" package_fullname: {package_fullname}")
print(f" package_directory: {package_directory}")
print(f" package_version_file: {package_directory}")
print(f" artifact_basename: {artifact_basename}")
# Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT"))
print(f"GITHUB_OUTPUT: {github_output}")
with github_output.open("a+", encoding="utf-8") as f:
f.write(dedent(f"""\
python_version={python_version}
package_fullname={package_fullname}
package_directory={package_directory}
package_version_file={package_version_file}
artifact_basename={artifact_basename}
"""))
- name: Generate 'artifact_names'
id: artifacts
shell: python
run: |
from json import dumps as json_dumps
from os import getenv
from pathlib import Path
from textwrap import dedent
package_namespace = "${{ inputs.package_namespace }}".strip()
package_name = "${{ inputs.package_name }}".strip()
artifact_basename = "${{ steps.variables.outputs.artifact_basename }}"
artifact_names = {
"unittesting_xml": f"{artifact_basename}-UnitTestReportSummary-XML",
"unittesting_html": f"{artifact_basename}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{artifact_basename}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{artifact_basename}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{artifact_basename}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{artifact_basename}-CodeCoverage-SQLite",
"codecoverage_xml": f"{artifact_basename}-CodeCoverage-XML",
"codecoverage_json": f"{artifact_basename}-CodeCoverage-JSON",
"codecoverage_html": f"{artifact_basename}-CodeCoverage-HTML",
"statictyping_cobertura": f"{artifact_basename}-StaticTyping-Cobertura-XML",
"statictyping_junit": f"{artifact_basename}-StaticTyping-JUnit-XML",
"statictyping_html": f"{artifact_basename}-StaticTyping-HTML",
"package_all": f"{artifact_basename}-Packages",
"documentation_html": f"{artifact_basename}-Documentation-HTML",
"documentation_latex": f"{artifact_basename}-Documentation-LaTeX",
"documentation_pdf": f"{artifact_basename}-Documentation-PDF",
}
print("Artifacts Names ({len(artifact_names)}):")
for id, artifactName in artifact_names.items():
print(f" {id:>24}: {artifactName}")
# Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT"))
print(f"GITHUB_OUTPUT: {github_output}")
with github_output.open("a+", encoding="utf-8") as f:
f.write(dedent(f"""\
artifact_names={json_dumps(artifact_names)}
"""))
- name: Generate 'python_jobs'
id: jobs
shell: python
run: |
from json import dumps as json_dumps
@@ -142,28 +279,20 @@ jobs:
from textwrap import dedent
from typing import Iterable
package_namespace = "${{ inputs.package_namespace }}".strip()
package_name = "${{ inputs.package_name }}".strip()
name = "${{ inputs.name }}".strip()
python_version = "${{ inputs.python_version }}".strip()
python_version = "${{ steps.variables.outputs.python_version }}"
name = "${{ steps.variables.outputs.artifact_basename }}"
systems = "${{ inputs.system_list }}".strip()
versions = "${{ inputs.python_version_list }}".strip()
include_list = "${{ inputs.include_list }}".strip()
exclude_list = "${{ inputs.exclude_list }}".strip()
disable_list = "${{ inputs.disable_list }}".strip()
if name == "":
if package_namespace == "" or package_namespace == ".":
name = f"{package_name}"
else:
name = f"{package_namespace}.{package_name}"
currentMSYS2Version = "3.12"
currentAlphaVersion = "3.14"
currentAlphaRelease = "3.14.0-alpha.1"
currentAlphaVersion = "3.15"
currentAlphaRelease = "3.15.0-a.1"
if systems == "":
print("::error title=Parameter::system_list is empty.")
print("::error title=Parameters::system_list is empty.")
else:
systems = [sys.strip() for sys in systems.split(" ")]
@@ -187,38 +316,38 @@ jobs:
else:
disabled = [disable.strip() for disable in disable_list.split(" ")]
if "3.8" in versions:
print("::warning title=Deprecated::Support for Python 3.8 ended in 2024.10.")
if "3.9" in versions:
print("::warning title=Deprecated::Support for Python 3.9 ended in 2025.10.")
if "msys2" in systems:
print("::warning title=Deprecated::System 'msys2' will be replaced by 'mingw64'.")
if currentAlphaVersion in versions:
print(f"::notice title=Experimental::Python {currentAlphaVersion} ({currentAlphaRelease}) is a pre-release.")
for disable in disabled:
print(f"::warning title=Disabled Python Job::System '{disable}' temporarily disabled.")
print(f"::warning title=Disabled Python Job::{name}: Job combination '{disable}' temporarily disabled.")
# see https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json
data = {
# Python and PyPy versions supported by "setup-python" action
"python": {
"3.8": { "icon": "⚫", "until": "2024.10" },
"3.9": { "icon": "🔴", "until": "2025.10" },
"3.10": { "icon": "🟠", "until": "2026.10" },
"3.11": { "icon": "🟡", "until": "2027.10" },
"3.12": { "icon": "🟢", "until": "2028.10" },
"3.9": { "icon": "⚫", "until": "2025.10" },
"3.10": { "icon": "🔴", "until": "2026.10" },
"3.11": { "icon": "🟠", "until": "2027.10" },
"3.12": { "icon": "🟡", "until": "2028.10" },
"3.13": { "icon": "🟢", "until": "2029.10" },
"3.14": { "icon": "🟣", "until": "2030.10" },
"pypy-3.7": { "icon": "⟲⚫", "until": "????.??" },
"pypy-3.8": { "icon": "⟲⚫", "until": "????.??" },
"3.14": { "icon": "🟢", "until": "2030.10" },
"3.15": { "icon": "🟣", "until": "2031.10" },
"pypy-3.9": { "icon": "⟲🔴", "until": "????.??" },
"pypy-3.10": { "icon": "⟲🟠", "until": "????.??" },
"pypy-3.11": { "icon": "⟲🟡", "until": "????.??" },
},
# Runner systems (runner images) supported by GitHub Actions
"sys": {
"ubuntu": { "icon": "🐧", "runs-on": "${{ inputs.ubuntu_image }}", "shell": "bash", "name": "Linux (x86-64)" },
"windows": { "icon": "🪟", "runs-on": "${{ inputs.windows_image }}", "shell": "pwsh", "name": "Windows (x86-64)" },
"macos": { "icon": "🍎", "runs-on": "${{ inputs.macos_intel_image }}", "shell": "bash", "name": "macOS (x86-64)" },
"macos-arm": { "icon": "🍏", "runs-on": "${{ inputs.macos_arm_image }}", "shell": "bash", "name": "macOS (aarch64)" },
"ubuntu": { "icon": "🐧", "runs-on": "${{ inputs.ubuntu_image }}", "shell": "bash", "name": "Linux (x86-64)" },
"ubuntu-arm": { "icon": "", "runs-on": "${{ inputs.ubuntu_arm_image }}", "shell": "bash", "name": "Linux (aarch64)" },
"windows": { "icon": "🪟", "runs-on": "${{ inputs.windows_image }}", "shell": "pwsh", "name": "Windows (x86-64)" },
"windows-arm": { "icon": "🏢", "runs-on": "${{ inputs.windows_arm_image }}", "shell": "pwsh", "name": "Windows (aarch64)" },
"macos": { "icon": "🍎", "runs-on": "${{ inputs.macos_intel_image }}", "shell": "bash", "name": "macOS (x86-64)" },
"macos-arm": { "icon": "🍏", "runs-on": "${{ inputs.macos_arm_image }}", "shell": "bash", "name": "macOS (aarch64)" },
},
# Runtimes provided by MSYS2
"runtime": {
@@ -300,7 +429,7 @@ jobs:
{
"sysicon": data["runtime"][runtime]["icon"],
"system": "msys2",
"runs-on": "windows-latest",
"runs-on": "${{ inputs.windows_image }}",
"runtime": runtime.upper(),
"shell": "msys2 {0}",
"pyicon": data["python"][currentMSYS2Version]["icon"],
@@ -310,31 +439,11 @@ jobs:
for runtime, version in combinations if runtime not in data["sys"]
]
artifact_names = {
"unittesting_xml": f"{name}-UnitTestReportSummary-XML",
"unittesting_html": f"{name}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{name}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{name}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{name}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{name}-CodeCoverage-SQLite",
"codecoverage_xml": f"{name}-CodeCoverage-XML",
"codecoverage_json": f"{name}-CodeCoverage-JSON",
"codecoverage_html": f"{name}-CodeCoverage-HTML",
"statictyping_html": f"{name}-StaticTyping-HTML",
"package_all": f"{name}-Packages",
"documentation_html": f"{name}-Documentation-HTML",
"documentation_latex": f"{name}-Documentation-LaTeX",
"documentation_pdf": f"{name}-Documentation-PDF",
}
print("Parameters:")
print(f" python_version: {python_version}")
print(f" python_jobs ({len(jobs)}):\n" +
"".join([f" {{ " + ", ".join([f"\"{key}\": \"{value}\"" for key, value in job.items()]) + f" }},\n" for job in jobs])
)
print(f" artifact_names ({len(artifact_names)}):")
for id, name in artifact_names.items():
print(f" {id:>20}: {name}")
# Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT"))
@@ -343,13 +452,18 @@ jobs:
f.write(dedent(f"""\
python_version={python_version}
python_jobs={json_dumps(jobs)}
artifact_names={json_dumps(artifact_names)}
"""))
- name: Verify out parameters
id: verify
run: |
printf "python_version: %s\n" '${{ steps.params.outputs.python_version }}'
printf "python_jobs: %s\n" '${{ steps.params.outputs.python_jobs }}'
printf "artifact_names: %s\n" '${{ steps.params.outputs.artifact_names }}'
printf "params: %s\n" '${{ steps.params.outputs.params }}'
printf "python_version: %s\n" '${{ steps.variables.outputs.python_version }}'
printf "package_fullname: %s\n" '${{ steps.variables.outputs.package_fullname }}'
printf "package_directory: %s\n" '${{ steps.variables.outputs.package_directory }}'
printf "package_version_file: %s\n" '${{ steps.variables.outputs.package_version_file }}'
printf "artifact_basename: %s\n" '${{ steps.variables.outputs.artifact_basename }}'
printf "================================================================================\n"
printf "artifact_names: %s\n" '${{ steps.artifacts.outputs.artifact_names }}'
printf "================================================================================\n"
printf "python_jobs: %s\n" '${{ steps.jobs.outputs.python_jobs }}'
printf "================================================================================\n"

View File

@@ -35,15 +35,18 @@ on:
type: string
outputs:
on_default_branch:
description: ""
value: ${{ jobs.Prepare.outputs.on_default_branch }}
on_main_branch:
description: ""
value: ${{ jobs.Prepare.outputs.on_main_branch }}
on_dev_branch:
description: ""
value: ${{ jobs.Prepare.outputs.on_dev_branch }}
on_release_branch:
description: ""
value: ${{ jobs.Prepare.outputs.on_release_branch }}
on_dev_branch:
description: ""
value: ${{ jobs.Prepare.outputs.on_dev_branch }}
is_regular_commit:
description: ""
value: ${{ jobs.Prepare.outputs.is_regular_commit }}
@@ -59,9 +62,15 @@ on:
is_release_tag:
description: ""
value: ${{ jobs.Prepare.outputs.is_release_tag }}
has_submodules:
description: ""
value: ${{ jobs.Prepare.outputs.has_submodules }}
ref_kind:
description: ""
value: ${{ jobs.Prepare.outputs.ref_kind }}
default_branch:
description: ""
value: ${{ jobs.Prepare.outputs.default_branch }}
branch:
description: ""
value: ${{ jobs.Prepare.outputs.branch }}
@@ -83,31 +92,46 @@ on:
# pr_mergedat:
# description: ""
# value: ${{ jobs.Prepare.outputs.pr_mergedat }}
git_submodule_count:
description: ""
value: ${{ jobs.Prepare.outputs.git_submodule_count }}
git_submodule_names:
description: ""
value: ${{ jobs.Prepare.outputs.git_submodule_names }}
git_submodule_paths:
description: ""
value: ${{ jobs.Prepare.outputs.git_submodule_paths }}
jobs:
Prepare:
name: Extract Information
runs-on: ubuntu-24.04
outputs:
on_main_branch: ${{ steps.Classify.outputs.on_main_branch }}
on_dev_branch: ${{ steps.Classify.outputs.on_dev_branch }}
on_release_branch: ${{ steps.Classify.outputs.on_release_branch }}
is_regular_commit: ${{ steps.Classify.outputs.is_regular_commit }}
is_merge_commit: ${{ steps.Classify.outputs.is_merge_commit }}
is_release_commit: ${{ steps.Classify.outputs.is_release_commit }}
is_nightly_tag: ${{ steps.Classify.outputs.is_nightly_tag }}
is_release_tag: ${{ steps.Classify.outputs.is_release_tag }}
ref_kind: ${{ steps.Classify.outputs.ref_kind }}
branch: ${{ steps.Classify.outputs.branch }}
tag: ${{ steps.Classify.outputs.tag }}
version: ${{ steps.Classify.outputs.version || steps.FindPullRequest.outputs.pr_version }}
# release_version: ${{ steps.FindPullRequest.outputs.release_version }}
pr_title: ${{ steps.FindPullRequest.outputs.pr_title }}
pr_number: ${{ steps.Classify.outputs.pr_number || steps.FindPullRequest.outputs.pr_number }}
on_default_branch: ${{ steps.Classify.outputs.on_default_branch }}
on_main_branch: ${{ steps.Classify.outputs.on_main_branch }}
on_release_branch: ${{ steps.Classify.outputs.on_release_branch }}
on_dev_branch: ${{ steps.Classify.outputs.on_dev_branch }}
is_regular_commit: ${{ steps.Classify.outputs.is_regular_commit }}
is_merge_commit: ${{ steps.Classify.outputs.is_merge_commit }}
is_release_commit: ${{ steps.Classify.outputs.is_release_commit }}
is_nightly_tag: ${{ steps.Classify.outputs.is_nightly_tag }}
is_release_tag: ${{ steps.Classify.outputs.is_release_tag }}
has_submodules: ${{ steps.Classify.outputs.has_submodules }}
ref_kind: ${{ steps.Classify.outputs.ref_kind }}
default_branch: ${{ steps.Classify.outputs.default_branch }}
branch: ${{ steps.Classify.outputs.branch }}
tag: ${{ steps.Classify.outputs.tag }}
version: ${{ steps.Classify.outputs.version || steps.FindPullRequest.outputs.pr_version }}
# release_version: ${{ steps.FindPullRequest.outputs.release_version }}
pr_title: ${{ steps.FindPullRequest.outputs.pr_title }}
pr_number: ${{ steps.Classify.outputs.pr_number || steps.FindPullRequest.outputs.pr_number }}
git_submodule_count: ${{ steps.Classify.outputs.git_submodule_count }}
git_submodule_names: ${{ steps.Classify.outputs.git_submodule_names }}
git_submodule_paths: ${{ steps.Classify.outputs.git_submodule_paths }}
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
# The command 'git describe' (used for version) needs the history.
fetch-depth: 0
@@ -132,89 +156,133 @@ jobs:
ANSI_LIGHT_BLUE=$'\x1b[94m'
ANSI_NOCOLOR=$'\x1b[0m'
export GH_TOKEN=${{ github.token }}
ref="${{ github.ref }}"
on_default_branch="false"
on_main_branch="false"
on_dev_branch="false"
on_release_branch="false"
on_dev_branch="false"
is_regular_commit="false"
is_merge_commit="false"
is_release_commit="false"
is_nightly_tag="false"
is_release_tag="false"
has_submodules="false"
ref_kind="unknown"
default_branch=""
branch=""
tag=""
pr_number=""
version=""
git_submodule_count="0"
git_submodule_names=""
git_submodule_paths=""
printf "Classify Git reference '%s' " "${ref}"
if [[ "${ref:0:11}" == "refs/heads/" ]]; then
printf "${ANSI_LIGHT_GREEN}[BRANCH]\n"
ref_kind="branch"
branch="${ref:11}"
printf "Commit check:\n"
printf "Get default branch name ... "
defaultBranch=$(gh repo view "${{ github.repository }}" --json defaultBranchRef --jq '.defaultBranchRef.name' 2>&1)
if [[ $? -eq 0 ]]; then
printf "${ANSI_LIGHT_GREEN} [OK]\n"
default_branch="${defaultBranch}"
printf " Default branch ${ANSI_LIGHT_BLUE}'%s'${ANSI_NOCOLOR}\n" "${default_branch}"
else
printf "${ANSI_LIGHT_RED} [FAILED]\n"
printf " ${ANSI_LIGHT_RED}%s${ANSI_NOCOLOR}\n" "${default_branch}"
fi
printf "Commit checks:\n"
printf " Commit: %s\n" "${{ github.sha }}"
printf " Commit kind "
if [[ -z "$(git rev-list -1 --merges ${{ github.sha }}~1..${{ github.sha }})" ]]; then
is_regular_commit="true"
printf "${ANSI_LIGHT_YELLOW}[REGULAR]${ANSI_NOCOLOR}\n"
else
is_merge_commit="true"
printf "${ANSI_LIGHT_GREEN}[MERGE]${ANSI_NOCOLOR}\n"
fi
printf "Branch checks:\n"
printf " Branch: %s\n" "${branch}"
printf " Commit on default branch ${ANSI_LIGHT_BLUE}'%s'${ANSI_NOCOLOR} " "${defaultBranch}"
if [[ "${branch}" == "${defaultBranch}" ]]; then
on_default_branch="true"
printf "${ANSI_LIGHT_GREEN}[YES]${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_RED}[NO]${ANSI_NOCOLOR}\n"
fi
printf " Commit on main branch ${ANSI_LIGHT_BLUE}'%s'${ANSI_NOCOLOR} " "${{ inputs.main_branch }}"
if [[ "${branch}" == "${{ inputs.main_branch }}" ]]; then
on_main_branch="true"
if [[ -z "$(git rev-list -1 --merges ${{ github.sha }}~1..${{ github.sha }})" ]]; then
is_regular_commit="true"
printf " ${ANSI_LIGHT_YELLOW}regular "
else
is_merge_commit="true"
printf " ${ANSI_LIGHT_GREEN}merge "
fi
printf "commit${ANSI_NOCOLOR} on main branch ${ANSI_LIGHT_BLUE}'%s'${ANSI_NOCOLOR}\n" "${{ inputs.main_branch }}"
fi
if [[ "${branch}" == "${{ inputs.development_branch }}" ]]; then
on_dev_branch="true"
if [[ -z "$(git rev-list -1 --merges ${{ github.sha }}~1..${{ github.sha }})" ]]; then
is_regular_commit="true"
printf " ${ANSI_LIGHT_YELLOW}regular "
else
is_merge_commit="true"
printf " ${ANSI_LIGHT_GREEN}merge "
fi
printf "commit${ANSI_NOCOLOR} on development branch ${ANSI_LIGHT_BLUE}'%s'${ANSI_NOCOLOR}\n" "${{ inputs.development_branch }}"
printf "${ANSI_LIGHT_GREEN}[YES]${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_RED}[NO]${ANSI_NOCOLOR}\n"
fi
printf " Commit on release branch ${ANSI_LIGHT_BLUE}'%s'${ANSI_NOCOLOR} " "${{ inputs.release_branch }}"
if [[ "${branch}" == "${{ inputs.release_branch }}" ]]; then
on_release_branch="true"
printf "${ANSI_LIGHT_GREEN}[YES]${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_RED}[NO]${ANSI_NOCOLOR}\n"
fi
if [[ -z "$(git rev-list -1 --merges ${{ github.sha }}~1..${{ github.sha }})" ]]; then
is_regular_commit="true"
printf " ${ANSI_LIGHT_YELLOW}regular "
else
printf " Commit on development branch ${ANSI_LIGHT_BLUE}'%s'${ANSI_NOCOLOR} " "${{ inputs.development_branch }}"
if [[ "${branch}" == "${{ inputs.development_branch }}" ]]; then
on_dev_branch="true"
printf "${ANSI_LIGHT_GREEN}[YES]${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_RED}[NO]${ANSI_NOCOLOR}\n"
fi
if [[ "${is_merge_commit}" == "true" ]]; then
printf "Release checks:\n"
printf " Release kind "
if [[ "${on_main_branch}" == "true" ]]; then
is_release_commit="true"
printf " ${ANSI_LIGHT_GREEN}release "
printf "${ANSI_LIGHT_GREEN}[RELEASE]${ANSI_NOCOLOR}\n"
elif [[ "${on_version_branch}" == "true" ]]; then
is_release_commit="true"
printf "${ANSI_LIGHT_GREEN}[RELEASE]${ANSI_NOCOLOR}\n"
elif [[ "${on_release_branch}" == "true" ]]; then
is_prerelease_commit="true"
printf "${ANSI_LIGHT_YELLOW}[PRERELEASE]${ANSI_NOCOLOR}\n"
fi
printf "commit${ANSI_NOCOLOR} on release branch ${ANSI_LIGHT_BLUE}'%s'${ANSI_NOCOLOR}\n" "${{ inputs.release_branch }}"
fi
elif [[ "${ref:0:10}" == "refs/tags/" ]]; then
printf "${ANSI_LIGHT_GREEN}[TAG]\n"
ref_kind="tag"
tag="${ref:10}"
printf "Tag check:\n"
printf " Check if tag is on release branch '%s' ... " "${{ inputs.release_branch }}"
git branch --remotes --contains $(git rev-parse --verify "tags/${tag}~0") | grep "origin/${{ inputs.release_branch }}" > /dev/null
printf "Tag checks:\n"
printf " Check if tag is on main branch '%s' ... " "${{ inputs.main_branch }}"
git branch --remotes --contains $(git rev-parse --verify "tags/${tag}~0") | grep "origin/${{ inputs.main_branch }}" > /dev/null
if [[ $? -eq 0 ]]; then
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf "${ANSI_LIGHT_RED}Tag '%s' isn't on branch '%s'.${ANSI_NOCOLOR}\n" "${tag}" "${{ inputs.release_branch }}"
printf "::error title=TagCheck::Tag '%s' isn't on branch '%s'.\n" "${tag}" "${{ inputs.release_branch }}"
printf "${ANSI_LIGHT_RED}Tag '%s' isn't on branch '%s'.${ANSI_NOCOLOR}\n" "${tag}" "${{ inputs.main_branch }}"
printf "::error title=TagCheck::Tag '%s' isn't on branch '%s'.\n" "${tag}" "${{ inputs.main_branch }}"
exit 1
fi
NIGHTLY_TAG_PATTERN='^${{ inputs.nightly_tag_pattern }}$'
RELEASE_TAG_PATTERN='^${{ inputs.release_tag_pattern }}$'
printf " Check tag name against regexp '%s' ... " "${RELEASE_TAG_PATTERN}"
if [[ "${tag}" =~ NIGHTLY_TAG_PATTERN ]]; then
printf "Tag checks:\n"
printf " Tag: %s\n" "${tag}"
printf " Check tag '%s' against regexp ... " "${tag}"
if [[ "${tag}" =~ ${NIGHTLY_TAG_PATTERN} ]]; then
printf "${ANSI_LIGHT_GREEN}[NIGHTLY]${ANSI_NOCOLOR}\n"
is_nightly_tag="true"
elif [[ "${tag}" =~ $RELEASE_TAG_PATTERN ]]; then
elif [[ "${tag}" =~ ${RELEASE_TAG_PATTERN} ]]; then
printf "${ANSI_LIGHT_GREEN}[RELEASE]${ANSI_NOCOLOR}\n"
version="${tag}"
is_release_tag="true"
@@ -226,35 +294,80 @@ jobs:
printf "::error title=RexExpCheck::Tag name '%s' doesn't conform to regexp '%s' nor '%s'.\n" "${tag}" "${NIGHTLY_TAG_PATTERN}" "${RELEASE_TAG_PATTERN}"
exit 1
fi
if [[ "${is_nightly_tag}" == "true" ]]; then
printf " Check if nightly tag is on main branch '%s' ... " "${{ inputs.main_branch }}"
git branch --remotes --contains $(git rev-parse --verify "tags/${tag}~0") | grep "origin/${{ inputs.main_branch }}" > /dev/null
if [[ $? -eq 0 ]]; then
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf " ${ANSI_LIGHT_RED}Tag '%s' isn't on branch '%s'.${ANSI_NOCOLOR}\n" "${tag}" "${{ inputs.main_branch }}"
printf "::error title=TagCheck::Tag '%s' isn't on branch '%s'.\n" "${tag}" "${{ inputs.main_branch }}"
exit 1
fi
elif [[ "${is_release_tag}" == "true" ]]; then
printf " Check if release tag is on main branch '%s' ... " "${{ inputs.main_branch }}"
git branch --remotes --contains $(git rev-parse --verify "tags/${tag}~0") | grep "origin/${{ inputs.main_branch }}" > /dev/null
if [[ $? -eq 0 ]]; then
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf " ${ANSI_LIGHT_RED}Tag '%s' isn't on branch '%s'.${ANSI_NOCOLOR}\n" "${tag}" "${{ inputs.main_branch }}"
printf "::error title=TagCheck::Tag '%s' isn't on branch '%s'.\n" "${tag}" "${{ inputs.main_branch }}"
exit 1
fi
fi
elif [[ "${ref:0:10}" == "refs/pull/" ]]; then
printf "${ANSI_LIGHT_YELLOW}[PULL REQUEST]\n"
ref_kind="pullrequest"
pr_number=${ref:11}
pr_number=${pr_number%%/*}
printf "Pull Request check:\n"
printf "Pull Request checks:\n"
printf " Number: %s\n" "${pr_number}"
else
printf "${ANSI_LIGHT_RED}[UNKNOWN]\n"
printf "${ANSI_LIGHT_RED}Unknown Git reference '%s'.${ANSI_NOCOLOR}\n" "${{ github.ref }}"
printf "::error title=Classify Commit::Unknown Git reference '%s'.\n" "${{ github.ref }}"
exit 1
fi
# Submodules
if [[ -f .gitsubmodules ]]; then
has_submodules="true"
git_modules_file=.gitmodules # $(git rev-parse --show-toplevel)/.gitmodules
git_submodule_count="$(grep -Po '(?<=\[submodule \")(.*)(?=\"\])' "${git_modules_file}" | wc -l)"
git_submodule_names="$(grep -Po '(?<=\[submodule \")(.*)(?=\"\])' "${git_modules_file}" | paste -sd ':' -)"
git_submodule_paths="$(git config --file "${git_modules_file}" --null --name-only --get-regexp '\.path$' | xargs -0 -n1 git config --file "${git_modules_file}" --get | paste -sd ':' -)"
fi
printf "\nWriting output variables ...\n"
tee --append "${GITHUB_OUTPUT}" <<EOF
on_default_branch=${on_default_branch}
on_main_branch=${on_main_branch}
on_dev_branch=${on_dev_branch}
on_release_branch=${on_release_branch}
on_dev_branch=${on_dev_branch}
is_regular_commit=${is_regular_commit}
is_merge_commit=${is_merge_commit}
is_release_commit=${is_release_commit}
is_nightly_tag=${is_nightly_tag}
is_release_tag=${is_release_tag}
has_submodules=${has_submodules}
ref_kind=${ref_kind}
default_branch=${default_branch}
branch=${branch}
tag=${tag}
pr_number=${pr_number}
version=${version}
git_submodule_count=${git_submodule_count}
git_submodule_names=${git_submodule_names}
git_submodule_paths=${git_submodule_paths}
EOF
# TODO: why not is_release_commit?
# TODO: how to support version branches and hotfix releases on version branches?
- name: 🔁 Find merged PullRequest from second parent of current SHA (${{ github.sha }})
id: FindPullRequest
if: steps.Classify.outputs.is_merge_commit == 'true'
@@ -325,21 +438,28 @@ jobs:
- name: Debug
run: |
printf "on_main_branch: %s\n" "${{ steps.Classify.outputs.on_main_branch }}"
printf "on_dev_branch: %s\n" "${{ steps.Classify.outputs.on_dev_branch }}"
printf "on_release_branch: %s\n" "${{ steps.Classify.outputs.on_release_branch }}"
printf "is_regular_commit: %s\n" "${{ steps.Classify.outputs.is_regular_commit }}"
printf "is_merge_commit: %s\n" "${{ steps.Classify.outputs.is_merge_commit }}"
printf "is_release_commit: %s\n" "${{ steps.Classify.outputs.is_release_commit }}"
printf "is_nightly_tag: %s\n" "${{ steps.Classify.outputs.is_nightly_tag }}"
printf "is_release_tag: %s\n" "${{ steps.Classify.outputs.is_release_tag }}"
printf "ref_kind: %s\n" "${{ steps.Classify.outputs.ref_kind }}"
printf "branch: %s\n" "${{ steps.Classify.outputs.branch }}"
printf "tag: %s\n" "${{ steps.Classify.outputs.tag }}"
printf "version: %s\n" "${{ steps.Classify.outputs.version || steps.FindPullRequest.outputs.pr_version }}"
printf " from tag: %s\n" "${{ steps.Classify.outputs.version }}"
printf " from pr: %s\n" "${{ steps.FindPullRequest.outputs.pr_version }}"
printf "pr title: %s\n" "${{ steps.FindPullRequest.outputs.pr_title }}"
printf "pr number: %s\n" "${{ steps.Classify.outputs.pr_number || steps.FindPullRequest.outputs.pr_number }}"
printf " from merge: %s\n" "${{ steps.Classify.outputs.pr_number }}"
printf " from pr: %s\n" "${{ steps.FindPullRequest.outputs.pr_number }}"
printf "on_default_branch: %s\n" "${{ steps.Classify.outputs.on_default_branch }}"
printf "on_main_branch: %s\n" "${{ steps.Classify.outputs.on_main_branch }}"
printf "on_release_branch: %s\n" "${{ steps.Classify.outputs.on_release_branch }}"
printf "on_dev_branch: %s\n" "${{ steps.Classify.outputs.on_dev_branch }}"
printf "is_regular_commit: %s\n" "${{ steps.Classify.outputs.is_regular_commit }}"
printf "is_merge_commit: %s\n" "${{ steps.Classify.outputs.is_merge_commit }}"
printf "is_release_commit: %s\n" "${{ steps.Classify.outputs.is_release_commit }}"
printf "is_nightly_tag: %s\n" "${{ steps.Classify.outputs.is_nightly_tag }}"
printf "is_release_tag: %s\n" "${{ steps.Classify.outputs.is_release_tag }}"
printf "has_submodules: %s\n" "${{ steps.Classify.outputs.has_submodules }}"
printf "ref_kind: %s\n" "${{ steps.Classify.outputs.ref_kind }}"
printf "default_branch: %s\n" "${{ steps.Classify.outputs.default_branch }}"
printf "branch: %s\n" "${{ steps.Classify.outputs.branch }}"
printf "tag: %s\n" "${{ steps.Classify.outputs.tag }}"
printf "version: %s\n" "${{ steps.Classify.outputs.version || steps.FindPullRequest.outputs.pr_version }}"
printf " from tag: %s\n" "${{ steps.Classify.outputs.version }}"
printf " from pr: %s\n" "${{ steps.FindPullRequest.outputs.pr_version }}"
printf "pr title: %s\n" "${{ steps.FindPullRequest.outputs.pr_title }}"
printf "pr number: %s\n" "${{ steps.Classify.outputs.pr_number || steps.FindPullRequest.outputs.pr_number }}"
printf " from merge: %s\n" "${{ steps.Classify.outputs.pr_number }}"
printf " from pr: %s\n" "${{ steps.FindPullRequest.outputs.pr_number }}"
printf "git_submodule_*:\n"
printf " *_count_: %s\n" "${{ steps.FindPullRequest.outputs.git_submodule_count }}"
printf " *_names: %s\n" "${{ steps.FindPullRequest.outputs.git_submodule_names }}"
printf " *_paths: %s\n" "${{ steps.FindPullRequest.outputs.git_submodule_paths }}"

View File

@@ -38,6 +38,31 @@ on:
required: false
default: 'pyproject.toml'
type: string
coverage_report_xml:
description: 'Directory containing the XML coverage report file.'
required: false
default: >-
{ "directory": "report/coverage",
"filename": "coverage.xml",
"fullpath": "report/coverage/coverage.xml"
}
type: string
coverage_report_json:
description: 'Directory containing the JSON coverage report file.'
required: false
default: >-
{ "directory": "report/coverage",
"filename": "coverage.json",
"fullpath": "report/coverage/coverage.json"
}
type: string
coverage_report_html:
description: 'HTML root directory of the generated coverage report.'
required: false
default: >-
{ "directory": "report/coverage/html"
}
type: string
coverage_sqlite_artifact:
description: 'Name of the SQLite coverage artifact.'
required: false
@@ -48,41 +73,16 @@ on:
required: false
default: ''
type: string
coverage_report_xml_directory:
description: 'Directory containing the XML coverage report file.'
required: false
default: 'report/coverage'
type: string
coverage_report_xml_filename:
description: 'Filename of the XML coverage report file.'
required: false
default: 'coverage.xml'
type: string
coverage_json_artifact:
description: 'Name of the JSON coverage artifact.'
required: false
default: ''
type: string
coverage_report_json_directory:
description: 'Directory containing the JSON coverage report file.'
required: false
default: 'report/coverage'
type: string
coverage_report_json_filename:
description: 'Filename of the JSON coverage report file.'
required: false
default: 'coverage.json'
type: string
coverage_html_artifact:
description: 'Name of the HTML coverage artifact.'
required: false
default: ''
type: string
coverage_report_html_directory:
description: 'HTML root directory of the generated coverage report.'
required: false
default: 'report/coverage/html'
type: string
codecov:
description: 'Publish merged coverage report to Codecov.'
required: false
@@ -109,13 +109,13 @@ jobs:
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
lfs: true
submodules: true
- name: 📥 Download Artifacts
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
with:
pattern: ${{ inputs.coverage_artifacts_pattern }}
path: artifacts
@@ -138,25 +138,25 @@ jobs:
run: coverage combine --data-file=.coverage coverage/
- name: Report code coverage
run: coverage report --rcfile=pyproject.toml --data-file=.coverage
run: coverage report --rcfile=${{ inputs.coverage_config }} --data-file=.coverage
- name: Convert to XML format (Cobertura)
if: inputs.coverage_xml_artifact != '' || inputs.codecov || inputs.codacy
run: coverage xml --data-file=.coverage
run: coverage xml --rcfile=${{ inputs.coverage_config }} --data-file=.coverage
- name: Convert to JSON format
if: inputs.coverage_json_artifact != ''
run: coverage json --data-file=.coverage
run: coverage json --rcfile=${{ inputs.coverage_config }} --data-file=.coverage
- name: Convert to HTML format
if: inputs.coverage_html_artifact != ''
run: |
coverage html --data-file=.coverage -d report/coverage/html
rm report/coverage/html/.gitignore
tree -pash report/coverage/html
coverage html --rcfile=${{ inputs.coverage_config }} --data-file=.coverage
rm ${{ fromJson(inputs.coverage_report_html).directory }}/.gitignore
tree -pash ${{ fromJson(inputs.coverage_report_html).directory }}
- name: 📤 Upload 'Coverage SQLite Database' artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
if: inputs.coverage_sqlite_artifact != ''
continue-on-error: true
with:
@@ -166,34 +166,34 @@ jobs:
retention-days: 1
- name: 📤 Upload 'Coverage XML Report' artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
if: inputs.coverage_xml_artifact != ''
continue-on-error: true
with:
name: ${{ inputs.coverage_xml_artifact }}
working-directory: ${{ inputs.coverage_report_xml_directory }}
path: ${{ inputs.coverage_report_xml_filename }}
working-directory: ${{ fromJson(inputs.coverage_report_xml).directory }}
path: ${{ fromJson(inputs.coverage_report_xml).filename }}
if-no-files-found: error
retention-days: 1
- name: 📤 Upload 'Coverage JSON Report' artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
if: inputs.coverage_json_artifact != ''
continue-on-error: true
with:
name: ${{ inputs.coverage_json_artifact }}
working-directory: ${{ inputs.coverage_report_json_directory }}
path: ${{ inputs.coverage_report_json_filename }}
working-directory: ${{ fromJson(inputs.coverage_report_json).directory }}
path: ${{ fromJson(inputs.coverage_report_json).filename }}
if-no-files-found: error
retention-days: 1
- name: 📤 Upload 'Coverage HTML Report' artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
if: inputs.coverage_html_artifact != ''
continue-on-error: true
with:
name: ${{ inputs.coverage_html_artifact }}
working-directory: ${{ inputs.coverage_report_html_directory }}
working-directory: ${{ fromJson(inputs.coverage_report_html).directory }}
path: '*'
if-no-files-found: error
retention-days: 1
@@ -205,8 +205,9 @@ jobs:
continue-on-error: true
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: "coverage"
disable_search: true
files: ${{ inputs.coverage_report_xml_directory }}/${{ inputs.coverage_report_xml_filename }}
files: ${{ fromJson(inputs.coverage_report_xml).fullpath }}
flags: unittests
env_vars: PYTHON
fail_ci_if_error: true
@@ -218,7 +219,7 @@ jobs:
continue-on-error: true
with:
project-token: ${{ secrets.CODACY_TOKEN }}
coverage-reports: ${{ inputs.coverage_report_xml_directory }}/${{ inputs.coverage_report_xml_filename }}
coverage-reports: ${{ fromJson(inputs.coverage_report_xml).fullpath }}
- name: Generate error messages
run: |

View File

@@ -33,7 +33,7 @@ on:
python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
@@ -56,31 +56,31 @@ jobs:
steps:
- name: 📥 Download artifacts '${{ inputs.artifact }}' from 'Package' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
with:
name: ${{ inputs.artifact }}
path: dist
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
- name: ⚙ Install dependencies for packaging and release
run: python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
- name: Release Python source package to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*.tar.gz
- name: ⤴ Release Python wheel package to PyPI
- name: Publish Python wheel package to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*.whl
- name: ⤴ Publish Python source package to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*.tar.gz
- name: 🗑️ Delete packaging Artifacts
uses: geekyeggo/delete-artifact@v5
with:

View File

@@ -41,8 +41,7 @@ on:
type: string
tag:
description: 'Name of the release (tag).'
required: false
default: ''
required: true
type: string
title:
description: 'Title of the release.'
@@ -82,7 +81,7 @@ on:
latest:
description: 'Specify if this is the latest release.'
required: false
default: false
default: true
type: boolean
replacements:
description: 'Multi-line string containing search=replace patterns.'
@@ -133,7 +132,7 @@ jobs:
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
# The command 'git describe' (used for version) needs the history.
fetch-depth: 0
@@ -192,198 +191,6 @@ jobs:
exit 1
fi
- name: 📑 Assemble Release Notes
id: createReleaseNotes
run: |
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_LIGHT_YELLOW=$'\x1b[93m'
ANSI_LIGHT_BLUE=$'\x1b[94m'
ANSI_NOCOLOR=$'\x1b[0m'
export GH_TOKEN=${{ github.token }}
# Save release description (from parameter in a file)
head -c -1 <<'EOF' > __DESCRIPTION__.md
${{ inputs.description }}
EOF
# Save release footer (from parameter in a file)
head -c -1 <<'EOF' > __FOOTER__.md
${{ inputs.description_footer }}
EOF
# Download Markdown from PullRequest
# Readout second parent's SHA
# Search PR with that SHA
# Load description of that PR
printf "Read second parent of current SHA (%s) ... " "${{ github.ref }}"
FATHER_SHA=$(git rev-parse ${{ github.ref }}^2 -- 2> /dev/null)
if [[ $? -ne 0 || "{FATHER_SHA}" == "" ]]; then
printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf "→ ${ANSI_LIGHT_YELLOW}Skipped readout of pull request description. This is not a merge commit.${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
printf "Search Pull Request to '%s' and branch containing SHA %s ... " "${{ inputs.release_branch }}" "${FATHER_SHA}"
PULL_REQUESTS=$(gh pr list --base "${{ inputs.release_branch }}" --search "${FATHER_SHA}" --state "merged" --json "title,number,mergedBy,mergedAt,body")
if [[ $? -ne 0 || "${PULL_REQUESTS}" == "" ]]; then
printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf "${ANSI_LIGHT_RED}Couldn't find a merged Pull Request to '%s'. -> %s${ANSI_NOCOLOR}\n" "${{ inputs.release_branch }}" "${PULL_REQUESTS}"
printf "::error title=PullRequest::Couldn't find a merged Pull Request to '%s'. -> %s\n" "${{ inputs.release_branch }}" "${PULL_REQUESTS}"
exit 1
else
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
PR_TITLE="$( printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].title")"
PR_NUMBER="$( printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].number")"
PR_BODY="$( printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].body")"
PR_MERGED_BY="$(printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].mergedBy.login")"
PR_MERGED_AT="$(printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].mergedAt")"
printf "Found Pull Request:\n"
printf " %s\n" "Title: ${PR_TITLE}"
printf " %s\n" "Number: ${PR_NUMBER}"
printf " %s\n" "MergedBy: ${PR_MERGED_BY}"
printf " %s\n" "MergedAt: ${PR_MERGED_AT} ($(date -d"${PR_MERGED_AT}" '+%d.%m.%Y - %H:%M:%S'))"
fi
echo "${PR_BODY}" > __PULLREQUEST__.md
fi
# Check if a release description file should be used and exists.
if [[ "${{ inputs.description_file }}" != "" ]]; then
if [[ ! -f "${{ inputs.description_file }}" ]]; then
printf "${ANSI_LIGHT_RED}Release description file '%s' not found.${ANSI_NOCOLOR}\n" "${{ inputs.description_file }}"
printf "::error title=%s::%s\n" "FileNotFound" "Release description file '${{ inputs.description_file }}' not found."
exit 1
elif [[ -s "${{ inputs.description_file }}" ]]; then
printf "Use '%s' as main release description.\n" "${{ inputs.description_file }}"
cp -v "${{ inputs.description_file }}" __NOTES__.md
else
printf "${ANSI_LIGHT_RED}Release description file '%s' is empty.${ANSI_NOCOLOR}\n" "${{ inputs.description_file }}"
printf "::error title=%s::%s\n" "FileNotFound" "Release description file '${{ inputs.description_file }}' is empty."
exit 1
fi
# Check if the main release description is provided by a template parameter
elif [[ -s __DESCRIPTION__.md ]]; then
printf "Use '__DESCRIPTION__.md' as main release description.\n"
mv -v __DESCRIPTION__.md __NOTES__.md
# Check if the pull request serves as the main release description text.
elif [[ -s __PULLREQUEST__.md ]]; then
printf "Use '__PULLREQUEST__.md' as main release description.\n"
mv -v __PULLREQUEST__.md __NOTES__.md
printf "Append '%%%%FOOTER%%%%' to '__NOTES__.md'.\n"
printf "\n%%%%FOOTER%%%%\n" >> __NOTES__.md
else
printf "${ANSI_LIGHT_RED}No release description specified (file, parameter, PR text).${ANSI_NOCOLOR}\n"
printf "::error title=%s::%s\n" "MissingDescription" "No release description specified (file, parameter, PR text)."
exit 1
fi
# Read release notes main file for placeholder substitution
NOTES=$(<__NOTES__.md)
# Inline description
if [[ -s __DESCRIPTION__.md ]]; then
NOTES="${NOTES//%%DESCRIPTION%%/$(<__DESCRIPTION__.md)}"
else
NOTES="${NOTES//%%DESCRIPTION%%/}"
fi
# Inline PullRequest and increase headline levels
if [[ -s __PULLREQUEST__.md ]]; then
while [[ "${NOTES}" =~ %%(PULLREQUEST(\+[0-3])?)%% ]]; do
case "${BASH_REMATCH[1]}" in
"PULLREQUEST+0" | "PULLREQUEST")
NOTES="${NOTES//${BASH_REMATCH[0]}/$(<__PULLREQUEST__.md)}"
;;
"PULLREQUEST+1")
NOTES="${NOTES//${BASH_REMATCH[0]}/$(cat __PULLREQUEST__.md | sed -E 's/^(#+) /\1# /gm;t')}"
;;
"PULLREQUEST+2")
NOTES="${NOTES//${BASH_REMATCH[0]}/$(cat __PULLREQUEST__.md | sed -E 's/^(#+) /\1### /gm;t')}"
;;
"PULLREQUEST+3")
NOTES="${NOTES//${BASH_REMATCH[0]}/$(cat __PULLREQUEST__.md | sed -E 's/^(#+) /\1### /gm;t')}"
;;
esac
done
else
while [[ "${NOTES}" =~ %%(PULLREQUEST(\+[0-3])?)%% ]]; do
NOTES="${NOTES//${BASH_REMATCH[0]}/}"
done
fi
# inline Footer
if [[ -s __FOOTER__.md ]]; then
NOTES="${NOTES//%%FOOTER%%/$(<__FOOTER__.md)}"
else
NOTES="${NOTES//%%FOOTER%%/}"
fi
# Apply replacements
while IFS=$'\r\n' read -r patternLine; do
# skip empty lines
[[ "$patternLine" == "" ]] && continue
pattern="%${patternLine%%=*}%"
replacement="${patternLine#*=}"
NOTES="${NOTES//$pattern/$replacement}"
done <<<'${{ inputs.replacements }}'
# Workarounds for stupid GitHub variables
owner_repo="${{ github.repository }}"
repo=${owner_repo##*/}
# Replace special identifiers
NOTES="${NOTES//%%gh_server%%/${{ github.server_url }}}"
NOTES="${NOTES//%%gh_workflow_name%%/${{ github.workflow }}}"
NOTES="${NOTES//%%gh_owner%%/${{ github.repository_owner }}}"
NOTES="${NOTES//%%gh_repo%%/${repo}}"
NOTES="${NOTES//%%gh_owner_repo%%/${{ github.repository_owner }}}"
#NOTES="${NOTES//%%gh_pages%%/https://${{ github.repository_owner }}.github.io/${repo}/}"
NOTES="${NOTES//%%gh_runid%%/${{ github.run_id }}}"
NOTES="${NOTES//%%gh_actor%%/${{ github.actor }}}"
NOTES="${NOTES//%%gh_sha%%/${{ github.sha }}}"
NOTES="${NOTES//%%date%%/$(date '+%Y-%m-%d')}"
NOTES="${NOTES//%%time%%/$(date '+%H:%M:%S %Z')}"
NOTES="${NOTES//%%datetime%%/$(date '+%Y-%m-%d %H:%M:%S %Z')}"
# Write final release notes to file
echo "${NOTES}" > __NOTES__.md
# Display partial contents for debugging
if [[ -s __DESCRIPTION__.md ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__DESCRIPTION__.md' ($(stat --printf="%s" "__DESCRIPTION__.md") B) ...."
cat __DESCRIPTION__.md
printf "::endgroup::\n"
else
printf "${ANSI_LIGHT_YELLOW}No '__DESCRIPTION__.md' found.${ANSI_NOCOLOR}\n"
fi
if [[ -s __PULLREQUEST__.md ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__PULLREQUEST__.md' ($(stat --printf="%s" "__PULLREQUEST__.md") B) ...."
cat __PULLREQUEST__.md
printf "::endgroup::\n"
else
printf "${ANSI_LIGHT_YELLOW}No '__PULLREQUEST__.md' found.${ANSI_NOCOLOR}\n"
fi
if [[ -s __FOOTER__.md ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__FOOTER__.md' ($(stat --printf="%s" "__FOOTER__.md") B) ...."
cat __FOOTER__.md
printf "::endgroup::\n"
else
printf "${ANSI_LIGHT_YELLOW}No '__FOOTER__.md' found.${ANSI_NOCOLOR}\n"
fi
# Print final release notes
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__NOTES__.md' ($(stat --printf="%s" "__NOTES__.md") B) ...."
cat __NOTES__.md
printf "::endgroup::\n"
- name: 📑 Create new Release Page
id: createReleasePage
if: inputs.mode == 'release'
@@ -398,6 +205,15 @@ jobs:
export GH_TOKEN=${{ github.token }}
tee "__PRELIMINARY_NOTES__.md" <<EOF
Release notes for ${{ inputs.tag }} are created right now ...
1. download artifacts &rarr; (compression?) &rarr; upload as assets
2. optional: create inventory.json
3. assemble release notes &rarr; update this text
4. optional: remove draft state
EOF
if [[ "${{ inputs.prerelease }}" == "true" ]]; then
addPreRelease="--prerelease"
fi
@@ -410,9 +226,7 @@ jobs:
addTitle=("--title" "${{ inputs.title }}")
fi
if [[ -s __NOTES__.md ]]; then
addNotes=("--notes-file" "__NOTES__.md")
fi
addNotes=("--notes-file" "__PRELIMINARY_NOTES__.md")
printf "Creating release '%s' ... " "${{ inputs.tag }}"
message="$(gh release create "${{ inputs.tag }}" --verify-tag --draft $addPreRelease $addLatest "${addTitle[@]}" "${addNotes[@]}" 2>&1)"
@@ -440,6 +254,14 @@ jobs:
export GH_TOKEN=${{ github.token }}
tee "__PRELIMINARY_NOTES__.md" <<EOF
Release notes for ${{ inputs.tag }} are updated right now ...
1. download artifacts &rarr; (compression?) &rarr; upload as assets
2. optional: create inventory.json
3. assemble release notes &rarr; update this text
EOF
addDraft="--draft"
if [[ "${{ inputs.prerelease }}" == "true" ]]; then
addPreRelease="--prerelease"
@@ -453,9 +275,7 @@ jobs:
addTitle=("--title" "${{ inputs.title }}")
fi
if [[ -s __NOTES__.md ]]; then
addNotes=("--notes-file" "__NOTES__.md")
fi
addNotes=("--notes-file" "__PRELIMINARY_NOTES__.md")
printf "Creating release '%s' ... " "${{ inputs.tag }}"
message="$(gh release create "${{ inputs.tag }}" --verify-tag --draft $addPreRelease $addLatest "${addTitle[@]}" "${addNotes[@]}" 2>&1)"
@@ -497,7 +317,28 @@ jobs:
# Create JSON inventory
if [[ "${{ inputs.inventory-json }}" != "" ]]; then
VERSION="1.0"
STRUCT_VERSION="1.1"
# Use GitHub API to ask for latest version
printf "Get latest released version via GitHub API ...\n"
printf " gh release list --json tagName,isLatest --jq '.[] | select(.isLatest == true) | .tagName' "
latestVersion=$(gh release list --json tagName,isLatest --jq '.[] | select(.isLatest == true) | .tagName')
if [[ $? -eq 0 ]]; then
if [[ -z "${latestVersion}" ]]; then
printf "${ANSI_LIGHT_RED}[UNKNOWN]${ANSI_NOCOLOR}\n"
printf " latest=unknown\n"
latestVersion="unknown"
else
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
printf " latest=%s\n" "${latestVersion}"
fi
else
printf "${ANSI_LIGHT_RED}[ERROR]${ANSI_NOCOLOR}\n"
printf " ${ANSI_LIGHT_RED}Couldn't get latest released version '%s'.${ANSI_NOCOLOR}\n" "${latestVersion}"
printf "::error title=%s::%s\n" "GitHub Release API" "Couldn't get latest released version '${latestVersion}'."
latestVersion="error"
fi
# Split categories by ',' into a Bash array.
# See https://stackoverflow.com/a/45201229/3719459
@@ -510,24 +351,33 @@ jobs:
fi
jsonInventory=$(jq -c -n \
--arg version "${VERSION}" \
--arg date "$(date +"%Y-%m-%dT%H-%M-%S%:z")" \
--arg structVersion "${STRUCT_VERSION}" \
--arg date "$(date +"%Y-%m-%dT%H:%M:%S%:z")" \
--argjson jsonMeta "$(jq -c -n \
--arg tag "${{ inputs.tag }}" \
--arg version "${{ inputs.inventory-version }}" \
--arg hash "${{ github.sha }}" \
--arg repo "${{ github.server_url }}/${{ github.repository }}" \
--arg release "${{ github.server_url }}/${{ github.repository }}/releases/download/${{ inputs.tag }}" \
--argjson jsonLatest "$(jq -c -n \
--arg version "${latestVersion}" \
--arg release "${{ github.server_url }}/${{ github.repository }}/releases/download/${latestVersion}" \
'{"version": $version, "release-url": $release}' \
)" \
--argjson categories "$(jq -c -n \
'$ARGS.positional' \
--args "${inventoryCategories[@]}" \
)" \
'{"tag": $tag, "version": $version, "git-hash": $hash, "repository-url": $repo, "release-url": $release, "categories": $categories}' \
'{"tag": $tag, "version": $version, "git-hash": $hash, "repository-url": $repo, "release-url": $release, "categories": $categories, "latest": $jsonLatest}' \
)" \
'{"version": 1.0, "timestamp": $date, "meta": $jsonMeta, "files": {}}'
'{"version": $structVersion, "timestamp": $date, "meta": $jsonMeta, "files": {}}'
)
fi
# Write Markdown table header
printf "| Asset Name | File Size | SHA256 |\n" > __ASSETS__.md
printf "|------------|-----------|--------|\n" >> __ASSETS__.md
ERRORS=0
# A dictionary of 0/1 to avoid duplicate downloads
declare -A downloadedArtifacts
@@ -578,7 +428,7 @@ jobs:
if [[ -n "${downloadedArtifacts[$artifact]}" ]]; then
printf " downloading artifact '%s' ... ${ANSI_LIGHT_YELLOW}[SKIPPED]${ANSI_NOCOLOR}\n" "${artifact}"
else
echo " downloading '${artifact}' ... "
printf " downloading '${artifact}' ...\n"
printf " gh run download $GITHUB_RUN_ID --dir \"%s\" --name \"%s\" " "${artifact}" "${artifact}"
gh run download $GITHUB_RUN_ID --dir "${artifact}" --name "${artifact}"
if [[ $? -eq 0 ]]; then
@@ -716,6 +566,13 @@ jobs:
sha256Checksums[$asset]="sha256:${sha256}"
printf "${ANSI_LIGHT_BLUE}${sha256}${ANSI_NOCOLOR}\n"
# Add asset to Markdown table
printf "| %s | %s | %s |\n" \
"[${title}](${{ github.server_url }}/${{ github.repository }}/releases/download/${{ inputs.tag }}/${uploadFile#*/})" \
"$(stat --printf="%s" "${uploadFile}" | numfmt --format "%.1f" --suffix=B --to=iec-i)" \
"\`${sha256}\`" \
>> __ASSETS__.md
# Add asset to JSON inventory
if [[ "${{ inputs.inventory-json }}" != "" ]]; then
if [[ "${categories}" != "${title}" ]]; then
@@ -750,7 +607,7 @@ jobs:
if [[ $? -eq 0 ]]; then
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
printf " checking assets SHA256 checksum ... \n"
printf " checking assets SHA256 checksum ... "
ghSHA256=$(gh release view --json assets --jq ".assets[] | select(.name == \"${asset}\") | .digest" ${{ inputs.tag }})
if [[ "${ghSHA256}" == "${sha256Checksums[$asset]}" ]]; then
printf "${ANSI_LIGHT_GREEN}[PASSED]${ANSI_NOCOLOR}\n"
@@ -804,6 +661,245 @@ jobs:
exit 1
fi
- name: 📑 Assemble Release Notes
id: createReleaseNotes
run: |
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_LIGHT_YELLOW=$'\x1b[93m'
ANSI_LIGHT_BLUE=$'\x1b[94m'
ANSI_NOCOLOR=$'\x1b[0m'
export GH_TOKEN=${{ github.token }}
# Save release description (from parameter in a file)
head -c -1 <<'EOF' > __DESCRIPTION__.md
${{ inputs.description }}
EOF
# Save release footer (from parameter in a file)
head -c -1 <<'EOF' > __FOOTER__.md
${{ inputs.description_footer }}
EOF
# Download Markdown from PullRequest
# Readout second parent's SHA
# Search PR with that SHA
# Load description of that PR
printf "Read second parent of current SHA (%s) ... " "${{ github.ref }}"
FATHER_SHA=$(git rev-parse ${{ github.ref }}^2 -- 2> /dev/null)
if [[ $? -ne 0 || "{FATHER_SHA}" == "" ]]; then
printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf "→ ${ANSI_LIGHT_YELLOW}Skipped readout of pull request description. This is not a merge commit.${ANSI_NOCOLOR}\n"
else
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
printf "Search Pull Request to '%s' and branch containing SHA %s ... " "${{ inputs.release_branch }}" "${FATHER_SHA}"
PULL_REQUESTS=$(gh pr list --base "${{ inputs.release_branch }}" --search "${FATHER_SHA}" --state "merged" --json "title,number,mergedBy,mergedAt,body")
if [[ $? -ne 0 || "${PULL_REQUESTS}" == "" ]]; then
printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf "${ANSI_LIGHT_RED}Couldn't find a merged Pull Request to '%s'. -> %s${ANSI_NOCOLOR}\n" "${{ inputs.release_branch }}" "${PULL_REQUESTS}"
printf "::error title=PullRequest::Couldn't find a merged Pull Request to '%s'. -> %s\n" "${{ inputs.release_branch }}" "${PULL_REQUESTS}"
exit 1
else
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
PR_TITLE="$( printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].title")"
PR_NUMBER="$( printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].number")"
PR_BODY="$( printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].body")"
PR_MERGED_BY="$(printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].mergedBy.login")"
PR_MERGED_AT="$(printf "%s\n" "${PULL_REQUESTS}" | jq --raw-output ".[0].mergedAt")"
printf "Found Pull Request:\n"
printf " %s\n" "Title: ${PR_TITLE}"
printf " %s\n" "Number: ${PR_NUMBER}"
printf " %s\n" "MergedBy: ${PR_MERGED_BY}"
printf " %s\n" "MergedAt: ${PR_MERGED_AT} ($(date -d"${PR_MERGED_AT}" '+%d.%m.%Y - %H:%M:%S'))"
fi
printf "%s\n" "${PR_BODY}" > __PULLREQUEST__.md
fi
# Check if a release description file should be used and exists.
if [[ "${{ inputs.description_file }}" != "" ]]; then
if [[ ! -f "${{ inputs.description_file }}" ]]; then
printf "${ANSI_LIGHT_RED}Release description file '%s' not found.${ANSI_NOCOLOR}\n" "${{ inputs.description_file }}"
printf "::error title=%s::%s\n" "FileNotFound" "Release description file '${{ inputs.description_file }}' not found."
exit 1
elif [[ -s "${{ inputs.description_file }}" ]]; then
printf "Use '%s' as main release description.\n" "${{ inputs.description_file }}"
cp -v "${{ inputs.description_file }}" __NOTES__.md
else
printf "${ANSI_LIGHT_RED}Release description file '%s' is empty.${ANSI_NOCOLOR}\n" "${{ inputs.description_file }}"
printf "::error title=%s::%s\n" "FileNotFound" "Release description file '${{ inputs.description_file }}' is empty."
exit 1
fi
# Check if the main release description is provided by a template parameter
elif [[ -s __DESCRIPTION__.md ]]; then
printf "Use '__DESCRIPTION__.md' as main release description.\n"
mv -v __DESCRIPTION__.md __NOTES__.md
# Check if the pull request serves as the main release description text.
elif [[ -s __PULLREQUEST__.md ]]; then
printf "Use '__PULLREQUEST__.md' as main release description.\n"
mv -v __PULLREQUEST__.md __NOTES__.md
printf "Append '%%%%FOOTER%%%%' to '__NOTES__.md'.\n"
printf "\n%%%%FOOTER%%%%\n" >> __NOTES__.md
else
printf "${ANSI_LIGHT_RED}No release description specified (file, parameter, PR text).${ANSI_NOCOLOR}\n"
printf "::error title=%s::%s\n" "MissingDescription" "No release description specified (file, parameter, PR text)."
exit 1
fi
# Read release notes main file for placeholder substitution
NOTES=$(<__NOTES__.md)
# Inline description
if [[ -s __DESCRIPTION__.md ]]; then
NOTES="${NOTES//%%DESCRIPTION%%/$(<__DESCRIPTION__.md)}"
else
NOTES="${NOTES//%%DESCRIPTION%%/}"
fi
# Inline PullRequest and increase headline levels
if [[ -s __PULLREQUEST__.md ]]; then
while [[ "${NOTES}" =~ %%(PULLREQUEST(\+[0-3])?)%% ]]; do
case "${BASH_REMATCH[1]}" in
"PULLREQUEST+0" | "PULLREQUEST")
NOTES="${NOTES//${BASH_REMATCH[0]}/$(<__PULLREQUEST__.md)}"
;;
"PULLREQUEST+1")
NOTES="${NOTES//${BASH_REMATCH[0]}/$(cat __PULLREQUEST__.md | sed -E 's/^(#+) /\1# /gm;t')}"
;;
"PULLREQUEST+2")
NOTES="${NOTES//${BASH_REMATCH[0]}/$(cat __PULLREQUEST__.md | sed -E 's/^(#+) /\1### /gm;t')}"
;;
"PULLREQUEST+3")
NOTES="${NOTES//${BASH_REMATCH[0]}/$(cat __PULLREQUEST__.md | sed -E 's/^(#+) /\1### /gm;t')}"
;;
esac
done
else
while [[ "${NOTES}" =~ %%(PULLREQUEST(\+[0-3])?)%% ]]; do
NOTES="${NOTES//${BASH_REMATCH[0]}/}"
done
fi
# Inline Files table
if [[ -s __ASSETS__.md ]]; then
NOTES="${NOTES//%%ASSETS%%/$(<__ASSETS__.md)}"
else
NOTES="${NOTES//%%ASSETS%%/}"
fi
# Inline Footer
if [[ -s __FOOTER__.md ]]; then
NOTES="${NOTES//%%FOOTER%%/$(<__FOOTER__.md)}"
else
NOTES="${NOTES//%%FOOTER%%/}"
fi
# Apply replacements
while IFS=$'\r\n' read -r patternLine; do
# skip empty lines
[[ "$patternLine" == "" ]] && continue
pattern="%${patternLine%%=*}%"
replacement="${patternLine#*=}"
NOTES="${NOTES//$pattern/$replacement}"
done <<<'${{ inputs.replacements }}'
# Workarounds for stupid GitHub variables
owner_repo="${{ github.repository }}"
repo=${owner_repo##*/}
# Replace special identifiers
NOTES="${NOTES//%%gh_server%%/${{ github.server_url }}}"
NOTES="${NOTES//%%gh_workflow_name%%/${{ github.workflow }}}"
NOTES="${NOTES//%%gh_owner%%/${{ github.repository_owner }}}"
NOTES="${NOTES//%%gh_repo%%/${repo}}"
NOTES="${NOTES//%%gh_owner_repo%%/${{ github.repository }}}"
#NOTES="${NOTES//%%gh_pages%%/https://${{ github.repository_owner }}.github.io/${repo}/}"
NOTES="${NOTES//%%gh_runid%%/${{ github.run_id }}}"
NOTES="${NOTES//%%gh_actor%%/${{ github.actor }}}"
NOTES="${NOTES//%%gh_sha%%/${{ github.sha }}}"
NOTES="${NOTES//%%date%%/$(date '+%Y-%m-%d')}"
NOTES="${NOTES//%%time%%/$(date '+%H:%M:%S %Z')}"
NOTES="${NOTES//%%datetime%%/$(date '+%Y-%m-%d %H:%M:%S %Z')}"
# Write final release notes to file
printf "%s\n" "${NOTES}" > __NOTES__.md
# Display partial contents for debugging
if [[ -s __DESCRIPTION__.md ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__DESCRIPTION__.md' ($(stat --printf="%s" "__DESCRIPTION__.md") B) ...."
cat __DESCRIPTION__.md
printf "::endgroup::\n"
else
printf "${ANSI_LIGHT_YELLOW}No '__DESCRIPTION__.md' found.${ANSI_NOCOLOR}\n"
fi
if [[ -s __PULLREQUEST__.md ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__PULLREQUEST__.md' ($(stat --printf="%s" "__PULLREQUEST__.md") B) ...."
cat __PULLREQUEST__.md
printf "::endgroup::\n"
else
printf "${ANSI_LIGHT_YELLOW}No '__PULLREQUEST__.md' found.${ANSI_NOCOLOR}\n"
fi
if [[ -s __ASSETS__.md ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__ASSETS__.md' ($(stat --printf="%s" "__ASSETS__.md") B) ...."
cat __ASSETS__.md
printf "::endgroup::\n"
else
printf "${ANSI_LIGHT_YELLOW}No '__ASSETS__.md' found.${ANSI_NOCOLOR}\n"
fi
if [[ -s __FOOTER__.md ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__FOOTER__.md' ($(stat --printf="%s" "__FOOTER__.md") B) ...."
cat __FOOTER__.md
printf "::endgroup::\n"
else
printf "${ANSI_LIGHT_YELLOW}No '__FOOTER__.md' found.${ANSI_NOCOLOR}\n"
fi
# Print final release notes
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '__NOTES__.md' ($(stat --printf="%s" "__NOTES__.md") B) ...."
cat __NOTES__.md
printf "::endgroup::\n"
- name: 📑 Update release notes
id: updateReleaseNotes
run: |
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_LIGHT_YELLOW=$'\x1b[93m'
ANSI_LIGHT_BLUE=$'\x1b[94m'
ANSI_NOCOLOR=$'\x1b[0m'
export GH_TOKEN=${{ github.token }}
if [[ -s __ASSETS__.md ]]; then
addNotes=("--notes-file" "__ASSETS__.md")
else
printf " ${ANSI_LIGHT_RED}File '%s' not found.${ANSI_NOCOLOR}\n" "__ASSETS__.md"
printf "::error title=%s::%s\n" "InternalError" "File '__ASSETS__.md' not found."
exit 1
fi
printf "Updating release '%s' ... " "${{ inputs.tag }}"
message="$(gh release edit "${addNotes[@]}" "${{ inputs.tag }}" 2>&1)"
if [[ $? -eq 0 ]]; then
printf "${ANSI_LIGHT_GREEN}[OK]${ANSI_NOCOLOR}\n"
printf " Release page: %s\n" "${message}"
else
printf "${ANSI_LIGHT_RED}[FAILED]${ANSI_NOCOLOR}\n"
printf " ${ANSI_LIGHT_RED}Couldn't update release '%s' -> Error: '%s'.${ANSI_NOCOLOR}\n" "${{ inputs.tag }}" "${message}"
printf "::error title=%s::%s\n" "InternalError" "Couldn't update release '${{ inputs.tag }}' -> Error: '${message}'."
exit 1
fi
- name: 📑 Remove draft state from Release Page
id: removeDraft
if: ${{ ! inputs.draft }}

View File

@@ -44,6 +44,16 @@ on:
required: false
default: ''
type: string
merge-input-dialect:
description: 'JUnit dialect used to load and parse inputs for merging.'
required: false
default: 'pyTest-JUnit'
type: string
merge-output-dialect:
description: 'JUnit dialect used for writing the merged report.'
required: false
default: 'pyTest-JUnit'
type: string
additional_merge_args:
description: 'Additional merging arguments.'
required: false
@@ -92,10 +102,10 @@ jobs:
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: 📥 Download Artifacts
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
with:
pattern: ${{ inputs.unittest_artifacts_pattern }}
path: artifacts
@@ -116,7 +126,10 @@ jobs:
- name: 🔁 Merge JUnit Unit Test Summaries
run: |
pyedaa-reports -v unittest "--name=${{ inputs.testsuite-summary-name }}" "--merge=pyTest-JUnit:junit/*.xml" ${{ inputs.additional_merge_args }} "--output=pyTest-JUnit:${{ inputs.merged_junit_filename }}"
if [[ -n "${{ inputs.testsuite-summary-name }}" ]]; then
name="\"--name=${{ inputs.testsuite-summary-name }}\""
fi
pyedaa-reports -v unittest $name "--merge=${{ inputs.merge-input-dialect }}:junit/*.xml" ${{ inputs.additional_merge_args }} "--output=${{ inputs.merge-output-dialect }}:${{ inputs.merged_junit_filename }}"
printf "%s\n" "cat ${{ inputs.merged_junit_filename }}"
cat ${{ inputs.merged_junit_filename }}
@@ -131,19 +144,20 @@ jobs:
reporter: java-junit
- name: 📊 Publish unittest results at CodeCov
uses: codecov/test-results-action@v1
uses: codecov/codecov-action@v5
id: codecov
if: inputs.codecov == 'true'
continue-on-error: true
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: "test_results"
disable_search: true
files: ${{ inputs.merged_junit_filename }}
flags: ${{ inputs.codecov_flags }}
fail_ci_if_error: true
- name: 📤 Upload merged 'JUnit Test Summary' artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
if: inputs.merged_junit_artifact != ''
with:
name: ${{ inputs.merged_junit_artifact }}

View File

@@ -45,45 +45,51 @@ on:
default: ''
type: string
outputs:
github_pages_url:
description: "URL to GitHub Pages."
value: ${{ jobs.PrepareGitHubPages.outputs.github_pages_url }}
jobs:
PublishToGitHubPages:
name: 📚 Publish to GH-Pages
PrepareGitHubPages:
name: 📖 Merge multiple contents for publishing
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
permissions:
pages: write # to deploy to Pages
id-token: write # to verify the deployment originates from an appropriate source
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
outputs:
github_pages_url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
- name: 📥 Download artifacts '${{ inputs.doc }}' from 'SphinxDocumentation' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
with:
name: ${{ inputs.doc }}
path: public
- name: 📥 Download artifacts '${{ inputs.coverage }}' from 'Coverage' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
if: ${{ inputs.coverage != '' }}
with:
name: ${{ inputs.coverage }}
path: public/coverage
- name: 📥 Download artifacts '${{ inputs.typing }}' from 'StaticTypeCheck' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
if: ${{ inputs.typing != '' }}
with:
name: ${{ inputs.typing }}
path: public/typing
- name: '📓 Publish site to GitHub Pages'
- name: 📑 Upload static files as artifact
if: github.event_name != 'pull_request'
run: |
cd public
touch .nojekyll
git init
cp ../.git/config ./.git/config
git add .
git config --local user.email "BuildTheDocs@GitHubActions"
git config --local user.name "GitHub Actions"
git commit -a -m "update ${{ github.sha }}"
git push -u origin +HEAD:gh-pages
uses: actions/upload-pages-artifact@v4
with:
path: public/
- name: 📖 Deploy to GitHub Pages
id: deployment
if: github.event_name != 'pull_request'
uses: actions/deploy-pages@v4

View File

@@ -32,7 +32,7 @@ on:
python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
@@ -44,24 +44,29 @@ on:
required: false
default: 'doc'
type: string
coverage_report_json_directory:
description: ''
required: false
type: string
coverage_json_artifact:
description: 'Name of the coverage JSON artifact.'
required: false
default: ''
type: string
coverage_report_json:
description: 'Directory where coverage JSON artifact will be extracted.'
required: false
default: >-
{ "directory": "report/coverage"
}
type: string
unittest_xml_artifact:
description: 'Name of the unittest XML artifact.'
required: false
default: ''
type: string
unittest_xml_directory:
description: 'Directory where unittest XML artifact is extracted.'
unittest_xml:
description: 'Directory where unittest XML artifact will be extracted.'
required: false
default: 'report/unit'
default: >-
{ "directory": "report/unit"
}
type: string
html_artifact:
description: 'Name of the HTML documentation artifact.'
@@ -81,7 +86,7 @@ jobs:
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
lfs: true
submodules: true
@@ -90,7 +95,7 @@ jobs:
run: sudo apt-get install -y --no-install-recommends graphviz
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
@@ -100,19 +105,19 @@ jobs:
python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
- name: 📥 Download artifacts '${{ inputs.unittest_xml_artifact }}' from 'Unittesting' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
if: inputs.unittest_xml_artifact != ''
with:
name: ${{ inputs.unittest_xml_artifact }}
path: ${{ inputs.unittest_xml_directory }}
path: ${{ fromJson(inputs.unittest_xml).directory }}
investigate: true
- name: 📥 Download artifacts '${{ inputs.coverage_json_artifact }}' from 'PublishCoverageResults' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
if: inputs.coverage_json_artifact != ''
with:
name: ${{ inputs.coverage_json_artifact }}
path: ${{ inputs.coverage_report_json_directory }}
path: ${{ fromJson(inputs.coverage_report_json).directory }}
investigate: true
- name: ☑ Generate HTML documentation
@@ -124,7 +129,7 @@ jobs:
sphinx-build -v -n -b html -d _build/doctrees -j $(nproc) -w _build/html.log . _build/html
- name: 📤 Upload 'HTML Documentation' artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
if: inputs.html_artifact != ''
continue-on-error: true
with:
@@ -140,7 +145,7 @@ jobs:
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
lfs: true
submodules: true
@@ -149,7 +154,7 @@ jobs:
run: sudo apt-get install -y --no-install-recommends graphviz
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
@@ -159,19 +164,19 @@ jobs:
python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
- name: 📥 Download artifacts '${{ inputs.unittest_xml_artifact }}' from 'Unittesting' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
if: inputs.unittest_xml_artifact != ''
with:
name: ${{ inputs.unittest_xml_artifact }}
path: ${{ inputs.unittest_xml_directory }}
path: ${{ fromJson(inputs.unittest_xml).directory }}
investigate: true
- name: 📥 Download artifacts '${{ inputs.coverage_json_artifact }}' from 'PublishCoverageResults' job
uses: pyTooling/download-artifact@v5
uses: pyTooling/download-artifact@v7
if: inputs.coverage_json_artifact != ''
with:
name: ${{ inputs.coverage_json_artifact }}
path: ${{ inputs.coverage_report_json_directory }}
path: ${{ fromJson(inputs.coverage_report_json).directory }}
investigate: true
- name: ☑ Generate LaTeX documentation
@@ -267,7 +272,7 @@ jobs:
done
- name: 📤 Upload 'LaTeX Documentation' artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
if: inputs.latex_artifact != ''
continue-on-error: true
with:

View File

@@ -33,49 +33,71 @@ on:
python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: '-r tests/requirements.txt'
default: '-r tests/typing/requirements.txt'
type: string
commands:
description: 'Commands to run the static type checks.'
required: true
mypy_options:
description: 'Additional mypy options.'
required: false
default: ''
type: string
cobertura_report:
description: 'Cobertura file to upload as an artifact.'
required: false
default: >-
{ "fullpath": "report/typing/cobertura.xml",
"directory": "report/typing",
"filename": "cobertura.xml"
}
type: string
junit_report:
description: 'JUnit file to upload as an artifact.'
required: false
default: >-
{ "fullpath": "report/typing/StaticTypingSummary.xml",
"directory": "report/typing",
"filename": "StaticTypingSummary.xml"
}
type: string
html_report:
description: 'Directory to upload as an artifact.'
required: false
default: 'htmlmypy'
default: >-
{ "directory": "report/typing/html"
}
# "fullpath": "report/typing/html",
type: string
junit_report:
description: 'junit file to upload as an artifact.'
cobertura_artifact:
description: 'Name of the typing cobertura artifact (Cobertura XML).'
required: false
default: 'StaticTypingSummary.xml'
default: ''
type: string
junit_artifact:
description: 'Name of the typing junit artifact (JUnit XML).'
required: false
default: ''
type: string
html_artifact:
description: 'Name of the typing artifact (HTML report).'
required: true
type: string
junit_artifact:
description: 'Name of the typing junit artifact (junit XML).'
required: false
default: ''
type: string
jobs:
StaticTypeCheck:
name: 👀 Check Static Typing using Python ${{ inputs.python_version }}
runs-on: "ubuntu-${{ inputs.ubuntu_image_version }}"
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
@@ -84,25 +106,70 @@ jobs:
- name: Check Static Typing
continue-on-error: true
run: ${{ inputs.commands }}
run: mypy ${{ inputs.mypy_options }}
- name: 📤 Upload 'Static Typing Report' HTML artifact
uses: pyTooling/upload-artifact@v4
- name: Debug output directories
continue-on-error: true
run: |
# List directory contents
set +e
ANSI_LIGHT_RED=$'\x1b[91m'
ANSI_LIGHT_GREEN=$'\x1b[92m'
ANSI_LIGHT_YELLOW=$'\x1b[93m'
ANSI_LIGHT_BLUE=$'\x1b[94m'
ANSI_NOCOLOR=$'\x1b[0m'
if [[ "${{ fromJson(inputs.html_report).directory }}" != "" ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '${{ fromJson(inputs.html_report).directory }}' ..."
tree ${{ fromJson(inputs.html_report).directory }}
printf "::endgroup::\n"
fi
if [[ "${{ fromJson(inputs.junit_report).directory }}" != "" ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '${{ fromJson(inputs.junit_report).directory }}' ..."
tree ${{ fromJson(inputs.junit_report).directory }}
printf "::endgroup::\n"
if [[ "${{ fromJson(inputs.cobertura_report).directory }}" != "" && "${{ fromJson(inputs.junit_report).directory }}" != "${{ fromJson(inputs.cobertura_report).directory }}" ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '${{ fromJson(inputs.cobertura_report).directory }}' ..."
tree ${{ fromJson(inputs.cobertura_report).directory }}
printf "::endgroup::\n"
fi
elif [[ "${{ fromJson(inputs.cobertura_report).directory }}" != "" ]]; then
printf "::group::${ANSI_LIGHT_BLUE}%s${ANSI_NOCOLOR}\n" "Content of '${{ fromJson(inputs.cobertura_report).directory }}' ..."
tree ${{ fromJson(inputs.cobertura_report).directory }}
printf "::endgroup::\n"
fi
- name: 📤 Upload '${{ inputs.html_artifact }}' HTML artifact
uses: pyTooling/upload-artifact@v6
if: ${{ inputs.html_artifact != '' }}
continue-on-error: true
with:
name: ${{ inputs.html_artifact }}
working-directory: ${{ inputs.html_report }}
working-directory: ${{ fromJson(inputs.html_report).directory }}
path: '*'
if-no-files-found: error
retention-days: 1
- name: 📤 Upload 'Static Typing Report' JUnit artifact
uses: pyTooling/upload-artifact@v4
- name: 📤 Upload '${{ inputs.junit_artifact }}' JUnit artifact
uses: pyTooling/upload-artifact@v6
if: ${{ inputs.junit_artifact != '' }}
continue-on-error: true
with:
name: ${{ inputs.junit_artifact }}
path: ${{ inputs.junit_report }}
working-directory: ${{ fromJson(inputs.junit_report).directory }}
path: ${{ fromJson(inputs.junit_report).filename }}
if-no-files-found: error
retention-days: 1
- name: 📤 Upload '${{ inputs.cobertura_artifact }}' Cobertura artifact
uses: pyTooling/upload-artifact@v6
if: ${{ inputs.cobertura_artifact != '' }}
continue-on-error: true
with:
name: ${{ inputs.cobertura_artifact }}
working-directory: ${{ fromJson(inputs.cobertura_report).directory }}
path: ${{ fromJson(inputs.cobertura_report).filename }}
if-no-files-found: error
retention-days: 1

View File

@@ -54,11 +54,11 @@ jobs:
permissions:
contents: write # required for tag creation
actions: write # required to start a new pipeline
actions: write # required to start a new pipeline
steps:
- name: 🏷 Create release tag '${{ steps.FindPullRequest.outputs.version }}'
uses: actions/github-script@v7
uses: actions/github-script@v8
id: createReleaseTag
# if: inputs.auto_tag == 'true'
with:
@@ -71,7 +71,7 @@ jobs:
})
- name: Trigger Workflow
uses: actions/github-script@v7
uses: actions/github-script@v8
id: runReleaseTag
# if: inputs.auto_tag == 'true'
with:

View File

@@ -1,176 +0,0 @@
# ==================================================================================================================== #
# Authors: #
# Unai Martinez-Corral #
# #
# ==================================================================================================================== #
# Copyright 2020-2025 The pyTooling Authors #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# #
# SPDX-License-Identifier: Apache-2.0 #
# ==================================================================================================================== #
name: Test Releaser
on:
push:
tags:
- '*'
- '!tip'
- '!v*'
branches:
- '**'
- '!r*'
workflow_dispatch:
schedule:
- cron: '0 0 * * 4'
env:
CI: true
jobs:
Image:
runs-on: ubuntu-24.04
env:
DOCKER_BUILDKIT: 1
steps:
- uses: actions/checkout@v5
- name: Build container image
run: docker build -t ghcr.io/pytooling/releaser -f releaser/Dockerfile releaser
- name: Push container image
uses: ./with-post-step
with:
main: |
echo '${{ github.token }}' | docker login ghcr.io -u GitHub-Actions --password-stdin
docker push ghcr.io/pytooling/releaser
post: docker logout ghcr.io
Composite:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- run: printf "%s\n" "Build some tool and generate some (versioned) artifacts" > artifact-$(date -u +"%Y-%m-%dT%H-%M-%SZ").txt
- name: Single
uses: ./releaser/composite
with:
rm: true
token: ${{ secrets.GITHUB_TOKEN }}
files: artifact-*.txt
- name: List
uses: ./releaser/composite
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: |
artifact-*.txt
README.md
- name: Add artifacts/*.txt
run: |
mkdir artifacts
printf "%s\n" "Build some tool and generate some artifacts" > artifacts/artifact.txt
touch artifacts/empty_file.txt
- name: Single in subdir
uses: ./releaser/composite
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: artifacts/artifact.txt
- name: Add artifacts/*.md
run: |
printf "%s\n" "releaser hello" > artifacts/hello.md
printf "%s\n" "releaser world" > artifacts/world.md
- name: Directory wildcard
uses: ./releaser/composite
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: artifacts/*
- name: Add artifacts/subdir
run: |
mkdir artifacts/subdir
printf "%s\n" "Test recursive glob" > artifacts/subdir/deep_file.txt
- name: Directory wildcard (recursive)
uses: ./releaser/composite
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: artifacts/**
Test:
needs:
- Image
- Composite
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- run: printf "%s\n" "Build some tool and generate some (versioned) artifacts" > artifact-$(date -u +"%Y-%m-%dT%H-%M-%SZ").txt
- name: Single
uses: ./releaser
with:
rm: true
token: ${{ secrets.GITHUB_TOKEN }}
files: artifact-*.txt
- name: List
uses: ./releaser
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: |
artifact-*.txt
README.md
- name: Add artifacts/*.txt
run: |
mkdir artifacts
printf "%s\n" "Build some tool and generate some artifacts" > artifacts/artifact.txt
touch artifacts/empty_file.txt
- name: Single in subdir
uses: ./releaser
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: artifacts/artifact.txt
- name: Add artifacts/*.md
run: |
printf "%s\n" "releaser hello" > artifacts/hello.md
printf "%s\n" "releaser world" > artifacts/world.md
- name: Directory wildcard
uses: ./releaser
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: artifacts/*
- name: Add artifacts/subdir
run: |
mkdir artifacts/subdir
printf "%s\n" "Test recursive glob" > artifacts/subdir/deep_file.txt
- name: Directory wildcard (recursive)
uses: ./releaser
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: artifacts/**

View File

@@ -47,7 +47,7 @@ on:
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: '-r tests/requirements.txt'
default: '-r ./requirements.txt'
type: string
mingw_requirements:
description: 'Override Python dependencies to be installed through pip on MSYS2 (MINGW64) only.'
@@ -82,37 +82,57 @@ on:
root_directory:
description: 'Working directory for running tests.'
required: false
default: ''
default: '.'
type: string
tests_directory:
description: 'Path to the directory containing tests (relative to root_directory).'
description: 'Path to the directory containing tests (relative from root_directory).'
required: false
default: 'tests'
type: string
unittest_directory:
description: 'Path to the directory containing unit tests (relative to tests_directory).'
description: 'Path to the directory containing unit tests (relative from tests_directory).'
required: false
default: 'unit'
type: string
unittest_report_xml_directory:
description: 'Path where to save the unittest summary report XML.'
unittest_report_xml:
description: 'JSON object describing the path where to save the unittest summary report XML.'
required: false
default: 'report/unit'
type: string
unittest_report_xml_filename:
description: 'Filename of the unittest summary report XML.'
required: false
default: 'TestReportSummary.xml'
default: >-
{ "directory": "report/unit",
"filename": "TestReportSummary.xml",
"fullpath": "report/unit/TestReportSummary.xml"
}
type: string
coverage_config:
description: 'Path to the .coveragerc file. Use pyproject.toml by default.'
required: false
default: 'pyproject.toml'
type: string
coverage_report_html_directory:
description: ''
coverage_report_xml:
description: 'JSON object describing the path where the coverage report in XML format will be generated.'
required: false
default: 'report/coverage/html'
default: >-
{ "directory": "report/coverage",
"filename": "coverage.xml",
"fullpath": "report/coverage/coverage.xml"
}
type: string
coverage_report_json:
description: 'JSON object describing the path where the coverage report in JSON format will be generated.'
required: false
default: >-
{ "directory": "report/coverage",
"filename": "coverage.json",
"fullpath": "report/coverage/coverage.json"
}
type: string
coverage_report_html:
description: 'JSON object describing the path where the coverage report in HTML format will be generated.'
required: false
default: >-
{ "directory": "report/coverage/html",
}
# "fullpath": "report/coverage/html"
type: string
unittest_xml_artifact:
description: "Generate unit test report with junitxml and upload results as an artifact."
@@ -161,7 +181,7 @@ jobs:
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
lfs: true
submodules: true
@@ -172,7 +192,7 @@ jobs:
run: brew install ${{ inputs.brew }}
- name: 🔧 Install apt dependencies on Ubuntu
if: matrix.system == 'ubuntu' && inputs.apt != ''
if: ( matrix.system == 'ubuntu' || matrix.system == 'ubuntu-arm' ) && inputs.apt != ''
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ${{ inputs.apt }}
@@ -183,7 +203,39 @@ jobs:
if: matrix.system == 'msys2'
shell: pwsh
run: |
py -3.9 -m pip install --disable-pip-version-check -U tomli
py -3.9 -m pip install --disable-pip-version-check --break-system-packages -U tomli
- name: Compute path to requirements file
id: requirements
shell: python
run: |
from os import getenv
from pathlib import Path
from sys import version
print(f"Python: {version}")
requirements = "${{ inputs.requirements }}"
if requirements.startswith("-r"):
requirements = requirements[2:].lstrip()
if requirements.startswith("./"):
requirementsFile = Path("${{ inputs.root_directory || '.' }}") / Path("${{ inputs.tests_directory || '.' }}") / Path("${{ inputs.unittest_directory || '.' }}") / Path(requirements[2:])
else:
requirementsFile = Path(requirements)
if not requirementsFile.exists():
print(f"::error title=FileNotFoundError::{ex}")
exit(1)
print(f"requirements file: {requirementsFile.as_posix()}")
# Write requirements path to special file
github_output = Path(getenv("GITHUB_OUTPUT"))
print(f"GITHUB_OUTPUT: {github_output}")
with github_output.open("a+") as f:
f.write(f"requirements=-r {requirementsFile.as_posix()}\n")
else:
print(f"requirements list: {requirements}")
- name: Compute pacman/pacboy packages
id: pacboy
@@ -195,8 +247,6 @@ jobs:
from re import compile
from sys import version
print(f"Python: {version}")
def loadRequirementsFile(requirementsFile: Path):
requirements = []
with requirementsFile.open("r") as file:
@@ -212,11 +262,10 @@ jobs:
return requirements
requirements = "${{ inputs.requirements }}"
requirements = "${{ steps.requirements.outputs.requirements }}"
if requirements.startswith("-r"):
requirementsFile = Path(requirements[2:].lstrip())
try:
dependencies = loadRequirementsFile(requirementsFile)
dependencies = loadRequirementsFile(Path(requirements[2:].lstrip()))
except FileNotFoundError as ex:
print(f"::error title=FileNotFoundError::{ex}")
exit(1)
@@ -293,7 +342,7 @@ jobs:
${{ inputs.pacboy }}
- name: 🐍 Setup Python ${{ matrix.python }}
uses: actions/setup-python@v5
uses: actions/setup-python@v6
if: matrix.system != 'msys2'
with:
python-version: ${{ matrix.python }}
@@ -304,15 +353,15 @@ jobs:
if: matrix.system != 'msys2'
run: |
python -m pip install --disable-pip-version-check -U wheel tomli
python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
python -m pip install --disable-pip-version-check ${{ steps.requirements.outputs.requirements }}
- name: 🔧 Install pip dependencies (MSYS2)
if: matrix.system == 'msys2'
run: |
if [ -n '${{ inputs.mingw_requirements }}' ]; then
python -m pip install --disable-pip-version-check ${{ inputs.mingw_requirements }}
python -m pip install --disable-pip-version-check --break-system-packages ${{ inputs.mingw_requirements }}
else
python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
python -m pip install --disable-pip-version-check --break-system-packages ${{ steps.requirements.outputs.requirements }}
fi
# Before scripts
@@ -326,10 +375,10 @@ jobs:
run: ${{ inputs.macos_arm_before_script }}
- name: 🐧 Ubuntu before scripts
if: matrix.system == 'ubuntu' && inputs.ubuntu_before_script != ''
if: ( matrix.system == 'ubuntu' || matrix.system == 'ubuntu-arm' ) && inputs.ubuntu_before_script != ''
run: ${{ inputs.ubuntu_before_script }}
# Windows before script
# TODO: Windows before script
- name: 🪟🟦 MinGW64 before scripts
if: matrix.system == 'msys2' && matrix.runtime == 'MINGW64' && inputs.mingw64_before_script != ''
@@ -341,16 +390,17 @@ jobs:
# Run pytests
# TODO: allow configuration of pytest_args
- name: ✅ Run unit tests (Ubuntu/macOS)
id: pytest_bash
if: matrix.system != 'windows'
if: ( matrix.system != 'windows' && matrix.system != 'windows-arm' )
continue-on-error: true
run: |
export ENVIRONMENT_NAME="${{ matrix.envname }}"
export PYTHONPATH=$(pwd)
cd "${{ inputs.root_directory || '.' }}"
[ -n '${{ inputs.unittest_xml_artifact }}' ] && PYTEST_ARGS='--junitxml=${{ inputs.unittest_report_xml_directory }}/${{ inputs.unittest_report_xml_filename }}' || unset PYTEST_ARGS
[ -n '${{ inputs.unittest_xml_artifact }}' ] && PYTEST_ARGS='--junitxml=${{ fromJson(inputs.unittest_report_xml).fullpath }}' || unset PYTEST_ARGS
if [ -n '${{ inputs.coverage_config }}' ]; then
printf "%s\n" "coverage run --data-file=.coverage --rcfile=pyproject.toml -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.unittest_directory }}"
coverage run --data-file=.coverage --rcfile=pyproject.toml -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.unittest_directory }}
@@ -361,14 +411,14 @@ jobs:
- name: ✅ Run unit tests (Windows)
id: pytest_posh
if: matrix.system == 'windows'
if: ( matrix.system == 'windows' || matrix.system == 'windows-arm' )
continue-on-error: true
run: |
$env:ENVIRONMENT_NAME = "${{ matrix.envname }}"
$env:PYTHONPATH = (Get-Location).ToString()
cd "${{ inputs.root_directory || '.' }}"
$PYTEST_ARGS = if ("${{ inputs.unittest_xml_artifact }}") { "--junitxml=${{ inputs.unittest_report_xml_directory }}/${{ inputs.unittest_report_xml_filename }}" } else { "" }
$PYTEST_ARGS = if ("${{ inputs.unittest_xml_artifact }}") { "--junitxml=${{ fromJson(inputs.unittest_report_xml).fullpath }}" } else { "" }
if ("${{ inputs.coverage_config }}") {
Write-Host "coverage run --data-file=.coverage --rcfile=pyproject.toml -m pytest -raP --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.unittest_directory }}"
coverage run --data-file=.coverage --rcfile=pyproject.toml -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.unittest_directory }}
@@ -394,36 +444,36 @@ jobs:
if: inputs.coverage_html_artifact != ''
continue-on-error: true
run: |
coverage html --data-file=.coverage -d ${{ inputs.coverage_report_html_directory }}
rm ${{ inputs.coverage_report_html_directory }}/.gitignore
coverage html --data-file=.coverage -d ${{ fromJson(inputs.coverage_report_html).directory }}
rm ${{ fromJson(inputs.coverage_report_html).directory }}/.gitignore
# Upload artifacts
- name: 📤 Upload '${{ inputs.unittest_report_xml_filename }}' artifact
uses: pyTooling/upload-artifact@v4
- name: 📤 Upload '${{ fromJson(inputs.unittest_report_xml).filename }}' artifact
uses: pyTooling/upload-artifact@v6
if: inputs.unittest_xml_artifact != ''
continue-on-error: true
with:
name: ${{ inputs.unittest_xml_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
working-directory: ${{ inputs.unittest_report_xml_directory }}
path: ${{ inputs.unittest_report_xml_filename }}
working-directory: ${{ fromJson(inputs.unittest_report_xml).directory }}
path: ${{ fromJson(inputs.unittest_report_xml).filename }}
if-no-files-found: error
retention-days: 1
# - name: 📤 Upload 'Unit Tests HTML Report' artifact
# if: inputs.unittest_html_artifact != ''
# continue-on-error: true
# uses: pyTooling/upload-artifact@v4
# uses: pyTooling/upload-artifact@v6
# with:
# name: ${{ inputs.unittest_html_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
# path: ${{ steps.getVariables.outputs.unittest_report_html_directory }}
# path: ${{ inputs.unittest_report_html_directory }}
# if-no-files-found: error
# retention-days: 1
- name: 📤 Upload 'Coverage SQLite Database' artifact
if: inputs.coverage_sqlite_artifact != ''
continue-on-error: true
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: ${{ inputs.coverage_sqlite_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
path: .coverage
@@ -434,30 +484,32 @@ jobs:
- name: 📤 Upload 'Coverage XML Report' artifact
if: inputs.coverage_xml_artifact != '' && steps.convert_xml.outcome == 'success'
continue-on-error: true
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: ${{ inputs.coverage_xml_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
path: ${{ steps.getVariables.outputs.coverage_report_xml }}
working-directory: ${{ fromJson(inputs.coverage_report_xml).directory }}
path: ${{ fromJson(inputs.coverage_report_xml).filename }}
if-no-files-found: error
retention-days: 1
- name: 📤 Upload 'Coverage JSON Report' artifact
if: inputs.coverage_json_artifact != '' && steps.convert_json.outcome == 'success'
continue-on-error: true
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: ${{ inputs.coverage_json_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
path: ${{ steps.getVariables.outputs.coverage_report_json }}
working-directory: ${{ fromJson(inputs.coverage_report_json).directory }}
path: ${{ fromJson(inputs.coverage_report_json).filename }}
if-no-files-found: error
retention-days: 1
- name: 📤 Upload 'Coverage HTML Report' artifact
if: inputs.coverage_html_artifact != '' && steps.convert_html.outcome == 'success'
continue-on-error: true
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: ${{ inputs.coverage_html_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
working-directory: ${{ steps.getVariables.outputs.coverage_report_html_directory }}
working-directory: ${{ fromJson(inputs.coverage_report_html).directory }}
path: '*'
if-no-files-found: error
retention-days: 1

View File

@@ -33,7 +33,7 @@ on:
python_version:
description: 'Python version.'
required: false
default: '3.13'
default: '3.14'
type: string
jobs:
@@ -44,10 +44,10 @@ jobs:
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: 🐍 Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}

View File

@@ -9,7 +9,7 @@ jobs:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
name: Example
python_version_list: "3.12 3.13"
python_version_list: "3.13 3.14" # py-1, py-0
system_list: "ubuntu windows"
Testing:
@@ -25,7 +25,7 @@ jobs:
run: printf "%s\n" "${{ matrix.runs-on }}-${{ matrix.python }}" >> artifact.txt
- name: 📤 Upload artifact for ${{ matrix.system }}-${{ matrix.python }}
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: ${{ fromJson(needs.Params.outputs.artifact_names).unittesting_xml }}-${{ matrix.system }}-${{ matrix.python }}
path: artifact.txt
@@ -42,7 +42,7 @@ jobs:
run: printf "%s\n" "Package" >> package.txt
- name: 📤 Upload artifact for ${{ matrix.system }}-${{ matrix.python }}
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: ${{ fromJson(needs.Params.outputs.artifact_names).package_all }}
path: package.txt

View File

@@ -0,0 +1,37 @@
name: Testing available GitHub Action Images
on:
push:
workflow_dispatch:
jobs:
RunnerTest:
name: ${{ matrix.icon }} ${{ matrix.name }}
runs-on: ${{ matrix.image }}
continue-on-error: ${{ matrix.can-fail }}
strategy:
fail-fast: false
matrix:
include:
- {icon: '🐧', name: 'Ubuntu 22.04 (x86-64)', image: 'ubuntu-22.04', shell: 'bash', can-fail: false}
- {icon: '🐧', name: 'Ubuntu 24.04 (x86-64)', image: 'ubuntu-24.04', shell: 'bash', can-fail: false} # latest
- {icon: '🍎', name: 'macOS-14 (x86-64)', image: 'macos-14-large', shell: 'bash', can-fail: true } # not in free plan
### - {icon: '🍎', name: 'macOS-15 (x86-64)', image: 'macos-15-large', shell: 'bash', can-fail: true } # same as -intel; not in free plan
- {icon: '🍎', name: 'macOS-15 (x86-64)', image: 'macos-15-intel', shell: 'bash', can-fail: false}
- {icon: '🍏', name: 'macOS-14 (aarch64)', image: 'macos-14', shell: 'bash', can-fail: false} # latest
- {icon: '🍏', name: 'macOS-15 (aarch64)', image: 'macos-15', shell: 'bash', can-fail: false}
- {icon: '🪟', name: 'Windows Server 2022', image: 'windows-2022', shell: 'bash', can-fail: false}
- {icon: '🪟', name: 'Windows Server 2025', image: 'windows-2025', shell: 'bash', can-fail: false} # latest
# Third party images by ARM for aarch64
- {icon: '⛄', name: 'Ubuntu 22.04 (aarch64)', image: 'ubuntu-22.04-arm', shell: 'bash', can-fail: false}
- {icon: '⛄', name: 'Ubuntu 24.04 (aarch64)', image: 'ubuntu-24.04-arm', shell: 'bash', can-fail: false}
- {icon: '🏢', name: 'Windows 11 (arch64)', image: 'windows-11-arm', shell: 'bash', can-fail: false}
defaults:
run:
shell: ${{ matrix.shell }}
steps:
- name: 'uname -a'
run: uname -a

View File

@@ -10,28 +10,28 @@ jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@main
with:
package_name: pyDummy
InstallParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
package_name: pyDummy
python_version_list: ''
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
name: pyDummy
python_version_list: "3.9 3.10 3.11 3.12 3.13 pypy-3.9 pypy-3.10"
# disable_list: "windows:pypy-3.10"
package_name: 'myPackage'
python_version_list: '3.11 3.12 3.13 3.14 pypy-3.11'
disable_list: 'windows-arm:pypy-3.11'
PlatformTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
name: Platform
python_version_list: ""
system_list: "ubuntu windows macos mingw64 clang64 ucrt64"
package_name: 'myPackage'
name: 'Platform'
python_version_list: ''
system_list: 'ubuntu ubuntu-arm windows windows-arm macos mingw64 clang64 ucrt64'
InstallParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
package_name: 'myPackage'
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
python_version_list: ''
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@main
@@ -39,16 +39,17 @@ jobs:
- ConfigParams
- UnitTestingParams
with:
jobs: ${{ needs.UnitTestingParams.outputs.python_jobs }}
unittest_report_xml_directory: ${{ needs.ConfigParams.outputs.unittest_report_xml_directory }}
unittest_report_xml_filename: ${{ needs.ConfigParams.outputs.unittest_report_xml_filename }}
coverage_report_html_directory: ${{ needs.ConfigParams.outputs.coverage_report_html_directory }}
unittest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
unittest_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_html }}
coverage_sqlite_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}
# coverage_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_xml }}
# coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
# coverage_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_html }}
jobs: ${{ needs.UnitTestingParams.outputs.python_jobs }}
unittest_report_xml: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
coverage_report_xml: ${{ needs.ConfigParams.outputs.coverage_report_xml }}
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
unittest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
unittest_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_html }}
coverage_sqlite_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}
coverage_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_xml }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
coverage_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_html }}
PlatformTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@main
@@ -56,18 +57,19 @@ jobs:
- ConfigParams
- PlatformTestingParams
with:
jobs: ${{ needs.PlatformTestingParams.outputs.python_jobs }}
jobs: ${{ needs.PlatformTestingParams.outputs.python_jobs }}
# tests_directory: ""
unittest_directory: platform
unittest_report_xml_directory: ${{ needs.ConfigParams.outputs.unittest_report_xml_directory }}
unittest_report_xml_filename: ${{ needs.ConfigParams.outputs.unittest_report_xml_filename }}
coverage_report_html_directory: ${{ needs.ConfigParams.outputs.coverage_report_html_directory }}
unittest_xml_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).unittesting_xml }}
unittest_html_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).unittesting_html }}
coverage_sqlite_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).codecoverage_sqlite }}
coverage_xml_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).codecoverage_xml }}
coverage_json_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).codecoverage_json }}
coverage_html_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).codecoverage_html }}
unittest_directory: platform
unittest_report_xml: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
coverage_report_xml: ${{ needs.ConfigParams.outputs.coverage_report_xml }}
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
unittest_xml_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).unittesting_xml }}
unittest_html_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).unittesting_html }}
coverage_sqlite_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).codecoverage_sqlite }}
coverage_xml_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).codecoverage_xml }}
coverage_json_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).codecoverage_json }}
coverage_html_artifact: ${{ fromJson(needs.PlatformTestingParams.outputs.artifact_names).codecoverage_html }}
StaticTypeCheck:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@main
@@ -76,11 +78,19 @@ jobs:
- UnitTestingParams
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
commands: |
${{ needs.ConfigParams.outputs.mypy_prepare_command }}
mypy --html-report report/typing -p ${{ needs.ConfigParams.outputs.package_fullname }}
html_report: 'report/typing'
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).statictyping_html }}
html_report: ${{ needs.ConfigParams.outputs.typing_report_html }}
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).statictyping_html }}
CodeQuality:
uses: pyTooling/Actions/.github/workflows/CheckCodeQuality.yml@main
needs:
- UnitTestingParams
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
package_directory: ${{ needs.UnitTestingParams.outputs.package_directory }}
bandit: 'true'
pylint: 'true'
artifact: 'CodeQuality'
DocCoverage:
uses: pyTooling/Actions/.github/workflows/CheckDocumentation.yml@main
@@ -105,14 +115,13 @@ jobs:
Install:
uses: pyTooling/Actions/.github/workflows/InstallPackage.yml@main
needs:
- ConfigParams
- UnitTestingParams
- InstallParams
- Package
with:
jobs: ${{ needs.InstallParams.outputs.python_jobs }}
wheel: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}
package_name: ${{ needs.ConfigParams.outputs.package_fullname }}
package_name: ${{ needs.UnitTestingParams.outputs.package_fullname }}
PublishCoverageResults:
uses: pyTooling/Actions/.github/workflows/PublishCoverageResults.yml@main
@@ -122,17 +131,15 @@ jobs:
- UnitTesting
- PlatformTesting
with:
coverage_sqlite_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}
coverage_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_xml }}
coverage_report_xml_directory: ${{ needs.ConfigParams.outputs.coverage_report_xml_directory }}
coverage_report_xml_filename: ${{ needs.ConfigParams.outputs.coverage_report_xml_filename }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
coverage_report_json_directory: ${{ needs.ConfigParams.outputs.coverage_report_json_directory }}
coverage_report_json_filename: ${{ needs.ConfigParams.outputs.coverage_report_json_filename }}
coverage_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_html }}
coverage_report_html_directory: ${{ needs.ConfigParams.outputs.coverage_report_html_directory }}
codecov: true
codacy: true
coverage_report_xml: ${{ needs.ConfigParams.outputs.coverage_report_xml }}
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
coverage_sqlite_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}
coverage_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_xml }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
coverage_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_html }}
codecov: true
codacy: true
secrets: inherit
PublishTestResults:
@@ -144,7 +151,7 @@ jobs:
- PlatformTesting
with:
additional_merge_args: '-d "--pytest=rewrite-dunder-init;reduce-depth:pytest.tests.unit;reduce-depth:pytest.tests.platform"'
testsuite-summary-name: ${{ needs.ConfigParams.outputs.package_fullname }}
testsuite-summary-name: ${{ needs.UnitTestingParams.outputs.package_fullname }}
merged_junit_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
codecov: true
dorny: true
@@ -166,12 +173,13 @@ jobs:
- PublishCoverageResults
# - VerifyDocs
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
coverage_report_json_directory: ${{ needs.ConfigParams.outputs.coverage_report_json_directory }}
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
unittest_xml: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
unittest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_html }}
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_html }}
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
IntermediateCleanUp:
uses: pyTooling/Actions/.github/workflows/IntermediateCleanUp.yml@main
@@ -189,9 +197,9 @@ jobs:
- UnitTestingParams
- Documentation
with:
document: Actions
document: 'Actions'
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
pdf_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_pdf }}
pdf_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_pdf }}
PublishToGitHubPages:
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@main
@@ -212,9 +220,9 @@ jobs:
- Prepare
- UnitTesting
- PlatformTesting
- Install
# - StaticTypeCheck
- Package
- Install
- PublishToGitHubPages
permissions:
contents: write # required for create tag
@@ -230,9 +238,9 @@ jobs:
- Prepare
- UnitTesting
- PlatformTesting
- Install
# - StaticTypeCheck
- Package
- Install
- PublishToGitHubPages
if: needs.Prepare.outputs.is_release_tag == 'true'
permissions:
@@ -268,6 +276,7 @@ jobs:
- PublishTestResults
- PublishCoverageResults
- PublishToGitHubPages
- Install
- IntermediateCleanUp
with:
package: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}

View File

@@ -8,11 +8,15 @@ jobs:
NamespacePackage:
uses: pyTooling/Actions/.github/workflows/CompletePipeline.yml@main
with:
package_namespace: pyExamples
package_name: Extensions
codecov: true
codacy: true
dorny: true
package_namespace: 'myFramework'
package_name: 'Extension'
unittest_python_version_list: '3.11 3.12 3.13 3.14 pypy-3.11'
bandit: 'true'
pylint: 'true'
codecov: 'true'
codacy: 'true'
dorny: 'true'
cleanup: 'false'
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -17,7 +17,7 @@ jobs:
printf "%s\n" "Build log $(date --utc '+%d.%m.%Y - %H:%M:%S')" > build.log
- name: 📤 Upload artifact
uses: pyTooling/upload-artifact@v4
uses: pyTooling/upload-artifact@v6
with:
name: document
path: |
@@ -29,10 +29,11 @@ jobs:
- name: 🖉 Program
run: |
printf "%s\n" "Document other $(date --utc '+%d.%m.%Y - %H:%M:%S')" > document1.txt
printf "%s\n" "Document other $(date --utc '+%d.%m.%Y - %H:%M:%S')" > document2.txt
printf "%s\n" "Program $(date --utc '+%d.%m.%Y - %H:%M:%S')" > program.py
- name: 📤 Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: other
path: |
@@ -42,7 +43,7 @@ jobs:
retention-days: 1
NightlyPage:
uses: pyTooling/Actions/.github/workflows/NightlyRelease.yml@main
uses: ./.github/workflows/PublishReleaseNotes.yml
needs:
- Build
permissions:
@@ -55,17 +56,22 @@ jobs:
version=4.2.0
tool=myTool
prog=program
nightly_title: "Nightly Test Release"
nightly_description: |
tag: v4.2.0
title: "Nightly Test Release"
description: |
This *nightly* release contains all latest and important artifacts created by %tool%'s CI pipeline.
# %tool% %version%
* %prog%
# Attached files:
%%ASSETS%%
assets: |
document: document1.txt: Documentation
document: build.log: Logfile - %tool% - %tool%
other: document1.txt: SBOM - %version%
other: document2.txt: SBOM - %version%
other: %prog%.py: Application - %tool% - %version%
document:!archive1.zip: Archive 1 - zip
document:!archive2.tgz: Archive 2 - tgz
@@ -79,7 +85,7 @@ jobs:
secrets: inherit
NightlyPageWithInventory:
uses: ./.github/workflows/NightlyRelease.yml
uses: ./.github/workflows/PublishReleaseNotes.yml
needs:
- Build
permissions:
@@ -91,15 +97,15 @@ jobs:
version=4.2.0
tool=myTool
prog=program
nightly_name: inventory
nightly_title: "Nightly Test Release with Inventory"
nightly_description: |
tag: inventory
title: "Nightly Test Release with Inventory"
description: |
This *nightly* release contains all latest and important artifacts created by %tool%'s CI pipeline.
# %tool% %version%
* %prog%
* iventory.json
* inventory.json
inventory-json: "inventory.json"
inventory-version: 4.2.5
inventory-categories: "kind1,kind2"
@@ -107,7 +113,7 @@ jobs:
# artifact: file: labels: asset title
document: document1.txt: doc,html: Documentation
document: build.log: build,log: Logfile - %tool% - %tool%
other: document1.txt: build,SBOM:SBOM - %version%
other: document2.txt: build,SBOM:SBOM - %version%
other: %prog%.py: app,binary:Application - %tool% - %version%
document:!archive1.zip: Archive 1 - zip
document:!archive2.tgz: Archive 2 - tgz

View File

@@ -14,7 +14,7 @@ jobs:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
name: Example
python_version_list: "3.11 3.12 pypy-3.9 pypy-3.10"
python_version_list: "3.12 3.13 pypy-3.10 pypy-3.11" # py-2, py-1, pypy-1, pypy-0
Params_Systems:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
@@ -26,489 +26,191 @@ jobs:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
name: Example
python_version_list: "3.11"
python_version_list: "3.12" # py-2
system_list: "ubuntu windows macos macos-arm"
include_list: "ubuntu:3.12 ubuntu:3.13"
include_list: "ubuntu:3.13 ubuntu:3.14 ubuntu-arm:3.12"
Params_Exclude:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
name: Example
python_version_list: "3.12"
python_version_list: "3.13" # py-1
system_list: "ubuntu windows macos macos-arm"
exclude_list: "windows:3.12 windows:3.13"
exclude_list: "windows:3.13 windows:3.14"
Params_Disable:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
name: Example
python_version_list: "3.12"
python_version_list: "3.13" # py-1
system_list: "ubuntu windows macos macos-arm"
disable_list: "windows:3.12 windows:3.13"
disable_list: "windows:3.13 windows:3.14"
Params_All:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@main
with:
name: Example
python_version_list: "3.12 3.13"
python_version_list: "3.12 3.13" # py-2, py-1
system_list: "ubuntu windows macos macos-arm"
include_list: "windows:3.10 windows:3.11 windows:3.13"
exclude_list: "macos:3.12 macos:3.13"
Params_Check:
Params_Check_Default:
needs:
- Params_Default
runs-on: ubuntu-24.04
defaults:
run:
shell: python
steps:
- name: Checkout repository to access local Action
uses: actions/checkout@v6
- name: Checking job matrix from 'Params_Default'
uses: ./.github/actions/CheckJobMatrix
with:
expected-default-version: '3.14'
expected-python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
expected-systems: '["ubuntu", "ubuntu-arm", "windows", "windows-arm", "macos", "macos-arm"]'
expected-exclude-jobs: '["windows-arm:3.10"]'
expected-include-jobs: '["mingw64:3.12", "ucrt64:3.12"]'
generated-default-version: ${{ needs.Params_Default.outputs.python_version }}
generated-jobmatrix: ${{ needs.Params_Default.outputs.python_jobs }}
- name: Checking artifact names from 'Params_Default'
uses: ./.github/actions/CheckArtifactNames
with:
prefix: 'Example'
generated-names: ${{ needs.Params_Default.outputs.artifact_names }}
Params_Check_PythonVersions:
needs:
- Params_PythonVersions
runs-on: ubuntu-24.04
defaults:
run:
shell: python
steps:
- name: Checkout repository to access local Action
uses: actions/checkout@v6
- name: Checking job matrix from 'Params_PythonVersions'
uses: ./.github/actions/CheckJobMatrix
with:
expected-default-version: '3.14'
expected-python-versions: '["3.12", "3.13", "pypy-3.10", "pypy-3.11"]'
expected-systems: '["ubuntu", "ubuntu-arm", "windows", "windows-arm", "macos", "macos-arm"]'
expected-exclude-jobs: '["windows-arm:pypy-3.10", "windows-arm:pypy-3.11"]'
expected-include-jobs: '["mingw64:3.12", "ucrt64:3.12"]'
generated-default-version: ${{ needs.Params_PythonVersions.outputs.python_version }}
generated-jobmatrix: ${{ needs.Params_PythonVersions.outputs.python_jobs }}
Params_Check_Systems:
needs:
- Params_Systems
runs-on: ubuntu-24.04
defaults:
run:
shell: python
steps:
- name: Checkout repository to access local Action
uses: actions/checkout@v6
- name: Checking job matrix from 'Params_Systems'
uses: ./.github/actions/CheckJobMatrix
with:
expected-default-version: '3.14'
expected-python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
expected-systems: '["windows"]'
expected-exclude-jobs: '[]'
expected-include-jobs: '["mingw32:3.12", "mingw64:3.12"]'
generated-default-version: ${{ needs.Params_Systems.outputs.python_version }}
generated-jobmatrix: ${{ needs.Params_Systems.outputs.python_jobs }}
Params_Check_Include:
needs:
- Params_Include
runs-on: ubuntu-24.04
defaults:
run:
shell: python
steps:
- name: Checkout repository to access local Action
uses: actions/checkout@v6
- name: Checking job matrix from 'Params_Include'
uses: ./.github/actions/CheckJobMatrix
with:
expected-default-version: '3.14'
expected-python-versions: '["3.12"]'
expected-systems: '["ubuntu", "windows", "macos", "macos-arm"]'
expected-exclude-jobs: '[]'
expected-include-jobs: '["ubuntu:3.13", "ubuntu:3.14", "ubuntu-arm:3.12"]'
generated-default-version: ${{ needs.Params_Include.outputs.python_version }}
generated-jobmatrix: ${{ needs.Params_Include.outputs.python_jobs }}
Params_Check_Exclude:
needs:
- Params_Exclude
runs-on: ubuntu-24.04
defaults:
run:
shell: python
steps:
- name: Checkout repository to access local Action
uses: actions/checkout@v6
- name: Checking job matrix from 'Params_Exclude'
uses: ./.github/actions/CheckJobMatrix
with:
expected-default-version: '3.14'
expected-python-versions: '["3.13"]'
expected-systems: '["ubuntu", "macos", "macos-arm"]'
expected-exclude-jobs: '[]'
expected-include-jobs: '[]'
generated-default-version: ${{ needs.Params_Exclude.outputs.python_version }}
generated-jobmatrix: ${{ needs.Params_Exclude.outputs.python_jobs }}
Params_Check_Disable:
needs:
- Params_Disable
runs-on: ubuntu-24.04
defaults:
run:
shell: python
steps:
- name: Checkout repository to access local Action
uses: actions/checkout@v6
- name: Checking job matrix from 'Params_Disable'
uses: ./.github/actions/CheckJobMatrix
with:
expected-default-version: '3.14'
expected-python-versions: '["3.13"]'
expected-systems: '["ubuntu", "windows", "macos", "macos-arm"]'
expected-exclude-jobs: '["windows:3.13"]'
expected-include-jobs: '[]'
generated-default-version: ${{ needs.Params_Disable.outputs.python_version }}
generated-jobmatrix: ${{ needs.Params_Disable.outputs.python_jobs }}
Params_Check_All:
needs:
- Params_All
runs-on: ubuntu-24.04
defaults:
run:
shell: python
steps:
- name: Install dependencies
shell: bash
run: pip install --disable-pip-version-check --break-system-packages pyTooling
# Params_Default
- name: Checking results from 'Params_Default'
run: |
from json import loads as json_loads
from sys import exit
- name: Checkout repository to access local Action
uses: actions/checkout@v6
from pyTooling.Common import zipdicts
expectedPythonVersion = "3.13"
expectedPythons = ["3.9", "3.10", "3.11", "3.12", "3.13"]
expectedSystems = ["ubuntu", "windows", "macos", "macos-arm"]
expectedJobs = [f"{system}:{python}" for system in expectedSystems for python in expectedPythons] + ["mingw64:3.12", "ucrt64:3.11"]
expectedName = "Example"
expectedArtifacts = {
"unittesting_xml": f"{expectedName}-UnitTestReportSummary-XML",
"unittesting_html": f"{expectedName}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{expectedName}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{expectedName}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{expectedName}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{expectedName}-CodeCoverage-SQLite",
"codecoverage_xml": f"{expectedName}-CodeCoverage-XML",
"codecoverage_json": f"{expectedName}-CodeCoverage-JSON",
"codecoverage_html": f"{expectedName}-CodeCoverage-HTML",
"statictyping_html": f"{expectedName}-StaticTyping-HTML",
"package_all": f"{expectedName}-Packages",
"documentation_html": f"{expectedName}-Documentation-HTML",
"documentation_latex": f"{expectedName}-Documentation-LaTeX",
"documentation_pdf": f"{expectedName}-Documentation-PDF",
}
actualPythonVersion = """${{ needs.Params_Default.outputs.python_version }}"""
actualPythonJobs = json_loads("""${{ needs.Params_Default.outputs.python_jobs }}""".replace("'", '"'))
actualArtifactNames = json_loads("""${{ needs.Params_Default.outputs.artifact_names }}""".replace("'", '"'))
errors = 0
if actualPythonVersion != expectedPythonVersion:
print(f"'python_version' does not match: '{actualPythonVersion}' != '{expectedPythonVersion}'.")
errors += 1
if len(actualPythonJobs) != len(expectedJobs):
print(f"Number of 'python_jobs' does not match: {len(actualPythonJobs)} != {len(expectedJobs)}.")
print("Actual jobs:")
for job in actualPythonJobs:
if job['system'] == "msys2":
print(f" {job['runtime'].lower()}:{job['python']}")
else:
print(f" {job['system']}:{job['python']}")
print("Expected jobs:")
for job in expectedJobs:
print(f" {job}")
errors += 1
if len(actualArtifactNames) != len(expectedArtifacts):
print(f"Number of 'artifact_names' does not match: {len(actualArtifactNames)} != {len(expectedArtifacts)}.")
errors += 1
else:
for key, actual, expected in zipdicts(actualArtifactNames, expectedArtifacts):
if actual != expected:
print(f"Artifact name '{key}' does not match: {actual} != {expected}.")
errors += 1
if errors == 0:
print(f"All checks PASSED.")
exit(errors)
# Params_PythonVersions
- name: Checking results from 'Params_PythonVersions'
run: |
from json import loads as json_loads
from sys import exit
from pyTooling.Common import zipdicts
expectedPythonVersion = "3.13"
expectedPythons = ["3.11", "3.12", "pypy-3.9", "pypy-3.10"]
expectedSystems = ["ubuntu", "windows", "macos", "macos-arm"]
expectedJobs = [f"{system}:{python}" for system in expectedSystems for python in expectedPythons] + ["mingw64:3.12", "ucrt64:3.11"]
expectedName = "Example"
expectedArtifacts = {
"unittesting_xml": f"{expectedName}-UnitTestReportSummary-XML",
"unittesting_html": f"{expectedName}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{expectedName}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{expectedName}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{expectedName}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{expectedName}-CodeCoverage-SQLite",
"codecoverage_xml": f"{expectedName}-CodeCoverage-XML",
"codecoverage_json": f"{expectedName}-CodeCoverage-JSON",
"codecoverage_html": f"{expectedName}-CodeCoverage-HTML",
"statictyping_html": f"{expectedName}-StaticTyping-HTML",
"package_all": f"{expectedName}-Packages",
"documentation_html": f"{expectedName}-Documentation-HTML",
"documentation_latex": f"{expectedName}-Documentation-LaTeX",
"documentation_pdf": f"{expectedName}-Documentation-PDF",
}
actualPythonVersion = """${{ needs.Params_PythonVersions.outputs.python_version }}"""
actualPythonJobs = json_loads("""${{ needs.Params_PythonVersions.outputs.python_jobs }}""".replace("'", '"'))
actualArtifactNames = json_loads("""${{ needs.Params_PythonVersions.outputs.artifact_names }}""".replace("'", '"'))
errors = 0
if actualPythonVersion != expectedPythonVersion:
print(f"'python_version' does not match: '{actualPythonVersion}' != '{expectedPythonVersion}'.")
errors += 1
if len(actualPythonJobs) != len(expectedJobs):
print(f"Number of 'python_jobs' does not match: {len(actualPythonJobs)} != {len(expectedJobs)}.")
print("Actual jobs:")
for job in actualPythonJobs:
if job['system'] == "msys2":
print(f" {job['runtime'].lower()}:{job['python']}")
else:
print(f" {job['system']}:{job['python']}")
print("Expected jobs:")
for job in expectedJobs:
print(f" {job}")
errors += 1
if len(actualArtifactNames) != len(expectedArtifacts):
print(f"Number of 'artifact_names' does not match: {len(actualArtifactNames)} != {len(expectedArtifacts)}.")
errors += 1
else:
for key, actual, expected in zipdicts(actualArtifactNames, expectedArtifacts):
if actual != expected:
print(f"Artifact name '{key}' does not match: {actual} != {expected}.")
errors += 1
if errors == 0:
print(f"All checks PASSED.")
exit(errors)
# Params_Systems
- name: Checking results from 'Params_Systems'
run: |
from json import loads as json_loads
from sys import exit
from pyTooling.Common import zipdicts
expectedPythonVersion = "3.13"
expectedPythons = ["3.9", "3.10", "3.11", "3.12", "3.13"]
expectedSystems = ["windows"]
expectedJobs = [f"{system}:{python}" for system in expectedSystems for python in expectedPythons] + ["mingw32:3.12", "mingw64:3.11"]
expectedName = "Example"
expectedArtifacts = {
"unittesting_xml": f"{expectedName}-UnitTestReportSummary-XML",
"unittesting_html": f"{expectedName}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{expectedName}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{expectedName}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{expectedName}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{expectedName}-CodeCoverage-SQLite",
"codecoverage_xml": f"{expectedName}-CodeCoverage-XML",
"codecoverage_json": f"{expectedName}-CodeCoverage-JSON",
"codecoverage_html": f"{expectedName}-CodeCoverage-HTML",
"statictyping_html": f"{expectedName}-StaticTyping-HTML",
"package_all": f"{expectedName}-Packages",
"documentation_html": f"{expectedName}-Documentation-HTML",
"documentation_latex": f"{expectedName}-Documentation-LaTeX",
"documentation_pdf": f"{expectedName}-Documentation-PDF",
}
actualPythonVersion = """${{ needs.Params_Systems.outputs.python_version }}"""
actualPythonJobs = json_loads("""${{ needs.Params_Systems.outputs.python_jobs }}""".replace("'", '"'))
actualArtifactNames = json_loads("""${{ needs.Params_Systems.outputs.artifact_names }}""".replace("'", '"'))
errors = 0
if actualPythonVersion != expectedPythonVersion:
print(f"'python_version' does not match: '{actualPythonVersion}' != '{expectedPythonVersion}'.")
errors += 1
if len(actualPythonJobs) != len(expectedJobs):
print(f"Number of 'python_jobs' does not match: {len(actualPythonJobs)} != {len(expectedJobs)}.")
print("Actual jobs:")
for job in actualPythonJobs:
if job['system'] == "msys2":
print(f" {job['runtime'].lower()}:{job['python']}")
else:
print(f" {job['system']}:{job['python']}")
print("Expected jobs:")
for job in expectedJobs:
print(f" {job}")
errors += 1
if len(actualArtifactNames) != len(expectedArtifacts):
print(f"Number of 'artifact_names' does not match: {len(actualArtifactNames)} != {len(expectedArtifacts)}.")
errors += 1
else:
for key, actual, expected in zipdicts(actualArtifactNames, expectedArtifacts):
if actual != expected:
print(f"Artifact name '{key}' does not match: {actual} != {expected}.")
errors += 1
if errors == 0:
print(f"All checks PASSED.")
exit(errors)
# Params_Include
- name: Checking results from 'Params_Include'
run: |
from json import loads as json_loads
from sys import exit
from pyTooling.Common import zipdicts
expectedPythonVersion = "3.13"
expectedPythons = ["3.12"]
expectedSystems = ["ubuntu", "windows", "macos", "macos-arm"]
expectedJobs = [f"{system}:{python}" for system in expectedSystems for python in expectedPythons] + ["ubuntu:3.11", "ubuntu:3.12"]
expectedName = "Example"
expectedArtifacts = {
"unittesting_xml": f"{expectedName}-UnitTestReportSummary-XML",
"unittesting_html": f"{expectedName}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{expectedName}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{expectedName}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{expectedName}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{expectedName}-CodeCoverage-SQLite",
"codecoverage_xml": f"{expectedName}-CodeCoverage-XML",
"codecoverage_json": f"{expectedName}-CodeCoverage-JSON",
"codecoverage_html": f"{expectedName}-CodeCoverage-HTML",
"statictyping_html": f"{expectedName}-StaticTyping-HTML",
"package_all": f"{expectedName}-Packages",
"documentation_html": f"{expectedName}-Documentation-HTML",
"documentation_latex": f"{expectedName}-Documentation-LaTeX",
"documentation_pdf": f"{expectedName}-Documentation-PDF",
}
actualPythonVersion = """${{ needs.Params_Include.outputs.python_version }}"""
actualPythonJobs = json_loads("""${{ needs.Params_Include.outputs.python_jobs }}""".replace("'", '"'))
actualArtifactNames = json_loads("""${{ needs.Params_Include.outputs.artifact_names }}""".replace("'", '"'))
errors = 0
if actualPythonVersion != expectedPythonVersion:
print(f"'python_version' does not match: '{actualPythonVersion}' != '{expectedPythonVersion}'.")
errors += 1
if len(actualPythonJobs) != len(expectedJobs):
print(f"Number of 'python_jobs' does not match: {len(actualPythonJobs)} != {len(expectedJobs)}.")
print("Actual jobs:")
for job in actualPythonJobs:
if job['system'] == "msys2":
print(f" {job['runtime'].lower()}:{job['python']}")
else:
print(f" {job['system']}:{job['python']}")
print("Expected jobs:")
for job in expectedJobs:
print(f" {job}")
errors += 1
if len(actualArtifactNames) != len(expectedArtifacts):
print(f"Number of 'artifact_names' does not match: {len(actualArtifactNames)} != {len(expectedArtifacts)}.")
errors += 1
else:
for key, actual, expected in zipdicts(actualArtifactNames, expectedArtifacts):
if actual != expected:
print(f"Artifact name '{key}' does not match: {actual} != {expected}.")
errors += 1
if errors == 0:
print(f"All checks PASSED.")
exit(errors)
# Params_Exclude
- name: Checking results from 'Params_Exclude'
run: |
from json import loads as json_loads
from sys import exit
from pyTooling.Common import zipdicts
expectedPythonVersion = "3.13"
expectedPythons = ["3.12"]
expectedSystems = ["ubuntu", "macos", "macos-arm"]
expectedJobs = [f"{system}:{python}" for system in expectedSystems for python in expectedPythons]
expectedName = "Example"
expectedArtifacts = {
"unittesting_xml": f"{expectedName}-UnitTestReportSummary-XML",
"unittesting_html": f"{expectedName}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{expectedName}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{expectedName}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{expectedName}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{expectedName}-CodeCoverage-SQLite",
"codecoverage_xml": f"{expectedName}-CodeCoverage-XML",
"codecoverage_json": f"{expectedName}-CodeCoverage-JSON",
"codecoverage_html": f"{expectedName}-CodeCoverage-HTML",
"statictyping_html": f"{expectedName}-StaticTyping-HTML",
"package_all": f"{expectedName}-Packages",
"documentation_html": f"{expectedName}-Documentation-HTML",
"documentation_latex": f"{expectedName}-Documentation-LaTeX",
"documentation_pdf": f"{expectedName}-Documentation-PDF",
}
actualPythonVersion = """${{ needs.Params_Exclude.outputs.python_version }}"""
actualPythonJobs = json_loads("""${{ needs.Params_Exclude.outputs.python_jobs }}""".replace("'", '"'))
actualArtifactNames = json_loads("""${{ needs.Params_Exclude.outputs.artifact_names }}""".replace("'", '"'))
errors = 0
if actualPythonVersion != expectedPythonVersion:
print(f"'python_version' does not match: '{actualPythonVersion}' != '{expectedPythonVersion}'.")
errors += 1
if len(actualPythonJobs) != len(expectedJobs):
print(f"Number of 'python_jobs' does not match: {len(actualPythonJobs)} != {len(expectedJobs)}.")
print("Actual jobs:")
for job in actualPythonJobs:
if job['system'] == "msys2":
print(f" {job['runtime'].lower()}:{job['python']}")
else:
print(f" {job['system']}:{job['python']}")
print("Expected jobs:")
for job in expectedJobs:
print(f" {job}")
errors += 1
if len(actualArtifactNames) != len(expectedArtifacts):
print(f"Number of 'artifact_names' does not match: {len(actualArtifactNames)} != {len(expectedArtifacts)}.")
errors += 1
else:
for key, actual, expected in zipdicts(actualArtifactNames, expectedArtifacts):
if actual != expected:
print(f"Artifact name '{key}' does not match: {actual} != {expected}.")
errors += 1
if errors == 0:
print(f"All checks PASSED.")
exit(errors)
# Params_Disable
- name: Checking results from 'Params_Disable'
run: |
from json import loads as json_loads
from sys import exit
from pyTooling.Common import zipdicts
expectedPythonVersion = "3.13"
expectedPythons = ["3.12"]
expectedSystems = ["ubuntu", "macos", "macos-arm"]
expectedJobs = [f"{system}:{python}" for system in expectedSystems for python in expectedPythons]
expectedName = "Example"
expectedArtifacts = {
"unittesting_xml": f"{expectedName}-UnitTestReportSummary-XML",
"unittesting_html": f"{expectedName}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{expectedName}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{expectedName}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{expectedName}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{expectedName}-CodeCoverage-SQLite",
"codecoverage_xml": f"{expectedName}-CodeCoverage-XML",
"codecoverage_json": f"{expectedName}-CodeCoverage-JSON",
"codecoverage_html": f"{expectedName}-CodeCoverage-HTML",
"statictyping_html": f"{expectedName}-StaticTyping-HTML",
"package_all": f"{expectedName}-Packages",
"documentation_html": f"{expectedName}-Documentation-HTML",
"documentation_latex": f"{expectedName}-Documentation-LaTeX",
"documentation_pdf": f"{expectedName}-Documentation-PDF",
}
actualPythonVersion = """${{ needs.Params_Exclude.outputs.python_version }}"""
actualPythonJobs = json_loads("""${{ needs.Params_Exclude.outputs.python_jobs }}""".replace("'", '"'))
actualArtifactNames = json_loads("""${{ needs.Params_Exclude.outputs.artifact_names }}""".replace("'", '"'))
errors = 0
if actualPythonVersion != expectedPythonVersion:
print(f"'python_version' does not match: '{actualPythonVersion}' != '{expectedPythonVersion}'.")
errors += 1
if len(actualPythonJobs) != len(expectedJobs):
print(f"Number of 'python_jobs' does not match: {len(actualPythonJobs)} != {len(expectedJobs)}.")
print("Actual jobs:")
for job in actualPythonJobs:
if job['system'] == "msys2":
print(f" {job['runtime'].lower()}:{job['python']}")
else:
print(f" {job['system']}:{job['python']}")
print("Expected jobs:")
for job in expectedJobs:
print(f" {job}")
errors += 1
if len(actualArtifactNames) != len(expectedArtifacts):
print(f"Number of 'artifact_names' does not match: {len(actualArtifactNames)} != {len(expectedArtifacts)}.")
errors += 1
else:
for key, actual, expected in zipdicts(actualArtifactNames, expectedArtifacts):
if actual != expected:
print(f"Artifact name '{key}' does not match: {actual} != {expected}.")
errors += 1
if errors == 0:
print(f"All checks PASSED.")
exit(errors)
# Params_All
- name: Checking results from 'Params_All'
run: |
from json import loads as json_loads
from sys import exit
from pyTooling.Common import zipdicts
expectedPythonVersion = "3.13"
expectedPythons = ["3.12", "3.13"]
expectedSystems = ["ubuntu", "macos-arm", "windows"]
expectedJobs = [f"{system}:{python}" for system in expectedSystems for python in expectedPythons] + ["windows:3.10", "windows:3.11", "windows:3.13"]
expectedName = "Example"
expectedArtifacts = {
"unittesting_xml": f"{expectedName}-UnitTestReportSummary-XML",
"unittesting_html": f"{expectedName}-UnitTestReportSummary-HTML",
"perftesting_xml": f"{expectedName}-PerformanceTestReportSummary-XML",
"benchtesting_xml": f"{expectedName}-BenchmarkTestReportSummary-XML",
"apptesting_xml": f"{expectedName}-ApplicationTestReportSummary-XML",
"codecoverage_sqlite": f"{expectedName}-CodeCoverage-SQLite",
"codecoverage_xml": f"{expectedName}-CodeCoverage-XML",
"codecoverage_json": f"{expectedName}-CodeCoverage-JSON",
"codecoverage_html": f"{expectedName}-CodeCoverage-HTML",
"statictyping_html": f"{expectedName}-StaticTyping-HTML",
"package_all": f"{expectedName}-Packages",
"documentation_html": f"{expectedName}-Documentation-HTML",
"documentation_latex": f"{expectedName}-Documentation-LaTeX",
"documentation_pdf": f"{expectedName}-Documentation-PDF",
}
actualPythonVersion = """${{ needs.Params_All.outputs.python_version }}"""
actualPythonJobs = json_loads("""${{ needs.Params_All.outputs.python_jobs }}""".replace("'", '"'))
actualArtifactNames = json_loads("""${{ needs.Params_All.outputs.artifact_names }}""".replace("'", '"'))
errors = 0
if actualPythonVersion != expectedPythonVersion:
print(f"'python_version' does not match: '{actualPythonVersion}' != '{expectedPythonVersion}'.")
errors += 1
if len(actualPythonJobs) != len(expectedJobs):
print(f"Number of 'python_jobs' does not match: {len(actualPythonJobs)} != {len(expectedJobs)}.")
print("Actual jobs:")
for job in actualPythonJobs:
if job['system'] == "msys2":
print(f" {job['runtime'].lower()}:{job['python']}")
else:
print(f" {job['system']}:{job['python']}")
print("Expected jobs:")
for job in expectedJobs:
print(f" {job}")
errors += 1
if len(actualArtifactNames) != len(expectedArtifacts):
print(f"Number of 'artifact_names' does not match: {len(actualArtifactNames)} != {len(expectedArtifacts)}.")
errors += 1
else:
for key, actual, expected in zipdicts(actualArtifactNames, expectedArtifacts):
if actual != expected:
print(f"Artifact name '{key}' does not match: {actual} != {expected}.")
errors += 1
if errors == 0:
print(f"All checks PASSED.")
exit(errors)
- name: Checking job matrix from 'Params_All'
uses: ./.github/actions/CheckJobMatrix
with:
expected-default-version: '3.14'
expected-python-versions: '["3.12", "3.13"]'
expected-systems: '["ubuntu", "windows", "macos-arm"]'
expected-exclude-jobs: '[]'
expected-include-jobs: '["windows:3.10", "windows:3.11", "windows:3.13"]'
generated-default-version: ${{ needs.Params_All.outputs.python_version }}
generated-jobmatrix: ${{ needs.Params_All.outputs.python_jobs }}

View File

@@ -8,11 +8,14 @@ jobs:
SimplePackage:
uses: pyTooling/Actions/.github/workflows/CompletePipeline.yml@main
with:
package_name: pyDummy
codecov: true
codacy: true
dorny: true
cleanup: false
package_name: 'myPackage'
unittest_python_version_list: '3.11 3.12 3.13 3.14 pypy-3.11'
bandit: 'true'
pylint: 'true'
codecov: 'true'
codacy: 'true'
dorny: 'true'
cleanup: 'false'
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

7
.gitignore vendored
View File

@@ -15,6 +15,9 @@ coverage.xml
/report/unit
/tests/*.github
# bandit
/report/bandit
# setuptools
/build/**/*.*
/dist/**/*.*
@@ -25,8 +28,8 @@ coverage.xml
# Sphinx
doc/_build/
doc/pyDummy/**/*.*
!doc/pyDummy/index.rst
doc/myPackage/**/*.*
!doc/myPackage/index.rst
# BuildTheDocs
doc/_theme/**/*.*

10
.idea/Actions.iml generated
View File

@@ -1,8 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/myFramework" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/myPackage" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/doc/_build" />
<excludeFolder url="file://$MODULE_DIR$/report" />
</content>
<orderEntry type="jdk" jdkName="Python 3.14" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -75,8 +75,6 @@ As shown in the screenshots above, the expected order is:
[**PublishCoverageResults**](.github/workflows/PublishCoverageResults.yml): publish ucode coverage results.
[**NightlyRelease**](.github/workflows/NightlyRelease.yml): publish GitHub Release.
[**PublishReleaseNotes**](.github/workflows/PublishReleaseNotes.yml): publish GitHub Release.
- **Documentation:**
[**SphinxDocumentation**](.github/workflows/PublishCoverageResults.yml): create HTML and LaTeX documentation using
@@ -90,12 +88,11 @@ As shown in the screenshots above, the expected order is:
[**IntermediateCleanUp**](.github/workflows/IntermediateCleanUp.yml): delete intermediate artifacts.
[**ArtifactCleanUp**](.github/workflows/ArtifactCleanUp.yml): delete artifacts.
- **⚠ Deprecated ⚠:**
[**CoverageCollection**](.github/workflows/CoverageCollection.yml): Use `UnitTesting`, because is can collect code
coverage too. This avoids code duplication in job templates.
- **Removed:**
**NightlyRelease**: Use `PublishReleaseNotes`, because it's more advanced and not limited to nightly releases.
**CoverageCollection**: Use `UnitTesting`, because is can collect code coverage too.
[**BuildTheDocs**](.github/workflows/BuildTheDocs.yml): Use `SphinxDocumentation`, `LaTeXDocumentation` and
`PublishToGitHubPages`. BuildTheDocs isn't maintained anymore.
**BuildTheDocs**: Use `SphinxDocumentation`, `LaTeXDocumentation` and `PublishToGitHubPages`.
### Example pipeline
@@ -116,8 +113,8 @@ Find further usage cases in the following list of projects:
## Contributors
* [Patrick Lehmann](https://GitHub.com/Paebbels)
* [Unai Martinez-Corral](https://GitHub.com/umarcor) (Maintainer)
* [Patrick Lehmann](https://GitHub.com/Paebbels) (Maintainer)
* [Unai Martinez-Corral](https://GitHub.com/umarcor)
* [and more...](https://GitHub.com/pyTooling/Actions/graphs/contributors)

View File

@@ -1,2 +1,2 @@
wheel ~= 0.45
twine ~= 6.1
twine ~= 6.2

15
doc/Action/Actions.rst Normal file
View File

@@ -0,0 +1,15 @@
.. grid:: 2
.. grid-item::
:columns: 2
.. rubric:: Post-Processing
* :ref:`ACTION/WithPostStep`
.. grid-item::
:columns: 2
.. rubric:: Deprecated
* :ref:`ACTION/Releaser`

View File

@@ -1,8 +1,17 @@
.. _ACTION/Releaser:
.. index::
single: GitHub Action; Releaser
Releaser
########
.. attention::
The **Releaser** action is deprecated.
Use the new GitHub Action workflow template :ref:`JOBTMPL/PublishReleaseNotes` as a replacement with lots of
additional features.
**Releaser** is a Docker GitHub Action written in Python.
**Releaser** allows to keep a GitHub Release of type pre-release and its artifacts up to date with latest builds.

View File

@@ -1,4 +1,6 @@
.. _ACTION/WithPostStep:
.. index::
single: GitHub Action; WithPostStep
with-post-step
##############

View File

@@ -1,7 +1,31 @@
.. _ACTION:
.. index::
single: GitHub Action
Overview
########
The following 2 actions are provided by **Actions**:
* :ref:`ACTION/Releaser`
* :ref:`ACTION/WithPostStep`
.. include:: Actions.rst
.. _ACTION/Instantiation:
.. index::
single: GitHub Action; Instantiation
Instantiation
*************
.. code-block:: yaml
jobs:
<JobName>:
steps:
- ...
- name: <Name>
uses: ./with-post-step
with:
<Param1>: <Value1>
<Param2>: <Value2>

25
doc/CodeCoverage.rst Normal file
View File

@@ -0,0 +1,25 @@
.. _CODECOV:
Code Coverage Report
####################
.. grid:: 2
.. grid-item::
:columns: 8
.. report:code-coverage::
:reportid: src
.. grid-item::
:columns: 4
.. report:code-coverage-legend::
:reportid: src
:style: vertical-table
----------
Code coverage report generated with `pytest <https://github.com/pytest-dev/pytest>`__,
`Coverage.py <https://github.com/nedbat/coveragepy/tree/master>`__ and visualized by
`sphinx-reports <https://github.com/pyTooling/sphinx-reports>`__.

24
doc/DocCoverage.rst Normal file
View File

@@ -0,0 +1,24 @@
.. _DOCCOV:
Documentation Coverage Report
#############################
.. grid:: 2
.. grid-item::
:columns: 5
.. report:doc-coverage::
:reportid: src
.. grid-item::
:columns: 7
.. report:doc-coverage-legend::
:reportid: src
:style: vertical-table
----------
Documentation coverage generated with `"""docstr-coverage""" <https://github.com/HunterMcGushion/docstr_coverage>`__ and
visualized by `sphinx-reports <https://github.com/pyTooling/sphinx-reports>`__.

128
doc/Glossary.rst Normal file
View File

@@ -0,0 +1,128 @@
Glossary
########
.. glossary::
Bandit
Bandit is a tool designed to find common security issues in Python code.
:Source Code: `github.com/PyCQA/bandit/ <https://github.com/PyCQA/bandit/>`__
:Package: `pypi.org/project/bandit/ <https://pypi.org/project/bandit/>`__
:Documentation: `bandit.readthedocs.io/ <https://bandit.readthedocs.io/>`__
build
A simple, correct Python build frontend.
:Source Code: `github.com/pypa/build/ <https://github.com/pypa/build/>`__
:Package: `pypi.org/project/build/ <https://pypi.org/project/build/>`__
:Documentation: `build.pypa.io/ <https://build.pypa.io/>`__
Codacy
.. todo:: Add description of Codacy.
:Cloud Service: `Codacy.com <https://www.codacy.com/>`__
CodeCov
.. todo:: Add description of CodeCov.
:Cloud Service: `Codecov.io <https://about.codecov.io/>`__
Coverage.py
The code coverage tool for Python.
:Source Code: `github.com/nedbat/coveragepy/ <https://github.com/nedbat/coveragepy/>`__
:Package: `pypi.org/project/coverage/ <https://pypi.org/project/coverage/>`__
:Documentation: `coverage.readthedocs.io/ <https://coverage.readthedocs.io/>`__
delete-artifact
A GitHub Action to deletes artifacts within the workflow run.
:Source Code: `github.com/GeekyEggo/delete-artifact/ <https://github.com/GeekyEggo/delete-artifact/>`__
:Marketplace: `github.com/marketplace/actions/delete-artifact/ <https://github.com/marketplace/actions/delete-artifact/>`__
:README: `github.com/GeekyEggo/delete-artifact ⭢ README.md <https://github.com/GeekyEggo/delete-artifact/blob/main/README.md>`__
docstr_coverage
Docstring coverage analysis and rating for Python.
:Source Code: `github.com/HunterMcGushion/docstr_coverage/ <https://github.com/HunterMcGushion/docstr_coverage/>`__
:Package: `pypi.org/project/docstr_coverage/ <https://pypi.org/project/docstr_coverage/>`__
:Documentation: `docstr-coverage.readthedocs.io/ <https://docstr-coverage.readthedocs.io/>`__
gh
GitHubs official command line tool.
:Source Code: `github.com/cli/cli/ <https://github.com/cli/cli/>`__
:Documentation: `cli.github.com/manual/ <https://cli.github.com/manual/>`__
GitHub Pages
GitHub Pages is a static site hosting service that takes HTML, CSS, and JavaScript files straight from a repository
on GitHub, optionally runs the files through a build process, and publishes a website.
:Documentation: https://docs.github.com/en/pages
interrogate
Explain yourself! Interrogate a codebase for docstring coverage.
:Source Code: `github.com/econchick/interrogate/ <https://github.com/econchick/interrogate/>`__
:Package: `pypi.org/project/interrogate/ <https://pypi.org/project/interrogate/>`__
:Documentation: `interrogate.readthedocs.io/ <https://interrogate.readthedocs.io/>`__
MikTeX
MiKTeX is a modern TeX distribution for Windows, Linux and macOS.
:Source Code: `github.com/MiKTeX/miktex/ <https://github.com/MiKTeX/miktex/>`__
:Documentation: `miktex.org/ <https://miktex.org/>`__
mypy
Optional static typing for Python.
:Source Code: `github.com/python/mypy/ <https://github.com/python/mypy/>`__
:Package: `pypi.org/project/mypy/ <https://pypi.org/project/mypy/>`__
:Documentation: `www.mypy-lang.org/ <https://www.mypy-lang.org/>`__
pyEDAA.Reports
A collection of various (EDA tool-specific) report data formats.
:Source Code: `github.com/edaa-org/pyEDAA.Reports/ <https://github.com/edaa-org/pyEDAA.Reports/>`__
:Package: `pypi.org/project/pyEDAA.Reports/ <https://pypi.org/project/pyEDAA.Reports/>`__
:Documentation: `edaa-org.github.io/pyEDAA.Reports/ <https://edaa-org.github.io/pyEDAA.Reports/>`__
pip
The Python package installer.
:Source Code: `github.com/pypa/pip/ <https://github.com/pypa/pip/>`__
:Package: `pypi.org/project/pip/ <https://pypi.org/project/pip/>`__
:Documentation: `pip.pypa.io/ <https://pip.pypa.io/>`__
PyPI
Find, install and publish Python packages with the Python Package Index.
:Cloud Service: `PyPI.org <https://pypi.org/>`__
pytest
The pytest framework makes it easy to write small tests, yet scales to support complex functional testing.
:Source Code: `github.com/pytest-dev/pytest/ <https://github.com/pytest-dev/pytest/>`__
:Package: `pypi.org/project/pytest/ <https://pypi.org/project/pytest/>`__
:Documentation: `pytest.org/ <https://pytest.org/>`__
Sphinx
The Sphinx documentation generator.
:Source Code: `github.com/sphinx-doc/sphinx/ <https://github.com/sphinx-doc/sphinx/>`__
:Package: `pypi.org/project/sphinx/ <https://pypi.org/project/sphinx/>`__
:Documentation: `www.sphinx-doc.org/ <https://www.sphinx-doc.org/>`__
Test Reporter
Displays test results from popular testing frameworks directly in GitHub.
:Source Code: `github.com/dorny/test-reporter/ <https://github.com/dorny/test-reporter/>`__
:Marketplace: `github.com/marketplace/actions/test-reporter/ <https://github.com/marketplace/actions/test-reporter/>`__
:README: `github.com/dorny/test-reporter ⭢ README.md <https://github.com/dorny/test-reporter/blob/main/README.md>`__
twine
Utilities for interacting with PyPI.
:Source Code: `github.com/pypa/twine/ <https://github.com/pypa/twine/>`__
:Package: `pypi.org/project/twine/ <https://pypi.org/project/twine/>`__
:Documentation: `twine.readthedocs.io/ <https://twine.readthedocs.io/>`__

View File

@@ -36,10 +36,13 @@ to handover input parameters to the template.
on:
push:
workflow_dispatch:
schedule:
# Every Friday at 22:00 - rerun pipeline to check for dependency-based issues
- cron: '0 22 * * 5'
jobs:
<InstanceName>:
uses: <GitHubOrganization>/<Repository>/.github/workflows/<Template>.yml@v0
uses: <GitHubOrganization>/<Repository>/.github/workflows/<Template>.yml@r6
with:
<Param1>: <Value>
@@ -57,15 +60,18 @@ Documentation Only (Sphinx)
on:
push:
workflow_dispatch:
schedule:
# Every Friday at 22:00 - rerun pipeline to check for dependency-based issues
- cron: '0 22 * * 5'
jobs:
BuildTheDocs:
uses: pyTooling/Actions/.github/workflows/BuildTheDocs.yml@r0
uses: pyTooling/Actions/.github/workflows/BuildTheDocs.yml@r6
with:
artifact: Documentation
PublishToGitHubPages:
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@r0
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@r6
needs:
- BuildTheDocs
with:

View File

@@ -0,0 +1,826 @@
.. _JOBTMPL/CompletePipeline:
.. index::
single: build; CompletePipeline Template
single: Bandit; CompletePipeline Template
single: CodeCov; CompletePipeline Template
single: Codacy; CompletePipeline Template
single: Coverage.py; CompletePipeline Template
single: docstr_coverage; CompletePipeline Template
single: GitHub Pages; CompletePipeline Template
single: interrogate; CompletePipeline Template
single: MikTeX; CompletePipeline Template
single: mypy; CompletePipeline Template
single: PyPI; CompletePipeline Template
single: pytest; CompletePipeline Template
single: pyEDAA.Reports; CompletePipeline Template
single: Sphinx; CompletePipeline Template
single: Test Reporter; CompletePipeline Template
single: twine; CompletePipeline Template
single: GitHub Action Reusable Workflow; CompletePipeline Template
CompletePipeline
################
The ``CompletePipeline`` job template is the combination of almost all job templates offered by pyTooling/Actions in a
single workflow template. If fulfills all needs to test, package, document, publish and release Python code from GitHub.
It can be used for simple Python packages as well as namespace packages.
.. topic:: Features
.. grid:: 3
.. grid-item::
:columns: 4
.. rubric:: Testing
* Run unit tests before packaging using :term:`pytest`.
* Run platform tests before packaging using :term:`pytest`.
* Run application tests using packaged code on target platform.
.. rubric:: Code Quality
* Collect code coverage using :term:`Coverage.py`.
* Check documentation coverage using :term:`docstr_coverage` and :term:`interrogate`.
* Check static typing closure using :term:`mypy`.
* Static Application Security Testing (SAST) using :term:`bandit`
.. rubric:: Report Handling
* Merge unit test results into one report using :term:`pyEDAA.Reports`.
* Merge code coverage results into one report using :term:`Coverage.py`.
.. grid-item::
:columns: 4
.. rubric:: Documentation
* Compile documentation using :term:`Sphinx` as HTML and LaTeX.
* Translate LaTeX documentation to PDF using :term:`MikTeX`.
.. rubric:: Publishing Results
* GitHub Pipeline Summary
* Publish unittest results using :term:`Test Reporter`.
* :term:`GitHub Pages`
* Publish HTML documentation to GitHub Pages.
* :term:`Codecov`
* Publish code coverage to CodeCov.
* Publish unittest results to CodeCov.
* :term:`Codacy`
* Publish code coverage to Codacy.
.. grid-item::
:columns: 4
.. rubric:: Packaging
* Package as wheel using :term:`build`.
* Install wheel on target platform using pip.
* Upload to PyPI using :term:`twine`.
.. rubric:: Releasing
* Automatic tagging of merge commits on main branch to trigger a tagged pipeline (release pipeline).
* Create a release page with text derived from pull request description.
.. topic:: Behavior
.. include:: _Behavior.rst
.. topic:: Pipeline Graph
.. image:: ../../_static/pyTooling-Actions-SimplePackage.png
.. topic:: Dependencies
.. dropdown:: Expand List
:animate: fade-in-slide-down
:icon: codescan
:color: muted
.. grid:: 2
.. grid-item::
:columns: 6
* :ref:`pyTooling/Actions/.github/workflows/PrepareJob.yml <JOBTMPL/PrepareJob>`
* :gh:`actions/checkout`
* :gh:`GitHub command line tool 'gh' <cli/cli>`
* :ref:`pyTooling/Actions/.github/workflows/Parameters.yml <JOBTMPL/Parameters>`
* :ref:`pyTooling/Actions/.github/workflows/ExtractConfiguration.yml <JOBTMPL/ExtractConfiguration>`
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :pypi:`wheel`
* :pypi:`tomli`
* :ref:`pyTooling/Actions/.github/workflows/UnitTesting.yml <JOBTMPL/UnitTesting>`
* :gh:`actions/checkout`
* :gh:`msys2/setup-msys2`
* :gh:`actions/setup-python`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* apt: Packages specified via :ref:`JOBTMPL/UnitTesting/Input/apt` parameter.
* homebrew: Packages specified via :ref:`JOBTMPL/UnitTesting/Input/brew` parameter.
* MSYS2: Packages specified via :ref:`JOBTMPL/UnitTesting/Input/pacboy` parameter.
* pip
* :pypi:`wheel`
* :pypi:`tomli`
* Python packages specified via :ref:`JOBTMPL/UnitTesting/Input/requirements` or
:ref:`JOBTMPL/UnitTesting/Input/mingw_requirements` parameter.
* :ref:`pyTooling/Actions/.github/workflows/ApplicationTesting.yml <JOBTMPL/ApplicationTesting>`
* :ref:`pyTooling/Actions/.github/workflows/CheckDocumentation.yml <JOBTMPL/CheckDocumentation>`
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* pip
* :pypi:`docstr_coverage`
* :pypi:`interrogate`
* :ref:`pyTooling/Actions/.github/workflows/StaticTypeCheck.yml <JOBTMPL/StaticTypeCheck>`
* :ref:`pyTooling/Actions/.github/workflows/Package.yml <JOBTMPL/Package>`
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* pip
* :pypi:`build`
* :pypi:`wheel`
* :ref:`pyTooling/Actions/.github/workflows/PublishTestResults.yml <JOBTMPL/PublishTestResults>`
* :gh:`actions/checkout`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* pip
* :pypi:`pyEDAA.Reports`
* :gh:`dorny/test-reporter`
* :gh:`codecov/test-results-action`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
.. grid-item::
:columns: 6
* :ref:`pyTooling/Actions/.github/workflows/PublishCoverageResults.yml <JOBTMPL/PublishCoverageResults>`
* :gh:`actions/checkout`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* pip
* :pypi:`coverage`
* :pypi:`tomli`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* :gh:`codecov/codecov-action`
* :gh:`codacy/codacy-coverage-reporter-action`
* :ref:`pyTooling/Actions/.github/workflows/SphinxDocumentation.yml <JOBTMPL/SphinxDocumentation>`
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* apt
* `graphviz <https://graphviz.org/>`__
* pip
* :pypi:`wheel`
* Python packages specified via :ref:`JOBTMPL/SphinxDocumentation/Input/requirements` parameter.
* :ref:`pyTooling/Actions/.github/workflows/LaTeXDocumentation.yml <JOBTMPL/LaTeXDocumentation>`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* :gh:`addnab/docker-run-action`
* :dockerhub:`pytooling/miktex <pytooling/miktex:sphinx>`
* :ref:`pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml <JOBTMPL/PublishToGitHubPages>`
* :ref:`pyTooling/Actions/.github/workflows/PublishOnPyPI.yml <JOBTMPL/PublishOnPyPI>`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`actions/setup-python`
* :gh:`geekyeggo/delete-artifact`
* pip
* :pypi:`wheel`
* :pypi:`twine`
* :ref:`pyTooling/Actions/.github/workflows/TagReleaseCommit.yml <JOBTMPL/TagReleaseCommit>`
* :gh:`actions/github-script`
* :ref:`pyTooling/Actions/.github/workflows/PublishReleaseNotes.yml <JOBTMPL/PublishReleaseNotes>`
* :gh:`actions/checkout`
* ``gh`` (GitHub command line interface)
* ``jq`` (JSON processing)
* apt
* zstd
* :ref:`pyTooling/Actions/.github/workflows/IntermediateCleanUp.yml <JOBTMPL/IntermediateCleanUp>`
* :gh:`geekyeggo/delete-artifact`
* :ref:`pyTooling/Actions/.github/workflows/ArtifactCleanUp.yml <JOBTMPL/ArtifactCleanUp>`
* :gh:`geekyeggo/delete-artifact`
.. _JOBTMPL/CompletePipeline/Instantiation:
Instantiation
*************
The following instantiation example creates a ``SimplePackage`` job derived from job template ``CompletePipeline``
version ``@r6``. It only requires the `package_name` parameter to run a full pipeline suitable for a Python project.
.. grid:: 2
.. grid-item::
:columns: 6
.. tab-set::
.. tab-item:: Simple Package
:sync: Simple
.. code-block:: yaml
name: Pipeline
jobs:
SimplePackage:
uses: pyTooling/Actions/.github/workflows/CompletePipeline.yml@r6
with:
package_name: myPackage
.. tab-item:: Namespace Package
:sync: Namespace
.. code-block:: yaml
name: Pipeline
jobs:
NamespacePackage:
uses: pyTooling/Actions/.github/workflows/CompletePipeline.yml@r6
with:
package_namespace: myFramework
package_name: Extension
.. grid-item::
:columns: 6
.. tab-set::
.. tab-item:: Simple Package
:sync: Simple
.. code-block::
📂ProjectRoot/
📂myFramework/
📦SubPackage/
🐍__init__.py
🐍SubModuleA.py
🐍__init__.py
🐍ModuleB.py
.. tab-item:: Namespace Package
:sync: Namespace
.. code-block::
📂ProjectRoot/
📂myFramework/
📂Extension/
📦SubPackage/
🐍__init__.py
🐍SubModuleA.py
🐍__init__.py
🐍ModuleB.py
.. _JOBTMPL/CompletePipeline/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/CompletePipeline/Inputs>`
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================+
| :ref:`JOBTMPL/CompletePipeline/Input/package_namespace` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/package_name` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/unittest_python_version` | no | string | ``'3.14'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/unittest_python_version_list` | no | string | ``'3.10 3.11 3.12 3.13 3.14'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/unittest_system_list` | no | string | ``'ubuntu windows macos macos-arm ucrt64'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/unittest_include_list` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/unittest_exclude_list` | no | string | ``'windows-arm:3.9 windows-arm:3.10'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/unittest_disable_list` | no | string | ``'windows-arm:pypy-3.10 windows-arm:pypy-3.11'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/apptest_python_version` | no | string | ``'3.14'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/apptest_python_version_list` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/apptest_system_list` | no | string | ``'ubuntu windows macos macos-arm ucrt64'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/apptest_include_list` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/apptest_exclude_list` | no | string | ``'windows-arm:3.9 windows-arm:3.10'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/apptest_disable_list` | no | string | ``'windows-arm:pypy-3.10 windows-arm:pypy-3.11'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/codecov` | no | string | ``'false'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/codacy` | no | string | ``'false'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/dorny` | no | string | ``'false'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/CompletePipeline/Input/cleanup` | no | string | ``'true'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/CompletePipeline/Secrets>`
+-----------------------------------------------------------+----------+----------+--------------+
| Token Name | Required | Type | Default |
+===========================================================+==========+==========+==============+
| :ref:`JOBTMPL/CompletePipeline/Secret/PYPI_TOKEN` | no | string | — — — — |
+-----------------------------------------------------------+----------+----------+--------------+
| :ref:`JOBTMPL/CompletePipeline/Secret/CODECOV_TOKEN` | no | string | — — — — |
+-----------------------------------------------------------+----------+----------+--------------+
| :ref:`JOBTMPL/CompletePipeline/Secret/CODACY_TOKEN` | no | string | — — — — |
+-----------------------------------------------------------+----------+----------+--------------+
.. rubric:: Goto :ref:`output parameters <JOBTMPL/CompletePipeline/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/CompletePipeline/Inputs:
Input Parameters
****************
.. _JOBTMPL/CompletePipeline/Input/package_namespace:
package_namespace
=================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid Python namespace.
:Description: In case the package is a Python namespace package, the name of the library's or package's namespace
needs to be specified using this parameter. |br|
In case of a simple Python package, this parameter must be specified as an empty string (``''``),
which is the default.
:Example:
.. grid:: 2
.. grid-item::
:columns: 5
.. rubric:: Example Instantiation
.. code-block:: yaml
name: Pipeline
jobs:
NamespacePackage:
uses: pyTooling/Actions/.github/workflows/CompletePipeline.yml@r6
with:
package_namespace: myFramework
package_name: Extension
.. grid-item::
:columns: 4
.. rubric:: Example Directory Structure
.. code-block::
📂ProjectRoot/
📂myFramework/
📂Extension/
📦SubPackage/
🐍__init__.py
🐍SubModuleA.py
🐍__init__.py
🐍ModuleB.py
.. _JOBTMPL/CompletePipeline/Input/package_name:
package_name
============
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid Python package name.
:Description: In case of a simple Python package, this package's name is specified using this parameter. |br|
In case the package is a Python namespace package, the parameter
:ref:`JOBTMPL/CompletePipeline/Input/package_namespace` must be specified, too.
:Example:
.. grid:: 2
.. grid-item::
:columns: 5
.. rubric:: Example Instantiation
.. code-block:: yaml
name: Pipeline
jobs:
SimplePackage:
uses: pyTooling/Actions/.github/workflows/CompletePipeline.yml@r6
with:
package_name: myPackage
.. grid-item::
:columns: 4
.. rubric:: Example Directory Structure
.. code-block::
📂ProjectRoot/
📂myFramework/
📦SubPackage/
🐍__init__.py
🐍SubModuleA.py
🐍__init__.py
🐍ModuleB.py
.. _JOBTMPL/CompletePipeline/Input/unittest_python_version:
unittest_python_version
=======================
:Type: string
:Required: no
:Default Value: ``'3.14'``
:Possible Values: Any valid Python version conforming to the pattern ``<major>.<minor>`` or ``pypy-<major>.<minor>``. |br|
See `actions/python-versions - available Python versions <https://github.com/actions/python-versions>`__
and `actions/setup-python - configurable Python versions <https://github.com/actions/setup-python>`__.
:Description: The default Python version used for intermediate jobs using Python tools.
In case :ref:`JOBTMPL/CompletePipeline/Input/unittest_python_version_list` is empty, this default
version is used to populate the :ref:`JOBTMPL/CompletePipeline/Input/unittest_python_version_list`
parameter.
.. _JOBTMPL/CompletePipeline/Input/unittest_python_version_list:
unittest_python_version_list
============================
:Type: string
:Required: no
:Default Value: ``'3.10 3.11 3.12 3.13 3.14'``
:Possible Values: A space separated list of valid Python versions conforming to the pattern ``<major>.<minor>`` or
``pypy-<major>.<minor>``.
:Description: The list of space-separated Python versions used for unit testing.
.. include:: ../PythonVersionList.rst
.. _JOBTMPL/CompletePipeline/Input/unittest_system_list:
unittest_system_list
====================
:Type: string
:Required: no
:Default Value: ``'ubuntu windows macos macos-arm mingw64 ucrt64'``
:Possible Values: A space separated list of system names.
:Description: The list of space-separated systems used for unit testing.
.. include:: ../SystemList.rst
.. _JOBTMPL/CompletePipeline/Input/unittest_include_list:
unittest_include_list
=====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be included into the list of unittest
variants.
For more details see :ref:`JOBTMPL/Parameters/Input/include_list`.
.. _JOBTMPL/CompletePipeline/Input/unittest_exclude_list:
unittest_exclude_list
=====================
:Type: string
:Required: no
:Default Value: ``'windows-arm:3.9 windows-arm:3.10'``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be excluded from the list of unittest
variants.
For more details see :ref:`JOBTMPL/Parameters/Input/exclude_list`.
.. _JOBTMPL/CompletePipeline/Input/unittest_disable_list:
unittest_disable_list
=====================
:Type: string
:Required: no
:Default Value: ``'windows-arm:pypy-3.10 windows-arm:pypy-3.11'``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be temporarily disabled from the list
of unittest variants. |br|
Each disabled item creates a warning in the workflow log.
For more details see :ref:`JOBTMPL/Parameters/Input/disable_list`.
.. _JOBTMPL/CompletePipeline/Input/apptest_python_version:
apptest_python_version
======================
:Type: string
:Required: no
:Default Value: ``'3.14'``
:Possible Values: Any valid Python version conforming to the pattern ``<major>.<minor>`` or ``pypy-<major>.<minor>``. |br|
See `actions/python-versions - available Python versions <https://github.com/actions/python-versions>`__
and `actions/setup-python - configurable Python versions <https://github.com/actions/setup-python>`__.
:Description: The default Python version used for intermediate jobs using Python tools.
In case :ref:`JOBTMPL/CompletePipeline/Input/apptest_python_version_list` is empty, this default
version is used to populate the :ref:`JOBTMPL/CompletePipeline/Input/apptest_python_version_list`
parameter.
.. _JOBTMPL/CompletePipeline/Input/apptest_python_version_list:
apptest_python_version_list
===========================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: A space separated list of valid Python versions conforming to the pattern ``<major>.<minor>`` or
``pypy-<major>.<minor>```.
:Description: The list of space-separated Python versions used for application testing.
As this list is empty by default, the value is derived from
:ref:`JOBTMPL/CompletePipeline/Input/apptest_python_version`.
.. include:: ../PythonVersionList.rst
.. _JOBTMPL/CompletePipeline/Input/apptest_system_list:
apptest_system_list
===================
:Type: string
:Required: no
:Default Value: ``'ubuntu windows macos macos-arm mingw64 ucrt64'``
:Possible Values: A space separated list of system names.
:Description: The list of space-separated systems used for application testing.
.. include:: ../SystemList.rst
.. _JOBTMPL/CompletePipeline/Input/apptest_include_list:
apptest_include_list
====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be included into the list of
application test variants.
For more details see :ref:`JOBTMPL/Parameters/Input/include_list`.
.. _JOBTMPL/CompletePipeline/Input/apptest_exclude_list:
apptest_exclude_list
====================
:Type: string
:Required: no
:Default Value: ``'windows-arm:3.9 windows-arm:3.10'``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be excluded from the list of
application test variants.
For more details see :ref:`JOBTMPL/Parameters/Input/exclude_list`.
.. _JOBTMPL/CompletePipeline/Input/apptest_disable_list:
apptest_disable_list
====================
:Type: string
:Required: no
:Default Value: ``'windows-arm:pypy-3.10 windows-arm:pypy-3.11'``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be temporarily disabled from the list
of application test variants. |br|
Each disabled item creates a warning in the workflow log.
For more details see :ref:`JOBTMPL/Parameters/Input/disable_list`.
.. _JOBTMPL/CompletePipeline/Input/codecov:
codecov
=======
:Type: string
:Required: no
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: If *true*, publish merged code coverage results and a merged unit test summary to CodeCov. |br|
Secret :ref:`JOBTMPL/CompletePipeline/Secret/CODECOV_TOKEN` must be set.
.. _JOBTMPL/CompletePipeline/Input/codacy:
codacy
======
:Type: string
:Required: no
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: If *true*, publish merged code coverage results to Codacy. |br|
Secret :ref:`JOBTMPL/CompletePipeline/Secret/CODACY_TOKEN` must be set.
.. _JOBTMPL/CompletePipeline/Input/dorny:
dorny
=====
:Type: string
:Required: no
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: If *true*, publish a merged unit test summary as pipeline result.
.. _JOBTMPL/CompletePipeline/Input/cleanup:
cleanup
=======
:Type: string
:Required: no
:Default Value: ``'true'``
:Possible Values: ``'true'``, ``'false'``
:Description: If *false*, do not remove intermediate artifacts. |br|
This might help debugging artifact handovers between jobs.
.. _JOBTMPL/CompletePipeline/Secrets:
Secrets
*******
The workflow template uses the following secrets to publish results to other services.
.. _JOBTMPL/CompletePipeline/Secret/PYPI_TOKEN:
PYPI_TOKEN
==========
:Type: string
:Required: no
:Default Value: — — — —
:Description: The token to publish and upload packages on :term:`PyPI`.
.. _JOBTMPL/CompletePipeline/Secret/CODECOV_TOKEN:
CODECOV_TOKEN
=============
:Type: string
:Required: no
:Default Value: — — — —
:Description: The token to publish code coverage and unit test results to :term:`CodeCov`.
.. _JOBTMPL/CompletePipeline/Secret/CODACY_TOKEN:
CODACY_TOKEN
============
:Type: string
:Required: no
:Default Value: — — — —
:Description: The token to publish code coverage results to :term:`Codacy`.
.. _JOBTMPL/CompletePipeline/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/CompletePipeline/Optimizations:
Optimizations
*************
The following optimizations can be used to reduce the template's runtime.
.. todo::
CompletePipeline::Optimizations Needs a list of optimizations.

View File

@@ -0,0 +1,16 @@
1. Infer information from ``${{ github.ref }}`` variable.
2. Extract Python project settings from :file:`pyproject.toml`.
3. Compute job matrix based on system, Python version, environment, ... for job variants.
4. Run unit tests using pytest and collect code coverage.
5. Run platform tests using pytest and collect code coverage.
6. Run application tests using pytest.
7. Package code as wheel.
8. Check documentation coverage using docstr_coverage and interrogate.
9. Verify type annotation using static typing analysis using mypy.
10. Merge unit test results and code coverage results.
11. Generate HTML and LaTeX documentations using Sphinx.
12. Translate LaTeX documentation to PDF using MikTeX.
13. Publish unit test and code coverage results to cloud services.
14. Publish documentation to GitHub Pages.
15. Publish wheel to PyPI.
16. Create a GitHub release page and upload release assets.

View File

@@ -0,0 +1,32 @@
.. _JOBTMPL/AllInOne:
All-In-One
##########
The category *all-in-one* provides workflow templates implementing all necessary steps (jobs) for testing and publishing
a Python project. It utilizes allmost all of ``pyTooling/Acion``'s workflow templates.
Such a all-in-one workflow template covers:
* unit testing
* code coverage collections
* documentation checking
* pulishing of unit test and code coverage results
* merging of test reports
* packaging as wheel
* publishing wheels to PyPI
* documentation generation via Sphinx and Miktex
* automatic tagging of release commits
* releasing
.. topic:: Provides *all-in-one* workflow templates
* :ref:`JOBTMPL/CompletePipeline` - Use all of ``pyTooling/Acion``'s workflow templates by instantiation of a single
workflow template.
.. image:: ../../_static/pyTooling-Actions-SimplePackage.png
.. toctree::
:hidden:
CompletePipeline

View File

@@ -1,90 +0,0 @@
.. _JOBTMPL/ArtifactCleanup:
ArtifactCleanUp
###############
This job removes artifacts used to exchange data from job to job.
**Behavior:**
1. Delete the package artifact if the current pipeline run was not a tagged run.
2. Delete all remaining artifacts if given as a parameter.
**Dependencies:**
* :gh:`geekyeggo/delete-artifact`
Instantiation
*************
Simple Example
==============
The simplest variant just uses the artifact name for the package.
.. code-block:: yaml
jobs:
ArtifactCleanUp:
uses: pyTooling/Actions/.github/workflows/ArtifactCleanUp.yml@r0
with:
package: Package
Complex Example
===============
.. code-block:: yaml
jobs:
ArtifactCleanUp:
uses: pyTooling/Actions/.github/workflows/ArtifactCleanUp.yml@r0
needs:
- Params
- UnitTesting
- BuildTheDocs
- PublishToGitHubPages
- PublishTestResults
with:
package: ${{ fromJson(needs.Params.outputs.artifact_names).package_all }}
remaining: |
${{ fromJson(needs.Params.outputs.artifact_names).unittesting_xml }}-*
Parameters
**********
package
=======
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| package | yes | string | — — — — |
+----------------+----------+----------+----------+
Artifacts to be removed on not tagged runs.
remaining
=========
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| remaining | optional | string | ``""`` |
+----------------+----------+----------+----------+
Artifacts to be removed unconditionally.
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -1,75 +0,0 @@
.. _JOBTMPL/BuildTheDocs:
BuildTheDocs
############
This jobs compiles the documentation written in ReStructured Text with Sphinx using BuildTheDocs.
**Behavior:**
1. Checkout repository.
2. Build the documentation.
3. Upload the HTML documentation as an artifact.
4. Publish the HTML documentation to GitHub Pages.
**Dependencies:**
* :gh:`actions/checkout`
* :gh:`buildthedocs/btd`
* :gh:`actions/upload-artifact`
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
BuildTheDocs:
uses: pyTooling/Actions/.github/workflows/BuildTheDocs.yml@r0
Complex Example
===============
.. code-block:: yaml
jobs:
BuildTheDocs:
uses: pyTooling/Actions/.github/workflows/BuildTheDocs.yml@r0
needs:
- Params
with:
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).documentation_html }}
Parameters
**********
artifact
========
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| artifact | optional | string | ``""`` |
+----------------+----------+----------+--------------+
Name of the documentation artifact.
If no artifact name is given, the job directly publishes the documentation's HTML content to GitHub Pages.
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -0,0 +1,157 @@
.. _JOBTMPL/ArtifactCleanup:
.. index::
single: delete-artifact; ArtifactCleanUp Template
single: GitHub Action Reusable Workflow; ArtifactCleanUp Template
ArtifactCleanUp
###############
This job removes artifacts which were used to exchange data between jobs.
.. topic:: Features
* Delete artifacts from pipeline.
.. topic:: Behavior
1. Delete the package artifact if the current pipeline run was not a tagged run.
2. Delete all remaining artifacts if given as a parameter.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-ArtifactCleanup.png
:width: 350px
.. topic:: Dependencies
* :gh:`geekyeggo/delete-artifact`
.. _JOBTMPL/ArtifactCleanup/Instantiation:
Instantiation
*************
Simple Example
==============
The simplest variant just uses the artifact name for the package.
.. code-block:: yaml
jobs:
ArtifactCleanUp:
uses: pyTooling/Actions/.github/workflows/ArtifactCleanUp.yml@r6
with:
package: Package
Complex Example
===============
.. code-block:: yaml
jobs:
ArtifactCleanUp:
uses: pyTooling/Actions/.github/workflows/ArtifactCleanUp.yml@r6
needs:
- Params
- UnitTesting
- BuildTheDocs
- PublishToGitHubPages
- PublishTestResults
with:
package: ${{ fromJson(needs.Params.outputs.artifact_names).package_all }}
remaining: |
${{ fromJson(needs.Params.outputs.artifact_names).unittesting_xml }}-*
.. seealso::
:ref:`JOBTMPL/IntermediateCleanUp`
``IntermediateCleanUp`` is used to remove intermediate artifacts like unit test artifacts for each job variant
after test results have been merged into a single file.
.. _JOBTMPL/ArtifactCleanup/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/ArtifactCleanup/Inputs>`
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================+
| :ref:`JOBTMPL/ArtifactCleanup/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/ArtifactCleanup/Input/package` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/ArtifactCleanup/Input/remaining` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/ArtifactCleanup/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/ArtifactCleanup/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/ArtifactCleanup/Inputs:
Input Parameters
****************
.. _JOBTMPL/ArtifactCleanup/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/ArtifactCleanup/Input/package:
package
=======
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Multi-line string accepting any valid artifact name per line.
:Description: Artifacts to be removed on not tagged runs.
.. _JOBTMPL/ArtifactCleanup/Input/remaining:
remaining
=========
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Multi-line string accepting any valid artifact name per line.
:Description: Versi
.. _JOBTMPL/ArtifactCleanup/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/ArtifactCleanup/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/ArtifactCleanup/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,141 @@
.. _JOBTMPL/IntermediateCleanUp:
.. index::
single: delete-artifact; IntermediateCleanUp Template
single: GitHub Action Reusable Workflow; IntermediateCleanUp Template
IntermediateCleanUp
###################
The ``IntermediateCleanUp`` job template is used to remove intermediate artifacts like unit test artifacts for each job
variant after test results have been merged into a single file.
.. topic:: Features
* Delete artifacts from pipeline.
.. topic:: Behavior
1. Delete all SQLite code coverage artifacts if given as a parameter.
2. Delete all JUnit XML report artifacts if given as a parameter.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-IntermediateCleanUp.png
:width: 400px
.. topic:: Dependencies
* :gh:`geekyeggo/delete-artifact`
.. _JOBTMPL/IntermediateCleanUp/Instantiation:
Instantiation
*************
The following instantiation example creates a ``Params`` job derived from job template ``Parameters`` version ``@r6``. It only
requires a `name` parameter to create the artifact names.
.. code-block:: yaml
jobs:
IntermediateCleanUp:
uses: pyTooling/Actions/.github/workflows/IntermediateCleanUp.yml@r6
needs:
- UnitTestingParams
- PublishCoverageResults
- PublishTestResults
if: success() || failure()
with:
sqlite_coverage_artifacts_prefix: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}-
xml_unittest_artifacts_prefix: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}-
.. seealso::
:ref:`JOBTMPL/ArtifactCleanup`
``ArtifactCleanup`` is used to remove artifacts like unit test report artifacts after artifact's content has been
(post-)processed or published.
.. _JOBTMPL/IntermediateCleanUp/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/IntermediateCleanUp/Inputs>`
+----------------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| Parameter Name | Required | Type | Default |
+============================================================================+==========+==========+===================================================+
| :ref:`JOBTMPL/IntermediateCleanUp/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+----------------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/IntermediateCleanUp/Input/sqlite_coverage_artifacts_prefix` | no | string | ``''`` |
+----------------------------------------------------------------------------+----------+----------+---------------------------------------------------+
| :ref:`JOBTMPL/IntermediateCleanUp/Input/xml_unittest_artifacts_prefix` | no | string | ``''`` |
+----------------------------------------------------------------------------+----------+----------+---------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/IntermediateCleanUp/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/IntermediateCleanUp/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/IntermediateCleanUp/Inputs:
Input Parameters
****************
.. _JOBTMPL/IntermediateCleanUp/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/IntermediateCleanUp/Input/sqlite_coverage_artifacts_prefix:
sqlite_coverage_artifacts_prefix
================================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name including ``*``.
:Description: Prefix for SQLite coverage artifacts to be removed.
.. _JOBTMPL/IntermediateCleanUp/Input/xml_unittest_artifacts_prefix:
xml_unittest_artifacts_prefix
=============================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name including ``*``.
:Description: Prefix for XML unittest artifacts to be removed.
.. _JOBTMPL/IntermediateCleanUp/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/IntermediateCleanUp/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/IntermediateCleanUp/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,28 @@
.. _JOBTMPL/Cleanup:
Cleanup
#######
The category *cleanup* provides workflow templates implementing artifact cleanups (removals) from pipelines.
Running lots of unit testing, platform testing and application testing variants (operating system |times| Python version
|times| environment) creates dozens to hundrets of artifacts (unit test report, code coverage report, ...). This
consumes pipeline storage which can be freed up. Moreover, it's unclear which artifacts contain the final unit test and
code coverage reports. Thus, it's benefitial, to remove intermediate artifacts after merging reports into one summary
report.
.. topic:: Intermediate cleanups
* :ref:`JOBTMPL/IntermediateCleanup` - remove intermediate artifacts after merging reports into one summary report.
.. topic:: Final cleanups
* :ref:`JOBTMPL/ArtifactCleanup` - remove artifacts after publising results and creating release assets.
.. toctree::
:hidden:
IntermediateCleanup
ArtifactCleanup

View File

@@ -1,168 +0,0 @@
.. _JOBTMPL/CodeCoverage:
CoverageCollection
##################
This jobs runs the specified unit tests with activated code coverage collection (incl. branch coverage).
It uses pytest, pytest-cov and coverage.py in a single job run, thus it uses one fixed Python version (usually latest).
It generates HTML and Cobertura reports (XML), then it uploads the HTML report as an artifact and the jUnit test results
(XML) to `Codecov <https://about.codecov.io/>`__ and `Codacy <https://www.codacy.com/>`__.
Configuration options to ``pytest`` and ``coverage.py`` should be given via sections ``[tool.pytest.ini_options]`` and
``[tool.coverage.*]`` in a ``pyproject.toml`` file.
**Behavior:**
1. Checkout repository
2. Setup Python and install dependencies
3. Extract configuration from ``pyproject.toml`` or ``.coveragerc``.
4. Run unit tests and collect code coverage
5. Convert coverage data to a Cobertura XML file
6. Convert coverage data to a HTML report
7. Upload HTML report as an artifact
8. Publish Cobertura file to CodeCov
9. Publish Cobertura file to Codacy
**Preconditions:**
* A CodeCov account was created.
* A Codacy account was created.
**Requirements:**
Setup a secret (e.g. ``codacy_token``) in GitHub to handover the Codacy project token to the job.
**Dependencies:**
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :gh:`actions/upload-artifact`
* :gh:`codecov/codecov-action`
* :gh:`codacy/codacy-coverage-reporter-action`
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
Coverage:
uses: pyTooling/Actions/.github/workflows/CoverageCollection.yml@r0
with:
artifact: Coverage
secrets: inherit
Complex Example
===============
.. code-block:: yaml
jobs:
Coverage:
uses: pyTooling/Actions/.github/workflows/CoverageCollection.yml@r0
needs:
- Params
with:
python_version: ${{ needs.Params.outputs.python_version }}
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).codecoverage_html }}
secrets: inherit
Parameters
**********
python_version
==============
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| python_version | optional | string | 3.11 |
+----------------+----------+----------+----------+
Python version used for running unit tests.
requirements
============
+----------------+----------+----------+-------------------------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+===============================+
| requirements | optional | string | ``-r tests/requirements.txt`` |
+----------------+----------+----------+-------------------------------+
Python dependencies to be installed through pip.
tests_directory
===============
+-----------------+----------+----------+-----------+
| Parameter Name | Required | Type | Default |
+=================+==========+==========+===========+
| tests_directory | optional | string | ``tests`` |
+-----------------+----------+----------+-----------+
Path to the directory containing tests (test working directory).
unittest_directory
==================
+--------------------+----------+----------+-----------+
| Parameter Name | Required | Type | Default |
+====================+==========+==========+===========+
| unittest_directory | optional | string | ``unit`` |
+--------------------+----------+----------+-----------+
Path to the directory containing unit tests (relative to tests_directory).
coverage_config
===============
+-----------------+----------+----------+--------------------+
| Parameter Name | Required | Type | Default |
+=================+==========+==========+====================+
| coverage_config | optional | string | ``pyproject.toml`` |
+-----------------+----------+----------+--------------------+
Path to the ``.coveragerc`` file. Use ``pyproject.toml`` by default.
artifact
========
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| artifact | yes | string | — — — — |
+----------------+----------+----------+--------------+
Name of the coverage artifact.
Secrets
*******
codacy_token
============
+----------------+----------+----------+--------------+
| Secret Name | Required | Type | Default |
+================+==========+==========+==============+
| codacy_token | yes | string | — — — — |
+----------------+----------+----------+--------------+
Token to push result to codacy.
Results
*******
This job template has no output parameters.

View File

@@ -0,0 +1,18 @@
.. _JOBTMPL/Deprecated:
Deprecated
##########
The category *deprecated* collects outdated job templates:
CoverageCollection
replaced by :ref:`JOBTMPL/UnitTesting`
NightlyRelease
replaced by :ref:`JOBTMPL/PublishReleaseNotes`
BuildTheDocs
replaced by :ref:`JOBTMPL/SphinxDocumentation` and :ref:`JOBTMPL/LaTeXDocumentation`
.. #toctree::
:hidden:
NightlyRelease

View File

@@ -0,0 +1,188 @@
.. _JOBTMPL/LaTeXDocumentation:
.. index::
single: MikTeX; LaTeXDocumentation Template
single: GitHub Action Reusable Workflow; LaTeXDocumentation Template
LaTeXDocumentation
##################
The ``LaTeXDocumentation`` job template downloads an artifact containing a LaTeX document and translates to a PDF file
using MikTeX.
The translation process uses ``latexmk`` for handling multiple passes. The default LaTeX processor is ``xelatex``, but
can be switched by a parameter.
.. topic:: Features
* Translate a LaTeX document to PDF.
.. topic:: Behavior
1. Download the LaTeX artifact.
2. Build the PDF using ``latexmk``.
3. Upload the generated PDF as an artifact.
.. topic:: Dependencies
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* :gh:`addnab/docker-run-action`
* :dockerhub:`pytooling/miktex <pytooling/miktex:sphinx>`
.. _JOBTMPL/LaTeXDocumentation/Instantiation:
Instantiation
*************
.. code-block:: yaml
jobs:
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
Documentation:
uses: pyTooling/Actions/.github/workflows/SphinxDocumentation.yml@r6
needs:
- UnitTestingParams
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_html }}
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
PDFDocumentation:
uses: pyTooling/Actions/.github/workflows/LaTeXDocumentation.yml@r6
needs:
- UnitTestingParams
- Documentation
with:
document: pyEDAA.ProjectModel
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
pdf_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_pdf }}
.. _JOBTMPL/LaTeXDocumentation/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/LaTeXDocumentation/Inputs>`
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/LaTeXDocumentation/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/LaTeXDocumentation/Input/latex_artifact` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/LaTeXDocumentation/Input/document` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/LaTeXDocumentation/Input/processor` | no | string | ``'xelatex'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/LaTeXDocumentation/Input/pdf_artifact` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/LaTeXDocumentation/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/LaTeXDocumentation/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/LaTeXDocumentation/Inputs:
Input Parameters
****************
.. _JOBTMPL/LaTeXDocumentation/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/LaTeXDocumentation/Input/latex_artifact:
latex_artifact
==============
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the LaTeX document to translate.
.. _JOBTMPL/LaTeXDocumentation/Input/document:
document
========
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid document name.
:Description: Name of the LaTeX document
.. _JOBTMPL/LaTeXDocumentation/Input/processor:
processor
=========
:Type: string
:Required: no
:Default Value: ``'xelatex'``
:Possible Values: Any supported LaTeX processor supported by MikTeX and ``latexmk``.
:Description: Name of the used LaTeX processor.
.. _JOBTMPL/LaTeXDocumentation/Input/pdf_artifact:
pdf_artifact
============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the generated PDF document.
:Optimization:
.. hint::
If this parameter is empty, no PDF file will be generated and no artifact will be uploaded.
.. _JOBTMPL/LaTeXDocumentation/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/LaTeXDocumentation/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/LaTeXDocumentation/Optimizations:
Optimizations
*************
The following optimizations can be used to reduce the template's runtime.
Disable PDF generation and PDF artifact
If parameter :ref:`JOBTMPL/LaTeXDocumentation/Input/pdf_artifact` is empty, no PDF will be generated and uploaded.

View File

@@ -0,0 +1,178 @@
.. _JOBTMPL/PublishToGitHubPages:
.. index::
single: GitHub Pages; PublishToGitHubPages Template
single: GitHub Action Reusable Workflow; PublishToGitHubPages Template
PublishToGitHubPages
####################
This job template publishes HTML content from artifacts of other jobs to GitHub Pages.
.. topic:: Features
tbd
.. topic:: Behavior
1. Checkout repository.
2. Download artifacts.
3. Push HTML files to branch ``gh-pages``.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-PublishToGitHubPages.png
:width: 500px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
.. _JOBTMPL/PublishToGitHubPages/Instantiation:
Instantiation
*************
.. grid:: 2
.. grid-item::
:columns: 5
.. rubric:: Simple Example
.. code-block:: yaml
jobs:
BuildTheDocs:
# ...
PublishToGitHubPages:
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@r6
needs:
- BuildTheDocs
with:
doc: Documentation
.. grid-item::
:columns: 5
.. rubric:: Complex Example
.. code-block:: yaml
jobs:
PublishToGitHubPages:
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@r6
needs:
- Params
- BuildTheDocs
- Coverage
- StaticTypeCheck
with:
doc: ${{ fromJson(needs.Params.outputs.artifact_names).documentation_html }}
coverage: ${{ fromJson(needs.Params.outputs.artifact_names).codecoverage_html }}
typing: ${{ fromJson(needs.Params.outputs.artifact_names).statictyping_html }}
.. _JOBTMPL/PublishToGitHubPages/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/PublishToGitHubPages/Inputs>`
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/PublishToGitHubPages/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishToGitHubPages/Input/doc` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishToGitHubPages/Input/coverage` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishToGitHubPages/Input/typing` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/PublishToGitHubPages/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/PublishToGitHubPages/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/PublishToGitHubPages/Inputs:
Input Parameters
****************
.. _JOBTMPL/PublishToGitHubPages/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/PublishToGitHubPages/Input/doc:
doc
===
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid artifact name.
:Description: Name of the documentation artifact containing the HTML website.
.. _JOBTMPL/PublishToGitHubPages/Input/coverage:
coverage
========
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the coverage artifact containing the HTML coverage report, which will be integrated as a
subdirectory.
.. _JOBTMPL/PublishToGitHubPages/Input/typing:
typing
======
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the type checking artifact containing the HTML type checker report, which will be integrated
as a subdirectory.
.. _JOBTMPL/PublishToGitHubPages/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/PublishToGitHubPages/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/PublishToGitHubPages/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,327 @@
.. _JOBTMPL/SphinxDocumentation:
.. index::
single: Sphinx; SphinxDocumentation Template
single: GitHub Action Reusable Workflow; SphinxDocumentation Template
SphinxDocumentation
###################
The ``SphinxDocumentation`` job template compiles the ReStructured Text documentation using :term:`Sphinx` to an HTML
website and a LaTeX documentation. This LaTeX document can be translated using e.g. :term:`MikTeX` to a PDF file.
.. topic:: Features
* Build documentation using Sphinx as HTML and upload as artifact. |br|
(see :ref:`JOBTMPL/SphinxDocumentation/Input/html_artifact`).
* Build documentation using Sphinx as LaTeX and upload as artifact. |br|
(see :ref:`JOBTMPL/SphinxDocumentation/Input/latex_artifact`).
* Workaround `sphinx-doc/sphinx#13189 <https://github.com/sphinx-doc/sphinx/issues/13189>`__
* Workaround `sphinx-doc/sphinx#13190 <https://github.com/sphinx-doc/sphinx/issues/13190>`__
* Optionally: download code coverage artifact (JSON format) given by :ref:`JOBTMPL/SphinxDocumentation/Input/coverage_json_artifact`.
* Optionally: download unit test report artifact (XML format) given by :ref:`JOBTMPL/SphinxDocumentation/Input/unittest_xml_artifact`.
.. topic:: Behavior
1. Checkout repository.
2. Install system dependencies.
3. Setup Python environment and install Python dependencies.
4. Download optional artifacts for integration of further reports into the documentation.
5. Build the HTML documentation using Sphinx.
6. Build the LaTeX documentation using Sphinx.
1. Apply LaTeX workaround I.
2. Apply LaTeX workaround II.
7. Upload the HTML and LaTeX artifacts.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-SphinxDocumentation.png
:width: 600px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* apt
* `graphviz <https://graphviz.org/>`__
* pip
* :pypi:`wheel`
* Python packages specified via :ref:`JOBTMPL/SphinxDocumentation/Input/requirements` parameter.
.. _JOBTMPL/SphinxDocumentation/Instantiation:
Instantiation
*************
.. code-block:: yaml
jobs:
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
Documentation:
uses: pyTooling/Actions/.github/workflows/SphinxDocumentation.yml@r6
needs:
- UnitTestingParams
with:
python_version: ${{ needs.UnitTestingParams.outputs.python_version }}
html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_html }}
latex_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).documentation_latex }}
.. _JOBTMPL/SphinxDocumentation/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/SphinxDocumentation/Inputs>`
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=========================================================================+==========+================+===================================================================+
| :ref:`JOBTMPL/SphinxDocumentation/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/python_version` | no | string | ``'3.14'`` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/requirements` | no | string | ``'-r doc/requirements.txt'`` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/doc_directory` | no | string | ``'doc'`` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/coverage_json_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/coverage_report_json` | no | string (JSON) | :jsoncode:`{"directory": "report/coverage"}` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/unittest_xml_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/unittest_xml` | no | string (JSON) | :jsoncode:`{"directory": "report/unit"}` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/html_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/SphinxDocumentation/Input/latex_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/SphinxDocumentation/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/SphinxDocumentation/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/SphinxDocumentation/Inputs:
Input Parameters
****************
.. _JOBTMPL/SphinxDocumentation/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/SphinxDocumentation/Input/python_version:
.. include:: ../_python_version.rst
.. _JOBTMPL/SphinxDocumentation/Input/requirements:
requirements
============
:Type: string
:Required: no
:Default Value: ``'-r doc/requirements.txt'``
:Possible Values: Any valid list of parameters for ``pip install``. |br|
Either a requirements file can be referenced using ``'-r path/to/requirements.txt'``, or a list of
packages can be specified using a space separated list like ``'Sphinx sphinx_rtd_theme sphinxcontrib-mermaid'``.
:Description: Python dependencies to be installed through *pip*.
.. _JOBTMPL/SphinxDocumentation/Input/doc_directory:
doc_directory
=============
:Type: string
:Required: no
:Default Value: ``'doc'``
:Possible Values: Any valid directory or sub-directory.
:Description: Directory where the Sphinx documentation is located.
.. attention::
When this parameter gets changed, :ref:`JOBTMPL/SphinxDocumentation/Input/requirements` needs to be
adjusted as well.
.. _JOBTMPL/SphinxDocumentation/Input/coverage_json_artifact:
coverage_json_artifact
======================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the code coverage report in JSON format.
.. _JOBTMPL/SphinxDocumentation/Input/coverage_report_json:
coverage_report_json
====================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/coverage",
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the code coverage JSON report will be extracted from
:ref:`artifact <JOBTMPL/SphinxDocumentation/Input/coverage_json_artifact>`.
:Description: Directory as JSON object where the code coverage JSON report will be extracted. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
Documentation:
uses: pyTooling/Actions/.github/workflows/SphinxDocumentation.yml@r6
needs:
- ConfigParams
with:
...
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
.. _JOBTMPL/SphinxDocumentation/Input/unittest_xml_artifact:
unittest_xml_artifact
=====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the unittest XML report summary in XML format.
.. _JOBTMPL/SphinxDocumentation/Input/unittest_xml:
unittest_xml
============
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/unit",
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the unittest JUnit XML report will be extracted from
:ref:`artifact <JOBTMPL/SphinxDocumentation/Input/unittest_xml_artifact>`.
:Description: Directory as JSON object where the unittest JUnit XML report will be extracted. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
Documentation:
uses: pyTooling/Actions/.github/workflows/SphinxDocumentation.yml@r6
needs:
- ConfigParams
with:
...
unittest_xml: ${{ needs.ConfigParams.outputs.unittest_xml }}
.. _JOBTMPL/SphinxDocumentation/Input/html_artifact:
html_artifact
=============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the generated HTML website.
:Optimization:
.. hint::
If this parameter is empty, no HTML website will be generated and no artifact will be uploaded.
.. _JOBTMPL/SphinxDocumentation/Input/latex_artifact:
latex_artifact
==============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the generated LaTeX document and resource files (e.g., images).
:Optimization:
.. hint::
If this parameter is empty, no LaTeX document will be generated and no artifact will be uploaded.
.. _JOBTMPL/SphinxDocumentation/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/SphinxDocumentation/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/SphinxDocumentation/Optimizations:
Optimizations
*************
The following optimizations can be used to reduce the template's runtime.
Disable HTML website generation and HTML artifact
If parameter :ref:`JOBTMPL/SphinxDocumentation/Input/html_artifact` is empty, no HTML website will be generated and
uploaded.
Disable LaTeX document generation and LaTeX artifact
If parameter :ref:`JOBTMPL/SphinxDocumentation/Input/latex_artifact` is empty, no LaTeX document will be generated and
uploaded.

View File

@@ -0,0 +1,17 @@
.. _JOBTMPL/Documentation:
Documentation
#############
The category *documentation* provides workflow templates implementing
* :ref:`JOBTMPL/SphinxDocumentation` - Translate ReStructured Text to HTML and LaTeX using Sphinx.
* :ref:`JOBTMPL/LaTeXDocumentation` - Translate LaTeX to PDF using MikTeX.
* :ref:`JOBTMPL/PublishToGitHubPages` - Upload HTML content to GitHub Pages.
.. toctree::
:hidden:
SphinxDocumentation
LaTeXDocumentation
PublishToGitHubPages

View File

@@ -1,106 +0,0 @@
.. _JOBTMPL/Package:
Package
#######
This job packages the Python source code as a source package (``*.tar.gz``) and wheel package (``*.whl``) and uploads it
as an artifact.
**Behavior:**
1. Checkout repository
2. Setup Python and install dependencies
3. Package Python sources:
* If parameter ``requirements`` is empty, use ``build`` package and run ``python build``.
* If parameter ``requirements`` is ``no-isolation``, use ``build`` package in *no-isolation* mode and run
``python build``.
* If parameter ``requirements`` is non-empty, use ``setuptools`` package and run ``python setup.py``.
**Dependencies:**
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :gh:`actions/upload-artifact`
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
Package:
uses: pyTooling/Actions/.github/workflows/Package.yml@r0
with:
artifact: Package
Complex Example
===============
.. code-block:: yaml
jobs:
Package:
uses: pyTooling/Actions/.github/workflows/Package.yml@r0
needs:
- Params
- Coverage
with:
python_version: ${{ needs.Params.outputs.python_version }}
requirements: -r build/requirements.txt
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).package_all }}
Parameters
**********
python_version
==============
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| python_version | optional | string | 3.11 |
+----------------+----------+----------+----------+
Python version.
requirements
============
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| requirements | optional | string | ``""`` |
+----------------+----------+----------+----------+
Python dependencies to be installed through pip; if empty, use pyproject.toml through build.
artifact
========
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| artifact | yes | string | — — — — |
+----------------+----------+----------+----------+
Name of the package artifact.
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -0,0 +1,184 @@
.. _JOBTMPL/InstallPackage:
.. index::
single: pip; InstallPackage Template
single: GitHub Action Reusable Workflow; InstallPackage Template
InstallPackage (beta)
#####################
The ``InstallPackage`` job template takes a generated Python package and installs it on the target platform. Afterwards
the installation is verified. This aims for packaging and dependency mistakes in the package.
.. topic:: Features
* Install generated Python package on the target platform.
* Verify the installed package's version.
.. topic:: Behavior
* Download Python package as artifact.
* Prepare the Python environment.
* Install the Python package using :term:`pip`.
* Read out and verify the package version.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-InstallPackage.png
:width: 500px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`msys2/setup-msys2`
* :gh:`actions/setup-python`
* pip
* :pypi:`pip`
* :pypi:`wheel`
.. _JOBTMPL/InstallPackage/Instantiation:
Instantiation
*************
The following instantiation example creates a ``Install`` job derived from job template ``InstallPackage`` version
`@r6`. It installs the Python package on various platforms using a precomputed job-matrix created by job
``InstallParams``. This job uses the same ``Parameters`` job template as job ``UnitTestingParams``, which was used to
define parameters for the packaging job ``Package``.
.. code-block:: yaml
jobs:
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
InstallParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
python_version_list: ''
Package:
uses: pyTooling/Actions/.github/workflows/Package.yml@r6
needs:
- UnitTestingParams
with:
artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}
Install:
uses: pyTooling/Actions/.github/workflows/InstallPackage.yml@r6
needs:
- UnitTestingParams
- InstallParams
- Package
with:
jobs: ${{ needs.InstallParams.outputs.python_jobs }}
wheel: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).package_all }}
package_name: ${{ needs.UnitTestingParams.outputs.package_fullname }}
.. _JOBTMPL/InstallPackage/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/InstallPackage/Inputs>`
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=========================================================================+==========+==========+===================================================================================================================================+
| :ref:`JOBTMPL/InstallPackage/Input/jobs` | yes | string | — — — — |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/InstallPackage/Input/wheel` | yes | string | — — — — |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/InstallPackage/Input/package_name` | yes | string | — — — — |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/InstallPackage/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/InstallPackage/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/InstallPackage/Inputs:
Input Parameters
****************
.. _JOBTMPL/InstallPackage/Input/jobs:
jobs
====
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: A JSON string with an array of dictionaries with the following key-value pairs:
:sysicon: icon to display
:system: name of the system
:runs-on: virtual machine image and base operating system
:runtime: name of the runtime environment if not running natively on the VM image
:shell: name of the shell
:pyicon: icon for CPython or pypy
:python: Python version
:envname: full name of the selected environment
:Description: A JSON encoded job matrix to run multiple Python job variations.
.. _JOBTMPL/InstallPackage/Input/wheel:
wheel
=====
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid artifact name.
:Description: The artifact containing the packaged Python code as wheel.
.. _JOBTMPL/InstallPackage/Input/package_name:
package_name
============
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid Python package, subpackage or module name.
:Description: The package or module containing the version information as a string in ``__version__``.
.. _JOBTMPL/InstallPackage/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/InstallPackage/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/InstallPackage/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,175 @@
.. _JOBTMPL/Package:
.. index::
single: build; Package Template
single: GitHub Action Reusable Workflow; Package Template
Package
#######
This job packages the Python source code as a source package (``*.tar.gz``) and wheel package (``*.whl``) and uploads it
as an artifact.
.. topic:: Features
* Package source code as wheel and source distribution.
* Support packaging using :pypi:`build` (recommended) or :pypi:`setuptools`.
.. topic:: Behavior
1. Checkout repository.
2. Setup Python and install dependencies.
3. Package Python sources:
* If parameter :ref:`JOBTMPL/Package/Input/requirements` is empty, use :pypi:`build` for packaging and execute
``python -m build ...``.
* If parameter :ref:`JOBTMPL/Package/Input/requirements` is ``no-isolation``, use :pypi:`build` for packaging in
*no-isolation* mode executing ``python -m build --no-isolation ...``.
* If parameter :ref:`JOBTMPL/Package/Input/requirements` is non-empty, use :pypi:`setuptools` for package and
execute ``python setup.py ...``.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-Package.png
:width: 500px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* pip
* :pypi:`build`
* :pypi:`wheel`
.. _JOBTMPL/Package/Instantiation:
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
Package:
uses: pyTooling/Actions/.github/workflows/Package.yml@r6
with:
artifact: Package
Complex Example
===============
.. code-block:: yaml
jobs:
Package:
uses: pyTooling/Actions/.github/workflows/Package.yml@r6
needs:
- Params
with:
python_version: ${{ needs.Params.outputs.python_version }}
requirements: -r build/requirements.txt
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).package_all }}
.. _JOBTMPL/Package/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/Package/Inputs>`
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/Package/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Package/Input/python_version` | no | string | ``'3.14'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Package/Input/requirements` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Package/Input/artifact` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/Package/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/Package/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/Package/Inputs:
Input Parameters
****************
.. _JOBTMPL/Package/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/Package/Input/python_version:
.. include:: ../_python_version.rst
.. _JOBTMPL/Package/Input/requirements:
requirements
============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid list of parameters for ``pip install``. |br|
Either a requirements file can be referenced using ``'-r path/to/requirements.txt'``, or a list of
packages can be specified using a space separated list like ``'build wheel'``.
:Behavior: If the value is an empty string, :pypi:`build` is used for packaging. |br|
if the value is ``no-isolation``, :pypi:`build` is used in *no-isolation* mode for packaging. |br|
otherwise, a list of requirements is assumed and :pypi:`setuptools` is used for packaging.
:Description: Python dependencies to be installed through *pip*.
.. _JOBTMPL/Package/Input/artifact:
artifact
========
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the packaged Python code.
.. _JOBTMPL/Package/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/Package/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/Package/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,210 @@
.. _JOBTMPL/PublishOnPyPI:
.. index::
single: PyPI; PublishOnPyPI Template
single: twine; PublishOnPyPI Template
single: GitHub Action Reusable Workflow; PublishOnPyPI Template
PublishOnPyPI
#############
Publish a wheel (``*.whl``) packages and/or source (``*.tar.gz``) package to :term:`PyPI`.
.. topic:: Features
* Publish a Python package to :term:`PyPI`.
.. topic:: Behavior
1. Download package artifact
2. Publish source package(s) (``*.tar.gz``)
3. Publish wheel package(s) (``*.whl``)
4. Delete the artifact
.. topic:: Preconditions
1. A PyPI account was created and the package name is either not occupied or the user has access rights for that
package.
2. An access token was generated at PyPI, which can be used for uploading packages.
3. A secret (e.g. ``PYPI_TOKEN``) was setup in GitHub Actions to handover the PyPI token to the pipeline.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-PublishOnPyPI.png
:width: 500px
.. topic:: Dependencies
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`actions/setup-python`
* :gh:`geekyeggo/delete-artifact`
* pip
* :pypi:`wheel`
* :pypi:`twine`
.. _JOBTMPL/PublishOnPyPI/Instantiation:
Instantiation
*************
Simple Example
==============
The following example demonstrates how to publish the artifact named ``Package`` to PyPI on every pipeline run triggered
by a Git tag. A secret is forwarded from GitHub secrets to a job secret.
.. code-block:: yaml
jobs:
# ...
PublishOnPyPI:
uses: pyTooling/Actions/.github/workflows/PublishOnPyPI.yml@r6
if: startsWith(github.ref, 'refs/tags')
with:
artifact: Package
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
Complex Example
===============
In this more complex example, the job depends on a parameter creation (``Params``) and packaging job (``Package``). The
used Python version is overwritten by a parameter calculated in the ``Params`` jobs. Also the artifact name is managed
by that job. Finally, the list of requirements is overwritten to load a list of requirements from ``dist/requirements.txt``.
.. code-block:: yaml
jobs:
Params:
# ...
Package:
# ...
PublishOnPyPI:
uses: pyTooling/Actions/.github/workflows/PublishOnPyPI.yml@r6
if: startsWith(github.ref, 'refs/tags')
needs:
- Params
- Package
with:
python_version: ${{ needs.Params.outputs.python_version }}
requirements: -r dist/requirements.txt
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).package_all }}
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
.. seealso::
:ref:`JOBTMPL/Package`
.. _JOBTMPL/PublishOnPyPI/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/PublishOnPyPI/Inputs>`
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/PublishOnPyPI/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishOnPyPI/Input/python_version` | no | string | ``'3.14'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishOnPyPI/Input/requirements` | no | string | ``'wheel twine'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishOnPyPI/Input/artifact` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/PublishOnPyPI/Secrets>`
+-----------------------------------------------------------+----------+----------+--------------+
| Token Name | Required | Type | Default |
+===========================================================+==========+==========+==============+
| :ref:`JOBTMPL/PublishOnPyPI/Secret/PYPI_TOKEN` | no | string | — — — — |
+-----------------------------------------------------------+----------+----------+--------------+
.. rubric:: Goto :ref:`output parameters <JOBTMPL/PublishOnPyPI/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/PublishOnPyPI/Inputs:
Input Parameters
****************
.. _JOBTMPL/PublishOnPyPI/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/PublishOnPyPI/Input/python_version:
.. include:: ../_python_version.rst
.. _JOBTMPL/PublishOnPyPI/Input/requirements:
requirements
============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid list of parameters for ``pip install``. |br|
Either a requirements file can be referenced using ``'-r path/to/requirements.txt'``, or a list of
packages can be specified using a space separated list like ``'wheel twine'``.
:Description: Python dependencies to be installed through *pip*.
.. _JOBTMPL/PublishOnPyPI/Input/artifact:
artifact
========
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the packaged Python package(s).
.. _JOBTMPL/PublishOnPyPI/Secrets:
Secrets
*******
.. _JOBTMPL/PublishOnPyPI/Secret/PYPI_TOKEN:
PYPI_TOKEN
==========
:Type: string
:Required: no
:Default Value: — — — —
:Description: The token to publish and upload packages on :term:`PyPI`.
.. _JOBTMPL/PublishOnPyPI/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/PublishOnPyPI/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,17 @@
.. _JOBTMPL/Packaging:
Packaging
#########
The category *packaging* provides workflow templates implementing
* :ref:`JOBTMPL/Package` - Package Python packages and modules as wheel.
* :ref:`JOBTMPL/InstallPackage` - Install Python package (wheel) on target platforms.
* :ref:`JOBTMPL/PublishOnPyPI` - Upload Python package (wheel) to PyPI.
.. toctree::
:hidden:
Package
InstallPackage
PublishOnPyPI

View File

@@ -1,413 +0,0 @@
.. _JOBTMPL/Parameters:
Parameters
##########
The ``Parameters`` job template is a workaround for the limitations of GitHub Actions to handle global variables in
GitHub Actions workflows (see `actions/runner#480 <https://github.com/actions/runner/issues/480>`__.
It generates output parameters with artifact names and a job matrix to be used in later running jobs.
**Behavior:**
.. todo:: Parameters:Behavior Needs documentation.
**Dependencies:**
*None*
Instantiation
*************
Simple Example
==============
The following instantiation example creates a job `Params` derived from job template `Parameters` version `r0`. It only
requires a `name` parameter to create the artifact names.
.. code-block:: yaml
name: Pipeline
on:
push:
workflow_dispatch:
jobs:
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r0
with:
name: pyTooling
Complex Example
===============
The following instantiation example creates 3 jobs from the same template, but with differing input parameters. The
first job `UnitTestingParams` might be used to create a job matrix of unit tests. It creates the cross of default
systems (Windows, Ubuntu, macOS, MinGW64, UCRT64) and the given list of Python versions including some mypy versions. In
addition a list of excludes (marked as :deletion:`deletions`) and includes (marked as :addition:`additions`) is handed
over resulting in the following combinations:
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| Version | 3.8 🔴 | 3.9 🟠 | 3.10 🟡 | 3.11 🟢 | 3.12 🟢 | 3.13.a1 🟣 | pypy-3.8 🔴 | pypy-3.9 🟠 | pypy-3.10 🟡 |
+============+=============+=============+==============+==============+=========================+============+=============+==============================+===============================+
| Windows 🧊 | windows:3.8 | windows:3.9 | windows:3.10 | windows:3.11 | | | | :deletion:`windows:pypy-3.9` | :deletion:`windows:pypy-3.10` |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| Ubuntu 🐧 | ubuntu:3.8 | ubuntu:3.9 | ubuntu:3.10 | ubuntu:3.11 | :addition:`ubuntu:3.12` | | | ubuntu:pypy-3.9 | ubuntu:pypy-3.10 |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| macOS 🍎 | macos:3.8 | macos:3.9 | macos:3.10 | macos:3.11 | :addition:`macos:3.12` | | | macos:pypy-3.9 | macos:pypy-3.10 |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| MSYS 🟪 | | | | | | | | | |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| MinGW32 ⬛ | | | | | | | | | |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| MinGW64 🟦 | | | | mingw64:3.11 | | | | | |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| Clang32 🟫 | | | | | | | | | |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| Clang64 🟧 | | | | | | | | | |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
| UCRT64 🟨 | | | | | | | | | |
+------------+-------------+-------------+--------------+--------------+-------------------------+------------+-------------+------------------------------+-------------------------------+
.. code-block:: yaml
name: Pipeline
on:
push:
workflow_dispatch:
jobs:
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r0
with:
name: pyTooling
python_version_list: "3.8 3.9 3.10 3.11 pypy-3.9 pypy-3.10"
include_list: "ubuntu:3.12 macos:3.12"
exclude_list: "windows:pypy-3.9 windows:pypy-3.10"
PerformanceTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r0
with:
name: pyTooling
python_version_list: "3.11 3.12"
system_list: "ubuntu windows macos"
PlatformTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@dev
with:
name: pyTooling
python_version_list: "3.12"
system_list: "ubuntu windows macos mingw32 mingw64 clang64 ucrt64"
Parameters
**********
name
====
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| name | yes | string | — — — — |
+----------------+----------+----------+--------------+
The name of the library or package.
It's used to create artifact names.
python_version
==============
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| python_version | optional | string | ``3.12`` |
+----------------+----------+----------+----------+
Python version to be used for all jobs requiring a single Python version.
python_version_list
===================
+----------------------+----------+----------+----------------------------+
| Parameter Name | Required | Type | Default |
+======================+==========+==========+============================+
| python_version_list | optional | string | ``3.8 3.9 3.10 3.11 3.12`` |
+----------------------+----------+----------+----------------------------+
Space separated list of CPython versions and/or mypy version to run tests with.
**Possible values:**
* ``3.7``, ``3.8``, ``3.9``, ``3.10`` , ``3.11``, ``3.12``, ``3.13``
* ``pypy-3.7``, ``pypy-3.8``, ``pypy-3.9``, ``pypy-3.10``
+------+-----------+------------------+-----------------------------------------+
| Icon | Version | Maintained until | Comments |
+======+===========+==================+=========================================+
| ⚫ | 3.7 | 2023.06.27 | :red:`outdated` |
+------+-----------+------------------+-----------------------------------------+
| 🔴 | 3.8 | 2024.10 | |
+------+-----------+------------------+-----------------------------------------+
| 🟠 | 3.9 | 2025.10 | |
+------+-----------+------------------+-----------------------------------------+
| 🟡 | 3.10 | 2026.10 | |
+------+-----------+------------------+-----------------------------------------+
| 🟢 | 3.11 | 2027.10 | |
+------+-----------+------------------+-----------------------------------------+
| 🟢 | 3.12 | 2028.10 | :green:`latest` |
+------+-----------+------------------+-----------------------------------------+
| 🟣 | 3.13 | 2029.10 | Python 3.13 alpha (or RC) will be used. |
+------+-----------+------------------+-----------------------------------------+
| ⟲⚫ | pypy-3.7 | ????.?? | |
+------+-----------+------------------+-----------------------------------------+
| ⟲🔴 | pypy-3.8 | ????.?? | |
+------+-----------+------------------+-----------------------------------------+
| ⟲🟠 | pypy-3.9 | ????.?? | |
+------+-----------+------------------+-----------------------------------------+
| ⟲🟡 | pypy-3.10 | ????.?? | |
+------+-----------+------------------+-----------------------------------------+
system_list
===========
+----------------+----------+----------+-----------------------------------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=========================================+
| system_list | optional | string | ``ubuntu windows macos mingw64 ucrt64`` |
+----------------+----------+----------+-----------------------------------------+
Space separated list of systems to run tests on.
**Possible values:**
* Native systems: ``ubuntu``, ``windows``, ``macos``
* MSYS2: ``msys``, ``mingw32``, ``mingw64``, ``clang32``, ``clang64``, ``ucrt64``
+------+-----------+------------------------------+-----------------------------------------------------------------+
| Icon | System | Used version | Comments |
+======+===========+==============================+=================================================================+
| 🧊 | Windows | Windows Server 2022 (latest) | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🐧 | Ubuntu | Ubuntu 22.04 (LTS) (latest) | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🍎 | macOS | macOS Monterey 12 (latest) | While this marked latest, macOS Ventura 13 is already provided. |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟪 | MSYS | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| ⬛ | MinGW32 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟦 | MinGW64 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟫 | Clang32 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟧 | Clang64 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟨 | UCRT64 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
Source: `Images provided by GitHub <https://github.com/actions/runner-images>`__
include_list
============
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| include_list | optional | string | ``""`` |
+----------------+----------+----------+----------+
Space separated list of ``system:python`` items to be included into the list of test.
**Example:**
.. code-block:: yaml
include_list: "ubuntu:3.11 macos:3.11"
exclude_list
============
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| exclude_list | optional | string | ``""`` |
+----------------+----------+----------+----------+
Space separated list of ``system:python`` items to be excluded from the list of test.
**Example:**
.. code-block:: yaml
exclude_list: "windows:pypy-3.8 windows:pypy-3.9"
disable_list
============
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| disable_list | optional | string | ``""`` |
+----------------+----------+----------+----------+
Space separated list of ``system:python`` items to be temporarily disabled from the list of test.
Each disabled item creates a warning in the workflow log:
.. image:: /_static/GH_Workflow_DisabledJobsWarnings.png
:scale: 80 %
**Example:**
.. code-block:: yaml
disable_list: "windows:3.10 windows:3.11"
Secrets
*******
This job template needs no secrets.
Results
*******
python_version
==============
A single string parameter representing the default Python version that should be used across multiple jobs in the same
pipeline.
Such a parameter is needed as a workaround, because GitHub Actions doesn't support proper handling of global pipeline
variables. Thus, this job is used to compute an output parameter that can be reused in other jobs.
**Usage Example:**
.. code-block:: yaml
jobs:
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r0
with:
name: pyTooling
CodeCoverage:
uses: pyTooling/Actions/.github/workflows/CoverageCollection.yml@r0
needs:
- Params
with:
python_version: ${{ needs.Params.outputs.python_version }}
python_jobs
===========
A list of dictionaries containing a job description.
A job description contains the following key-value pairs:
* ``sysicon`` - icon to display
* ``system`` - name of the system
* ``runs-on`` - virtual machine image and base operating system
* ``runtime`` - name of the runtime environment if not running natively on the VM image
* ``shell`` - name of the shell
* ``pyicon`` - icon for CPython or pypy
* ``python`` - Python version
* ``envname`` - full name of the selected environment
**Usage Example:**
.. code-block:: yaml
jobs:
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r0
with:
name: pyTooling
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@dev
needs:
- Params
with:
jobs: ${{ needs.Params.outputs.python_jobs }}
This list can be unpacked with ``fromJson(...)`` in a job ``strategy:matrix:include``:
.. code-block:: yaml
UnitTesting:
name: ${{ matrix.sysicon }} ${{ matrix.pyicon }} Unit Tests using Python ${{ matrix.python }}
runs-on: ${{ matrix.runs-on }}
strategy:
matrix:
include: ${{ fromJson(inputs.jobs) }}
defaults:
run:
shell: ${{ matrix.shell }}
steps:
- name: 🐍 Setup Python ${{ matrix.python }}
if: matrix.system != 'msys2'
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
artifact_names
==============
A dictionary of artifact names sharing a common prefix.
The supported artifacts are:
* ``unittesting_xml`` - UnitTesting XML summary report
* ``unittesting_html`` - UnitTesting HTML summary report
* ``codecoverage_sqlite`` - Code Coverage internal database (SQLite)
* ``codecoverage_json`` - Code Coverage JSON report
* ``codecoverage_xml`` - Code Coverage XML report
* ``codecoverage_html`` - Code Coverage HTML report
* ``statictyping_html`` - Static Type Checking HTML report
* ``package_all`` - Packaged Python project (multiple formats)
* ``documentation_pdf`` - Documentation in PDF format
* ``documentation_html`` - Documentation in HTML format
**Usage Example:**
.. code-block:: yaml
jobs:
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r0
with:
name: pyTooling
Coverage:
uses: pyTooling/Actions/.github/workflows/CoverageCollection.yml@dev
needs:
- Params
with:
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).codecoverage_html }}
Params
======
.. attention:: ``Params`` is deprecated.
* ``params['unittesting']`` |rarr| ``artifact_names['unittesting_xml']``
* ``params['coverage']`` |rarr| ``artifact_names['codecoverage_xml']``
* ``params['typing']`` |rarr| ``artifact_names['statictyping_html']``
* ``params['package']`` |rarr| ``artifact_names['package_all']``
* ``params['doc']`` |rarr| ``artifact_names['documentation_html']``

View File

@@ -0,0 +1,462 @@
.. _JOBTMPL/PublishCoverageResults:
.. index::
single: CodeCov; PublishCoverageResults Template
single: Codacy; PublishCoverageResults Template
single: Coverage.py; PublishCoverageResults Template
single: GitHub Action Reusable Workflow; PublishCoverageResults Template
PublishCoverageResults
######################
The ``PublishCoverageResults`` job template downloads artifacts provided by :ref:`JOBTMPL/UnitTesting` containing code
coverage databases created by :term:`Coverage.py`. These databases are then merged and converted into various output
formats like Cobertura XML, JSON or HTML. These outputs are either uploaded as a new artifact or can be published to
cloud services like :term:`CodeCov` or :term:`Codacy`.
.. topic:: Features
* Merge SQLite code coverage databases generated by Coverage.py into a single
SQLite database.
* Convert SQLite database to Cobertura XML format.
* Convert SQLite database to Coverage.py JSON format.
* Convert SQLite database to HTML code coverage report.
* Publish code coverage at CodeCov.
* Publish code coverage as Codacy.
.. topic:: Behavior
1. Checkout repository.
2. Download artifact matching the :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_artifacts_pattern`.
3. Install Python dependencies especially :pypi:`coverage`.
4. Rename SQLite database files within artifact download directory to match the required filename pattern for
Coverage.py's merge operation.
5. Report code coverage as table into job log.
6. Convert code coverage to Cobertura XML format.
7. Convert code coverage to JSON format.
8. Convert code coverage to HTML report (website).
9. Upload merged SQLite database as artifact.
10. Upload Cobertura XML file as artifact.
11. Upload JSON file as artifact.
12. Upload HTML report as artifact.
13. Publish Cobertura report to CodeCov.
14. Publish Cobertura report to Codacy.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-PublishCoverageResults.png
:width: 600px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* pip
* :pypi:`coverage`
* :pypi:`tomli`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* :gh:`codecov/codecov-action`
* :gh:`codacy/codacy-coverage-reporter-action`
.. _JOBTMPL/PublishCoverageResults/Instantiation:
Instantiation
*************
The following
.. code-block:: yaml
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
with:
package_name: myPackage
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- ConfigParams
- UnitTestingParams
with:
jobs: ${{ needs.UnitTestingParams.outputs.python_jobs }}
coverage_sqlite_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}
PublishCoverageResults:
uses: pyTooling/Actions/.github/workflows/PublishCoverageResults.yml@r6
needs:
- ConfigParams
- UnitTestingParams
- UnitTesting
with:
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
coverage_json_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_json }}
coverage_html_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_html }}
codecov: 'true'
codacy: 'true'
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CODACY_TOKEN: ${{ secrets.CODACY_TOKEN }}
.. _JOBTMPL/PublishCoverageResults/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/PublishCoverageResults/Inputs>`
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=============================================================================+==========+================+==========================================================================================================================+
| :ref:`JOBTMPL/PublishCoverageResults/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_artifacts_pattern` | no | string | ``'*-CodeCoverage-SQLite-*'`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_config` | no | string | ``'pyproject.toml'`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_report_xml` | no | string (JSON) | :jsoncode:`{"directory": "report/coverage", "filename": "coverage.xml", "fullpath": "report/coverage/coverage.xml"}` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_report_json` | no | string (JSON) | :jsoncode:`{"directory": "report/coverage", "filename": "coverage.json", "fullpath": "report/coverage/coverage.json"}` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_report_html` | no | string (JSON) | :jsoncode:`{"directory": "report/coverage/html"}` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_sqlite_artifact` | no | string | ``''`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_xml_artifact` | no | string | ``''`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_json_artifact` | no | string | ``''`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_html_artifact` | no | string | ``''`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/codecov` | no | string | ``'false'`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishCoverageResults/Input/codacy` | no | string | ``'false'`` |
+-----------------------------------------------------------------------------+----------+----------------+--------------------------------------------------------------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/PublishCoverageResults/Secrets>`
+-----------------------------------------------------------+----------+----------+--------------+
| Token Name | Required | Type | Default |
+===========================================================+==========+==========+==============+
| :ref:`JOBTMPL/PublishCoverageResults/Secret/CODECOV_TOKEN`| no | string | — — — — |
+-----------------------------------------------------------+----------+----------+--------------+
| :ref:`JOBTMPL/PublishCoverageResults/Secret/CODACY_TOKEN` | no | string | — — — — |
+-----------------------------------------------------------+----------+----------+--------------+
.. rubric:: Goto :ref:`output parameters <JOBTMPL/PublishCoverageResults/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/PublishCoverageResults/Inputs:
Input Parameters
****************
.. _JOBTMPL/PublishCoverageResults/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/PublishCoverageResults/Input/coverage_artifacts_pattern:
coverage_artifacts_pattern
==========================
:Type: string
:Required: no
:Default Value: ``'*-CodeCoverage-SQLite-*'``
:Possible Values: Any valid artifact matching pattern using fixed text and ``*`` characters.
:Description: tbd
.. _JOBTMPL/PublishCoverageResults/Input/coverage_config:
coverage_config
===============
:Type: string
:Required: no
:Default Value: ``'pyproject.toml'``
:Possible Values: Any valid path to a :file:`pyproject.toml` file. |br|
Alternatively, path to a :file:`.coveragerc` file (deprecated).
:Description: Path to a Python project configuration file for extraction of project settings.
:Example:
.. grid:: 2
.. grid-item::
:columns: 5
.. rubric:: Extracted values from :file:`pyproject.toml`
.. code-block:: toml
[tool.pytest]
junit_xml = "report/unit/UnittestReportSummary.xml"
[tool.pyedaa-reports]
junit_xml = "report/unit/unittest.xml"
[tool.coverage.xml]
output = "report/coverage/coverage.xml"
[tool.coverage.json]
output = "report/coverage/coverage.json"
[tool.coverage.html]
directory = "report/coverage/html"
title="Code Coverage of pyDummy"
.. _JOBTMPL/PublishCoverageResults/Input/coverage_report_xml:
coverage_report_xml
===================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/coverage",
"filename": "coverage.xml",
"fullpath": "reports/coverage/coverage.xml"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the code coverage report in Cobertura XML format will be
saved.
:filename: Filename of the generated Cobertura XML report. |br|
Any valid XML filename.
:fullpath: The concatenation of both previous fields using the ``/`` separator.
:Description: Directory, filename and fullpath as JSON object where the code coverage report in Cobertura XML format
will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
PublishCoverageResults:
uses: pyTooling/Actions/.github/workflows/PublishCoverageResults.yml@r6
needs:
- ConfigParams
with:
...
coverage_report_xml: ${{ needs.ConfigParams.outputs.coverage_report_xml }}
.. _JOBTMPL/PublishCoverageResults/Input/coverage_report_json:
coverage_report_json
====================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/coverage",
"filename": "coverage.json",
"fullpath": "reports/coverage/coverage.json"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the code coverage report in Coverage.py's JSON format
will be saved.
:filename: Filename of the generated Coverage.py JSON report. |br|
Any valid JSON filename.
:fullpath: The concatenation of both previous fields using the ``/`` separator.
:Description: Directory, filename and fullpath as JSON object where the code coverage report in Coverage.py's JSON
format will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
PublishCoverageResults:
uses: pyTooling/Actions/.github/workflows/PublishCoverageResults.yml@r6
needs:
- ConfigParams
with:
...
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
.. _JOBTMPL/PublishCoverageResults/Input/coverage_report_html:
coverage_report_html
====================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/coverage/html"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the code coverage report in HTML format will be saved.
:Description: Directory as JSON object where the code coverage report in HTML format will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
PublishCoverageResults:
uses: pyTooling/Actions/.github/workflows/PublishCoverageResults.yml@r6
needs:
- ConfigParams
with:
...
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
.. _JOBTMPL/PublishCoverageResults/Input/coverage_sqlite_artifact:
coverage_sqlite_artifact
========================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the merged SQLite code coverage database.
.. _JOBTMPL/PublishCoverageResults/Input/coverage_xml_artifact:
coverage_xml_artifact
=====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the merged code coverage report as Cobertura XML file.
.. _JOBTMPL/PublishCoverageResults/Input/coverage_json_artifact:
coverage_json_artifact
======================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the merged code coverage report as Coverage.py JSON file.
.. _JOBTMPL/PublishCoverageResults/Input/coverage_html_artifact:
coverage_html_artifact
======================
:Type: string
:Required: no
:Default Value: ``'report/coverage/html'``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the merged code coverage report as HTML report.
.. _JOBTMPL/PublishCoverageResults/Input/codecov:
codecov
=======
:Type: string
:Required: no
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: If *true*, publish code coverage results to CodeCov.
.. _JOBTMPL/PublishCoverageResults/Input/codacy:
codacy
======
:Type: string
:Required: no
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: If *true*, publish code coverage results to Codacy.
.. _JOBTMPL/PublishCoverageResults/Secrets:
Secrets
*******
The workflow template uses the following secrets to publish results to other services.
.. _JOBTMPL/PublishCoverageResults/Secret/CODECOV_TOKEN:
CODECOV_TOKEN
=============
:Type: string
:Required: no
:Default Value: — — — —
:Description: The token to publish code coverage and unit test results to :term:`CodeCov`.
.. _JOBTMPL/PublishCoverageResults/Secret/CODACY_TOKEN:
CODACY_TOKEN
============
:Type: string
:Required: no
:Default Value: — — — —
:Description: The token to publish code coverage results to :term:`Codacy`.
.. _JOBTMPL/PublishCoverageResults/Outputs:
Outputs
*******
The following optimizations can be used to reduce the template's runtime.
Disable code coverage SQLite database artifact upload
If parameter :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_sqlite_artifact` is empty, the collected code coverage database
(SQLlite format) wont be uploaded as an artifact.
Disable code coverage report conversion to the Cobertura XML format.
If parameter :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_xml_artifact` is empty, no Cobertura XML file will be generated
from code coverage report. As no Cobertura XML file exists, no code coverage XML artifact will be uploaded.
Disable code coverage report conversion to the *Coverage.py* JSON format.
If parameter :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_json_artifact` is empty, no *Coverage.py* JSON file will be
generated from code coverage report. As no JSON file exists, no code coverage JSON artifact will be uploaded.
Disable code coverage report conversion to an HTML website.
If parameter :ref:`JOBTMPL/PublishCoverageResults/Input/coverage_html_artifact` is empty, no coverage report HTML report will be
generated from code coverage report. As no HTML report exists, no code coverage HTML artifact will be uploaded.
.. todo:: PublishCoverageResults:: Disable publishing to codecov and codacy. |br| If upload is enabled, XML will be always generated.

View File

@@ -0,0 +1,344 @@
.. _JOBTMPL/PublishTestResults:
.. index::
single: CodeCov; PublishTestResults Template
single: pyEDAA.Reports; PublishTestResults Template
single: Test Reporter; PublishTestResults Template
single: GitHub Action Reusable Workflow; PublishTestResults Template
PublishTestResults
##################
This job downloads all matching JUnit report artifacts, merged the reports into one single report and then publishes the
JUnit test summary reports to multiple services for visualization and longterm statistical tracking.
Supported services are:
* GitHub Actions job results using :term:`Test Reporter`
* :term:`Codecov`
.. topic:: Features
* Merge multiple JUnit XML reports generated by pytest into a single JUnit XML report.
* Publish unit test results to currently running pipeline as job result.
* Publish unit test results to CodeCov.
.. topic:: Behavior
1. Checkout repository
2. Download multiple artifacts containing test report summaries in JUnit XML format conforming to an artifact name
pattern (see :ref:`JOBTMPL/PublishTestResults/Input/unittest_artifacts_pattern`) for limiting the number of
downloaded artifacts and the hereby generated traffic.
3. Rename the found JUnit XML files.
4. Merge all found JUnit XML files using :term:`pyEDAA.Reports` into a new JUnit XML file. |br|
Optionally, apply certain transformation and cleanup operations to the JUnit report structure.
5. Publish test results as a markdown report page to GitHub Actions using :term:`Test Reporter`.
6. Publish test results to :term:`Codecov` using :gh:`codecov/test-results-action`.
.. topic:: Job Execution
.. grid:: 2
.. grid-item::
:columns: 4
.. rubric:: Job Steps
.. image:: ../../_static/pyTooling-Actions-PublishTestResults.png
:width: 500px
.. grid-item::
:columns: 4
.. rubric:: Generated Dorny Report below Pipeline Graph
.. image:: ../../_static/pyTooling-Actions-PublishTestResults-Dorny.png
:width: 500px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* pip
* :pypi:`pyEDAA.Reports`
* :gh:`dorny/test-reporter`
* :gh:`codecov/test-results-action`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
.. _JOBTMPL/PublishTestResults/Instantiation:
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
PublishTestResults:
uses: pyTooling/Actions/.github/workflows/PublishTestResults.yml@r6
Complex Example
===============
.. code-block:: yaml
jobs:
CodeCoverage:
# ...
UnitTesting:
# ...
PublishTestResults:
uses: pyTooling/Actions/.github/workflows/PublishTestResults.yml@r6
needs:
- CodeCoverage
- UnitTesting
.. _JOBTMPL/PublishTestResults/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/PublishTestResults/Inputs>`
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+=====================================================================+
| :ref:`JOBTMPL/PublishTestResults/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/unittest_artifacts_pattern` | no | string | ``'*-UnitTestReportSummary-XML-*'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/merged_junit_filename` | no | string | ``'Unittesting.xml'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/merged_junit_artifact` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/merge-input-dialect` | no | string | ``'pyTest-JUnit'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/merge-output-dialect` | no | string | ``'pyTest-JUnit'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/additional_merge_args` | no | string | ``'"--pytest=rewrite-dunder-init;reduce-depth:pytest.tests.unit"'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/testsuite-summary-name` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/publish` | no | string | ``'true'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/report_title` | no | string | ``'Unit Test Results'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/dorny` | no | string | ``'true'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/codecov` | no | string | ``'false'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishTestResults/Input/codecov_flags` | no | string | ``'unittest'`` |
+---------------------------------------------------------------------+----------+----------+---------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/PublishTestResults/Secrets>`
+-----------------------------------------------------------+----------+----------+--------------+
| Token Name | Required | Type | Default |
+===========================================================+==========+==========+==============+
| :ref:`JOBTMPL/PublishTestResults/Secret/CODECOV_TOKEN` | no | string | — — — — |
+-----------------------------------------------------------+----------+----------+--------------+
.. rubric:: Goto :ref:`output parameters <JOBTMPL/PublishTestResults/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/PublishTestResults/Inputs:
Input Parameters
****************
.. _JOBTMPL/PublishTestResults/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/PublishTestResults/Input/unittest_artifacts_pattern:
unittest_artifacts_pattern
==========================
:Type: string
:Required: no
:Default Value: ``'*-UnitTestReportSummary-XML-*'``
:Possible Values: Any valid artifact matching pattern using fixed text and ``*`` characters.
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/merged_junit_filename:
merged_junit_filename
=====================
:Type: string
:Required: no
:Default Value: ``'Unittesting.xml'``
:Possible Values: Any valid filename suitable for a JUnit XML report.
:Description: The filename for the merged JUnit report in XML format. |br|
See :ref:`JOBTMPL/PublishTestResults/Input/merge-output-dialect` for the used JUnit dialect in the
merged report file.
.. _JOBTMPL/PublishTestResults/Input/merged_junit_artifact:
merged_junit_artifact
=====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description:
.. _JOBTMPL/PublishTestResults/Input/merge-input-dialect:
merge-input-dialect
===================
:Type: string
:Required: no
:Default Value: ``'pyTest-JUnit'``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/merge-output-dialect:
merge-output-dialect
====================
:Type: string
:Required: no
:Default Value: ``'pyTest-JUnit'``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/additional_merge_args:
additional_merge_args
=====================
:Type: string
:Required: no
:Default Value: ``'"--pytest=rewrite-dunder-init;reduce-depth:pytest.tests.unit"'``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/testsuite-summary-name:
testsuite-summary-name
======================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/publish:
publish
=======
:Type: string
:Required: no
:Default Value: ``'true'``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/report_title:
report_title
============
:Type: string
:Required: no
:Default Value: ``'Unit Test Results'``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/dorny:
dorny
=====
:Type: string
:Required: no
:Default Value: ``'true'``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/codecov:
codecov
=======
:Type: string
:Required: no
:Default Value: ``'false'``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Input/codecov_flags:
codecov_flags
=============
:Type: string
:Required: no
:Default Value: ``'unittest'``
:Possible Values: tbd
:Description: tbd
.. _JOBTMPL/PublishTestResults/Secrets:
Secrets
*******
.. _JOBTMPL/PublishTestResults/Secret/CODECOV_TOKEN:
CODECOV_TOKEN
=============
:Type: string
:Required: no
:Default Value: — — — —
:Description: The token to publish unit test results on :term:`CodeCov`.
.. _JOBTMPL/PublishTestResults/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/PublishTestResults/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,15 @@
.. _JOBTMPL/Publish:
Publish
#######
The category *publish* provides workflow templates implementing
* :ref:`JOBTMPL/PublishTestResults`- Merge and publish unit test results.
* :ref:`JOBTMPL/PublishCoverageResults` - Merge and publish code coverage results.
.. toctree::
:hidden:
PublishTestResults
PublishCoverageResults

View File

@@ -1,139 +0,0 @@
.. _JOBTMPL/PyPI:
PublishOnPyPI
#############
Publish a source (``*.tar.gz``) package and/or wheel (``*.whl``) packages to `PyPI <https://pypi.org/>`__.
**Behavior:**
1. Download package artifact
2. Publish source package(s) (``*.tar.gz``)
3. Publish wheel package(s) (``*.whl``)
4. Delete the artifact
**Preconditions:**
A PyPI account was created and the package name is either not occupied or the user has access rights for that package.
**Requirements:**
Setup a secret (e.g. ``PYPI_TOKEN``) in GitHub to handover the PyPI token to the job.
**Dependencies:**
* :gh:`actions/download-artifact`
* :gh:`actions/setup-python`
* :gh:`geekyeggo/delete-artifact`
Instantiation
*************
Simple Example
==============
The following example demonstrates how to publish the artifact named ``Package`` to PyPI on every pipeline run triggered
by a Git tag. A secret is forwarded from GitHub secrets to a job secret.
.. code-block:: yaml
jobs:
# ...
PublishOnPyPI:
uses: pyTooling/Actions/.github/workflows/PublishOnPyPI.yml@r0
if: startsWith(github.ref, 'refs/tags')
with:
artifact: Package
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
Complex Example
===============
In this more complex example, the job depends on a parameter creation (``Params``) and packaging job (``Package``). The
used Python version is overwritten by a parameter calculated in the ``Params`` jobs. Also the artifact name is managed
by that job. Finally, the list of requirements is overwritten to load a list of requirements from ``dist/requirements.txt``.
.. code-block:: yaml
jobs:
Params:
# ...
Package:
# ...
PublishOnPyPI:
uses: pyTooling/Actions/.github/workflows/PublishOnPyPI.yml@r0
if: startsWith(github.ref, 'refs/tags')
needs:
- Params
- Package
with:
python_version: ${{ needs.Params.outputs.python_version }}
requirements: -r dist/requirements.txt
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).package_all }}
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
Parameters
**********
python_version
==============
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| python_version | optional | string | ``3.11`` |
+----------------+----------+----------+----------+
Python version used for uploading the package contents via `twine` to PyPI.
requirements
============
+----------------+----------+----------+-----------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=================+
| requirements | optional | string | ``wheel twine`` |
+----------------+----------+----------+-----------------+
List of requirements to be installed for uploading the package contents to PyPI.
artifact
========
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| artifact | yes | string | — — — — |
+----------------+----------+----------+--------------+
Name of the artifact containing the package(s).
Secrets
*******
PYPI_TOKEN
==========
+----------------+----------+----------+--------------+
| Secret Name | Required | Type | Default |
+================+==========+==========+==============+
| PYPI_TOKEN | yes | string | — — — — |
+----------------+----------+----------+--------------+
The token to access the package at PyPI for uploading new data.
Results
*******
This job template has no output parameters.

View File

@@ -1,88 +0,0 @@
.. _JOBTMPL/PublishTestResults:
PublishTestResults
##################
This job downloads all artifacts and uploads jUnit XML reports as a Markdown page to GitHub Actions to visualize the
results a an item in the job list. For publishing, :gh:`dorny/test-reporter` is used.
**Behavior:**
1. Checkout repository
2. Download (all) artifacts
3. Publish test results as a markdown report page to GitHub Actions.
.. note::
The :gh:`actions/download-artifact` does not support wildcards to specify a subset of artifacts for downloading.
Thus, all artifacts need to be downloaded.
**Dependencies:**
* :gh:`actions/checkout`
* :gh:`actions/download-artifact`
* :gh:`dorny/test-reporter`
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
PublishTestResults:
uses: pyTooling/Actions/.github/workflows/PublishTestResults.yml@r0
Complex Example
===============
.. code-block:: yaml
jobs:
CodeCoverage:
# ...
UnitTesting:
# ...
PublishTestResults:
uses: pyTooling/Actions/.github/workflows/PublishTestResults.yml@r0
needs:
- CodeCoverage
- UnitTesting
Parameters
**********
report_files
============
+----------------+----------+----------+---------------------------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=================================+
| report_files | optional | string | ``artifacts/**/*.xml`` |
+----------------+----------+----------+---------------------------------+
Pattern of jUnit report files to publish as Markdown.
The parameter can be a comma separated list. Wildcards are supported.
.. hint::
All artifacts are downloaded into directory ``artifacts``, thus the pattern should include this directory as a
prefix.
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -1,106 +0,0 @@
.. _JOBTMPL/PublishToGitHubPages:
PublishToGitHubPages
####################
This job publishes HTML content from artifacts of other jobs to GitHub Pages.
**Behavior:**
1. Checkout repository.
2. Download artifacts.
3. Push HTML files to branch ``gh-pages``.
**Dependencies:**
* :gh:`actions/checkout`
* :gh:`actions/download-artifact`
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
BuildTheDocs:
# ...
PublishToGitHubPages:
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@r0
needs:
- BuildTheDocs
with:
doc: Documentation
Complex Example
===============
.. code-block:: yaml
jobs:
PublishToGitHubPages:
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@r0
needs:
- Params
- BuildTheDocs
- Coverage
- StaticTypeCheck
with:
doc: ${{ fromJson(needs.Params.outputs.artifact_names).documentation_html }}
coverage: ${{ fromJson(needs.Params.outputs.artifact_names).codecoverage_html }}
typing: ${{ fromJson(needs.Params.outputs.artifact_names).statictyping_html }}
Parameters
**********
doc
===
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| doc | yes | string | — — — — |
+----------------+----------+----------+--------------+
Name of the documentation artifact.
coverage
========
+----------------+----------+----------+-----------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=================+
| coverage | optional | string | ``""`` |
+----------------+----------+----------+-----------------+
Name of the coverage artifact.
typing
======
+----------------+----------+----------+-----------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=================+
| typing | optional | string | ``""`` |
+----------------+----------+----------+-----------------+
Name of the typing artifact.
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -0,0 +1,32 @@
.. rubric:: Possible values
* ``3.8``, ``3.9``, ``3.10`` , ``3.11``, ``3.12``, ``3.13``, ``3.14``
* ``pypy-3.7``, ``pypy-3.8``, ``pypy-3.9``, ``pypy-3.10``, ``pypy-3.11``
+------+-----------+------------------+-----------------------------------------------+
| Icon | Version | Maintained until | Comments |
+======+===========+==================+===============================================+
| ⚫ | 3.8 | 2024.10 | :red:`outdated` |
+------+-----------+------------------+-----------------------------------------------+
| 🔴 | 3.9 | 2025.10 | |
+------+-----------+------------------+-----------------------------------------------+
| 🟠 | 3.10 | 2026.10 | |
+------+-----------+------------------+-----------------------------------------------+
| 🟡 | 3.11 | 2027.10 | |
+------+-----------+------------------+-----------------------------------------------+
| 🟢 | 3.12 | 2028.10 | |
+------+-----------+------------------+-----------------------------------------------+
| 🟢 | 3.13 | 2029.10 | :green:`latest CPython` |
+------+-----------+------------------+-----------------------------------------------+
| 🟣 | 3.14 | 2030.10 | Python 3.14 alpha, beta (or RC) will be used. |
+------+-----------+------------------+-----------------------------------------------+
| ⟲⚫ | pypy-3.7 | ????.?? | |
+------+-----------+------------------+-----------------------------------------------+
| ⟲⚫ | pypy-3.8 | ????.?? | |
+------+-----------+------------------+-----------------------------------------------+
| ⟲🔴 | pypy-3.9 | ????.?? | |
+------+-----------+------------------+-----------------------------------------------+
| ⟲🟠 | pypy-3.10 | ????.?? | |
+------+-----------+------------------+-----------------------------------------------+
| ⟲🟡 | pypy-3.11 | ????.?? | :green:`latest PyPy` |
+------+-----------+------------------+-----------------------------------------------+

View File

@@ -0,0 +1,150 @@
.. _JOBTMPL/CheckDocumentation:
.. index::
single: docstr_coverage; CheckDocumentation Template
single: interrogate; CheckDocumentation Template
single: GitHub Action Reusable Workflow; CheckDocumentation Template
CheckDocumentation
##################
The ``CheckDocumentation`` job checks the level of documentation coverage for Python files.
.. topic:: Features
* Check documentation coverage in Python code using :pypi:`docstr_coverage`.
* Check documentation coverage in Python code using :pypi:`interrogate`.
.. topic:: Behavior
1. Checkout repository.
2. Setup Python environment and install dependencies.
3. Run ``docstr_coverage``.
4. Run ``interrogate``.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-CheckDocumentation.png
:width: 600px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* pip
* :pypi:`docstr_coverage`
* :pypi:`interrogate`
.. _JOBTMPL/CheckDocumentation/Instantiation:
Instantiation
*************
The following instantiation example creates a ``Params`` job derived from job template ``Parameters`` version ``@r6``. It only
requires a `name` parameter to create the artifact names.
.. code-block:: yaml
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
with:
package_name: myPackage
DocCoverage:
uses: pyTooling/Actions/.github/workflows/CheckDocumentation.yml@r6
needs:
- ConfigParams
with:
directory: ${{ needs.ConfigParams.outputs.package_directory }}
.. seealso::
:ref:`JOBTMPL/ExtractConfiguration`
``ExtractConfiguration`` is usually used to compute the path to the package's source code directory.
.. _JOBTMPL/CheckDocumentation/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/CheckDocumentation/Inputs>`
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=========================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/CheckDocumentation/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/CheckDocumentation/Input/python_version` | no | string | ``'3.14'`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/CheckDocumentation/Input/directory` | yes | string | — — — — |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/CheckDocumentation/Input/fail_under` | no | string | ``'80'`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/CheckDocumentation/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/CheckDocumentation/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/CheckDocumentation/Inputs:
Input Parameters
****************
.. _JOBTMPL/CheckDocumentation/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/CheckDocumentation/Input/python_version:
.. include:: ../_python_version.rst
.. _JOBTMPL/CheckDocumentation/Input/directory:
directory
=========
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid directory or sub-directory.
:Description: Directory where the Python code is located.
.. _JOBTMPL/CheckDocumentation/Input/fail_under:
fail_under
==========
:Type: string
:Required: no
:Default Value: ``'80'``
:Possible Values: Any valid percentage from 0 to 100 encoded as string.
:Description: A minimum percentage level for good documentation. If the documentation coverage is below this level,
the coverage is considered a fail.
.. _JOBTMPL/CheckDocumentation/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/CheckDocumentation/Outputs:
Outputs
*******
This job template has no output parameters.

View File

@@ -0,0 +1,365 @@
.. _JOBTMPL/StaticTypeCheck:
.. index::
single: mypy; StaticTypeCheck Template
single: GitHub Action Reusable Workflow; StaticTypeCheck Template
StaticTypeCheck
###############
This job template runs a static type check using :term:`mypy` and collects the results. These results can be converted
to a HTML report and uploaded as an artifact.
.. topic:: Features
* Run static type check using :term:`mypy`.
.. topic:: Behavior
1. Checkout repository
2. Setup Python and install dependencies
3. Run type checking.
4. Upload type checking report as an artifact
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-StaticTypeCheck.png
:width: 400px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* pip
* Python packages specified via :ref:`JOBTMPL/StaticTypeCheck/Input/requirements`.
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
.. _JOBTMPL/StaticTypeCheck/Instantiation:
Instantiation
*************
Simple Example
==============
This example runs mypy for the Python package ``myPackage`` according to the configuration stored in
:file:`pyproject.toml`. It prints a report into the job's log. In addition is generates a report in HTML format into the
directory ``report/typing``.
.. grid:: 2
.. grid-item::
:columns: 6
.. code-block:: yaml
jobs:
StaticTypeCheck:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@r6
with:
cobertura_artifact: 'TypeChecking-Cobertura'
junit_artifact: 'TypeChecking-JUnit'
html_artifact: 'TypeChecking-HTML'
.. grid-item::
:columns: 6
.. code-block:: toml
[tool.mypy]
packages = ["myPackage"]
strict = true
pretty = true
html_report = "report/typing/html"
junit_xml = "report/typing/StaticTypingSummary.xml"
cobertura_xml_report = "report/typing"
Complex Example
===============
To ease the handling of mypy parameters stored in :file:`pyproject.toml`, the :ref:`JOBTMPL/ExtractConfiguration` is
used to extract the set configuration parameters for later usage. Similarly, :ref:`JOBTMPL/Parameters` is used to
precompute the artifact's name.
.. code-block:: yaml
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
with:
package_name: myPackage
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
StaticTypeCheck:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@r6
needs:
- ConfigParams
- Params
with:
python_version: ${{ needs.Params.outputs.python_version }}
junit_report: ${{ needs.ConfigParams.outputs.typing_report_junit }}
junit_artifact: ${{ fromJson(needs.Params.outputs.artifact_names).statictyping_junit }}
.. _JOBTMPL/StaticTypeCheck/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/StaticTypeCheck/Inputs>`
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+================+==========================================================================================================================================+
| :ref:`JOBTMPL/StaticTypeCheck/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/python_version` | no | string | ``'3.14'`` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/requirements` | no | string | ``'-r tests/requirements.txt'`` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/mypy_options` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/cobertura_report` | no | string (JSON) | :jsoncode:`{"fullpath": "report/typing/cobertura.xml", "directory": "report/typing", "filename": "cobertura.xml"}` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/junit_report` | no | string (JSON) | :jsoncode:`{"fullpath": "report/typing/StaticTypingSummary.xml", "directory": "report/typing", "filename": "StaticTypingSummary.xml"}` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/html_report` | no | string (JSON) | :jsoncode:`{"directory": "report/typing/html"}` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/cobertura_artifact` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/junit_artifact` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/StaticTypeCheck/Input/html_artifact` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------------+------------------------------------------------------------------------------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/StaticTypeCheck/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/StaticTypeCheck/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/StaticTypeCheck/Inputs:
Input Parameters
****************
.. _JOBTMPL/StaticTypeCheck/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/StaticTypeCheck/Input/python_version:
.. include:: ../_python_version.rst
.. _JOBTMPL/StaticTypeCheck/Input/requirements:
requirements
============
:Type: string
:Required: no
:Default Value: ``'-r tests/requirements.txt'``
:Possible Values: Any valid list of parameters for ``pip install``. |br|
Either a requirements file can be referenced using ``'-r path/to/requirements.txt'``, or a list of
packages can be specified using a space separated list like ``'mypy lxml'``.
:Description: Python dependencies to be installed through *pip*.
.. _JOBTMPL/StaticTypeCheck/Input/mypy_options:
mypy_options
============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid command line options for :term:`mypy`.
:Description: Additional options handed over to mypy as ``mypy ${mypy_options}``.
.. _JOBTMPL/StaticTypeCheck/Input/cobertura_report:
cobertura_report
================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/typing",
"filename": "cobertura.xml",
"fullpath": "reports/typing/cobertura.xml"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the type checking report in Cobertura XML format will be
saved.
:filename: Filename of the generated type checking report in Cobertura XML format. |br|
Currently, this filename is hardcoded within :term:`mypy` as :file:`cobertura.xml`.
:fullpath: The concatenation of both previous fields using the ``/`` separator.
:Description: Directory, filename and fullpath as JSON object where the type checking report in Cobertura XML format
will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTesting:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@r6
needs:
- ConfigParams
with:
...
cobertura_report: ${{ needs.ConfigParams.outputs.statictyping_cobertura }}
.. _JOBTMPL/StaticTypeCheck/Input/junit_report:
junit_report
============
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/typing",
"filename": "StaticTypingSummary.xml",
"fullpath": "reports/typing/StaticTypingSummary.xml"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the type checking report in JUnit XML format will be
saved.
:filename: Filename of the generated type checking report in JUnit XML format. |br|
Any valid file name for mypy's JUnit XML report.
:fullpath: The concatenation of both previous fields using the ``/`` separator.
:Description: Directory, filename and fullpath as JSON object where the type checking report in JUnit XML format
will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTesting:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@r6
needs:
- ConfigParams
with:
...
junit_report: ${{ needs.ConfigParams.outputs.statictyping_junit }}
.. _JOBTMPL/StaticTypeCheck/Input/html_report:
html_report
===========
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/typing/html"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the type checking report in HTML format will be saved.
:Description: Directory as JSON object where the type checking report in HTML format will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTesting:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@r6
needs:
- ConfigParams
with:
...
html_report: ${{ needs.ConfigParams.outputs.statictyping_html }}
.. _JOBTMPL/StaticTypeCheck/Input/cobertura_artifact:
cobertura_artifact
==================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the Cobertura XML report.
.. _JOBTMPL/StaticTypeCheck/Input/junit_artifact:
junit_artifact
==============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the JUnit XML report.
.. _JOBTMPL/StaticTypeCheck/Input/html_artifact:
html_artifact
=============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the HTML report.
.. _JOBTMPL/StaticTypeCheck/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/StaticTypeCheck/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/StaticTypeCheck/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,6 @@
.. _JOBTMPL/VerifyDocs:
VerifyDocs (idea)
#################
.. todo:: VerifyDocs:: Needs documentation.

View File

@@ -0,0 +1,17 @@
.. _JOBTMPL/Quality:
Quality
#######
The category *quality* provides workflow templates implementing
* :ref:`JOBTMPL/VerifyDocs` - Verify code snippets in documentations for correctness.
* :ref:`JOBTMPL/CheckDocumentation` - Check documentation coverage in Python modules.
* :ref:`JOBTMPL/StaticTypeCheck` - Check type annotations using mypy.
.. toctree::
:hidden:
VerifyDocs
StaticTypeCheck
CheckDocumentation

View File

@@ -1,96 +0,0 @@
.. _JOBTMPL/GitHubReleasePage:
Release
#######
This job creates a Release Page on GitHub.
**Release Template in Markdown**:
.. parsed-literal::
**Automated Release created on: ${{ steps.getVariables.outputs.datetime }}**
# New Features
* tbd
* tbd
# Changes
* tbd
* tbd
# Bug Fixes
* tbd
* tbd
# Documentation
* tbd
* tbd
# Unit Tests
* tbd
* tbd
----------
# Related Issues and Pull-Requests
* tbd
* tbd
**Behavior:**
1. Extract information from environment variables provided by GitHub Actions.
2. Create a Release Page on GitHub
**Dependencies:**
* :gh:`actions/create-release` (unmaintained)
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
Release:
uses: pyTooling/Actions/.github/workflows/Release.yml@r0
Complex Example
===============
.. code-block:: yaml
jobs:
Release:
uses: pyTooling/Actions/.github/workflows/Release.yml@r0
if: startsWith(github.ref, 'refs/tags')
needs:
- Package
Parameters
**********
This job template needs no input parameters.
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -0,0 +1,580 @@
.. _JOBTMPL/PublishReleaseNotes:
.. index::
single: gh; PublishReleaseNotes Template
single: GitHub Action Reusable Workflow; PublishReleaseNotes Template
PublishReleaseNotes
###################
This template creates a GitHub Release Page and uploads assets to that page.
.. topic:: Features
* Assembly a release description from various sources:
* Description file in the repository.
* Description via job template parameter.
* Description from associated pull-request.
* Download artifact and upload selected files as assets to the release page.
* Add an inventory file in JSON format as asset to each release.
* Replace placeholders with variable contents.
* Override the release's title.
* Create draft releases.
* Create pre-release release.
* Create nightly/rolling releases.
.. topic:: Behavior
1. Checkout repository.
2. Install dependencies.
3. Check if it's a full release or nightly release (rolling release).
4. Delete old release.
5. Assemble release notes.
6. Create a new or recreate the release page as draft.
7. Attach files from artifacts as assets:
1. Download artifact
2. Optionally, create compressed archives of that content.
3. Upload assets to release page.
8. Remove draft state from new release page.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-PublishReleaseNotes.png
:width: 600px
.. topic:: Dependencies
* :gh:`actions/checkout`
* ``gh`` (GitHub command line interface)
* ``jq`` (JSON processing)
* apt
* zstd
.. _JOBTMPL/PublishReleaseNotes/Instantiation:
Instantiation
*************
.. code-block:: yaml
jobs:
Prepare:
uses: pyTooling/Actions/.github/workflows/PrepareJob.yml@r6
Release:
uses: pyTooling/Actions/.github/workflows/PublishReleaseNotes.yml@r6
needs:
- Prepare
if: needs.Prepare.outputs.is_release_tag == 'true'
permissions:
contents: write
actions: write
with:
tag: ${{ needs.Prepare.outputs.version }}
secrets: inherit
.. _JOBTMPL/PublishReleaseNotes/ReleaseNotes:
Release Notes
*************
Providing a release description (a.k.a release page content) can be achieved from various sources. These sources can
also be combined to a single description. Moreover, the resulting description can contain placeholders which can be
replaced by values provided via parameter :ref:`JOBTMPL/PublishReleaseNotes/Input/replacements`.
Description text from file in the repository
The job template's parameter :ref:`JOBTMPL/PublishReleaseNotes/Input/description_file` provides a way to read a
predefined content from a file within the repository. This allows sharing the same text between nightly releases and
full releases.
.. note::
This file can't be computed/modified at pipeline runtime, because a fixed Git commit is checked out for this job
template run.
Descriptions text from pipeline parameter
The job template's parameter :ref:`JOBTMPL/PublishReleaseNotes/Input/description` provides a way to either hard code
a release description in YAML code, or connect a GitHub Action variable ``${{ ... }}`` to that parameter.
The content is available in replacement variable ``%%DESCRIPTION%%``.
Description text from associated PullRequest
If an associated pull-request can be identified for a merge-commit, the pull-requests description can be used as a
release description.
The content is available in replacement variable ``%%PULLREQUEST%%``.
Additional text from :ref:`JOBTMPL/PublishReleaseNotes/Input/description_footer`
Additionally, a footer text is provided.
The content is available in replacement variable ``%%FOOTER%%``.
.. topic:: Order of Processing
1. If :ref:`JOBTMPL/PublishReleaseNotes/Input/description_file` exists and is not empty, it will serve as the main
description. If the description contains ``%%...%%`` placeholders, these placeholders will be replaced
accordingly. If description contains ``%...%`` placeholders, replacement rules provided by
:ref:`JOBTMPL/PublishReleaseNotes/Input/replacements` will be applied.
2. If :ref:`JOBTMPL/PublishReleaseNotes/Input/description` is not empty, it will serve as the main description. If
the description contains ``%%...%%`` placeholders, these placeholders will be replaced accordingly. If description
contains ``%...%`` placeholders, replacement rules provided by :ref:`JOBTMPL/PublishReleaseNotes/Input/replacements`
will be applied.
3. If the associated pull-request exists and is not empty, it's description will serve as the main description. If
the description contains ``%%...%%`` placeholders, these placeholders will be replaced accordingly. If description
contains ``%...%`` placeholders, replacement rules provided by :ref:`JOBTMPL/PublishReleaseNotes/Input/replacements`
will be applied.
4. Otherwise, an error is raised.
.. topic:: Replacements
``%%DESCRIPTION%%``
Replaces the placeholder with the content from :ref:`JOBTMPL/PublishReleaseNotes/Input/description`.
``%%PULLREQUEST%%``, ``%%PULLREQUEST+0%%``, ``%%PULLREQUEST+1%%``, ``%%PULLREQUEST+2%%``, ``%%PULLREQUEST+3%%``
Replaces the content by the associated pull-requests description text.
If an indentation level +N (``+1``, ``+2``, ``+3``) is specified, headlines in the pull-request description will be
moved by N levels down.
``%%FOOTER%%``
Replaces the placeholder with the content from :ref:`JOBTMPL/PublishReleaseNotes/Input/description_footer`.
``%%gh_server%%``
Replaced by the GitHub server URL. |br|
The value is derived from ``${{ github.server_url }}``.
``%%gh_workflow_name%%``
Replaced by the workflow name. |br|
The value is derived from ``${{ github.workflow }}``.
``%%gh_owner%%``
Replaced by the repository owner, which is either the name of a GitHub organisation or a GitHub user account. |br|
The value is derived from ``${{ github.repository_owner }}``.
``%%gh_repo%%``
Replaced by the repository name. |br|
The value is derived from ``${{ github.repository }}`` by splitting namespace and repository name into the
``${repo}`` variable.
``%%gh_owner_repo%%``
Replaced by the repository slug, which is either the name of a GitHub organisation or a GitHub user account
followed by the repository name concatenated by the slash character. |br|
The value is derived from ``${{ github.repository }}``.
``%%gh_pages%%``
Replaced by the URL to the associated GitHub Pages webspace. |br|
The value is formatted as ``https://${{ github.repository_owner }}.github.io/${repo}``.
``%%gh_runid%%``
Replaced by the pipelines ID. |br|
The value is derived from ``${{ github.run_id }}``
``%%gh_actor%%``
Replaced by the actor (user or bot), who launched the pipeline. |br|
The value is derived from ``${{ github.actor }}``.
``%%gh_sha%%``
Replaced by the associated commit's SHA. |br|
The value is derived from ``${{ github.sha }}``
``%%date%%``
Replaced by the current date. |br|
The value is formatted as ``$(date '+%Y-%m-%d')``.
``%%time%%``
Replaced by the current date. |br|
The value is formatted as ``$(date '+%H:%M:%S %Z')``.
``%%datetime%%``
Replaced by the current date. |br|
The value is formatted as ``$(date '+%Y-%m-%d %H:%M:%S %Z')``.
Examples
========
.. todo::
* GHDL - uses description_file and description
* pyTooling - uses pullrequest
.. _JOBTMPL/PublishReleaseNotes/Assets:
Assets
******
.. todo::
PublishReleaseNotes::Assets Describe artifact to asset transformation
Format: ``artifact:file:title``
See also: :ref:`JOBTMPL/PublishReleaseNotes/Input/replacements`
.. _JOBTMPL/PublishReleaseNotes/Inventory:
Inventory
*********
.. todo::
PublishReleaseNotes::Inventory Describe how inventory files are created.
.. _JOBTMPL/PublishReleaseNotes/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/PublishReleaseNotes/Inputs>`
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=========================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/ubuntu_image` | no | string | ``'ubuntu-24.04'`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/release_branch` | no | string | ``'main'`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/mode` | no | string | ``'release'`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/tag` | yes | string | — — — — |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/title` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/description` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/description_file` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/description_footer` | no | string | see parameter details |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/draft` | no | boolean | ``false`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/prerelease` | no | boolean | ``false`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/latest` | no | boolean | ``false`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/replacements` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/assets` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/inventory-json` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/inventory-version` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/inventory-categories` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/tarball-name` | no | string | ``'__pyTooling_upload_artifact__.tar'`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PublishReleaseNotes/Input/can-fail` | no | boolean | ``false`` |
+-------------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/PublishReleaseNotes/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/PublishReleaseNotes/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/PublishReleaseNotes/Inputs:
Input Parameters
****************
.. _JOBTMPL/PublishReleaseNotes/Input/ubuntu_image:
ubuntu_image
============
:Type: string
:Required: usually no
:Default Value: ``'ubuntu-24.04'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
for available Ubuntu image versions.
:Description: Name of the Ubuntu image used to run a job.
.. _JOBTMPL/PublishReleaseNotes/Input/release_branch:
release_branch
==============
:Type: string
:Required: no
:Default Value: ``'main'``
:Possible Values: Any valid Git branch name.
:Description: Name of the branch containing releases.
.. _JOBTMPL/PublishReleaseNotes/Input/mode:
mode
====
:Type: string
:Required: no
:Default Value: ``'release'``
:Possible Values: ``'release'``, ``'nightly'``
:Description: The release mode, which is either *nightly* (a.k.a *rolling* release) or *release*.
.. _JOBTMPL/PublishReleaseNotes/Input/tag:
tag
===
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid Git tag name.
:Description: Name of the release (tag).
:Condition: It must match an existing tag name in the repository.
.. _JOBTMPL/PublishReleaseNotes/Input/title:
title
=====
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid string suitable for a release title (headline).
:Description: If this parameter is not empty, the releases title is set, which overrides the default title inferred
from the associated tag name.
.. _JOBTMPL/PublishReleaseNotes/Input/description:
description
===========
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid (multi-line) Markdown string.
:Description: The description of the release usually used to render the *release notes*. |br|
See :ref:`JOBTMPL/PublishReleaseNotes/ReleaseNotes` for more details.
.. _JOBTMPL/PublishReleaseNotes/Input/description_file:
description_file
================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid Markdown file. |br|
Suggested value: :file:`.github/ReleaseDescription.md`.
:Description: Path to a Markdown file used for the release description. |br|
See :ref:`JOBTMPL/PublishReleaseNotes/ReleaseNotes` for more details.
.. _JOBTMPL/PublishReleaseNotes/Input/description_footer:
description_footer
==================
:Type: string
:Required: no
:Default Value:
.. code-block::
--------
Published from [%%gh_workflow_name%%](%%gh_server%%/%%gh_owner_repo%%/actions/runs/%%gh_runid%%) workflow triggered by %%gh_actor%% on %%datetime%%.
This automatic release was created by [pyTooling/Actions](http://github.com/pyTooling/Actions)::Release.yml
:Possible Values: Any valid (multi-line) Markdown text.
:Description: A footer added to the description. |br|
See :ref:`JOBTMPL/PublishReleaseNotes/ReleaseNotes` for more details.
.. _JOBTMPL/PublishReleaseNotes/Input/draft:
draft
=====
:Type: :red:`boolean`
:Required: no
:Default Value: ``false``
:Possible Values: ``false``, ``true``
:Description: If *true*, the release is kept in *draft* state.
.. note::
GitHub doesn't send e-mail notifications to subscribed users for draft releases.
.. _JOBTMPL/PublishReleaseNotes/Input/prerelease:
prerelease
==========
:Type: :red:`boolean`
:Required: no
:Default Value: ``false``
:Possible Values: ``false``, ``true``
:Description: If *true*, the release is marked as a *pre-release*.
.. _JOBTMPL/PublishReleaseNotes/Input/latest:
latest
======
:Type: :red:`boolean`
:Required: no
:Default Value: ``false``
:Possible Values: ``false``, ``true``
:Description: If *true*, the release is marked as *latest release*.
.. _JOBTMPL/PublishReleaseNotes/Input/replacements:
replacements
============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid multi-line string of format ``search=replace`` patterns.
:Description: The given replacements are used to replace placeholders in :ref:`JOBTMPL/PublishReleaseNotes/Input/description`,
:ref:`JOBTMPL/PublishReleaseNotes/Input/description_file`, :ref:`JOBTMPL/PublishReleaseNotes/Input/description_footer`. |br|
See :ref:`JOBTMPL/PublishReleaseNotes/ReleaseNotes` for more details.
:Example: The following example replaces the placeholder ``%version%`` with the actual version number (inferred
from tag name by :ref:`JOBTMPL/PrepareJob`.
.. code-block:: yaml
ReleasePage:
uses: pyTooling/Actions/.github/workflows/PublishReleaseNotes.yml@r6
needs:
- Prepare
if: needs.Prepare.outputs.is_release_tag == 'true'
permissions:
contents: write
actions: write
with:
tag: ${{ needs.Prepare.outputs.version }}
description: |
# myPackage %version%
This is the latest release of myPackage.
replacements: |
version=${{ needs.Prepare.outputs.version }}
.. _JOBTMPL/PublishReleaseNotes/Input/assets:
assets
======
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid multi-line string containing artifact to asset transformations. |br|
The ``artifact:file:title`` format is explained at :ref:`JOBTMPL/PublishReleaseNotes/Assets`
:Description: Each line describes which artifacts to download and extract as well as which extracted file to upload
as a release asset. The files title can be changed. |br|
Replacement rules from parameter :ref:`JOBTMPL/PublishReleaseNotes/Input/replacements` can be used,
too. |br|
See :ref:`JOBTMPL/PublishReleaseNotes/Assets` for more details.
.. _JOBTMPL/PublishReleaseNotes/Input/inventory-json:
inventory-json
==============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid JSON filename. |br|
Suggested value: :file:`inventory.json`.
:Description: If this parameter is not empty, an inventory of all assets will be created and attached as a JSON file
to the release. |br|
See :ref:`JOBTMPL/PublishReleaseNotes/Inventory` for more details.
.. _JOBTMPL/PublishReleaseNotes/Input/inventory-version:
inventory-version
=================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid version string.
:Description: If this parameter is not empty, the version field in the inventory JSON is set to this value. |br|
See :ref:`JOBTMPL/PublishReleaseNotes/Inventory` for more details.
.. hint::
Especially for *nightly*/*rolling* releases, the used Git tag is a name rather then a version
number. Therefore, a version number must be provided thus a nightly release can be identified as
``vX.Y.Z``.
.. _JOBTMPL/PublishReleaseNotes/Input/inventory-categories:
inventory-categories
====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: A colon separated list of identifiers used as category names in an inventory JSON.
:Description: For decoding hierarchy levels (categories) in an inventory JSON, the hierarchy of categories can be
added to the inventoy JSON. |br|
See :ref:`JOBTMPL/PublishReleaseNotes/Inventory` for more details.
.. _JOBTMPL/PublishReleaseNotes/Input/tarball-name:
tarball-name
============
:Type: string
:Required: no
:Default Value: ``'__pyTooling_upload_artifact__.tar'``
:Possible Values: Any valid name for a tarball file.
:Description:
.. todo:: PublishReleaseNotes::tarball-name Needs documentation.
.. _JOBTMPL/PublishReleaseNotes/Input/can-fail:
can-fail
========
:Type: :red:`boolean`
:Required: no
:Default Value: ``false``
:Possible Values: ``false``, ``true``
:Description:
.. todo:: PublishReleaseNotes::can-fail Needs documentation.
.. _JOBTMPL/PublishReleaseNotes/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/PublishReleaseNotes/Outputs:
Outputs
*******
.. _JOBTMPL/PublishReleaseNotes/Output/release-page:
release-page
============
:Type: string
:Description: Returns the URL to the release page.
:Example: ``tbd``
.. _JOBTMPL/PublishReleaseNotes/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,198 @@
.. _JOBTMPL/TagReleaseCommit:
.. index::
single: gh; TagReleaseCommit Template
single: GitHub Action Reusable Workflow; TagReleaseCommit Template
TagReleaseCommit
################
The ``TagReleaseCommit`` job template creates a tag at the commit currently used by the pipeline run and then it
triggers a new pipeline run for that tag, a.k.a *tag pipeline* or *release pipeline*.
.. note::
When the *tag pipeline* is launched, the pipeline is displayed in GitHub Actions with the name in the YAML file. In
contrast, when a tag is manually added and pushed via Git on command line, the tag name is displayed.
Currently, no command, API or similar is known to add a tag and trigger a matching pipeline run, where the pipeline
is named like the used tag. Thus, currently this job template has a slightly different behavior compared to manual
tagging and pushing a tag to GitHub.
In addition, GitHub doesn't support *project access token*, thus there is no solution to create a user independent
token to emulate a manual push operation.
.. topic:: Features
* Tag the current pipeline's commit.
* Trigger a new pipeline run for this new tag.
.. topic:: Behavior
1. Tag the current commit with a tag named like :ref:`JOBTMPL/TagReleaseCommit/Input/version`.
2. Trigger a pipeline run for the new tag.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-TagReleaseCommit.png
:width: 350px
.. topic:: Dependencies
* :gh:`actions/github-script`
.. _JOBTMPL/TagReleaseCommit/Instantiation:
Instantiation
*************
The following instantiation example depicts three jobs within a bigger pipeline. The ``prepare`` job derived from job
template ``PrepareJob`` version ``@r6`` figures out if a pipeline runs for a release merge-commit, for a tag or any
other reason. Its outputs are used to either run a ``TriggerTaggedRelease`` job derived from job template
``TagReleaseCommit`` version ``@r6``, or alternatively run the ``ReleasePage`` job derived from job template
``PublishReleaseNotes`` version ``@r6``.
.. code-block:: yaml
jobs:
Prepare:
uses: pyTooling/Actions/.github/workflows/PrepareJob.yml@r6
# Other pipeline jobs
TriggerTaggedRelease:
uses: pyTooling/Actions/.github/workflows/TagReleaseCommit.yml@r6
needs:
- Prepare
if: needs.Prepare.outputs.is_release_commit == 'true' && github.event_name != 'schedule'
permissions:
contents: write # required for create tag
actions: write # required for trigger workflow
with:
version: ${{ needs.Prepare.outputs.version }}
auto_tag: ${{ needs.Prepare.outputs.is_release_commit }}
secrets: inherit
ReleasePage:
uses: pyTooling/Actions/.github/workflows/PublishReleaseNotes.yml@r6
needs:
- Prepare
if: needs.Prepare.outputs.is_release_tag == 'true'
permissions:
contents: write
actions: write
with:
tag: ${{ needs.Prepare.outputs.version }}
secrets: inherit
.. _JOBTMPL/TagReleaseCommit/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/TagReleaseCommit/Inputs>`
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/TagReleaseCommit/Input/ubuntu_image` | no | string | ``'ubuntu-24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/TagReleaseCommit/Input/version` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/TagReleaseCommit/Input/auto_tag` | yes | string | — — — — |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/TagReleaseCommit/Input/workflow` | no | string | ``'Pipeline.yml'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/TagReleaseCommit/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/TagReleaseCommit/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/TagReleaseCommit/Inputs:
Input Parameters
****************
.. _JOBTMPL/TagReleaseCommit/Input/ubuntu_image:
ubuntu_image
============
:Type: string
:Required: no
:Default Value: ``'ubuntu-24.04'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
for available Ubuntu image versions.
:Description: Name of the Ubuntu image used to run this job.
.. _JOBTMPL/TagReleaseCommit/Input/version:
version
=======
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: Any valid Git tag name.
:Description: The version string to be used for tagging.
.. _JOBTMPL/TagReleaseCommit/Input/auto_tag:
auto_tag
========
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: ``'false'``, ``'true'```
:Description: If *true*, tag the current commit.
.. _JOBTMPL/TagReleaseCommit/Input/workflow:
workflow
========
:Type: string
:Required: no
:Default Value: ``'Pipeline.yml'``
:Possible Values: Any valid GitHub Action pipeline filename.
:Description: Github Action pipeline (workflow) to trigger after tag creation.
.. note::
Compared to manual tagging and pushing a tag, where a pipeline is triggered automatically, here a
pipeline must be trigger separately by API. Therefore the pipeline doesn't run with the name of the
tag, but with the name specified within the workflow YAML file.
.. _JOBTMPL/TagReleaseCommit/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/TagReleaseCommit/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/TagReleaseCommit/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,15 @@
.. _JOBTMPL/Release:
Release
#######
The category *release* provides workflow templates implementing
* :ref:`JOBTMPL/TagReleaseCommit` - Automatically tag current commit in Git using the associate pull-requests title.
* :ref:`JOBTMPL/PublishReleaseNotes` - Create GitHub release page and upload release assets.
.. toctree::
:hidden:
TagReleaseCommit
PublishReleaseNotes

View File

@@ -0,0 +1,877 @@
.. _JOBTMPL/ExtractConfiguration:
.. index::
single: GitHub Action Reusable Workflow; ExtractConfiguration Template
ExtractConfiguration
####################
The ``ExtractConfiguration`` job template extracts Python project settings from :file:`pyproject.toml` and shares the
values via output parameters with other jobs. Thus, only a single centralized implementation is needed to avoid code
duplications within jobs.
.. topic:: Features
* Extract the unittest report file in pytest's JUnit XML format as directory name, filename and full path from
:file:`pyproject.toml`.
* Extract the merged unittest XML report file as directory name, filename and full path from :file:`pyproject.toml`.
* Extract code coverage report in HTML format as directory from :file:`pyproject.toml`.
* Extract code coverage report file in Cobertura XML format as directory name, filename and full path from
:file:`pyproject.toml`.
* Extract code coverage report file in Coverage.py's JSON format as directory name, filename and full path from
:file:`pyproject.toml`.
* Extract static typing report file in Cobertura XML format as directory name, filename and full path from
:file:`pyproject.toml`.
* Extract static typing report file in JUnit XML format as directory name, filename and full path from
:file:`pyproject.toml`.
* Extract static typing report in HTML format as directory name from :file:`pyproject.toml`.
.. topic:: Behavior
1. Checkout repository.
2. Install Python dependencies.
3. Compute the full package name and the package source directory.
4. Read :file:`pyproject.toml` and extract settings for:
* :term:`Coverage.py`
* :term:`mypy`
* :term:`pyEDAA.Reports`
* :term:`pytest`
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-ExtractConfiguration.png
:width: 600px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :pypi:`wheel`
* :pypi:`tomli`
.. _JOBTMPL/ExtractConfiguration/Instantiation:
Instantiation
*************
The following instantiation example creates a ``ConfigParams`` job derived from job template ``ExtractConfiguration``
version ``@r6``. It requires no special parameters to extract unit test (pytest) and code coverage (Coverage.py)
settings.
.. code-block:: yaml
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- ConfigParams
with:
unittest_report_xml: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
coverage_report_xml: ${{ needs.ConfigParams.outputs.coverage_report_xml }}
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
.. seealso::
:ref:`JOBTMPL/UnitTesting`
``UnitTesting`` is usually
:ref:`JOBTMPL/StaticTypeCheck`
xxx
:ref:`JOBTMPL/CheckDocumentation`
xxx
:ref:`JOBTMPL/InstallPackage`
xxx
:ref:`JOBTMPL/PublishCoverageResults`
xxx
:ref:`JOBTMPL/PublishTestResults`
xxx
:ref:`JOBTMPL/SphinxDocumentation`
xxx
.. _JOBTMPL/ExtractConfiguration/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/ExtractConfiguration/Inputs>`
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/ExtractConfiguration/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Input/python_version` | no | string | ``'3.14'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Input/coverage_config` | no | string | ``'pyproject.toml'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/ExtractConfiguration/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/ExtractConfiguration/Outputs>`
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| Result Name | Type | Description |
+=================================================================================+================+===================================================================+
| :ref:`JOBTMPL/ExtractConfiguration/Output/unittest_report_xml` | string (JSON) | |
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Output/unittest_merged_report_xml` | string (JSON) | |
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Output/coverage_report_html` | string (JSON) | |
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Output/coverage_report_xml` | string (JSON) | |
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Output/coverage_report_json` | string (JSON) | |
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Output/typing_report_cobertura` | string (JSON) | |
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Output/typing_report_junit` | string (JSON) | |
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/ExtractConfiguration/Output/typing_report_html` | string (JSON) | |
+---------------------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
.. _JOBTMPL/ExtractConfiguration/Inputs:
Input Parameters
****************
.. _JOBTMPL/ExtractConfiguration/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/ExtractConfiguration/Input/python_version:
.. include:: ../_python_version.rst
.. _JOBTMPL/ExtractConfiguration/Input/coverage_config:
coverage_config
===============
:Type: string
:Required: no
:Default Value: ``'pyproject.toml'``
:Possible Values: Any valid path to a :file:`pyproject.toml` file. |br|
Alternatively, path to a :file:`.coveragerc` file (deprecated).
:Description: Path to a Python project configuration file for extraction of project settings.
:Example:
.. grid:: 2
.. grid-item::
:columns: 5
.. rubric:: Extracted values from :file:`pyproject.toml`
.. code-block:: toml
[tool.coverage.xml]
output = "report/coverage/coverage.xml"
[tool.coverage.json]
output = "report/coverage/coverage.json"
[tool.coverage.html]
directory = "report/coverage/html"
title="Code Coverage of myPackage"
[tool.mypy]
html_report = "report/typing/html"
junit_xml = "report/typing/StaticTypingSummary.xml"
cobertura_xml_report = "report/typing"
[tool.pyedaa-reports]
junit_xml = "report/unit/unittest.xml"
[tool.pytest]
junit_xml = "report/unit/UnittestReportSummary.xml"
.. _JOBTMPL/ExtractConfiguration/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/ExtractConfiguration/Outputs:
Outputs
*******
.. _JOBTMPL/ExtractConfiguration/Output/unittest_report_xml:
unittest_report_xml
===================
:Type: string (JSON)
:Description: Returns a string in JSON format containing the directory, the filename and the fullpath to the unit
test's report XML file created by :term:`pytest` in JUnit XML format.
The JSON object contains these fields:
:directory: Contains the directory where the unittest XML report file will be created. |br|
Example: :file:`reports/unit`
:filename: Contains the filename of the unittest XML report file. |br|
Example: :file:`UnittestReportSummary.xml`
:fullpath: Contains the path where the unittest XML report file will be created. |br|
This is the concatenation of both previous JSON fields. |br|
Example: :file:`reports/unit/UnittestReportSummary.xml`
:pyproject.toml: Matching :file:`pyproject.toml` configuration for tool :term:`pytest`.
.. code-block:: toml
[tool.pytest]
junit_xml = "report/unit/UnittestReportSummary.xml"
:Example:
.. code-block:: json
{ "directory": "reports/unit",
"filename": "UnittestReportSummary.xml",
"fullpath": "reports/unit/UnittestReportSummary.xml"
}
:Usage:
.. tab-set::
.. tab-item:: Forwarding complete JSON object
:sync: ForwardParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
.. tab-item:: Assembling new JSON object
:sync: AssembleParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: >-
{ "dir": "${{ fromJson(needs.ConfigParams.outputs.unittest_report_xml).directory }}",
"file": "${{ fromJson(needs.ConfigParams.outputs.unittest_report_xml).filename }}"
}
.. tab-item:: Using single field from JSON object
:sync: SingleFieldFromParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
fullpath: ${{ fromJson(needs.ConfigParams.outputs.unittest_report_xml).fullpath }}
.. _JOBTMPL/ExtractConfiguration/Output/unittest_merged_report_xml:
unittest_merged_report_xml
==========================
:Type: string (JSON)
:Description: Returns a string in JSON format containing the directory, the filename and the fullpath to the merged
unittest report file in JUnit XML format created by :term:`pyEDAA.Reports`.
The JSON object contains these fields:
:directory: Contains the directory where the merged unittest XML report file will be created. |br|
Example: :file:`reports/unit`
:filename: Contains the filename of the merged unittest XML report file. |br|
Example: :file:`unittest.xml`
:fullpath: Contains the path where the merged unittest XML report file will be created. |br|
This is the concatenation of both previous JSON fields. |br|
Example: :file:`reports/unit/unittest.xml`
:pyproject.toml: Matching :file:`pyproject.toml` configuration for tool :term:`pyEDAA.Reports`.
.. code-block:: toml
[tool.pyedaa-reports]
junit_xml = "report/unit/unittest.xml"
:Example:
.. code-block:: json
{ "directory": "reports/unit",
"filename": "unittest.xml",
"fullpath": "reports/unit/unittest.xml"
}
:Usage:
.. tab-set::
.. tab-item:: Forwarding complete JSON object
:sync: ForwardParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: ${{ needs.ConfigParams.outputs.unittest_merged_report_xml }}
.. tab-item:: Assembling new JSON object
:sync: AssembleParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: >-
{ "dir": "${{ fromJson(needs.ConfigParams.outputs.unittest_merged_report_xml).directory }}",
"file": "${{ fromJson(needs.ConfigParams.outputs.unittest_merged_report_xml).filename }}"
}
.. tab-item:: Using single field from JSON object
:sync: SingleFieldFromParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
fullpath: ${{ fromJson(needs.ConfigParams.outputs.unittest_merged_report_xml).fullpath }}
.. _JOBTMPL/ExtractConfiguration/Output/coverage_report_html:
coverage_report_html
====================
:Type: string (JSON)
:Description: Returns a string in JSON format containing the directory and the full path, where the code coverage
HTML report will be generated by :term:`Coverage.py`.
The JSON object contains these fields:
:directory: Contains the directory where the code coverage report in HTML format will be created. |br|
Example: :file:`report/coverage/html`
:fullpath: Contains the path where the code coverage report in HTML format will be created. |br|
This is the same as the previous JSON field. |br|
Example: :file:`report/coverage/html`
:pyproject.toml: Matching :file:`pyproject.toml` configuration for tool :term:`Coverage.py`.
.. code-block:: toml
[tool.coverage.html]
directory = "report/coverage/html"
title="Code Coverage of pyDummy"
:Example:
.. code-block:: json
{ "directory": "report/coverage/html",
"fullpath": "report/coverage/html"
}
:Usage:
.. tab-set::
.. tab-item:: Forwarding complete JSON object
:sync: ForwardParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: ${{ needs.ConfigParams.outputs.coverage_report_html }}
.. tab-item:: Assembling new JSON object
:sync: AssembleParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: >-
{ "dir": "${{ fromJson(needs.ConfigParams.outputs.coverage_report_html).directory }}",
"file": "${{ fromJson(needs.ConfigParams.outputs.coverage_report_html).filename }}"
}
.. tab-item:: Using single field from JSON object
:sync: SingleFieldFromParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
fullpath: ${{ fromJson(needs.ConfigParams.outputs.coverage_report_html).fullpath }}
.. _JOBTMPL/ExtractConfiguration/Output/coverage_report_xml:
coverage_report_xml
===================
:Type: string (JSON)
:Description: Returns a string in JSON format containing the directory, the filename and the fullpath to the code
coverage XML report file in Cobertura XML format created by :term:`Coverage.py`.
The JSON object contains these fields:
:directory: Contains the directory where the code coverage XML report file will be created. |br|
Example: :file:`reports/unit`
:filename: Contains the filename of the code coverage XML report file. |br|
Example: :file:`unittest.xml`
:fullpath: Contains the path where the code coverage XML report file will be created. |br|
This is the concatenation of both previous JSON fields. |br|
Example: :file:`reports/unit/unittest.xml`
:pyproject.toml: Matching :file:`pyproject.toml` configuration for tool :term:`Coverage.py`.
.. code-block:: toml
[tool.coverage.xml]
output = "report/coverage/coverage.xml"
:Example:
.. code-block:: json
{ "directory": "reports/coverage",
"filename": "coverage.xml",
"fullpath": "reports/coverage/coverage.xml"
}
:Usage:
.. tab-set::
.. tab-item:: Forwarding complete JSON object
:sync: ForwardParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: ${{ needs.ConfigParams.outputs.coverage_report_xml }}
.. tab-item:: Assembling new JSON object
:sync: AssembleParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: >-
{ "dir": "${{ fromJson(needs.ConfigParams.outputs.coverage_report_xml).directory }}",
"file": "${{ fromJson(needs.ConfigParams.outputs.coverage_report_xml).filename }}"
}
.. tab-item:: Using single field from JSON object
:sync: SingleFieldFromParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
fullpath: ${{ fromJson(needs.ConfigParams.outputs.coverage_report_xml).fullpath }}
.. _JOBTMPL/ExtractConfiguration/Output/coverage_report_json:
coverage_report_json
====================
:Type: string (JSON)
:Description: Returns a string in JSON format containing the directory, the filename and the fullpath to the code
coverage JSON report file created by :term:`Coverage.py`.
The JSON object contains these fields:
:directory: Contains the directory where the code coverage JSON report file will be created. |br|
Example: :file:`reports/unit`
:filename: Contains the filename of the code coverage JSON report file. |br|
Example: :file:`unittest.json`
:fullpath: Contains the path where the code coverage JSON report file will be created. |br|
This is the concatenation of both previous JSON fields. |br|
Example: :file:`reports/unit/unittest.json`
:pyproject.toml: Matching :file:`pyproject.toml` configuration for tool :term:`Coverage.py`.
.. code-block:: toml
[tool.coverage.json]
output = "report/coverage/coverage.json"
:Example:
.. code-block:: json
{ "directory": "reports/coverage",
"filename": "coverage.json",
"fullpath": "reports/coverage/coverage.json"
}
:Usage:
.. tab-set::
.. tab-item:: Forwarding complete JSON object
:sync: ForwardParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: ${{ needs.ConfigParams.outputs.coverage_report_json }}
.. tab-item:: Assembling new JSON object
:sync: AssembleParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: >-
{ "dir": "${{ fromJson(needs.ConfigParams.outputs.coverage_report_json).directory }}",
"file": "${{ fromJson(needs.ConfigParams.outputs.coverage_report_json).filename }}"
}
.. tab-item:: Using single field from JSON object
:sync: SingleFieldFromParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
fullpath: ${{ fromJson(needs.ConfigParams.outputs.coverage_report_json).fullpath }}
.. _JOBTMPL/ExtractConfiguration/Output/typing_report_cobertura:
typing_report_cobertura
=======================
:Type: string (JSON)
:Description: Returns a string in JSON format containing the directory, the filename and the fullpath to the static
type checking report file in Cobertura format created by :term:`mypy`.
The JSON object contains these fields:
:directory: Contains the directory where the type checking XML report file will be created. |br|
Example: :file:`reports/typing`
:filename: Contains the filename of the type checking XML report file. |br|
Example: :file:`cobertura.xml`
:fullpath: Contains the path where the type checking XML report file will be created. |br|
This is the concatenation of both previous JSON fields. |br|
Example: :file:`reports/typing/cobertura.xml`
:pyproject.toml: Matching :file:`pyproject.toml` configuration for tool :term:`mypy`.
.. code-block:: toml
[tool.mypy]
cobertura_xml_report = "report/typing"
:Example:
.. code-block:: json
{ "directory": "reports/typing",
"filename": "cobertura.xml",
"fullpath": "reports/typing/cobertura.xml"
}
:Usage:
.. tab-set::
.. tab-item:: Forwarding complete JSON object
:sync: ForwardParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: ${{ needs.ConfigParams.outputs.typing_report_cobertura }}
.. tab-item:: Assembling new JSON object
:sync: AssembleParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: >-
{ "dir": "${{ fromJson(needs.ConfigParams.outputs.typing_report_cobertura).directory }}",
"file": "${{ fromJson(needs.ConfigParams.outputs.typing_report_cobertura).filename }}"
}
.. tab-item:: Using single field from JSON object
:sync: SingleFieldFromParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
fullpath: ${{ fromJson(needs.ConfigParams.outputs.typing_report_cobertura).fullpath }}
.. _JOBTMPL/ExtractConfiguration/Output/typing_report_junit:
typing_report_junit
===================
:Type: string (JSON)
:Description: Returns a string in JSON format containing the directory, the filename and the fullpath to the static
type checking report file in JUnit XML format created by :term:`mypy`.
The JSON object contains these fields:
:directory: Contains the directory where the static typing JUnit XML report file will be created. |br|
Example: :file:`reports/typing`
:filename: Contains the filename of the static typing JUnit XML report file. |br|
Example: :file:`StaticTypingSummary.xml`
:fullpath: Contains the path where the cstatic typing JUnit XML report file will be created. |br|
This is the concatenation of both previous JSON fields. |br|
Example: :file:`reports/typing/StaticTypingSummary.xml`
:pyproject.toml: Matching :file:`pyproject.toml` configuration for tool :term:`mypy`.
.. code-block:: toml
[tool.mypy]
junit_xml = "report/typing/StaticTypingSummary.xml"
:Example:
.. code-block:: json
{ "directory": "reports/typing",
"filename": "StaticTypingSummary.xml",
"fullpath": "reports/typing/StaticTypingSummary.xml"
}
:Usage:
.. tab-set::
.. tab-item:: Forwarding complete JSON object
:sync: ForwardParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: ${{ needs.ConfigParams.outputs.typing_report_junit }}
.. tab-item:: Assembling new JSON object
:sync: AssembleParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: >-
{ "dir": "${{ fromJson(needs.ConfigParams.outputs.typing_report_junit).directory }}",
"file": "${{ fromJson(needs.ConfigParams.outputs.typing_report_junit).filename }}"
}
.. tab-item:: Using single field from JSON object
:sync: SingleFieldFromParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
fullpath: ${{ fromJson(needs.ConfigParams.outputs.typing_report_junit).fullpath }}
.. _JOBTMPL/ExtractConfiguration/Output/typing_report_html:
typing_report_html
==================
:Type: string (JSON)
:Description: Returns a string in JSON format containing the directory, the filename and the fullpath to the static
type checking report in HTML format created by :term:`mypy`.
The JSON object contains these fields:
:directory: Contains the directory where the static typing HTML report will be created. |br|
Example: :file:`reports/typing/html`
:fullpath: Contains the path where the static typing HTML report will be created. |br|
This is the same as the previous JSON field. |br|
Example: :file:`reports/typing/html`
:pyproject.toml: Matching :file:`pyproject.toml` configuration for tool :term:`mypy`.
.. code-block:: toml
[tool.mypy]
html_report = "report/typing/html"
:Example:
.. code-block:: json
{ "directory": "reports/typing/html",
"fullpath": "reports/typing/html"
}
:Usage:
.. tab-set::
.. tab-item:: Forwarding complete JSON object
:sync: ForwardParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: ${{ needs.ConfigParams.outputs.typing_report_html }}
.. tab-item:: Assembling new JSON object
:sync: AssembleParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
report: >-
{ "dir": "${{ fromJson(needs.ConfigParams.outputs.typing_report_html).directory }}",
"file": "${{ fromJson(needs.ConfigParams.outputs.typing_report_html).filename }}"
}
.. tab-item:: Using single field from JSON object
:sync: SingleFieldFromParam
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
OtherJob:
uses: some/path/to/a/template@r6
needs:
- ConfigParams
with:
fullpath: ${{ fromJson(needs.ConfigParams.outputs.typing_report_html).fullpath }}
.. _JOBTMPL/ExtractConfiguration/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,780 @@
.. _JOBTMPL/Parameters:
.. index::
single: GitHub Action Reusable Workflow; Parameters Template
Parameters
##########
The ``Parameters`` job template is a workaround for the limitations of GitHub Actions to handle global variables in
GitHub Actions workflows (see `actions/runner#480 <https://github.com/actions/runner/issues/480>`__).
It generates output parameters containing a list of artifact names and a job matrix to be used in later-running jobs.
.. topic:: Features
* Generate artifact names for various artifacts.
* Generate a matrix of job combinations as a JSON string made from:
* systems (Ubuntu, macOS, Windows)
* architecture (x64-64, aarch64)
* Python versions (3.9, 3.10, ..., 3.13),
* Python implementation (CPython, PyPy), and
* environments (Native, MinGW64, UCRT64, ...).
* Provide a (default) Python version for other jobs.
.. topic:: Behavior
1. Delay job execution by :ref:`JOBTMPL/Parameters/Input/pipeline-delay` seconds.
2. Compute job matrix using an embedded Python script.
3. Assemble artifact names using a common prefix derived from Python namespace and package name.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-Parameters.png
:width: 1000px
.. topic:: Dependencies
* Python from base-system.
.. _JOBTMPL/Parameters/Instantiation:
Instantiation
*************
Simple Example
==============
.. grid:: 2
.. grid-item::
:columns: 5
The following instantiation example creates a ``Params`` job derived from job template ``Parameters`` version
``@r6``. It only requires a :ref:`JOBTMPL/Parameters/Input/package_name` parameter to create the artifact names.
.. grid-item::
:columns: 7
.. code-block:: yaml
jobs:
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- Params
with:
jobs: ${{ needs.Params.outputs.python_jobs }}
Complex Example
===============
.. grid:: 2
.. grid-item::
:columns: 5
The following instantiation example creates 3 jobs from the same template, but with differing input parameters.
The first ``UnitTestingParams`` job might be used to create a job matrix of unit tests. It creates the cross of
default systems (Windows, Ubuntu, macOS, macOS-ARM, MinGW64, UCRT64) and the given list of Python versions
including some mypy versions. In addition a list of excludes (marked as :deletion:`deletions`) and includes
(marked as :addition:`additions`) is handed over resulting in the following combinations.
The second ``PerformanceTestingParams`` job might be used to create a job matrix for performance tests. Here a
pipeline might be limited to the latest two Python versions on a selected list of platforms.
The third ``PlatformTestingParams`` job might be used to create a job matrix for platform compatibility tests.
Here a pipeline might be limited to the latest Python version, but all available platforms.
.. grid-item::
:columns: 7
.. code-block:: yaml
jobs:
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_namespace: myFramework
package_name: Extension
python_version_list: '3.9 3.10 3.11 3.12 pypy-3.10 pypy-3.11'
system_list: 'ubuntu windows macos macos-arm mingw64 ucrt64'
include_list: 'ubuntu:3.13 macos:3.13 macos-arm:3.13'
exclude_list: 'windows:pypy-3.10 windows:pypy-3.11'
PerformanceTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_namespace: myFramework
package_name: Extension
python_version_list: '3.12 3.13'
system_list: 'ubuntu windows macos macos-arm'
PlatformTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_namespace: myFramework
package_name: Extension
python_version_list: '3.13'
system_list: 'ubuntu windows macos macos-arm mingw32 mingw64 clang64 ucrt64'
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| Version | 3.9 🔴 | 3.10 🟠 | 3.11 🟡 | 3.12 🟢 | 3.13 🟢 | 3.14.b1 🟣 | pypy-3.9 🔴 | pypy-3.10 🟠 | pypy-3.11 🟡 |
+================================+================+=================+=================+=================+============================+============+=============+===============================+===============================+
| Ubuntu 🐧 | ubuntu:3.9 | ubuntu:3.10 | ubuntu:3.11 | ubuntu:3.12 | :addition:`ubuntu:3.13` | | | ubuntu:pypy-3.10 | ubuntu:pypy-3.11 |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| macOS (x86-64) 🍎 | macos:3.9 | macos:3.10 | macos:3.11 | macos:3.12 | :addition:`macos:3.13` | | | macos:pypy-3.10 | macos:pypy-3.11 |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| macOS (aarch64) 🍏 | macos-arm:3.9 | macos-arm:3.10 | macos-arm:3.11 | macos-arm:3.12 | :addition:`macos-arm:3.13` | | | macos:pypy-3.10 | macos:pypy-3.11 |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| Windows Server 🪟 | windows:3.9 | windows:3.10 | windows:3.11 | windows:3.12 | | | | :deletion:`windows:pypy-3.10` | :deletion:`windows:pypy-3.11` |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| Windows Server 🪟 + MSYS 🟪 | | | | | | | | | |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| Windows Server 🪟 + MinGW32 ⬛ | | | | | | | | | |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| Windows Server 🪟 + MinGW64 🟦 | | | | mingw64:3.12 | | | | | |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| Windows Server 🪟 + Clang32 🟫 | | | | | | | | | |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| Windows Server 🪟 + Clang64 🟧 | | | | | | | | | |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
| Windows Server 🪟 + UCRT64 🟨 | | | | ucrt64:3.12 | | | | | |
+--------------------------------+----------------+-----------------+-----------------+-----------------+----------------------------+------------+-------------+-------------------------------+-------------------------------+
.. _JOBTMPL/Parameters/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/Parameters/Inputs>`
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/Parameters/Input/ubuntu_image_version` | no | string | ``'24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/name` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/package_namespace` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/package_name` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/python_version` | no | string | ``'3.14'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/python_version_list` | no | string | ``'3.10 3.11 3.12 3.13 3.14'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/system_list` | no | string | ``'ubuntu windows macos macos-arm mingw64 ucrt64'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/include_list` | no | string | ``''`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/exclude_list` | no | string | ``'windows-arm:3.9 windows-arm:3.10'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/disable_list` | no | string | ``'windows-arm:pypy-3.10 windows-arm:pypy-3.11'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/ubuntu_image` | no | string | ``'ubuntu-24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/ubuntu_arm_image` | no | string | ``'ubuntu-24.04-arm'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/windows_image` | no | string | ``'windows-2025'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/windows_arm_image` | no | string | ``'windows-11-arm'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/macos_intel_image` | no | string | ``'macos-13'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/macos_arm_image` | no | string | ``'macos-15'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Input/pipeline-delay` | no | number | ``0`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/Parameters/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/Parameters/Outputs>`
+---------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| Result Name | Type | Description |
+=====================================================================+================+===================================================================+
| :ref:`JOBTMPL/Parameters/Output/python_version` | string | |
+---------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Output/package_fullname` | string | |
+---------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Output/package_directory` | string | |
+---------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Output/artifact_basename` | string | |
+---------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Output/artifact_names` | string (JSON) | |
+---------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/Parameters/Output/python_jobs` | string (JSON) | |
+---------------------------------------------------------------------+----------------+-------------------------------------------------------------------+
.. _JOBTMPL/Parameters/Inputs:
Input Parameters
****************
.. _JOBTMPL/Parameters/Input/ubuntu_image_version:
.. include:: ../_ubuntu_image_version.rst
.. _JOBTMPL/Parameters/Input/name:
name
====
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid artifact name.
:Description: Prefix used to generate artifact names. Usually, the name of the Python package. |br|
In case this parameter is an empty string, the artifact prefix is derived from :ref:`JOBTMPL/Parameters/Input/package_name`
if the package is a simple Python package, **or** from :ref:`JOBTMPL/Parameters/Input/package_namespace`
and :ref:`JOBTMPL/Parameters/Input/package_name`, if the package is a Python namespace package.
.. _JOBTMPL/Parameters/Input/package_namespace:
package_namespace
=================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid Python namespace.
:Description: In case the package is a Python namespace package, the name of the library's or package's namespace
needs to be specified using this parameter. |br|
In case of a simple Python package, this parameter must be specified as an empty string (``''``),
which is the default. |br|
This parameter is used to derive :ref:`JOBTMPL/Parameters/Input/name`, if it's an empty string.
:Example:
.. grid:: 2
.. grid-item::
:columns: 5
.. rubric:: Example Instantiation
.. code-block:: yaml
name: Pipeline
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_namespace: myFramework
package_name: Extension
.. grid-item::
:columns: 4
.. rubric:: Example Directory Structure
.. code-block::
📂ProjectRoot/
📂myFramework/
📂Extension/
📦SubPackage/
🐍__init__.py
🐍SubModuleA.py
🐍__init__.py
🐍ModuleB.py
.. _JOBTMPL/Parameters/Input/package_name:
package_name
============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid Python package name.
:Description: In case of a simple Python package, this package's name is specified using this parameter. |br|
In case the package is a Python namespace package, the parameter
:ref:`JOBTMPL/Parameters/Input/package_namespace` must be specified, too. |br|
This parameter is used to derive :ref:`JOBTMPL/Parameters/Input/name`, if it's an empty string.
:Example:
.. grid:: 2
.. grid-item::
:columns: 5
.. rubric:: Example Instantiation
.. code-block:: yaml
name: Pipeline
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
.. grid-item::
:columns: 4
.. rubric:: Example Directory Structure
.. code-block::
📂ProjectRoot/
📂myFramework/
📦SubPackage/
🐍__init__.py
🐍SubModuleA.py
🐍__init__.py
🐍ModuleB.py
.. _JOBTMPL/Parameters/Input/python_version:
python_version
==============
:Type: string
:Required: no
:Default Value: ``'3.14'``
:Possible Values: Any valid Python version conforming to the pattern ``<major>.<minor>`` or ``pypy-<major>.<minor>``. |br|
See `actions/python-versions - available Python versions <https://github.com/actions/python-versions>`__
and `actions/setup-python - configurable Python versions <https://github.com/actions/setup-python>`__.
:Description: Python version used as default for other jobs requiring a single Python version. |br|
In case :ref:`JOBTMPL/Parameters/Input/python_version_list` is an empty string, this version is used
to populate the version list.
.. _JOBTMPL/Parameters/Input/python_version_list:
python_version_list
===================
:Type: string
:Required: no
:Default Value: ``'3.10 3.11 3.12 3.13 3.14'``
:Possible Values: A space separated list of valid Python versions conforming to the pattern ``<major>.<minor>`` or
``pypy-<major>.<minor>``. |br|
See `actions/python-versions - available Python versions <https://github.com/actions/python-versions>`__
and `actions/setup-python - configurable Python versions <https://github.com/actions/setup-python>`__.
:Description: The list of space-separated Python versions used for Python variation testing.
.. include:: ../PythonVersionList.rst
.. _JOBTMPL/Parameters/Input/system_list:
system_list
===========
:Type: string
:Required: no
:Default Value: ``'ubuntu windows macos macos-arm mingw64 ucrt64'``
:Possible Values: A space separated list of system names.
:Description: The list of space-separated systems used for application testing.
.. include:: ../SystemList.rst
.. _JOBTMPL/Parameters/Input/include_list:
include_list
============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be included into the list of test
variants.
:Example:
.. code-block:: yaml
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
include_list: "ubuntu:3.11 macos:3.11"
.. _JOBTMPL/Parameters/Input/exclude_list:
exclude_list
============
:Type: string
:Required: no
:Default Value: ``'windows-arm:3.9 windows-arm:3.10'``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be excluded from the list of test
variants.
:Example:
.. code-block:: yaml
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
exclude_list: "windows:pypy-3.8 windows:pypy-3.9"
.. _JOBTMPL/Parameters/Input/disable_list:
disable_list
============
:Type: string
:Required: no
:Default Value: ``'windows-arm:pypy-3.10 windows-arm:pypy-3.11'``
:Possible Values: A space separated list of ``<system>:<python_version>`` tuples.
:Description: List of space-separated ``<system>:<python_version>`` tuples to be temporarily disabled from the list
of test variants. |br|
Each disabled item creates a warning in the workflow log.
:Warning Example:
.. code-block:: yaml
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
disable_list: "windows:3.10 windows:3.11"
.. image:: ../../_static/GH_Workflow_DisabledJobsWarnings.png
:width: 400px
.. _JOBTMPL/Parameters/Input/ubuntu_image:
ubuntu_image
============
:Type: string
:Required: no
:Default Value: ``'ubuntu-24.04'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
for available Ubuntu image versions.
:Description: Name of the Ubuntu x86-64 image and version used to run a Ubuntu jobs when selected via :ref:`JOBTMPL/Parameters/Input/system_list`.
.. _JOBTMPL/Parameters/Input/ubuntu_arm_image:
ubuntu_arm_image
================
:Type: string
:Required: no
:Default Value: ``'ubuntu-24.04-arm'``
:Possible Values: See `actions/partner-runner-images - Available Images <https://github.com/actions/partner-runner-images?tab=readme-ov-file#available-images>`__
for available Ubuntu ARM image versions.
:Description: Name of the Ubuntu aarch64 image and version used to run a Ubuntu ARM jobs when selected via :ref:`JOBTMPL/Parameters/Input/system_list`.
.. _JOBTMPL/Parameters/Input/windows_image:
windows_image
=============
:Type: string
:Required: no
:Default Value: ``'windows-2025'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
:Description: Name of the Windows Server x86-64 image and version used to run a Windows jobs when selected via :ref:`JOBTMPL/Parameters/Input/system_list`.
.. _JOBTMPL/Parameters/Input/windows_arm_image:
windows_arm_image
=================
:Type: string
:Required: no
:Default Value: ``'windows-11-arm'``
:Possible Values: See `actions/partner-runner-images - Available Images <https://github.com/actions/partner-runner-images?tab=readme-ov-file#available-images>`__
:Description: Name of the Windows aarch64 image and version used to run a Windows ARM jobs when selected via :ref:`JOBTMPL/Parameters/Input/system_list`.
.. _JOBTMPL/Parameters/Input/macos_intel_image:
macos_intel_image
=================
:Type: string
:Required: no
:Default Value: ``'macos-13'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
:Description: Name of the macOS x86-64 image and version used to run a macOS Intel jobs when selected via :ref:`JOBTMPL/Parameters/Input/system_list`.
.. _JOBTMPL/Parameters/Input/macos_arm_image:
macos_arm_image
===============
:Type: string
:Required: no
:Default Value: ``'macos-15'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
:Description: Name of the macOS aarch64 image and version used to run a macOS ARM jobs when selected via :ref:`JOBTMPL/Parameters/Input/system_list`.
.. _JOBTMPL/Parameters/Input/pipeline-delay:
pipeline-delay
==============
:Type: number
:Required: no
:Default Value: ``0``
:Possible Values: Any integer number.
:Description: Slow down this job, to delay the startup of the GitHub Action pipline.
.. _JOBTMPL/Parameters/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/Parameters/Outputs:
Outputs
*******
.. _JOBTMPL/Parameters/Output/python_version:
python_version
==============
:Type: string
:Default Value: ``'3.14'``
:Possible Values: Any valid Python version conforming to the pattern ``<major>.<minor>`` or ``pypy-<major>.<minor>``.
:Description: Returns
A single string parameter representing the default Python version that should be used across multiple jobs in the same
pipeline.
Such a parameter is needed as a workaround, because GitHub Actions doesn't support proper handling of global pipeline
variables. Thus, this job is used to compute an output parameter that can be reused in other jobs.
:Example:
.. code-block:: yaml
jobs:
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
name: pyTooling
CodeCoverage:
uses: pyTooling/Actions/.github/workflows/CoverageCollection.yml@r6
needs:
- Params
with:
python_version: ${{ needs.Params.outputs.python_version }}
.. _JOBTMPL/Parameters/Output/package_fullname:
package_fullname
================
:Type: string
:Description: Returns the full package name composed from :ref:`JOBTMPL/Parameters/Input/package_namespace`
and :ref:`JOBTMPL/Parameters/Input/package_name`.
:Example: ``myFramework.Extension``
.. _JOBTMPL/Parameters/Output/package_directory:
package_directory
=================
:Type: string
:Description: Returns the full package path composed from :ref:`JOBTMPL/Parameters/Input/package_namespace`
and :ref:`JOBTMPL/Parameters/Input/package_name`.
:Example: ``myFramework/Extension``
.. _JOBTMPL/Parameters/Output/artifact_basename:
artifact_basename
=================
:Type: string
:Description: Returns the basename (prefix) of all :ref:`artifact names <JOBTMPL/Parameters/Output/artifact_names>` |br|.
The basename is either :ref:`JOBTMPL/Parameters/Input/name` if set, otherwise its
:ref:`JOBTMPL/Parameters/Output/package_fullname`.
:Example: ``myFramework.Extension``
.. _JOBTMPL/Parameters/Output/artifact_names:
artifact_names
==============
:Type: string (JSON)
:Description: Returns a JSON dictionary of artifact names sharing a common prefix (see :ref:`JOBTMPL/Parameters/Input/name`). |br|
As artifacts are handed from jo to job, a consistent naming scheme is advised to avoid duplications
and naming artifacts by hand. This technique solves again the problem of global variables in GitHub
Action YAMl files and the need for assigning the same value (here artifact name) to multiple jobs
templates.
The supported artifacts are:
:unittesting_xml: UnitTesting XML summary report
:unittesting_html: UnitTesting HTML summary report
:perftesting_xml: PerformanceTesting XML summary report
:benchtesting_xml: Benchmarking XML summary report
:apptesting_xml: ApplicationTesting XML summary report
:codecoverage_sqlite: Code Coverage internal database (SQLite)
:codecoverage_xml: Code Coverage Cobertura XML report
:codecoverage_json: Code Coverage Coverage.py JSON report
:codecoverage_html: Code Coverage HTML report
:statictyping_cobertura: Static Type Checking Cobertura XML report
:statictyping_junit: Static Type Checking JUnit XML report
:statictyping_html: Static Type Checking HTML report
:package_all: Packaged Python project (multiple formats)
:documentation_html: Documentation in HTML format
:documentation_latex: Documentation in LaTeX format
:documentation_pdf: Documentation in PDF format
:Example:
.. code-block:: yaml
jobs:
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
name: pyTooling
Coverage:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- Params
with:
unittest_xml_artifact: ${{ fromJson(needs.Params.outputs.artifact_names).unittesting_xml }}
.. _JOBTMPL/Parameters/Output/python_jobs:
python_jobs
===========
:Type: string (JSON)
:Description: Returns a JSON array of job descriptions, wherein each job description is a dictionary providing the
following key-value pairs:
:sysicon: icon to display
:system: name of the system
:runs-on: virtual machine image and base operating system
:runtime: name of the runtime environment if not running natively on the VM image
:shell: name of the shell
:pyicon: icon for CPython or pypy
:python: Python version
:envname: full name of the selected environment
:Example:
.. code-block:: yaml
jobs:
Params:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
name: pyDummy
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- Params
with:
jobs: ${{ needs.Params.outputs.python_jobs }}
:Usage: The generated JSON array can be unpacked using the ``fromJson(...)`` function in a job's matrix
``strategy:matrix:include`` like this:
.. code-block:: yaml
name: Unit Testing (Matrix)
on:
workflow_call:
inputs:
jobs:
required: true
type: string
jobs:
UnitTesting:
name: ${{ matrix.sysicon }} ${{ matrix.pyicon }} Unit Tests using Python ${{ matrix.python }}
runs-on: ${{ matrix.runs-on }}
strategy:
matrix:
include: ${{ fromJson(inputs.jobs) }}
defaults:
run:
shell: ${{ matrix.shell }}
steps:
- name: 🐍 Setup Python ${{ matrix.python }}
if: matrix.system != 'msys2'
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
:Debugging: The job prints debugging information like system |times| Python version |times| environment
combinations as well as the generated JSON array in the job's log.
.. code-block:: json
[
{"sysicon": "🐧", "system": "ubuntu", "runs-on": "ubuntu-24.04", "runtime": "native", "shell": "bash", "pyicon": "🔴", "python": "3.9", "envname": "Linux (x86-64)" },
{"sysicon": "🐧", "system": "ubuntu", "runs-on": "ubuntu-24.04", "runtime": "native", "shell": "bash", "pyicon": "🟠", "python": "3.10", "envname": "Linux (x86-64)" },
{"sysicon": "🐧", "system": "ubuntu", "runs-on": "ubuntu-24.04", "runtime": "native", "shell": "bash", "pyicon": "🟡", "python": "3.11", "envname": "Linux (x86-64)" },
{"sysicon": "🐧", "system": "ubuntu", "runs-on": "ubuntu-24.04", "runtime": "native", "shell": "bash", "pyicon": "🟢", "python": "3.12", "envname": "Linux (x86-64)" },
{"sysicon": "🐧", "system": "ubuntu", "runs-on": "ubuntu-24.04", "runtime": "native", "shell": "bash", "pyicon": "🟢", "python": "3.13", "envname": "Linux (x86-64)" },
{"sysicon": "🪟", "system": "windows", "runs-on": "windows-2025", "runtime": "native", "shell": "pwsh", "pyicon": "🔴", "python": "3.9", "envname": "Windows (x86-64)" },
{"sysicon": "🪟", "system": "windows", "runs-on": "windows-2025", "runtime": "native", "shell": "pwsh", "pyicon": "🟠", "python": "3.10", "envname": "Windows (x86-64)" },
{"sysicon": "🪟", "system": "windows", "runs-on": "windows-2025", "runtime": "native", "shell": "pwsh", "pyicon": "🟡", "python": "3.11", "envname": "Windows (x86-64)" },
{"sysicon": "🪟", "system": "windows", "runs-on": "windows-2025", "runtime": "native", "shell": "pwsh", "pyicon": "🟢", "python": "3.12", "envname": "Windows (x86-64)" },
{"sysicon": "🪟", "system": "windows", "runs-on": "windows-2025", "runtime": "native", "shell": "pwsh", "pyicon": "🟢", "python": "3.13", "envname": "Windows (x86-64)" },
{"sysicon": "🍎", "system": "macos", "runs-on": "macos-13", "runtime": "native", "shell": "bash", "pyicon": "🔴", "python": "3.9", "envname": "macOS (x86-64)" },
{"sysicon": "🍎", "system": "macos", "runs-on": "macos-13", "runtime": "native", "shell": "bash", "pyicon": "🟠", "python": "3.10", "envname": "macOS (x86-64)" },
{"sysicon": "🍎", "system": "macos", "runs-on": "macos-13", "runtime": "native", "shell": "bash", "pyicon": "🟡", "python": "3.11", "envname": "macOS (x86-64)" },
{"sysicon": "🍎", "system": "macos", "runs-on": "macos-13", "runtime": "native", "shell": "bash", "pyicon": "🟢", "python": "3.12", "envname": "macOS (x86-64)" },
{"sysicon": "🍎", "system": "macos", "runs-on": "macos-13", "runtime": "native", "shell": "bash", "pyicon": "🟢", "python": "3.13", "envname": "macOS (x86-64)" },
{"sysicon": "🍏", "system": "macos-arm", "runs-on": "macos-15", "runtime": "native", "shell": "bash", "pyicon": "🔴", "python": "3.9", "envname": "macOS (aarch64)" },
{"sysicon": "🍏", "system": "macos-arm", "runs-on": "macos-15", "runtime": "native", "shell": "bash", "pyicon": "🟠", "python": "3.10", "envname": "macOS (aarch64)" },
{"sysicon": "🍏", "system": "macos-arm", "runs-on": "macos-15", "runtime": "native", "shell": "bash", "pyicon": "🟡", "python": "3.11", "envname": "macOS (aarch64)" },
{"sysicon": "🍏", "system": "macos-arm", "runs-on": "macos-15", "runtime": "native", "shell": "bash", "pyicon": "🟢", "python": "3.12", "envname": "macOS (aarch64)" },
{"sysicon": "🍏", "system": "macos-arm", "runs-on": "macos-15", "runtime": "native", "shell": "bash", "pyicon": "🟢", "python": "3.13", "envname": "macOS (aarch64)" },
{"sysicon": "🪟🟦", "system": "msys2", "runs-on": "windows-2025", "runtime": "MINGW64", "shell": "msys2 {0}", "pyicon": "🟢", "python": "3.12", "envname": "Windows+MSYS2 (x86-64) - MinGW64"},
{"sysicon": "🪟🟨", "system": "msys2", "runs-on": "windows-2025", "runtime": "UCRT64", "shell": "msys2 {0}", "pyicon": "🟢", "python": "3.12", "envname": "Windows+MSYS2 (x86-64) - UCRT64" }
]
.. _JOBTMPL/Parameters/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).
Nontheless, the generated output :ref:`JOBTMPL/Parameters/Output/python_jobs` is influenced by many input parameters
generating :math:`N^2` many Python job combinations, which in turn will influence the overall pipeline runtime and how many
jobs (parallel VMs) are needed to execute the matrix.
.. hint::
Some VM images (macOS, Windows) have parallelism limitations and run slower then Ubuntu-based jobs. Additionally,
environments like MSYS2 require an additional setup time increasing a jobs runtime significantly.

View File

@@ -0,0 +1,464 @@
.. _JOBTMPL/PrepareJob:
.. index::
single: GitHub Action Reusable Workflow; PrepareJob Template
PrepareJob
##########
The ``PrepareJob`` template is a workaround for the limitations of GitHub Actions to handle global variables in GitHub
Actions workflows (see `actions/runner#480 <https://github.com/actions/runner/issues/480>`__) as well as providing basic
string operations (see GitHub Action's `limited set of functions <https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#functions>`__).
The job template generates various output parameters derived from
`${{ github }} context <https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#github-context>`__.
.. topic:: Features
* Provide branch name or tag name from ``${{ github.ref }}`` variable.
* Check if pipeline is a branch, tag or pull-request pipeline.
* Check if a commit is a merge commit.
* Check if a commit is a release commit.
* Check if a tag is a nightly tag (rolling release tag).
* Check if a tag is a release tag.
* Find associated pull-request (PR) for a merge commit/release commit.
* Provide a version from tag name or pull-request name.
.. note::
Due to GitHub Action's broken type system and missing implicit type conversions in YAML files, *boolean* values need
to be returned as *string* values otherwise type compatibility and comparison are broken. This also requires all
inputs to be *string* parameters, otherwise step's, job's or template's output cannot be assigned to a template's
input.
**Problems:**
1. Scripts (Bash, Python, ...) return results as strings. There is no option or operation provided by GitHub Actions
to convert outputs of ``printf`` to a ``boolean`` in the YAML file.
2. Unlike job template inputs, outputs have no type information. These are all considered *string* values. Again no
implicit or explicit type conversion is provided.
3. While inputs might be defined as ``boolean`` and a matching default can be set as a *boolean* value (e.g.,
``false``), a connected variable from ``${{ needs }}`` context will either cause a typing error or a later
comparison will not work as expected. Either the comparison works with ``inputs.param == false`` for the default
value, **or** it works with a value from ``${{ needs }}`` context, which is a string ``inputs.param == 'false'``.
.. topic:: Behavior
1. Checkout repository.
2. Classify ``${{ github.ref }}`` into branch, tag or pull-request.
3. Compute output parameters.
4. Find associated pull-request.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-PrepareJob.png
:width: 600px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`GitHub command line tool 'gh' <cli/cli>`
.. _JOBTMPL/PrepareJob/Instantiation:
Instantiation
*************
The following instantiation example creates a ``Prepare`` job derived from job template ``PrepareJob`` version ``@r6``.
In a default usecase, no input parameters need to be specified for the job template assuming a main-branch and
release-branch called ``main``, a development-branch called ``dev``, as well as semantic versioning for tags and
pull-request titles.
.. code-block:: yaml
jobs:
Prepare:
uses: pyTooling/Actions/.github/workflows/PrepareJob.yml@r6
<ReleaseJob>:
needs:
- Prepare
if: needs.Prepare.outputs.is_release_tag == 'true'
...
with:
version: ${{ needs.Prepare.outputs.version }}
.. seealso::
:ref:`JOBTMPL/TagReleaseCommit`
``PrepareJob`` is usually used to identify if a pipeline's commit is a merge commit created by a pull-request. If
so, this commit can be tagged automatically to trigger a release pipeline (tag pipeline) for the same commit
resulting in a full release (PyPI, GitHub Pages, GitHub Release, ...).
:ref:`JOBTMPL/PublishReleaseNotes`
``PrepareJob`` is usually used to identify if a tag pipeline is a release pipeline.
.. _JOBTMPL/PrepareJob/Parameters:
Parameter Summary
*****************
.. rubric:: Goto :ref:`input parameters <JOBTMPL/PrepareJob/Inputs>`
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=====================================================================+==========+==========+===================================================================+
| :ref:`JOBTMPL/PrepareJob/Input/ubuntu_image` | no | string | ``'ubuntu-24.04'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Input/main_branch` | no | string | ``'main'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Input/development_branch` | no | string | ``'dev'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Input/release_branch` | no | string | ``'main'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Input/nightly_tag_pattern` | no | string | ``'nightly'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Input/release_tag_pattern` | no | string | ``'(v|r)?[0-9]+(\.[0-9]+){0,2}(-(dev|alpha|beta|rc)([0-9]*))?'`` |
+---------------------------------------------------------------------+----------+----------+-------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/PrepareJob/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/PrepareJob/Outputs>`
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| Result Name | Type | Description |
+=====================================================================+==========+===================================================================+
| :ref:`JOBTMPL/PrepareJob/Output/on_main_branch` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/on_dev_branch` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/on_release_branch` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/is_regular_commit` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/is_merge_commit` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/is_release_commit` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/is_nightly_tag` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/is_release_tag` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/ref_kind` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/branch` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/tag` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/version` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/pr_title` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
| :ref:`JOBTMPL/PrepareJob/Output/pr_number` | string | |
+---------------------------------------------------------------------+----------+-------------------------------------------------------------------+
.. _JOBTMPL/PrepareJob/Inputs:
Input Parameters
****************
.. _JOBTMPL/PrepareJob/Input/ubuntu_image:
ubuntu_image
============
:Type: string
:Required: no
:Default Value: ``'ubuntu-24.04'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
for available Ubuntu image versions.
:Description: Name of the Ubuntu image used to run this job.
.. _JOBTMPL/PrepareJob/Input/main_branch:
main_branch
===========
:Type: string
:Required: no
:Default Value: ``'main'``
:Possible Values: Any valid branch name.
:Description: Name of the main branch.
.. _JOBTMPL/PrepareJob/Input/development_branch:
development_branch
==================
:Type: string
:Required: no
:Default Value: ``'dev'``
:Possible Values: Any valid branch name.
:Description: Name of the development branch.
.. _JOBTMPL/PrepareJob/Input/release_branch:
release_branch
==============
:Type: string
:Required: no
:Default Value: ``'main'``
:Possible Values: Any valid branch name.
:Description: Name of the branch containing releases.
.. _JOBTMPL/PrepareJob/Input/nightly_tag_pattern:
nightly_tag_pattern
===================
:Type: string
:Required: no
:Default Value: ``'nightly'``
:Possible Values: Any valid regular expression. |br|
Suggested alternative values: ``latest``, ``rolling``
:Description: Name of the tag used for rolling releases, a.k.a nightly builds.
.. _JOBTMPL/PrepareJob/Input/release_tag_pattern:
release_tag_pattern
===================
:Type: string
:Required: no
:Default Value: ``'(v|r)?[0-9]+(\.[0-9]+){0,2}(-(dev|alpha|beta|rc)([0-9]*))?'``
:Possible Values: Any valid regular expression.
:Description: A regular expression describing a pattern for identifying a release tag.
The default pattern matches on a `semantic version number <https://semver.org/>`__ separated by dots.
It supports up to 3 digit groups. It accepts an optional ``v`` or ``r`` prefix. Optionally, a postfix
of ``dev``, ``alpha``, ``beta`` or ``rc`` separated by a hyphen can be appended. If needed, the
postfix can have a digit group.
**Matching tag names as releases:**
* ``v1``, ``r1``
* ``1``, ``1.1``, ``1.1.1``
* ``v1.2.8-dev``
* ``v3.13.5-alpha2``
* ``v4.7.22-beta3``
* ``v10.2-rc1``
.. _JOBTMPL/PrepareJob/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/PrepareJob/Outputs:
Outputs
*******
.. _JOBTMPL/PrepareJob/Output/on_main_branch:
on_main_branch
==============
:Type: string
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns ``'true'`` if the pipeline's commit is on :ref:`main branch <JOBTMPL/PrepareJob/Input/main_branch>`,
otherwise return ``'false'``.
.. _JOBTMPL/PrepareJob/Output/on_dev_branch:
on_dev_branch
=============
:Type: string
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns ``'true'`` if the pipeline's commit is on :ref:`development branch <JOBTMPL/PrepareJob/Input/development_branch>`,
otherwise return ``'false'``.
.. _JOBTMPL/PrepareJob/Output/on_release_branch:
on_release_branch
=================
:Type: string
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns ``'true'`` if the pipeline's commit is on :ref:`release branch <JOBTMPL/PrepareJob/Input/release_branch>`,
otherwise return ``'false'``.
.. _JOBTMPL/PrepareJob/Output/is_regular_commit:
is_regular_commit
=================
:Type: string
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns ``'true'`` if the pipeline's commit is not a :ref:`merge commit <JOBTMPL/PrepareJob/Output/is_merge_commit>`
nor :ref:`release commit <JOBTMPL/PrepareJob/Output/is_release_commit>`, otherwise return ``'false'``.
.. _JOBTMPL/PrepareJob/Output/is_merge_commit:
is_merge_commit
===============
:Type: string
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns ``'true'`` if the pipeline's commit is on :ref:`main branch <JOBTMPL/PrepareJob/Input/main_branch>`
or :ref:`development branch <JOBTMPL/PrepareJob/Input/development_branch>` and has more than one
parent (merge commit), otherwise return ``'false'``.
.. _JOBTMPL/PrepareJob/Output/is_release_commit:
is_release_commit
=================
:Type: string
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns ``'true'`` if the pipeline's commit is on :ref:`release branch <JOBTMPL/PrepareJob/Input/release_branch>`
and has more than one parent (merge commit), otherwise return ``'false'``.
.. _JOBTMPL/PrepareJob/Output/is_nightly_tag:
is_nightly_tag
==============
:Type: string
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns ``'true'`` if the pipeline is a tag pipeline for a commit on :ref:`release branch <JOBTMPL/PrepareJob/Input/release_branch>`
and the tag's name matches the :ref:`nightly tag pattern <JOBTMPL/PrepareJob/Input/nightly_tag_pattern>`,
otherwise return ``'false'``.
.. _JOBTMPL/PrepareJob/Output/is_release_tag:
is_release_tag
==============
:Type: string
:Default Value: ``'false'``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns ``'true'`` if the pipeline is a tag pipeline for a commit on :ref:`release branch <JOBTMPL/PrepareJob/Input/release_branch>`
and the tag's name matches the :ref:`release tag pattern <JOBTMPL/PrepareJob/Input/release_tag_pattern>`,
otherwise return ``'false'``.
.. _JOBTMPL/PrepareJob/Output/ref_kind:
ref_kind
========
:Type: string
:Default Value: ``'unknown'``
:Possible Values: ``'branch'``, ``'tag'``, ``'pullrequest'``, ``'unknown'``
:Description: Returns ``'branch'`` if pipeline's commit is on a branch or returns ``'tag'`` if the pipeline runs for
a tagged commit, otherwise returns ``'unknown'`` in case of an internal error.
If the kind is a branch, the branch name is available in the job's :ref:`JOBTMPL/PrepareJob/Output/branch`
result. |br|
If the kind is a tag, the tags name is available in the job's :ref:`JOBTMPL/PrepareJob/Output/tag`
result. |br|
If the kind is a pull-request, the pull request's id is available in the job's :ref:`JOBTMPL/PrepareJob/Output/pr_number`
result. |br|
Moreover, if the tag matches the :ref:`JOBTMPL/PrepareJob/Input/release_tag_pattern`, the extracted
version is available in the job's :ref:`JOBTMPL/PrepareJob/Output/version` result.
.. note::
GitHub doesn't provide standalone branch or tag information, but provides the variable
``${{ github.ref }}`` specifying the currently active reference (branch, tag, pull, ...). This job
template parses the context's variable and derives if a pipeline runs for a commit on a branch or a
tagged commit.
.. _JOBTMPL/PrepareJob/Output/branch:
branch
======
:Type: string
:Default Value: ``''``
:Possible Values: Any valid branch name.
:Description: Returns the branch's name the pipeline's commit is associated to, if :ref:`JOBTMPL/PrepareJob/Output/ref_kind`
is ``'branch'``, otherwise returns an empty string ``''``.
.. _JOBTMPL/PrepareJob/Output/tag:
tag
===
:Type: string
:Default Value: ``''``
:Possible Values: Any valid tag name.
:Description: Returns the tag's name the pipeline's commit is associated to, if :ref:`JOBTMPL/PrepareJob/Output/ref_kind`
is ``'tag'``, otherwise returns an empty string ``''``.
.. _JOBTMPL/PrepareJob/Output/version:
version
=======
:Type: string
:Default Value: ``''``
:Possible Values: Any valid version matching :ref:`JOBTMPL/PrepareJob/Input/release_tag_pattern`.
:Description: In case the pipeline runs for a tag, it returns the tag's name, if the name matches
:ref:`JOBTMPL/PrepareJob/Input/release_tag_pattern`, otherwise returns an empty string ``''``. |br|
In case the pipeline runs for a branch, then the commit is checked if it's a
:ref:`merge commit <JOBTMPL/PrepareJob/Output/is_merge_commit>` and corresponding pull-request (PR) is
searched. When a matching PR can be located and it's title matches
:ref:`JOBTMPL/PrepareJob/Input/release_tag_pattern`, then this title is returned as a version,
otherwise it returns an empty string ``''``.
.. _JOBTMPL/PrepareJob/Output/pr_title:
pr_title
========
:Type: string
:Default Value: ``''``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns the associated pull-request's title, if the pipeline's commit is a
:ref:`merge commit <JOBTMPL/PrepareJob/Output/is_merge_commit>` and the located pull-request's title
for this commit matches :ref:`JOBTMPL/PrepareJob/Input/release_tag_pattern`, otherwise returns an
empty string ``''``.
.. _JOBTMPL/PrepareJob/Output/pr_number:
pr_number
=========
:Type: string
:Default Value: ``''``
:Possible Values: ``'true'``, ``'false'``
:Description: Returns the associated pull-request's number, if the pipeline's commit is a
:ref:`merge commit <JOBTMPL/PrepareJob/Output/is_merge_commit>` and the located pull-request's title
for this commit matches :ref:`JOBTMPL/PrepareJob/Input/release_tag_pattern`, otherwise returns an
empty string ``''``.
.. _JOBTMPL/PrepareJob/Optimizations:
Optimizations
*************
This template offers no optimizations (reduced job runtime).

View File

@@ -0,0 +1,21 @@
.. _JOBTMPL/Setup:
Pipeline Setup
##############
The category *pipeline setup* provides workflow templates implementing preparation steps suitable for every pipeline.
* :ref:`JOBTMPL/Parameters` - Compute a matrix of (operating system |times| Python version |times| environment)
combinations for unit testing, platform testing or application testing and provide the result as a JSON string via
pipeline outputs.
* :ref:`JOBTMPL/PrepareJob` - Check GitHub Action environment variables and commits to provide precomputed variables as
pipeline outputs.
* :ref:`JOBTMPL/ExtractConfiguration` - Extract configuration settings from :file:`pyproject.toml` and provide settings
as pipeline outputs.
.. toctree::
:hidden:
PrepareJob
Parameters
ExtractConfiguration

View File

@@ -1,182 +0,0 @@
.. _JOBTMPL/StaticTypeChecking:
StaticTypeCheck
###############
This job runs a static type check using mypy and collects the results. These results can be converted to a HTML report
and then uploaded as an artifact.
**Behavior:**
1. Checkout repository
2. Setup Python and install dependencies
3. Run type checking command(s).
4. Upload type checking report as an artifact
**Dependencies:**
* :gh:`actions/checkout`
* :gh:`actions/setup-python`
* :gh:`actions/upload-artifact`
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
StaticTypeCheck:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@r0
with:
commands: |
touch pyTooling/__init__.py
mypy --html-report htmlmypy -p pyTooling
report: 'htmlmypy'
artifact: TypeChecking
Complex Example
===============
.. code-block:: yaml
jobs:
StaticTypeCheck:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@r0
needs:
- Params
with:
python_version: ${{ needs.Params.outputs.python_version }}
commands: |
touch pyTooling/__init__.py
mypy --html-report htmlmypy -p pyTooling
report: 'htmlmypy'
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).statictyping_html }}
Commands
========
Example ``commands``:
1. Regular package
.. code-block:: yaml
commands: mypy --html-report htmlmypy -p ToolName
2. Parent namespace package
.. code-block:: yaml
commands: |
touch Parent/__init__.py
mypy --html-report htmlmypy -p ToolName
3. Child namespace package
.. code-block:: yaml
commands: |
cd Parent
mypy --html-report ../htmlmypy -p ToolName
Parameters
**********
python_version
==============
+----------------+----------+----------+-----------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=================+
| python_version | optional | string | ``3.11`` |
+----------------+----------+----------+-----------------+
Python version.
requirements
============
+----------------+----------+----------+-------------------------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+===============================+
| requirements | optional | string | ``-r tests/requirements.txt`` |
+----------------+----------+----------+-------------------------------+
Python dependencies to be installed through pip.
commands
========
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| commands | yes | string | — — — — |
+----------------+----------+----------+--------------+
Commands to run the static type checks.
html_report
===========
+----------------+----------+----------+-----------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=================+
| report | optional | string | ``htmlmypy`` |
+----------------+----------+----------+-----------------+
HTML output directory to upload as an artifact.
junit_report
============
+----------------+----------+----------+-----------------------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=============================+
| report | optional | string | ``StaticTypingSummary.xml`` |
+----------------+----------+----------+-----------------------------+
junit file to upload as an artifact.
html_artifact
=============
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| html_artifact | yes | string | — — — — |
+----------------+----------+----------+--------------+
Name of the typing artifact (HTML report).
junit_artifact
==============
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| junit_artifact | optional | string | ``""`` |
+----------------+----------+----------+--------------+
Name of the typing junit artifact (junit XML).
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -0,0 +1,30 @@
.. rubric:: Possible values
* Native systems: ``ubuntu``, ``windows``, ``macos``
* MSYS2: ``msys``, ``mingw32``, ``mingw64``, ``clang32``, ``clang64``, ``ucrt64``
+------+-----------+------------------------------+-----------------------------------------------------------------+
| Icon | System | Used version | Comments |
+======+===========+==============================+=================================================================+
| 🪟 | Windows | Windows Server 2025 (latest) | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🐧 | Ubuntu | Ubuntu 24.04 (LTS) (latest) | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🍎 | macOS | macOS Ventura 13 (latest) | While this marked latest, macOS Ventura 13 is already provided. |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🍏 | macOS-arm | macOS Sonoma 14 (latest) | While this marked latest, macOS Ventura 13 is already provided. |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟪 | MSYS | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| ⬛ | MinGW32 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟦 | MinGW64 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟫 | Clang32 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟧 | Clang64 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
| 🟨 | UCRT64 | | |
+------+-----------+------------------------------+-----------------------------------------------------------------+
Source: `Images provided by GitHub <https://github.com/actions/runner-images>`__

View File

@@ -0,0 +1,77 @@
.. grid:: 5
.. grid-item::
:columns: 2
.. rubric:: All-In-One
* :ref:`JOBTMPL/CompletePipeline` |br| |br| |br| |br|
.. rubric:: Pipeline Setup
* :ref:`JOBTMPL/Parameters`
* :ref:`JOBTMPL/PrepareJob`
* :ref:`JOBTMPL/ExtractConfiguration`
.. grid-item::
:columns: 2
.. rubric:: Documentation
* :ref:`JOBTMPL/CheckDocumentation`
* :ref:`JOBTMPL/SphinxDocumentation`
* :ref:`JOBTMPL/LaTeXDocumentation`
.. #* :ref:`JOBTMPL/VerifyDocs`
.. rubric:: Unit Tests, Code Coverage
* :ref:`JOBTMPL/ApplicationTesting`
* :ref:`JOBTMPL/UnitTesting`
.. grid-item::
:columns: 2
.. rubric:: Code Quality
* :ref:`JOBTMPL/StaticTypeCheck`
* *code formatting (planned)*
* *coding style (planned)*
* *code linting (planned)*
.. rubric:: Build and Packaging
* :ref:`JOBTMPL/Package`
* :ref:`JOBTMPL/InstallPackage`
.. grid-item::
:columns: 2
.. rubric:: Publishing
* :ref:`JOBTMPL/PublishOnPyPI`
* :ref:`JOBTMPL/PublishTestResults`
* :ref:`JOBTMPL/PublishCoverageResults`
* :ref:`JOBTMPL/PublishToGitHubPages`
.. rubric:: Releasing
* :ref:`JOBTMPL/PublishReleaseNotes`
* :ref:`JOBTMPL/TagReleaseCommit`
.. grid-item::
:columns: 2
.. rubric:: Cleanup
* :ref:`JOBTMPL/IntermediateCleanup`
* :ref:`JOBTMPL/ArtifactCleanup`
.. #grid-item::
:columns: 2
.. rubric:: :ref:`JOBTMPL/Deprecated`
* :ref:`JOBTMPL/CoverageCollection`
* :ref:`JOBTMPL/NightlyRelease`
* :ref:`JOBTMPL/BuildTheDocs`

View File

@@ -0,0 +1,6 @@
.. _JOBTMPL/ApplicationTesting:
ApplicationTesting (idea)
#########################
.. todo:: ApplicationTesting:: Needs documentation.

View File

@@ -0,0 +1,756 @@
.. _JOBTMPL/UnitTesting:
.. index::
single: pytest; UnitTesting Template
single: Coverage.py; UnitTesting Template
single: GitHub Action Reusable Workflow; UnitTesting Template
UnitTesting
###########
This template runs multiple jobs from a matrix as a cross of Python versions and systems. The summary report in junit
XML format is optionally uploaded as an artifact.
Configuration options to :term:`pytest` should be given via section ``[tool.pytest.ini_options]`` in a
``pyproject.toml`` file.
.. topic:: Features
* Execute unit tests using :term:`pytest`.
* Provide unit test results as JUnit XML file (pyTest XML dialect).
* Collect code coverage using :term:`Coverage.py`.
* Provide code coverage results as pytest SQLite database.
* Provide code coverage results as Cobertura XML file.
* Provide code coverage results as pytest JSON file.
* Provide code coverage results as HTML report.
.. topic:: Behavior
1. Checkout repository.
2. Setup environment and install dependencies (``apt``, ``homebrew``, ``pacman``, ...).
3. Setup Python and install dependencies (:term:`pip`).
4. Run instructions from ``*_before_script`` parameter.
5. Run unit tests using *pytest* and if enabled in combination with *Coverage.py*.
6. Convert gathered results to other formats.
7. Upload results (test reports, code coverage reports, ...) as an artifacts.
.. topic:: Job Execution
.. image:: ../../_static/pyTooling-Actions-UnitTesting.png
:width: 600px
.. topic:: Dependencies
* :gh:`actions/checkout`
* :gh:`msys2/setup-msys2`
* :gh:`actions/setup-python`
* :gh:`pyTooling/download-artifact`
* :gh:`actions/download-artifact`
* :gh:`pyTooling/upload-artifact`
* :gh:`actions/upload-artifact`
* apt: Packages specified via :ref:`JOBTMPL/UnitTesting/Input/apt` parameter.
* homebrew: Packages specified via :ref:`JOBTMPL/UnitTesting/Input/brew` parameter.
* MSYS2: Packages specified via :ref:`JOBTMPL/UnitTesting/Input/pacboy` parameter.
* pip
* :pypi:`wheel`
* :pypi:`tomli`
* Python packages specified via :ref:`JOBTMPL/UnitTesting/Input/requirements` or
:ref:`JOBTMPL/UnitTesting/Input/mingw_requirements` parameter.
.. _JOBTMPL/UnitTesting/Instantiation:
Instantiation
*************
The following instantiation example creates a ``UnitTesting`` job derived from job template ``UnitTesting`` version
`@r6`. For providing the job matrix as a JSON string, the :ref:`JOBTMPL/Parameters` job template is used. Additionally,
the job needs configuration settings, which are stored in :file:`pyproject.toml`. Instead of duplicating these settings,
the :ref:`JOBTMPL/ExtractConfiguration` job template is used to extract these settings.
.. code-block:: yaml
jobs:
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTestingParams:
uses: pyTooling/Actions/.github/workflows/Parameters.yml@r6
with:
package_name: myPackage
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- ConfigParams
- UnitTestingParams
with:
jobs: ${{ needs.UnitTestingParams.outputs.python_jobs }}
requirements: '-r tests/unit/requirements.txt'
unittest_report_xml: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
unittest_xml_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).unittesting_xml }}
coverage_sqlite_artifact: ${{ fromJson(needs.UnitTestingParams.outputs.artifact_names).codecoverage_sqlite }}
.. seealso::
:ref:`JOBTMPL/Parameters`
``Parameters`` is usually used to pre-compute the job matrix as a JSON string with all system |times| environment
|times| Python version combinations.
:ref:`JOBTMPL/PublishTestResults`
``PublishTestResults`` can be used to merge all JUnit test reports into one file.
:ref:`JOBTMPL/PublishCoverageResults`
``PublishCoverageResults`` can be used to merge all code coverage reports into one file.
.. _JOBTMPL/UnitTesting/Parameters:
Parameter Summary
*****************
.. # |unittest_report_xml| code-block:: json
{ "directory": "report/unit",
"filename": "TestReportSummary.xml",
"fullpath": "report/unit/TestReportSummary.xml"
}
.. rubric:: Goto :ref:`input parameters <JOBTMPL/UnitTesting/Inputs>`
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| Parameter Name | Required | Type | Default |
+=========================================================================+==========+==========+===================================================================================================================================+
| :ref:`JOBTMPL/UnitTesting/Input/jobs` | yes | string | — — — — |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/apt` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/brew` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/pacboy` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/requirements` | no | string | ``'-r tests/requirements.txt'`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/mingw_requirements` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/macos_before_script` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/macos_arm_before_script` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/ubuntu_before_script` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/mingw64_before_script` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/ucrt64_before_script` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/root_directory` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/tests_directory` | no | string | ``'tests'`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/unittest_directory` | no | string | ``'unit'`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/unittest_report_xml` | no | string | :jsoncode:`{"directory": "report/unit", "filename": "TestReportSummary.xml", "fullpath": "report/unit/TestReportSummary.xml"}` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/coverage_config` | no | string | ``'pyproject.toml'`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/coverage_report_xml` | no | string | :jsoncode:`{"directory": "report/coverage", "filename": "coverage.xml", "fullpath": "report/coverage/coverage.xml"}` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/coverage_report_json` | no | string | :jsoncode:`{"directory": "report/coverage", "filename": "coverage.json", "fullpath": "report/coverage/coverage.json"}` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/coverage_report_html` | no | string | :jsoncode:`{"directory": "report/coverage"}` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/unittest_xml_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/unittest_html_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/coverage_sqlite_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/coverage_xml_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/coverage_json_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| :ref:`JOBTMPL/UnitTesting/Input/coverage_html_artifact` | no | string | ``''`` |
+-------------------------------------------------------------------------+----------+----------+-----------------------------------------------------------------------------------------------------------------------------------+
.. rubric:: Goto :ref:`secrets <JOBTMPL/UnitTesting/Secrets>`
This job template needs no secrets.
.. rubric:: Goto :ref:`output parameters <JOBTMPL/UnitTesting/Outputs>`
This job template has no output parameters.
.. _JOBTMPL/UnitTesting/Inputs:
Input Parameters
****************
.. _JOBTMPL/UnitTesting/Input/jobs:
jobs
====
:Type: string
:Required: yes
:Default Value: — — — —
:Possible Values: A JSON string with an array of dictionaries with the following key-value pairs:
:sysicon: icon to display
:system: name of the system
:runs-on: virtual machine image and base operating system
:runtime: name of the runtime environment if not running natively on the VM image
:shell: name of the shell
:pyicon: icon for CPython or pypy
:python: Python version
:envname: full name of the selected environment
:Description: A JSON encoded job matrix to run multiple Python job variations.
.. _JOBTMPL/UnitTesting/Input/apt:
apt
===
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid list of parameters for ``apt install``. |br|
Packages are specified as a space separated list like ``'graphviz curl gzip'``.
:Description: Additional Ubuntu system dependencies to be installed through *apt*.
:Example:
.. code-block:: yaml
UnitTests:
...
with:
apt: >-
graphviz
curl
gzip
.. _JOBTMPL/UnitTesting/Input/brew:
brew
====
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid list of parameters for ``brew install``. |br|
Packages are specified as a space separated list.
:Description: Additional macOS system dependencies to be installed through *brew*.
.. _JOBTMPL/UnitTesting/Input/pacboy:
pacboy
======
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid list of parameters for ``pacboy``. |br|
Packages are specified as a space separated list like ``'python-lxml:p python-numpy:p'``.
:Description: Additional MSYS2 system dependencies to be installed through *pacboy* (*pacman*). |br|
Usually, Python packages start with ``python-``. The suffix ``:p`` ensures pacboy figures out the
correct package repository prefix for MinGW64, UCRT64, ...
.. note::
Internally, a dedicated workflow step reads the :ref:`JOBTMPL/UnitTesting/Input/requirements` file
for Python and compares requested packages with a list of packages that should be installed through
*pacman*/*pacboy* compared to installation via *pip*. These are mainly core packages or packages
with embedded C code. |br|
The list of identified packages is handed over to *pacboy* for preinstallation. Otherwise *pip*
will later raise an error. |br|
The packages listed by this parameter will be installed in addition to the identified packages.
.. attention::
Ensure your Python requirements match the available version from MSYS2 packages list, otherwise
if your :file:`requirements.txt` requests a newer version then provided by MSYS2, such a dependency
will fail.
:Example:
.. code-block:: yaml
UnitTests:
...
with:
pacboy: >-
python-lxml:p
:Packages: The following list of Python packages is identified to be installed via *pacboy*:
* :ucrt64:`python-coverage` |rarr| :pypi:`coverage`
* :ucrt64:`igraph` |rarr| :pypi:`igraph`
* :ucrt64:`python-lxml` |rarr| :pypi:`lxml`
* :ucrt64:`python-markupsafe` |rarr| :pypi:`markupsafe`
* :ucrt64:`python-numpy` |rarr| :pypi:`numpy`
* :ucrt64:`python-pip` |rarr| :pypi:`pip`
* :ucrt64:`python-pyaml` |rarr| :pypi:`pyaml`
* :ucrt64:`python-ruamel-yaml` |rarr| :pypi:`ruamel-yaml`
* :ucrt64:`python-wheel` |rarr| :pypi:`wheel`
* :ucrt64:`python-tomli` |rarr| :pypi:`tomli`
* :ucrt64:`python-types-pyyaml` |rarr| :pypi:`types.pyyaml`
.. _JOBTMPL/UnitTesting/Input/requirements:
requirements
============
:Type: string
:Required: no
:Default Value: ``'-r tests/requirements.txt'``
:Possible Values: Any valid list of parameters for ``pip install``. |br|
Either a requirements file can be referenced using ``'-r path/to/requirements.txt'``, or a list of
packages can be specified using a space separated list like ``'coverage pytest'``.
:Description: Python dependencies to be installed through *pip*.
.. _JOBTMPL/UnitTesting/Input/mingw_requirements:
mingw_requirements
==================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid list of parameters for ``pip install``. |br|
Either a requirements file can be referenced using ``'-r path/to/requirements.txt'``, or a list of
packages can be specified using a space separated list like ``'coverage pytest'``.
:Description: Override Python dependencies to be installed through *pip* in MSYS2 (MinGW64/UCRT64) only.
.. _JOBTMPL/UnitTesting/Input/macos_before_script:
macos_before_script
===================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid *Bash* instructions as single-line or multi-line string suitable for macOS (Intel platform).
:Description: These optional *Bash* instructions for macOS are executed after setting up the environment and
installing the platform specific dependencies and before running the unit test.
.. _JOBTMPL/UnitTesting/Input/macos_arm_before_script:
macos_arm_before_script
=======================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid *Bash* instructions as single-line or multi-line string suitable for macOS (ARM platform).
:Description: These optional *Bash* instructions for macOS are executed after setting up the environment and
installing the platform specific dependencies and before running the unit test.
.. _JOBTMPL/UnitTesting/Input/ubuntu_before_script:
ubuntu_before_script
====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid *Bash* instructions as single-line or multi-line string suitable for Ubuntu.
:Description: These optional *Bash* instructions for Ubuntu are executed after setting up the environment and
installing the platform specific dependencies and before running the unit test.
.. _JOBTMPL/UnitTesting/Input/mingw64_before_script:
mingw64_before_script
=====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid *Bash* instructions as single-line or multi-line string suitable for MinGW64 on Windows.
:Description: These optional *Bash* instructions for MinGW64 on Windows are executed after setting up the
environment and installing the platform specific dependencies and before running the unit test.
.. _JOBTMPL/UnitTesting/Input/ucrt64_before_script:
ucrt64_before_script
====================
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid *Bash* instructions as single-line or multi-line string suitable for UCRT64 on Windows.
:Description: These optional *Bash* instructions for UCRT64 on Windows are executed after setting up the
environment and installing the platform specific dependencies and before running the unit test.
.. hint::
The next parameters allow running different test kinds (unit tests, performance tests, platform tests, ...) with the
same job template, but isolated in sub-directories, thus pytest only discovers a subset of tests. The following code
blocks showcase how the job template uses these parameters and how it relates to a proposed directory structure.
.. grid:: 3
.. grid-item::
:columns: 5
.. card:: Relation between :ref:`JOBTMPL/UnitTesting/Input/root_directory`, :ref:`JOBTMPL/UnitTesting/Input/tests_directory` and :ref:`JOBTMPL/UnitTesting/Input/unittest_directory`
.. code-block:: bash
cd <RepositoryRoot>
cd ${root_directory}
python -m \
pytest -raP \
--color=yes ..... \
"${tests_directory}/${unittest_directory}"
.. grid-item::
:columns: 3
.. card:: Directory Structure
.. code-block::
<RepositoryRoot>/
doc/
myPackage/
__init__.py
tests/
unit/
myTests.py
.. grid-item::
:columns: 3
.. card:: Example for Default Values
.. code-block:: bash
cd <RepositoryRoot>
cd .
python -m \
pytest -raP \
--color=yes ..... \
"tests/unit"
.. _JOBTMPL/UnitTesting/Input/root_directory:
root_directory
==============
:Type: string
:Required: no
:Default Value: ``''``
:Possible Values: Any valid directory or sub-directory.
:Description: Working directory for running tests. |br|
Usually, this is the repository's root directory. Tests are called relatively from here. See
:ref:`JOBTMPL/UnitTesting/Input/tests_directory` and :ref:`JOBTMPL/UnitTesting/Input/unittest_directory`.
.. _JOBTMPL/UnitTesting/Input/tests_directory:
tests_directory
===============
:Type: string
:Required: no
:Default Value: ``'tests'``
:Possible Values: Any valid directory or sub-directory.
:Description: Path to the directory containing tests (relative from :ref:`JOBTMPL/UnitTesting/Input/root_directory`).
.. _JOBTMPL/UnitTesting/Input/unittest_directory:
unittest_directory
==================
:Type: string
:Required: no
:Default Value: ``'unit'``
:Possible Values: Any valid directory or sub-directory.
:Description: Path to the directory containing unit tests (relative from :ref:`JOBTMPL/UnitTesting/Input/tests_directory`).
.. _JOBTMPL/UnitTesting/Input/unittest_report_xml:
unittest_report_xml
===================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/unit",
"filename": "UnittestReportSummary.xml",
"fullpath": "reports/unit/UnittestReportSummary.xml"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the unittest summary report in XML format will be saved.
:filename: Filename of the generated JUnit XML report. |br|
Any valid filename accepted by ``pytest ... --junitxml=${unittest_report_xml}``.
:fullpath: The concatenation of both previous fields using the ``/`` separator.
:Description: Directory, filename and fullpath as JSON object where the unittest summary report in XML format will
be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- ConfigParams
with:
...
unittest_report_xml: ${{ needs.ConfigParams.outputs.unittest_report_xml }}
.. _JOBTMPL/UnitTesting/Input/coverage_config:
coverage_config
===============
:Type: string
:Required: no
:Default Value: ``'pyproject.toml'``
:Possible Values: TBD
.. _JOBTMPL/UnitTesting/Input/coverage_report_xml:
coverage_report_xml
===================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/coverage",
"filename": "coverage.xml",
"fullpath": "reports/coverage/coverage.xml"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the code coverage report in Cobertura XML format will be
saved.
:filename: Filename of the generated Cobertura XML report. |br|
Any valid XML filename.
:fullpath: The concatenation of both previous fields using the ``/`` separator.
:Description: Directory, filename and fullpath as JSON object where the code coverage report in Cobertura XML format
will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- ConfigParams
with:
...
coverage_report_xml: ${{ needs.ConfigParams.outputs.coverage_report_xml }}
.. _JOBTMPL/UnitTesting/Input/coverage_report_json:
coverage_report_json
====================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/coverage",
"filename": "coverage.json",
"fullpath": "reports/coverage/coverage.json"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the code coverage report in Coverage.py's JSON format
will be saved.
:filename: Filename of the generated Coverage.py JSON report. |br|
Any valid JSON filename.
:fullpath: The concatenation of both previous fields using the ``/`` separator.
:Description: Directory, filename and fullpath as JSON object where the code coverage report in Coverage.py's JSON
format will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- ConfigParams
with:
...
coverage_report_json: ${{ needs.ConfigParams.outputs.coverage_report_json }}
.. _JOBTMPL/UnitTesting/Input/coverage_report_html:
coverage_report_html
====================
:Type: string (JSON)
:Required: no
:Default Value:
.. code-block:: json
{ "directory": "reports/coverage/html"
}
:Possible Values: Any valid JSON string containing a JSON object with fields:
:directory: Directory or sub-directory where the code coverage report in HTML format will be saved.
:Description: Directory as JSON object where the code coverage report in HTML format will be saved. |br|
This path is configured in :file:`pyproject.toml` and can be extracted by
:ref:`JOBTMPL/ExtractConfiguration`.
:Example:
.. code-block:: yaml
ConfigParams:
uses: pyTooling/Actions/.github/workflows/ExtractConfiguration.yml@r6
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r6
needs:
- ConfigParams
with:
...
coverage_report_html: ${{ needs.ConfigParams.outputs.coverage_report_html }}
.. _JOBTMPL/UnitTesting/Input/unittest_xml_artifact:
unittest_xml_artifact
=====================
:Type: string
:Required: no
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the unittest report summary in XML format.
.. _JOBTMPL/UnitTesting/Input/unittest_html_artifact:
unittest_html_artifact
======================
:Type: string
:Required: no
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the unittest report in HTML format.
.. _JOBTMPL/UnitTesting/Input/coverage_sqlite_artifact:
coverage_sqlite_artifact
========================
:Type: string
:Required: no
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the code coverage report as SQLite database.
.. _JOBTMPL/UnitTesting/Input/coverage_xml_artifact:
coverage_xml_artifact
=====================
:Type: string
:Required: no
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the code coverage report in XML format.
.. _JOBTMPL/UnitTesting/Input/coverage_json_artifact:
coverage_json_artifact
======================
:Type: string
:Required: no
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the code coverage report in JSON format.
.. _JOBTMPL/UnitTesting/Input/coverage_html_artifact:
coverage_html_artifact
======================
:Type: string
:Required: no
:Possible Values: Any valid artifact name.
:Description: Name of the artifact containing the code coverage report in HTML format.
.. _JOBTMPL/UnitTesting/Secrets:
Secrets
*******
This job template needs no secrets.
.. _JOBTMPL/UnitTesting/Outputs:
Outputs
*******
This job template has no output parameters.
.. _JOBTMPL/UnitTesting/Optimizations:
Optimizations
*************
The following optimizations can be used to reduce the template's runtime.
Disable unit test XML generation
If parameter :ref:`JOBTMPL/UnitTesting/Input/unittest_xml_artifact` is empty, no unit test summary report will be
generated and no JUnit XML artifact will be uploaded.
Disabled code coverage collection
If parameter :ref:`JOBTMPL/UnitTesting/Input/coverage_config` is empty, no code coverage will be collected.
Disable code coverage SQLite database artifact upload
If parameter :ref:`JOBTMPL/UnitTesting/Input/coverage_sqlite_artifact` is empty, the collected code coverage database
(SQLlite format) wont be uploaded as an artifact.
Disable code coverage report conversion to the Cobertura XML format.
If parameter :ref:`JOBTMPL/UnitTesting/Input/coverage_xml_artifact` is empty, no Cobertura XML file will be generated
from code coverage report. As no Cobertura XML file exists, no code coverage XML artifact will be uploaded.
Disable code coverage report conversion to the *Coverage.py* JSON format.
If parameter :ref:`JOBTMPL/UnitTesting/Input/coverage_json_artifact` is empty, no *Coverage.py* JSON file will be
generated from code coverage report. As no JSON file exists, no code coverage JSON artifact will be uploaded.
Disable code coverage report conversion to an HTML website.
If parameter :ref:`JOBTMPL/UnitTesting/Input/coverage_html_artifact` is empty, no coverage report HTML report will be
generated from code coverage report. As no HTML report exists, no code coverage HTML artifact will be uploaded.

View File

@@ -0,0 +1,15 @@
.. _JOBTMPL/Testing:
Testing
#######
The category *testing* provides workflow templates implementing
* :ref:`JOBTMPL/UnitTesting` - Run unit tests and collect code coverage.
* :ref:`JOBTMPL/ApplicationTesting` - Run application tests.
.. toctree::
:hidden:
UnitTesting
ApplicationTesting

View File

@@ -1,159 +0,0 @@
.. _JOBTMPL/UnitTesting:
UnitTesting
###########
This template runs multiple jobs from a matrix as a cross of Python versions and systems. The summary report in junit
XML format is optionally uploaded as an artifact.
Configuration options to ``pytest`` should be given via section ``[tool.pytest.ini_options]`` in a ``pyproject.toml``
file.
**Behavior:**
1. Checkout repository
2. Setup Python and install dependencies
3. Run unit tests using ``pytest``.
4. Upload junit test summary as an artifact
**Dependencies:**
* :gh:`actions/checkout`
* :gh:`msys2/setup-msys2`
* :gh:`actions/setup-python`
* :gh:`actions/upload-artifact`
Instantiation
*************
Simple Example
==============
.. code-block:: yaml
jobs:
Params:
# ...
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@r0
needs:
- Params
with:
jobs: ${{ needs.Params.outputs.python_jobs }}
artifact: ${{ fromJson(needs.Params.outputs.artifact_names).unittesting }}
Complex Example
===============
.. code-block:: yaml
TBD
Parameters
**********
jobs
====
+----------------+----------+----------+--------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==============+
| jobs | yes | string | — — — — |
+----------------+----------+----------+--------------+
JSON list with environment fields, telling the system and Python versions to run tests with.
requirements
============
+----------------+----------+----------+---------------------------------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+=================================+
| requirements | optional | string | ``-r tests/requirements.txt`` |
+----------------+----------+----------+---------------------------------+
Python dependencies to be installed through pip.
pacboy
======
+----------------+----------+----------+-----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+===========+
| pacboy | optional | string | ``""`` |
+----------------+----------+----------+-----------+
Additional MSYS2 dependencies to be installed through pacboy (pacman).
Internally, a workflow step reads the requirements file for Python and compares requested packages with a list of
packages that should be installed through pacman/pacboy compared to installation via pip. These are mainly core packages
or packages with embedded C code.
.. code-block:: yaml
pacboy: >-
python-lxml:p
mingw_requirements
==================
+--------------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+====================+==========+==========+==========+
| mingw_requirements | optional | string | ``""`` |
+--------------------+----------+----------+----------+
Override Python dependencies to be installed through pip on MSYS2 (MINGW64) only.
tests_directory
===============
+-----------------+----------+----------+-----------+
| Parameter Name | Required | Type | Default |
+=================+==========+==========+===========+
| tests_directory | optional | string | ``tests`` |
+-----------------+----------+----------+-----------+
Path to the directory containing tests (test working directory).
unittest_directory
==================
+--------------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+====================+==========+==========+==========+
| unittest_directory | optional | string | ``unit`` |
+--------------------+----------+----------+----------+
Path to the directory containing unit tests (relative to tests_directory).
artifact
========
+----------------+----------+----------+----------+
| Parameter Name | Required | Type | Default |
+================+==========+==========+==========+
| artifact | optional | string | ``""`` |
+----------------+----------+----------+----------+
Generate unit test report with junitxml and upload results as an artifact.
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -1,42 +0,0 @@
.. _JOBTMPL/VerifyDocumentation:
VerifyDocs
##########
This job extracts code examples from the README and tests these code snippets.
**Behavior:**
TBD
**Dependencies:**
TBD
Instantiation
*************
Simple Example
==============
.. todo:: VerifyDocs:SimpleExample Needs documentation.
Complex Example
===============
.. todo:: VerifyDocs:ComplexExample Needs documentation.
Parameters
**********
.. todo:: VerifyDocs:Parameters Needs documentation.
Secrets
*******
This job template needs no secrets.
Results
*******
This job template has no output parameters.

View File

@@ -0,0 +1,10 @@
python_version
==============
:Type: string
:Required: no
:Default Value: ``'3.14'``
:Possible Values: Any valid Python version conforming to the pattern ``<major>.<minor>`` or ``pypy-<major>.<minor>``. |br|
See `actions/python-versions - available Python versions <https://github.com/actions/python-versions>`__
and `actions/setup-python - configurable Python versions <https://github.com/actions/setup-python>`__.
:Description: Python version used to run Python code in the job.

View File

@@ -0,0 +1,15 @@
ubuntu_image_version
====================
:Type: string
:Required: no
:Default Value: ``'24.04'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
for available Ubuntu image versions.
:Description: Version of the Ubuntu image used to run the job.
.. note::
Unfortunately, GitHub Actions has only a `limited set of functions <https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#functions>`__,
thus, the usual Ubuntu image name like ``'ubuntu-24.04'`` can't be split into image name and image
version.

View File

@@ -1,67 +1,98 @@
.. _JOBTMPL:
.. index::
single: GitHub Action Reusable Workflow
Overview
########
The following list categorizes all pre-defined job templates, which can be instantiated in a pipeline (GitHub Action
Workflow). They can also serve as an example for creating or driving own job templates.
Workflow). They can also serve as an example for creating or deriving own job templates. All job templates are highly
customizable.
**Table of Contents:**
.. include:: Templates.rst
.. hlist::
:columns: 2
* **Global Templates**
* :ref:`JOBTMPL/Parameters`
* **Unit Tests, Code Coverage, Code Quality, ...**
* :ref:`JOBTMPL/UnitTesting`
* :ref:`JOBTMPL/CodeCoverage`
* :ref:`JOBTMPL/StaticTypeChecking`
* *code formatting (planned)*
* *coding style (planned)*
* *code linting (planned)*
* **Build and Packaging**
* :ref:`JOBTMPL/Package`
* **Documentation**
* :ref:`JOBTMPL/VerifyDocumentation`
* :ref:`JOBTMPL/BuildTheDocs`
* **Releasing, Publishing**
* :ref:`JOBTMPL/GitHubReleasePage`
* :ref:`JOBTMPL/PyPI`
* :ref:`JOBTMPL/PublishTestResults`
* :ref:`JOBTMPL/PublishToGitHubPages`
* **Cleanups**
* :ref:`JOBTMPL/ArtifactCleanup`
.. _JOBTMPL/Instantiation:
.. index::
single: GitHub Action Reusable Workflow; Instantiation
Instantiation
*************
When instantiating a template, a ``jobs:<Name>:uses`` is used to refer to a template file. Unfortunately, besides the
GitHub SLUG (*<Organization>/<Repository>*), also the full path to the template needs to be gives, but still it can't be
outside of ``.github/workflows`` to create a cleaner repository structure. Finally, the path contains a branch name
postfixed by ``@<branch>`` (tags are still not supported by GitHub Actions). A ``jobs:<Name>:with:`` section can be used
to handover input parameters to the template.
GitHub SLUG (*<Organization>/<Repository>*), also the full path to the template needs to be gives. Unfortunately, it
can't be outside of the ``.github/workflows`` directory creating a cleaner repository structure. Finally, the path
contains a branch name postfixed by ``@<branch>`` (tags are still not supported by GitHub Actions). Repositories usually
offer a ``@v2``/``@r2`` syntax for refering to the second version/revision.
Allmost all templates are generic and offer lots of configuration options. For handing over input parameters, a
``jobs:<Name>:with:`` node with a dictionary can be used. Additionally, some templates might require secrets, which
are passed from GitHub's ``secrets`` context to the template by using a ``jobs:<Name>:secrets:`` node.
Some templates might provide output parameters, which can be used in dependent jobs by setting a job dependency using
``jobs:<Name>:needs:``. The output parameter can be retrieved by accessing the ``needs`` context.
.. code-block:: yaml
name: Pipeline
on:
push:
workflow_dispatch:
schedule:
# Every Friday at 22:00 - rerun pipeline to check for dependency-based issues
- cron: '0 22 * * 5'
jobs:
<InstanceName>:
uses: <GitHubOrganization>/<Repository>/.github/workflows/<Template>.yml@v0
uses: <GitHubOrganization>/<Repository>/.github/workflows/<Template>.yml@r6
with:
<Param1>: <Value>
<Param1>: <Value1>
<Param2>: <Value2>
secrets:
<Secret1>: ${{ secrets.<SecretVariable1> }}
<Secret2>: ${{ secrets.<SecretVariable2> }}
<OtherInstance>:
needs:
- <InstanceName>
...
with:
<Param1>: ${{ needs.<InstanceName>.outputs.<Output1> }}
.. _JOBTMPL/CommonParameters:
.. index::
single: GitHub Action Reusable Workflow; Common Parameters
Common Parameters
*****************
All jobs specified in the templates are executed in
`images provided by GitHub Actions <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__.
Except for platform specific jobs (e.g., unit testing on macOS, Ubuntu, Windows Server, ...) allmost all other jobs use
an Ubuntu image. This image can be configured by the job template input parameter :ref:`JOBTMPL/Common/Input/ubuntu_image`.
Similarly, many jobs rely on Python scripting and therefore need a Python version, which can be configured via
:ref:`JOBTMPL/Common/Input/python_version`.
.. _JOBTMPL/Common/Input/ubuntu_image:
ubuntu_image
============
:Type: string
:Required: usually no
:Default Value: ``'ubuntu-24.04'``
:Possible Values: See `actions/runner-images - Available Images <https://github.com/actions/runner-images?tab=readme-ov-file#available-images>`__
for available Ubuntu image versions.
:Description: Name of the Ubuntu image used to run a job.
.. _JOBTMPL/Common/Input/ubuntu_image_version:
.. include:: _ubuntu_image_version.rst
.. _JOBTMPL/Common/Input/python_version:
.. include:: _python_version.rst

View File

@@ -1,4 +1,23 @@
/* theme overrides */
/*
Theme overrides for ReadTheDocs Theme by Patrick Lehmann
*/
/* General overrides */
html {
font-size: 15px;
}
footer {
font-size: 95%;
text-align: center
}
footer p {
margin-bottom: 0px /* 12px */;
font-size: 95%
}
/* Headline */
.rst-content h1,
.rst-content h2 {
margin-top: 24px;
@@ -14,34 +33,10 @@
margin-bottom: 6px;
}
.rst-content p {
margin-bottom: 6px
}
/* general overrides */
html {
font-size: 15px;
}
footer {
font-size: 95%;
text-align: center
}
footer p {
margin-bottom: 0px /* 12px */;
font-size: 95%
}
section > p,
.section p,
.simple li {
text-align: justify
}
.rst-content .topic-title {
font-size: larger;
font-weight: 700;
text-decoration: underline;
margin-top: 18px;
margin-bottom: 6px;
}
@@ -53,6 +48,31 @@ section > p,
margin-bottom: 16px;
}
/* Paragraphs */
.rst-content p {
margin-bottom: 6px
}
section > p,
.section p,
.simple li {
text-align: justify
}
/* Field lists*/
/* last paragraph in a field list*/
dl.field-list > dd > p:last-of-type {
margin-bottom: 6px /* 12px */
}
/* Nested field lists */
dl.field-list > dd > dl.field-list.simple {
margin-bottom: 0px /* 24px */
}
/* code-block within field list */
dl.field-list > dd > div:has(div.highlight) {
margin-bottom: 0px /* 24px */
}
/* wyrm overrides */
.wy-menu-vertical header,
.wy-menu-vertical p.caption {
@@ -65,8 +85,6 @@ section > p,
.wy-side-nav-search {
margin-bottom: 0 /* .809em */;
background-color: #333333 /* #2980b9 */;
/* BTD: */
/*color: #fcfcfc*/
}
.wy-side-nav-search input[type=text] {
@@ -74,15 +92,11 @@ section > p,
}
.wy-side-nav-search .wy-dropdown > a, .wy-side-nav-search > a {
/* BTD: */
/*color: #fcfcfc;*/
margin-bottom: 0.404em /* .809em */;
}
.wy-side-nav-search > div.version {
margin: 0 0 6px 0;
/* BTD: */
/*margin-top: -.4045em;*/
}
.wy-nav .wy-menu-vertical a:hover {
@@ -97,7 +111,7 @@ section > p,
background: #333333 /* #2980b9 */;
}
/* Sphinx Design */
/* Theme overrides for Sphinx Design by Patrick Lehmann */
.sd-tab-set {
margin: 0
}
@@ -113,3 +127,11 @@ section > p,
padding-left: 0;
padding-right: 0;
}
.sd-container-fluid > .sd-row > .sd-col > p.rubric {
margin-bottom: 6px;
}
.sd-container-fluid > .sd-row > .sd-col > ul.simple {
margin-bottom: 0px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Some files were not shown because too many files have changed in this diff Show More