Files
Actions/.github/workflows/Parameters.yml
2026-01-17 19:37:23 +01:00

470 lines
22 KiB
YAML

# ==================================================================================================================== #
# Authors: #
# Patrick Lehmann #
# Unai Martinez-Corral #
# #
# ==================================================================================================================== #
# Copyright 2020-2026 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: Parameters
on:
workflow_call:
inputs:
ubuntu_image_version:
description: 'Ubuntu image version.'
required: false
default: '24.04'
type: string
name:
description: 'Name of the tool.'
required: false
default: ''
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: 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.14'
type: string
python_version_list:
description: 'Space separated list of Python versions to run tests with.'
required: false
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 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.'
required: false
default: ''
type: string
exclude_list:
description: 'Space separated list of system:python items to be excluded from the list of test.'
required: false
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: 'windows-arm:pypy-3.10 windows-arm:pypy-3.11'
type: string
ubuntu_image:
description: 'The used GitHub Action image for Ubuntu (x86-64) based jobs.'
required: false
default: 'ubuntu-24.04'
type: string
ubuntu_arm_image:
description: 'The used GitHub Action image for Ubuntu (aarch64) based jobs.'
required: false
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-15-intel'
type: string
macos_arm_image:
description: 'The used GitHub Action image for macOS (ARM aarch64) based jobs.'
required: false
default: 'macos-15'
type: string
pipeline-delay:
description: 'Slow down this job, to delay the startup of the GitHub Action pipline.'
required: false
default: 0
type: number
outputs:
python_version:
description: "Default Python version for other jobs."
value: ${{ jobs.Parameters.outputs.python_version }}
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 }}
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.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 '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
from os import getenv
from pathlib import Path
from textwrap import dedent
from typing import Iterable
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()
currentMSYS2Version = "3.13"
currentAlphaVersion = "3.15"
currentAlphaRelease = "3.15.0-a.4"
if systems == "":
print("::error title=Parameters::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.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::{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.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" },
"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)" },
"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": {
"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": "${{ inputs.windows_image }}",
"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"]
]
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])
)
# 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)}
"""))
- name: Verify out parameters
id: verify
run: |
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"