This commit is contained in:
Patrick Lehmann
2024-09-27 21:36:25 +02:00
committed by GitHub
37 changed files with 661 additions and 132 deletions

View File

@@ -1,16 +1,30 @@
# New Features # New Features
* tbd
* tbd * tbd
# Changes # Changes
* tbd
* tbd * tbd
# Bug Fixes # Bug Fixes
* tbd
* tbd * tbd
---------- # Documentation
# Related PRs:
* tbd * tbd
* tbd
# Unit Tests
* tbd
* tbd
----------
# Related Issues and Pull-Requests
* tbd
* tbd

View File

@@ -124,22 +124,30 @@ jobs:
requirements = "${{ inputs.requirements }}" requirements = "${{ inputs.requirements }}"
if requirements.startswith("-r"): if requirements.startswith("-r"):
requirementsFile = Path(requirements[2:].lstrip()) requirementsFile = Path(requirements[2:].lstrip())
try:
dependencies = loadRequirementsFile(requirementsFile) dependencies = loadRequirementsFile(requirementsFile)
except FileNotFoundError as ex:
print(f"::error title=FileNotFoundError::{ex}")
exit(1)
else: else:
dependencies = [req.strip() for req in requirements.split(" ")] dependencies = [req.strip() for req in requirements.split(" ")]
packages = { packages = {
"coverage": "python-coverage:p", "coverage": "python-coverage:p",
"docstr_coverage": "python-pyyaml:p",
"igraph": "igraph:p", "igraph": "igraph:p",
"jinja2": "python-markupsafe:p", "jinja2": "python-markupsafe:p",
"lxml": "python-lxml:p", "lxml": "python-lxml:p",
"numpy": "python-numpy:p", "numpy": "python-numpy:p",
"markupsafe": "python-markupsafe:p", "markupsafe": "python-markupsafe:p",
"pip": "python-pip:p", "pip": "python-pip:p",
"pyyaml": "python-pyyaml:p",
"ruamel.yaml": "python-ruamel-yaml:p python-ruamel.yaml.clib:p", "ruamel.yaml": "python-ruamel-yaml:p python-ruamel.yaml.clib:p",
"sphinx": "python-markupsafe:p", "sphinx": "python-markupsafe:p",
"tomli": "python-tomli:p", "tomli": "python-tomli:p",
"wheel": "python-wheel:p", "wheel": "python-wheel:p",
"pyEDAA.ProjectModel": "python-ruamel-yaml:p python-ruamel.yaml.clib:p python-lxml:p",
"pyEDAA.Reports": "python-ruamel-yaml:p python-ruamel.yaml.clib:p python-lxml:p",
} }
subPackages = { subPackages = {
"pytooling": { "pytooling": {
@@ -215,7 +223,7 @@ jobs:
ls -l install ls -l install
python -m pip install --disable-pip-version-check -U install/*.whl python -m pip install --disable-pip-version-check -U install/*.whl
- name: Run application tests (Ubuntu/macOS) - name: Run application tests (Ubuntu/macOS)
if: matrix.system != 'windows' if: matrix.system != 'windows'
run: | run: |
export ENVIRONMENT_NAME="${{ matrix.envname }}" export ENVIRONMENT_NAME="${{ matrix.envname }}"
@@ -230,7 +238,7 @@ jobs:
python -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.apptest_directory }} python -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.apptest_directory }}
fi fi
- name: Run application tests (Windows) - name: Run application tests (Windows)
if: matrix.system == 'windows' if: matrix.system == 'windows'
run: | run: |
$env:ENVIRONMENT_NAME = "${{ matrix.envname }}" $env:ENVIRONMENT_NAME = "${{ matrix.envname }}"

View File

@@ -38,7 +38,7 @@ on:
jobs: jobs:
ArtifactCleanUp: ArtifactCleanUp:
name: 🗑️ Artifact Cleanup name: 🗑️ Artifact Cleanup
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: 🗑️ Delete package Artifacts - name: 🗑️ Delete package Artifacts

View File

@@ -34,7 +34,7 @@ on:
jobs: jobs:
BuildTheDocs: BuildTheDocs:
name: 📓 Run BuildTheDocs name: 📓 Run BuildTheDocs
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository

View File

@@ -33,16 +33,16 @@ on:
description: 'Source code directory to check.' description: 'Source code directory to check.'
required: true required: true
type: string type: string
# fail_below: fail_under:
# description: 'Minimum required documentation coverage level' description: 'Minimum required documentation coverage level'
# required: false required: false
# default: 75 default: 80
# type: string type: string
jobs: jobs:
DocCoverage: DocCoverage:
name: 👀 Check documentation coverage name: 👀 Check documentation coverage
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -59,9 +59,9 @@ jobs:
- name: Run 'interrogate' Documentation Coverage Check - name: Run 'interrogate' Documentation Coverage Check
continue-on-error: true continue-on-error: true
run: | run: |
interrogate -c pyproject.toml interrogate -c pyproject.toml --fail-under=${{ inputs.fail_under }} && echo "::error title=interrogate::Insufficient documentation quality (goal: ${{ inputs.fail_under }})"
- name: Run 'docstr_coverage' Documentation Coverage Check - name: Run 'docstr_coverage' Documentation Coverage Check
continue-on-error: true continue-on-error: true
run: | run: |
docstr_coverage -v ${{ inputs.directory }} docstr-coverage -v 2 --fail-under=${{ inputs.fail_under }} ${{ inputs.directory }} && echo "::error title=docstr-coverage::Insufficient documentation quality (goal: ${{ inputs.fail_under }})"

View File

@@ -63,7 +63,7 @@ jobs:
Coverage: Coverage:
name: 📈 Collect Coverage Data using Python ${{ inputs.python_version }} name: 📈 Collect Coverage Data using Python ${{ inputs.python_version }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository
@@ -102,7 +102,9 @@ jobs:
htmlDirectory = pyProjectSettings["tool"]["coverage"]["html"]["directory"] htmlDirectory = pyProjectSettings["tool"]["coverage"]["html"]["directory"]
xmlFile = pyProjectSettings["tool"]["coverage"]["xml"]["output"] xmlFile = pyProjectSettings["tool"]["coverage"]["xml"]["output"]
else: else:
print(f"File '{pyProjectFile}' not found and no ' .coveragerc' file specified.") print(f"File '{pyProjectFile}' not found.")
print(f"::error title=FileNotFoundError::File '{pyProjectFile}' not found.")
exit(1)
# Read output paths from '.coveragerc' file # Read output paths from '.coveragerc' file
elif len(coverageRC) > 0: elif len(coverageRC) > 0:
@@ -115,6 +117,8 @@ jobs:
xmlFile = coverageRCSettings["xml"]["output"] xmlFile = coverageRCSettings["xml"]["output"]
else: else:
print(f"File '{coverageRCFile}' not found.") print(f"File '{coverageRCFile}' not found.")
print(f"::error title=FileNotFoundError::File '{coverageRCFile}' not found.")
exit(1)
# Write jobs to special file # Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT")) github_output = Path(getenv("GITHUB_OUTPUT"))

View File

@@ -36,7 +36,7 @@ on:
jobs: jobs:
IntermediateCleanUp: IntermediateCleanUp:
name: 🗑️ Intermediate Artifact Cleanup name: 🗑️ Intermediate Artifact Cleanup
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: 🗑️ Delete SQLite coverage artifacts from matrix jobs - name: 🗑️ Delete SQLite coverage artifacts from matrix jobs
uses: geekyeggo/delete-artifact@v5 uses: geekyeggo/delete-artifact@v5

View File

@@ -42,7 +42,7 @@ on:
jobs: jobs:
PDFDocumentation: PDFDocumentation:
name: 📓 Converting LaTeX Documentation to PDF name: 📓 Converting LaTeX Documentation to PDF
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: 📥 Download artifacts '${{ inputs.latex_artifact }}' from 'SphinxDocumentation' job - name: 📥 Download artifacts '${{ inputs.latex_artifact }}' from 'SphinxDocumentation' job
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4

View File

@@ -44,7 +44,7 @@ jobs:
Package: Package:
name: 📦 Package in Source and Wheel Format name: 📦 Package in Source and Wheel Format
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository

View File

@@ -42,7 +42,7 @@ on:
system_list: system_list:
description: 'Space separated list of systems to run tests on.' description: 'Space separated list of systems to run tests on.'
required: false required: false
default: 'ubuntu windows macos mingw64 ucrt64' default: 'ubuntu windows macos-arm mingw64 ucrt64'
type: string type: string
include_list: include_list:
description: 'Space separated list of system:python items to be included into the list of test.' description: 'Space separated list of system:python items to be included into the list of test.'
@@ -59,6 +59,26 @@ on:
required: false required: false
default: '' default: ''
type: string type: string
ubuntu_image:
description: 'The used GitHub Action image for Ubuntu based jobs.'
required: false
default: 'ubuntu-24.04'
type: string
windows_image:
description: 'The used GitHub Action image for Windows based jobs.'
required: false
default: 'windows-latest'
type: string
macos_intel_image:
description: 'The used GitHub Action image for macOS (Intel x86-64) based jobs.'
required: false
default: 'macos-latest-large'
type: string
macos_arm_image:
description: 'The used GitHub Action image for macOS (ARM arm64) based jobs.'
required: false
default: 'macos-latest'
type: string
outputs: outputs:
python_version: python_version:
@@ -76,7 +96,7 @@ on:
jobs: jobs:
Parameters: Parameters:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
outputs: outputs:
python_version: ${{ steps.params.outputs.python_version }} python_version: ${{ steps.params.outputs.python_version }}
python_jobs: ${{ steps.params.outputs.python_jobs }} python_jobs: ${{ steps.params.outputs.python_jobs }}
@@ -91,8 +111,8 @@ jobs:
from json import dumps as json_dumps from json import dumps as json_dumps
from os import getenv from os import getenv
from pathlib import Path from pathlib import Path
from pprint import pprint
from textwrap import dedent from textwrap import dedent
from typing import Iterable
name = "${{ inputs.name }}".strip() name = "${{ inputs.name }}".strip()
python_version = "${{ inputs.python_version }}".strip() python_version = "${{ inputs.python_version }}".strip()
@@ -138,7 +158,7 @@ jobs:
if currentAlphaVersion in versions: if currentAlphaVersion in versions:
print(f"::notice title=Experimental::Python {currentAlphaVersion} ({currentAlphaRelease}) is a pre-release.") print(f"::notice title=Experimental::Python {currentAlphaVersion} ({currentAlphaRelease}) is a pre-release.")
for disable in disabled: for disable in disabled:
print(f"::warning title=Disabled Python Job::System '{disable}' temporary disabled.") print(f"::warning title=Disabled Python Job::System '{disable}' temporarily disabled.")
# see https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json # see https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json
data = { data = {
@@ -158,9 +178,10 @@ jobs:
}, },
# Runner systems (runner images) supported by GitHub Actions # Runner systems (runner images) supported by GitHub Actions
"sys": { "sys": {
"ubuntu": { "icon": "🐧", "runs-on": "ubuntu-latest", "shell": "bash", "name": "Linux (x86-64)", "minPy": (3, 7)}, "ubuntu": { "icon": "🐧", "runs-on": "${{ inputs.ubuntu_image }}", "shell": "bash", "name": "Linux (x86-64)" },
"windows": { "icon": "🪟", "runs-on": "windows-latest", "shell": "pwsh", "name": "Windows (x86-64)", "minPy": (3, 7)}, "windows": { "icon": "🪟", "runs-on": "${{ inputs.windows_image }}", "shell": "pwsh", "name": "Windows (x86-64)" },
"macos": { "icon": "🍎", "runs-on": "macos-latest", "shell": "bash", "name": "MacOS (x86-64)", "minPy": (3, 10)}, "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 (arm64)" },
}, },
# Runtimes provided by MSYS2 # Runtimes provided by MSYS2
"runtime": { "runtime": {
@@ -183,9 +204,23 @@ jobs:
for disable in disabled: for disable in disabled:
print(f"- {disable}") print(f"- {disable}")
def toVersion(value): def match(combination: str, pattern: str) -> bool:
major, minor = value.split(".") system, version = combination.split(":")
return int(major[-1]), int(minor) sys, ver = pattern.split(":")
if sys == "*":
return (ver == "*") or (version == ver)
elif system == sys:
return (ver == "*") or (version == ver)
else:
return False
def notIn(combination: str, patterns: Iterable[str]) -> bool:
for pattern in patterns:
if match(combination, pattern):
return False
return True
combinations = [ combinations = [
(system, version) (system, version)
@@ -193,22 +228,20 @@ jobs:
if system in data["sys"] if system in data["sys"]
for version in versions for version in versions
if version in data["python"] if version in data["python"]
and toVersion(version) >= data["sys"][system]["minPy"] and notIn(f"{system}:{version}", excludes)
and f"{system}:{version}" not in excludes and notIn(f"{system}:{version}", disabled)
and f"{system}:{version}" not in disabled
] + [ ] + [
(system, currentMSYS2Version) (system, currentMSYS2Version)
for system in systems for system in systems
if system in data["runtime"] if system in data["runtime"]
and f"{system}:{currentMSYS2Version}" not in excludes and notIn(f"{system}:{currentMSYS2Version}", excludes)
and f"{system}:{currentMSYS2Version}" not in disabled and notIn(f"{system}:{currentMSYS2Version}", disabled)
] + [ ] + [
(system, version) (system, version)
for system, version in includes for system, version in includes
if system in data["sys"] if system in data["sys"]
and version in data["python"] and version in data["python"]
and toVersion(version) >= data["sys"][system]["minPy"] and notIn(f"{system}:{version}", disabled)
and f"{system}:{version}" not in disabled
] ]
print(f"Combinations ({len(combinations)}):") print(f"Combinations ({len(combinations)}):")
for system, version in combinations: for system, version in combinations:

View File

@@ -57,7 +57,7 @@ on:
jobs: jobs:
PublishCoverageResults: PublishCoverageResults:
name: 📊 Publish Code Coverage Results name: 📊 Publish Code Coverage Results
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: always() if: always()
steps: steps:
@@ -71,7 +71,7 @@ jobs:
- name: 🔧 Install coverage and tomli - name: 🔧 Install coverage and tomli
run: | run: |
python -m pip install --disable-pip-version-check -U coverage[toml] tomli python -m pip install -U --disable-pip-version-check --break-system-packages coverage[toml] tomli
- name: 🔁 Extract configurations from pyproject.toml - name: 🔁 Extract configurations from pyproject.toml
id: getVariables id: getVariables
@@ -102,7 +102,9 @@ jobs:
xmlFile = Path(pyProjectSettings["tool"]["coverage"]["xml"]["output"]) xmlFile = Path(pyProjectSettings["tool"]["coverage"]["xml"]["output"])
jsonFile = Path(pyProjectSettings["tool"]["coverage"]["json"]["output"]) jsonFile = Path(pyProjectSettings["tool"]["coverage"]["json"]["output"])
else: else:
print(f"File '{pyProjectFile}' not found and no '.coveragerc' file specified.") print(f"File '{pyProjectFile}' not found.")
print(f"::error title=FileNotFoundError::File '{pyProjectFile}' not found.")
exit(1)
# Read output paths from '.coveragerc' file # Read output paths from '.coveragerc' file
elif len(coverageRC) > 0: elif len(coverageRC) > 0:
@@ -116,6 +118,8 @@ jobs:
jsonFile = Path(coverageRCSettings["json"]["output"]) jsonFile = Path(coverageRCSettings["json"]["output"])
else: else:
print(f"File '{coverageRCFile}' not found.") print(f"File '{coverageRCFile}' not found.")
print(f"::error title=FileNotFoundError::File '{coverageRCFile}' not found.")
exit(1)
# Write jobs to special file # Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT")) github_output = Path(getenv("GITHUB_OUTPUT"))
@@ -131,8 +135,10 @@ jobs:
- name: Rename .coverage files and collect them all to coverage/ - name: Rename .coverage files and collect them all to coverage/
run: | run: |
ls -lAh artifacts/
ls -lAh artifacts/*/.coverage
mkdir -p coverage mkdir -p coverage
find . -type f -path "*artifacts*SQLite*.coverage" -exec sh -c 'cp -v $0 "coverage/$(basename $0).$(basename $(dirname $0))"' {} ';' find artifacts/ -type f -path "*SQLite*.coverage" -exec sh -c 'cp -v $0 "coverage/$(basename $0).$(basename $(dirname $0))"' {} ';'
tree -a coverage tree -a coverage
- name: Combine SQLite files (using Coverage.py) - name: Combine SQLite files (using Coverage.py)

View File

@@ -48,7 +48,7 @@ jobs:
PublishOnPyPI: PublishOnPyPI:
name: 🚀 Publish to PyPI name: 🚀 Publish to PyPI
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: 📥 Download artifacts '${{ inputs.artifact }}' from 'Package' job - name: 📥 Download artifacts '${{ inputs.artifact }}' from 'Package' job

View File

@@ -30,11 +30,21 @@ on:
required: false required: false
default: '' default: ''
type: string type: string
additional_merge_args:
description: 'Additional merging arguments.'
required: false
default: '"--pytest=rewrite-dunder-init;reduce-depth:pytest.tests.unit"'
type: string
report_title:
description: 'Title of the summary report in the pipeline''s sidebar'
required: false
default: 'Unit Test Results'
type: string
jobs: jobs:
PublishTestResults: PublishTestResults:
name: 📊 Publish Test Results name: 📊 Publish Test Results
runs-on: ubuntu-latest runs-on: ubuntu-24.04
if: always() if: always()
steps: steps:
@@ -46,37 +56,28 @@ jobs:
with: with:
path: artifacts path: artifacts
- name: 🔧 Install junitparser - name: 🔧 Install pyEDAA.Reports (JUunit Parser and Merger)
run: | run: |
python -m pip install --disable-pip-version-check -U junitparser python -m pip install --disable-pip-version-check --break-system-packages -U pyEDAA.Reports
- name: Move JUnit files and collect them all to junit/ - name: Move JUnit files and collect them all to junit/
run: | run: |
mkdir -p junit mkdir -p junit
find . -type f -path "*artifacts*UnitTestReportSummary*.xml" -exec sh -c 'cp -v $0 "junit/$(basename $(dirname $0)).$(basename $0)"' {} ';' ls -lAh artifacts/*/*.xml
find artifacts/ -type f -path "*TestReportSummary*.xml" -exec sh -c 'cp -v $0 "junit/$(basename $(dirname $0)).$(basename $0)"' {} ';'
tree -a junit tree -a junit
- name: 🔁 Merge JUnit Unit Test Summaries - name: 🔁 Merge JUnit Unit Test Summaries
shell: python
run: | run: |
from pathlib import Path pyedaa-reports -v unittest "--merge=pytest-junit:junit/*.xml" ${{ inputs.additional_merge_args }} "--output=ant-junit:Unittesting.xml"
from junitparser import JUnitXml echo "cat Unittesting.xml"
cat Unittesting.xml
junitDirectory = Path("junit")
junitXml = None
for file in junitDirectory.iterdir():
if junitXml is None:
junitXml = JUnitXml.fromfile(file)
else:
junitXml += JUnitXml.fromfile(file)
junitXml.write(junitDirectory / "merged.xml")
- name: 📊 Publish Unit Test Results - name: 📊 Publish Unit Test Results
uses: dorny/test-reporter@v1 uses: dorny/test-reporter@v1
with: with:
name: Unit Test Results name: ${{ inputs.report_title }}
path: junit/merged.xml path: Unittesting.xml
reporter: java-junit reporter: java-junit
- name: 📤 Upload merged 'JUnit Test Summary' artifact - name: 📤 Upload merged 'JUnit Test Summary' artifact
@@ -84,6 +85,6 @@ jobs:
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ inputs.merged_junit_artifact }} name: ${{ inputs.merged_junit_artifact }}
path: junit/merged.xml path: Unittesting.xml
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1

View File

@@ -44,7 +44,7 @@ jobs:
PublishToGitHubPages: PublishToGitHubPages:
name: 📚 Publish to GH-Pages name: 📚 Publish to GH-Pages
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository

View File

@@ -29,7 +29,7 @@ jobs:
Release: Release:
name: 📝 Create 'Release Page' on GitHub name: 📝 Create 'Release Page' on GitHub
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: 🔁 Extract Git tag from GITHUB_REF - name: 🔁 Extract Git tag from GITHUB_REF
@@ -55,12 +55,34 @@ jobs:
**Automated Release created on: ${{ steps.getVariables.outputs.datetime }}** **Automated Release created on: ${{ steps.getVariables.outputs.datetime }}**
# New Features # New Features
* tbd
* tbd * tbd
# Changes # Changes
* tbd
* tbd * tbd
# Bug Fixes # Bug Fixes
* tbd * tbd
draft: false * tbd
# Documentation
* tbd
* tbd
# Unit Tests
* tbd
* tbd
----------
# Related Issues and Pull-Requests
* tbd
* tbd
draft: true
prerelease: false prerelease: false

View File

@@ -73,7 +73,7 @@ on:
jobs: jobs:
Sphinx: Sphinx:
name: 📓 Documentation generation using Sphinx and Python ${{ inputs.python_version }} name: 📓 Documentation generation using Sphinx and Python ${{ inputs.python_version }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository
@@ -121,7 +121,9 @@ jobs:
xmlFile = Path(pyProjectSettings["tool"]["coverage"]["xml"]["output"]) xmlFile = Path(pyProjectSettings["tool"]["coverage"]["xml"]["output"])
jsonFile = Path(pyProjectSettings["tool"]["coverage"]["json"]["output"]) jsonFile = Path(pyProjectSettings["tool"]["coverage"]["json"]["output"])
else: else:
print(f"File '{pyProjectFile}' not found and no '.coveragerc' file specified.") print(f"File '{pyProjectFile}' not found.")
print(f"::error title=FileNotFoundError::File '{pyProjectFile}' not found.")
exit(1)
# Read output paths from '.coveragerc' file # Read output paths from '.coveragerc' file
elif len(coverageRC) > 0: elif len(coverageRC) > 0:
@@ -135,6 +137,8 @@ jobs:
jsonFile = Path(coverageRCSettings["json"]["output"]) jsonFile = Path(coverageRCSettings["json"]["output"])
else: else:
print(f"File '{coverageRCFile}' not found.") print(f"File '{coverageRCFile}' not found.")
print(f"::error title=FileNotFoundError::File '{coverageRCFile}' not found.")
exit(1)
# Write jobs to special file # Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT")) github_output = Path(getenv("GITHUB_OUTPUT"))

View File

@@ -63,7 +63,7 @@ jobs:
StaticTypeCheck: StaticTypeCheck:
name: 👀 Check Static Typing using Python ${{ inputs.python_version }} name: 👀 Check Static Typing using Python ${{ inputs.python_version }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository

View File

@@ -41,7 +41,7 @@ jobs:
Image: Image:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
env: env:
DOCKER_BUILDKIT: 1 DOCKER_BUILDKIT: 1
steps: steps:
@@ -60,7 +60,7 @@ jobs:
Composite: Composite:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -120,7 +120,7 @@ jobs:
needs: needs:
- Image - Image
- Composite - Composite
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -29,21 +29,56 @@ on:
description: 'JSON list with environment fields, telling the system and Python versions to run tests with.' description: 'JSON list with environment fields, telling the system and Python versions to run tests with.'
required: true required: true
type: string type: string
apt:
description: 'Ubuntu dependencies to be installed through apt.'
required: false
default: ''
type: string
brew:
description: 'macOS dependencies to be installed through brew.'
required: false
default: ''
type: string
pacboy:
description: 'MSYS2 dependencies to be installed through pacboy (pacman).'
required: false
default: ''
type: string
requirements: requirements:
description: 'Python dependencies to be installed through pip.' description: 'Python dependencies to be installed through pip.'
required: false required: false
default: '-r tests/requirements.txt' default: '-r tests/requirements.txt'
type: string type: string
pacboy:
description: 'MSYS2 dependencies to be installed through pacboy (pacman).'
required: false
default: ""
type: string
mingw_requirements: mingw_requirements:
description: 'Override Python dependencies to be installed through pip on MSYS2 (MINGW64) only.' description: 'Override Python dependencies to be installed through pip on MSYS2 (MINGW64) only.'
required: false required: false
default: '' default: ''
type: string type: string
macos_before_script:
description: 'Scripts to execute before pytest on macOS (Intel).'
required: false
default: ''
type: string
macos_arm_before_script:
description: 'Scripts to execute before pytest on macOS (ARM).'
required: false
default: ''
type: string
ubuntu_before_script:
description: 'Scripts to execute before pytest on Ubuntu.'
required: false
default: ''
type: string
mingw64_before_script:
description: 'Scripts to execute before pytest on Windows within MSYS2 MinGW64.'
required: false
default: ''
type: string
ucrt64_before_script:
description: 'Scripts to execute before pytest on Windows within MSYS2 UCRT64.'
required: false
default: ''
type: string
root_directory: root_directory:
description: 'Working directory for running tests.' description: 'Working directory for running tests.'
required: false required: false
@@ -113,6 +148,19 @@ jobs:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
# Package Manager steps
- name: 🔧 Install homebrew dependencies on macOS
if: ( matrix.system == 'macos' || matrix.system == 'macos-arm' ) && inputs.brew != ''
run: brew install ${{ inputs.brew }}
- name: 🔧 Install apt dependencies on Ubuntu
if: matrix.system == 'ubuntu' && inputs.apt != ''
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ${{ inputs.apt }}
# Compute Dependencies for MSYS2 steps
- name: 🔧 Install dependencies (system Python for Python shell) - name: 🔧 Install dependencies (system Python for Python shell)
if: matrix.system == 'msys2' if: matrix.system == 'msys2'
shell: pwsh shell: pwsh
@@ -149,18 +197,24 @@ jobs:
requirements = "${{ inputs.requirements }}" requirements = "${{ inputs.requirements }}"
if requirements.startswith("-r"): if requirements.startswith("-r"):
requirementsFile = Path(requirements[2:].lstrip()) requirementsFile = Path(requirements[2:].lstrip())
try:
dependencies = loadRequirementsFile(requirementsFile) dependencies = loadRequirementsFile(requirementsFile)
except FileNotFoundError as ex:
print(f"::error title=FileNotFoundError::{ex}")
exit(1)
else: else:
dependencies = [req.strip() for req in requirements.split(" ")] dependencies = [req.strip() for req in requirements.split(" ")]
packages = { packages = {
"coverage": "python-coverage:p", "coverage": "python-coverage:p",
"docstr_coverage": "python-pyyaml:p",
"igraph": "igraph:p", "igraph": "igraph:p",
"jinja2": "python-markupsafe:p", "jinja2": "python-markupsafe:p",
"lxml": "python-lxml:p", "lxml": "python-lxml:p",
"numpy": "python-numpy:p", "numpy": "python-numpy:p",
"markupsafe": "python-markupsafe:p", "markupsafe": "python-markupsafe:p",
"pip": "python-pip:p", "pip": "python-pip:p",
"pyyaml": "python-pyyaml:p",
"ruamel.yaml": "python-ruamel-yaml:p python-ruamel.yaml.clib:p", "ruamel.yaml": "python-ruamel-yaml:p python-ruamel.yaml.clib:p",
"sphinx": "python-markupsafe:p", "sphinx": "python-markupsafe:p",
"tomli": "python-tomli:p", "tomli": "python-tomli:p",
@@ -206,6 +260,8 @@ jobs:
with github_output.open("a+") as f: with github_output.open("a+") as f:
f.write(f"pacboy_packages={' '.join(pacboyPackages)}\n") f.write(f"pacboy_packages={' '.join(pacboyPackages)}\n")
# Python setup
- name: '🟦 Setup MSYS2 for ${{ matrix.runtime }}' - name: '🟦 Setup MSYS2 for ${{ matrix.runtime }}'
if: matrix.system == 'msys2' if: matrix.system == 'msys2'
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
@@ -222,6 +278,8 @@ jobs:
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
# Python Dependency steps
- name: 🔧 Install wheel,tomli and pip dependencies (native) - name: 🔧 Install wheel,tomli and pip dependencies (native)
if: matrix.system != 'msys2' if: matrix.system != 'msys2'
run: | run: |
@@ -237,6 +295,32 @@ jobs:
python -m pip install --disable-pip-version-check ${{ inputs.requirements }} python -m pip install --disable-pip-version-check ${{ inputs.requirements }}
fi fi
# Before scripts
- name: 🍎 macOS (Intel) before scripts
if: matrix.system == 'macos' && inputs.macos_before_script != ''
run: ${{ inputs.macos_before_script }}
- name: 🍏 macOS (ARM) before scripts
if: matrix.system == 'macos-arm' && inputs.macos_arm_before_script != ''
run: ${{ inputs.macos_arm_before_script }}
- name: 🐧 Ubuntu before scripts
if: matrix.system == 'ubuntu' && inputs.ubuntu_before_script != ''
run: ${{ inputs.ubuntu_before_script }}
# Windows before script
- name: 🪟🟦 MinGW64 before scripts
if: matrix.system == 'msys2' && matrix.runtime == 'MINGW64' && inputs.mingw64_before_script != ''
run: ${{ inputs.mingw64_before_script }}
- name: 🪟🟨 UCRT64 before scripts
if: matrix.system == 'msys2' && matrix.runtime == 'UCRT64' && inputs.ucrt64_before_script != ''
run: ${{ inputs.ucrt64_before_script }}
# Read pyproject.toml
- name: 🔁 Extract configurations from pyproject.toml - name: 🔁 Extract configurations from pyproject.toml
id: getVariables id: getVariables
shell: python shell: python
@@ -266,7 +350,9 @@ jobs:
xmlFile = Path(pyProjectSettings["tool"]["coverage"]["xml"]["output"]) xmlFile = Path(pyProjectSettings["tool"]["coverage"]["xml"]["output"])
jsonFile = Path(pyProjectSettings["tool"]["coverage"]["json"]["output"]) jsonFile = Path(pyProjectSettings["tool"]["coverage"]["json"]["output"])
else: else:
print(f"File '{pyProjectFile}' not found and no '.coveragerc' file specified.") print(f"File '{pyProjectFile}' not found.")
print(f"::error title=FileNotFoundError::File '{pyProjectFile}' not found.")
exit(1)
# Read output paths from '.coveragerc' file # Read output paths from '.coveragerc' file
elif len(coverageRC) > 0: elif len(coverageRC) > 0:
@@ -280,6 +366,8 @@ jobs:
jsonFile = Path(coverageRCSettings["json"]["output"]) jsonFile = Path(coverageRCSettings["json"]["output"])
else: else:
print(f"File '{coverageRCFile}' not found.") print(f"File '{coverageRCFile}' not found.")
print(f"::error title=FileNotFoundError::File '{coverageRCFile}' not found.")
exit(1)
# Write jobs to special file # Write jobs to special file
github_output = Path(getenv("GITHUB_OUTPUT")) github_output = Path(getenv("GITHUB_OUTPUT"))
@@ -294,7 +382,9 @@ jobs:
print(f"DEBUG:\n html={htmlDirectory}\n xml={xmlFile}\n json={jsonFile}") print(f"DEBUG:\n html={htmlDirectory}\n xml={xmlFile}\n json={jsonFile}")
- name: ☑ Run unit tests (Ubuntu/macOS) # Run pytests
- name: ✅ Run unit tests (Ubuntu/macOS)
if: matrix.system != 'windows' if: matrix.system != 'windows'
run: | run: |
export ENVIRONMENT_NAME="${{ matrix.envname }}" export ENVIRONMENT_NAME="${{ matrix.envname }}"
@@ -310,7 +400,7 @@ jobs:
python -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.unittest_directory }} python -m pytest -raP $PYTEST_ARGS --color=yes ${{ inputs.tests_directory || '.' }}/${{ inputs.unittest_directory }}
fi fi
- name: Run unit tests (Windows) - name: Run unit tests (Windows)
if: matrix.system == 'windows' if: matrix.system == 'windows'
run: | run: |
$env:ENVIRONMENT_NAME = "${{ matrix.envname }}" $env:ENVIRONMENT_NAME = "${{ matrix.envname }}"
@@ -328,20 +418,26 @@ jobs:
- name: Convert coverage to XML format (Cobertura) - name: Convert coverage to XML format (Cobertura)
if: inputs.coverage_xml_artifact != '' if: inputs.coverage_xml_artifact != ''
continue-on-error: true
run: coverage xml --data-file=.coverage run: coverage xml --data-file=.coverage
- name: Convert coverage to JSON format - name: Convert coverage to JSON format
if: inputs.coverage_json_artifact != '' if: inputs.coverage_json_artifact != ''
continue-on-error: true
run: coverage json --data-file=.coverage run: coverage json --data-file=.coverage
- name: Convert coverage to HTML format - name: Convert coverage to HTML format
if: inputs.coverage_html_artifact != '' if: inputs.coverage_html_artifact != ''
continue-on-error: true
run: | run: |
coverage html --data-file=.coverage -d ${{ steps.getVariables.outputs.coverage_report_html_directory }} coverage html --data-file=.coverage -d ${{ steps.getVariables.outputs.coverage_report_html_directory }}
rm ${{ steps.getVariables.outputs.coverage_report_html_directory }}/.gitignore rm ${{ steps.getVariables.outputs.coverage_report_html_directory }}/.gitignore
# Upload artifacts
- name: 📤 Upload 'TestReportSummary.xml' artifact - name: 📤 Upload 'TestReportSummary.xml' artifact
if: inputs.unittest_xml_artifact != '' if: inputs.unittest_xml_artifact != ''
continue-on-error: true
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ inputs.unittest_xml_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }} name: ${{ inputs.unittest_xml_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
@@ -366,6 +462,7 @@ jobs:
with: with:
name: ${{ inputs.coverage_sqlite_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }} name: ${{ inputs.coverage_sqlite_artifact }}-${{ matrix.system }}-${{ matrix.runtime }}-${{ matrix.python }}
path: .coverage path: .coverage
include-hidden-files: true
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1

View File

@@ -35,7 +35,7 @@ jobs:
VerifyDocs: VerifyDocs:
name: 👍 Verify example snippets using Python ${{ inputs.python_version }} name: 👍 Verify example snippets using Python ${{ inputs.python_version }}
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: ⏬ Checkout repository - name: ⏬ Checkout repository
@@ -72,7 +72,7 @@ jobs:
- name: Print example.py - name: Print example.py
run: cat tests/docs/example.py run: cat tests/docs/example.py
- name: Run example snippet - name: Run example snippet
working-directory: tests/docs working-directory: tests/docs
run: | run: |
python3 example.py python3 example.py

View File

@@ -36,7 +36,7 @@ jobs:
name: Package generation name: Package generation
needs: needs:
- Params - Params
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: Package creation - name: Package creation
run: echo "Package" >> package.txt run: echo "Package" >> package.txt

View File

@@ -64,14 +64,14 @@ jobs:
- Params_Exclude - Params_Exclude
- Params_Disable - Params_Disable
- Params_All - Params_All
runs-on: ubuntu-latest runs-on: ubuntu-24.04
defaults: defaults:
run: run:
shell: python shell: python
steps: steps:
- name: Install dependencies - name: Install dependencies
shell: bash shell: bash
run: pip install pyTooling run: pip install --disable-pip-version-check --break-system-packages pyTooling
# Params_Default # Params_Default
- name: Checking results from 'Params_Default' - name: Checking results from 'Params_Default'
run: | run: |

View File

@@ -84,10 +84,12 @@ jobs:
codacy_token: ${{ secrets.CODACY_PROJECT_TOKEN }} codacy_token: ${{ secrets.CODACY_PROJECT_TOKEN }}
PublishTestResults: PublishTestResults:
uses: pyTooling/Actions/.github/workflows/PublishTestResults.yml@r1 uses: pyTooling/Actions/.github/workflows/PublishTestResults.yml@dev
needs: needs:
- UnitTesting - UnitTesting
- PlatformTesting - PlatformTesting
with:
additional_merge_args: '-d "--pytest=rewrite-dunder-init;reduce-depth:pytest.tests.unit;reduce-depth:pytest.tests.platform"'
Package: Package:
uses: pyTooling/Actions/.github/workflows/Package.yml@r1 uses: pyTooling/Actions/.github/workflows/Package.yml@r1

1
.gitignore vendored
View File

@@ -13,6 +13,7 @@ coverage.xml
# pytest # pytest
/report/unit /report/unit
/tests/*.github
# setuptools # setuptools
/build/**/*.* /build/**/*.*

2
dist/requirements.txt vendored Normal file
View File

@@ -0,0 +1,2 @@
wheel ~= 0.44
twine ~= 5.1

View File

@@ -81,7 +81,7 @@ The following block shows a minimal YAML workflow file:
jobs: jobs:
mwe: mwe:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
# Clone repository # Clone repository
@@ -171,7 +171,7 @@ For prototyping purposes, the following job might be useful:
Release: Release:
name: '📦 Release' name: '📦 Release'
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- ... - ...
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/'>`__) if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/'>`__)

View File

@@ -76,7 +76,7 @@ Documentation Only (Sphinx)
needs: needs:
- BuildTheDocs - BuildTheDocs
- PublishToGitHubPages - PublishToGitHubPages
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
- name: 🗑️ Delete artifacts - name: 🗑️ Delete artifacts

View File

@@ -12,13 +12,36 @@ This job creates a Release Page on GitHub.
**Automated Release created on: ${{ steps.getVariables.outputs.datetime }}** **Automated Release created on: ${{ steps.getVariables.outputs.datetime }}**
# New Features # New Features
* tbd
* tbd * tbd
# Changes # Changes
* tbd
* tbd * tbd
# Bug Fixes # Bug Fixes
* tbd * tbd
* tbd
# Documentation
* tbd
* tbd
# Unit Tests
* tbd
* tbd
----------
# Related Issues and Pull-Requests
* tbd
* tbd
**Behavior:** **Behavior:**

View File

@@ -60,10 +60,10 @@ pygments_style = "manni"
# ============================================================================== # ==============================================================================
# Restructured Text settings # Restructured Text settings
# ============================================================================== # ==============================================================================
prologPath = "prolog.inc" prologPath = Path("prolog.inc")
try: try:
with open(prologPath, "r") as prologFile: with prologPath.open("r", encoding="utf-8") as fileHandle:
rst_prolog = prologFile.read() rst_prolog = fileHandle.read()
except Exception as ex: except Exception as ex:
print(f"[ERROR:] While reading '{prologPath}'.") print(f"[ERROR:] While reading '{prologPath}'.")
print(ex) print(ex)

View File

@@ -100,6 +100,9 @@ References
- `hdl/containers#48 <https://github.com/hdl/containers/issues/48>`__ - `hdl/containers#48 <https://github.com/hdl/containers/issues/48>`__
.. _CONTRIBUTORS:
Contributors Contributors
************ ************
@@ -108,6 +111,8 @@ Contributors
* `and more... <https://GitHub.com/pyTooling/Actions/graphs/contributors>`__ * `and more... <https://GitHub.com/pyTooling/Actions/graphs/contributors>`__
.. _LICENSE:
License License
******* *******

View File

@@ -1,10 +1,10 @@
-r ../requirements.txt -r ../requirements.txt
pyTooling ~= 6.1 pyTooling ~= 6.6
# Enforce latest version on ReadTheDocs # Enforce latest version on ReadTheDocs
sphinx ~= 7.3 sphinx ~= 7.4
docutils ~= 0.18.0 docutils ~= 0.20
# Sphinx Extenstions # Sphinx Extenstions
#sphinx.ext.coverage #sphinx.ext.coverage
@@ -16,5 +16,5 @@ sphinxcontrib-mermaid>=0.9.2
autoapi >= 2.0.1 autoapi >= 2.0.1
sphinx_fontawesome >= 0.0.6 sphinx_fontawesome >= 0.0.6
sphinx-inline-tabs >= 2023.4.21 sphinx-inline-tabs >= 2023.4.21
sphinx_autodoc_typehints ~= 2.1 sphinx_autodoc_typehints ~= 2.3
# changelog>=0.3.5 # changelog>=0.3.5

View File

@@ -1,8 +1,8 @@
[build-system] [build-system]
requires = [ requires = [
"setuptools ~= 69.5", "setuptools ~= 75.1",
"wheel ~= 0.40.0", "wheel ~= 0.44",
"pyTooling ~= 6.1" "pyTooling ~= 6.6"
] ]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"

View File

@@ -75,7 +75,7 @@ on:
jobs: jobs:
mwe: mwe:
runs-on: ubuntu-latest runs-on: ubuntu-24.04
steps: steps:
# Clone repository # Clone repository
@@ -156,7 +156,7 @@ For prototyping purposes, the following job might be useful:
```yml ```yml
Release: Release:
name: '📦 Release' name: '📦 Release'
runs-on: ubuntu-latest runs-on: ubuntu-24.04
needs: needs:
- ... - ...
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/')) if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/master' || contains(github.ref, 'refs/tags/'))

View File

@@ -1 +1 @@
pyTooling ~= 6.1 pyTooling ~= 6.6

91
tests/pacman_packages.py Normal file
View File

@@ -0,0 +1,91 @@
from os import getenv
from pathlib import Path
from re import compile
from sys import version
print(f"Python: {version}")
def loadRequirementsFile(requirementsFile: Path):
requirements = []
with requirementsFile.open("r") as file:
for line in file.readlines():
line = line.strip()
if line.startswith("#") or line.startswith("https") or line == "":
continue
elif line.startswith("-r"):
# Remove the first word/argument (-r)
requirements += loadRequirementsFile(requirementsFile.parent / line[2:].lstrip())
else:
requirements.append(line)
return requirements
requirements = "-r ../tests/requirements.txt"
if requirements.startswith("-r"):
requirementsFile = Path(requirements[2:].lstrip())
try:
dependencies = loadRequirementsFile(requirementsFile)
except FileNotFoundError as ex:
print(f"::error title=FileNotFound::{ex}")
exit(1)
else:
dependencies = [req.strip() for req in requirements.split(" ")]
packages = {
"coverage": "python-coverage:p",
"igraph": "igraph:p",
"jinja2": "python-markupsafe:p",
"lxml": "python-lxml:p",
"numpy": "python-numpy:p",
"markupsafe": "python-markupsafe:p",
"pip": "python-pip:p",
"ruamel.yaml": "python-ruamel-yaml:p python-ruamel.yaml.clib:p",
"sphinx": "python-markupsafe:p",
"tomli": "python-tomli:p",
"wheel": "python-wheel:p",
"pyEDAA.ProjectModel": "python-ruamel-yaml:p python-ruamel.yaml.clib:p python-lxml:p",
"pyEDAA.Reports": "python-ruamel-yaml:p python-ruamel.yaml.clib:p python-lxml:p",
}
subPackages = {
"pytooling": {
"yaml": "python-ruamel-yaml:p python-ruamel.yaml.clib:p",
},
}
regExp = compile(
r"(?P<PackageName>[\w_\-\.]+)(?:\[(?P<SubPackages>(?:\w+)(?:\s*,\s*\w+)*)\])?(?:\s*(?P<Comperator>[<>~=]+)\s*)(?P<Version>\d+(?:\.\d+)*)(?:-(?P<VersionExtension>\w+))?")
pacboyPackages = set(("python-pip:p", "python-wheel:p", "python-tomli:p"))
print(f"Processing dependencies ({len(dependencies)}):")
for dependency in dependencies:
print(f" {dependency}")
match = regExp.match(dependency.lower())
if not match:
print(f" Wrong format: {dependency}")
print(f"::error title=Identifying Pacboy Packages::Unrecognized dependency format '{dependency}'")
continue
package = match["PackageName"]
if package in packages:
rewrite = packages[package]
print(f" Found rewrite rule for '{package}': {rewrite}")
pacboyPackages.add(rewrite)
if match["SubPackages"] and package in subPackages:
for subPackage in match["SubPackages"].split(","):
if subPackage in subPackages[package]:
rewrite = subPackages[package][subPackage]
print(f" Found rewrite rule for '{package}[..., {subPackage}, ...]': {rewrite}")
pacboyPackages.add(rewrite)
# Write jobs 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"pacboy_packages={' '.join(pacboyPackages)}\n")
print(f"GITHUB_OUTPUT:")
print(f"pacboy_packages={' '.join(pacboyPackages)}\n")

216
tests/python_jobs.py Normal file
View File

@@ -0,0 +1,216 @@
from json import dumps as json_dumps
from os import getenv
from pathlib import Path
from textwrap import dedent
from typing import Iterable
name = "example".strip()
python_version = "3.12".strip()
systems = "ubuntu windows macos-arm mingw64 ucrt64".strip()
versions = "3.8 3.9 3.10 3.11 3.12".strip()
include_list = "".strip()
exclude_list = "".strip()
disable_list = "".strip()
currentMSYS2Version = "3.11"
currentAlphaVersion = "3.13"
currentAlphaRelease = "3.13.0-alpha.1"
if systems == "":
print("::error title=Parameter::system_list is empty.")
else:
systems = [sys.strip() for sys in systems.split(" ")]
if versions == "":
versions = [python_version]
else:
versions = [ver.strip() for ver in versions.split(" ")]
if include_list == "":
includes = []
else:
includes = [tuple(include.strip().split(":")) for include in include_list.split(" ")]
if exclude_list == "":
excludes = []
else:
excludes = [exclude.strip() for exclude in exclude_list.split(" ")]
if disable_list == "":
disabled = []
else:
disabled = [disable.strip() for disable in disable_list.split(" ")]
if "3.7" in versions:
print("::warning title=Deprecated::Support for Python 3.7 ended in 2023.06.27.")
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.")
# see https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json
data = {
# Python and PyPy versions supported by "setup-python" action
"python": {
"3.7": {"icon": "", "until": "2023.06.27"},
"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.13": { "icon": "🟣", "until": "2028.10" },
"pypy-3.7": {"icon": "⟲⚫", "until": "????.??"},
"pypy-3.8": {"icon": "⟲🔴", "until": "????.??"},
"pypy-3.9": {"icon": "⟲🟠", "until": "????.??"},
"pypy-3.10": {"icon": "⟲🟡", "until": "????.??"},
},
# Runner systems (runner images) supported by GitHub Actions
"sys": {
"ubuntu": {"icon": "🐧", "runs-on": "ubuntu-24.04", "shell": "bash", "name": "Linux (x86-64)"},
"windows": {"icon": "🪟", "runs-on": "windows-latest", "shell": "pwsh", "name": "Windows (x86-64)"},
"macos": {"icon": "🍎", "runs-on": "macos-latest-large", "shell": "bash", "name": "macOS (x86-64)"},
"macos-arm": {"icon": "🍏", "runs-on": "macos-latest", "shell": "bash", "name": "macOS (arm64)"},
},
# Runtimes provided by MSYS2
"runtime": {
"msys": {"icon": "🪟🟪", "name": "Windows+MSYS2 (x86-64) - MSYS"},
"mingw32": {"icon": "🪟⬛", "name": "Windows+MSYS2 (x86-64) - MinGW32"},
"mingw64": {"icon": "🪟🟦", "name": "Windows+MSYS2 (x86-64) - MinGW64"},
"clang32": {"icon": "🪟🟫", "name": "Windows+MSYS2 (x86-64) - Clang32"},
"clang64": {"icon": "🪟🟧", "name": "Windows+MSYS2 (x86-64) - Clang64"},
"ucrt64": {"icon": "🪟🟨", "name": "Windows+MSYS2 (x86-64) - UCRT64"},
}
}
print(f"includes ({len(includes)}):")
for system, version in includes:
print(f"- {system}:{version}")
print(f"excludes ({len(excludes)}):")
for exclude in excludes:
print(f"- {exclude}")
print(f"disabled ({len(disabled)}):")
for disable in disabled:
print(f"- {disable}")
def match(combination: str, pattern: str) -> bool:
system, version = combination.split(":")
sys, ver = pattern.split(":")
if sys == "*":
return (ver == "*") or (version == ver)
elif system == sys:
return (ver == "*") or (version == ver)
else:
return False
def notIn(combination: str, patterns: Iterable[str]) -> bool:
for pattern in patterns:
if match(combination, pattern):
return False
return True
combinations = [
(system, version)
for system in systems
if system in data["sys"]
for version in versions
if version in data["python"]
and notIn(f"{system}:{version}", excludes)
and notIn(f"{system}:{version}", disabled)
] + [
(system, currentMSYS2Version)
for system in systems
if system in data["runtime"]
and notIn(f"{system}:{currentMSYS2Version}", excludes)
and notIn(f"{system}:{currentMSYS2Version}", disabled)
] + [
(system, version)
for system, version in includes
if system in data["sys"]
and version in data["python"]
and notIn(f"{system}:{version}", disabled)
]
print(f"Combinations ({len(combinations)}):")
for system, version in combinations:
print(f"- {system}:{version}")
jobs = [
{
"sysicon": data["sys"][system]["icon"],
"system": system,
"runs-on": data["sys"][system]["runs-on"],
"runtime": "native",
"shell": data["sys"][system]["shell"],
"pyicon": data["python"][version]["icon"],
"python": currentAlphaRelease if version == currentAlphaVersion else version,
"envname": data["sys"][system]["name"],
}
for system, version in combinations if system in data["sys"]
] + [
{
"sysicon": data["runtime"][runtime]["icon"],
"system": "msys2",
"runs-on": "windows-latest",
"runtime": runtime.upper(),
"shell": "msys2 {0}",
"pyicon": data["python"][currentMSYS2Version]["icon"],
"python": version,
"envname": data["runtime"][runtime]["name"],
}
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",
}
# Deprecated structure
params = {
"python_version": python_version,
"artifacts": {
"unittesting": f"{artifact_names['unittesting_xml']}",
"coverage": f"{artifact_names['codecoverage_html']}",
"typing": f"{artifact_names['statictyping_html']}",
"package": f"{artifact_names['package_all']}",
"doc": f"{artifact_names['documentation_html']}",
}
}
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"))
print(f"GITHUB_OUTPUT: {github_output}")
with github_output.open("a+", encoding="utf-8") as f:
f.write(dedent(f"""\
python_version={python_version}
python_jobs={json_dumps(jobs)}
artifact_names={json_dumps(artifact_names)}
params={json_dumps(params)}
"""))

View File

@@ -1,13 +1,13 @@
-r ../requirements.txt -r ../requirements.txt
# Coverage collection # Coverage collection
Coverage ~= 7.5 Coverage ~= 7.6
# Test Runner # Test Runner
pytest ~= 8.1 pytest ~= 8.3
pytest-cov ~= 5.0 pytest-cov ~= 5.0
# Static Type Checking # Static Type Checking
mypy ~= 1.10 mypy ~= 1.11
typing_extensions ~= 4.11 typing_extensions ~= 4.12
lxml ~= 5.1 lxml ~= 5.3