CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
202 lines
6.2 KiB
Python
202 lines
6.2 KiB
Python
# Copyright Advanced Micro Devices, Inc.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""Shared helpers for workflow YAML tests."""
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
WORKFLOWS_DIR = Path(__file__).resolve().parents[3] / ".github" / "workflows"
|
|
_MATRIX_REFERENCE_RE = re.compile(r"\bmatrix\.([A-Za-z_][A-Za-z0-9_]*)")
|
|
|
|
|
|
def load_workflow(path: Path) -> dict:
|
|
"""Loads a YAML workflow file from the given Path as a JSON dictionary."""
|
|
with open(path) as f:
|
|
return yaml.safe_load(f)
|
|
|
|
|
|
def get_workflow_job(workflow: dict, job_name: str) -> dict:
|
|
"""Returns a workflow job definition.
|
|
|
|
For a workflow with:
|
|
jobs:
|
|
build_wheels:
|
|
uses: ./.github/workflows/build_wheels.yml
|
|
with:
|
|
python_version: ${{ matrix.python_version }}
|
|
|
|
get_workflow_job(workflow, "build_wheels") returns the dictionary
|
|
containing the uses/with blocks for that job.
|
|
"""
|
|
jobs = workflow.get("jobs")
|
|
if not isinstance(jobs, dict):
|
|
raise KeyError("workflow has no jobs block")
|
|
|
|
job = jobs[job_name]
|
|
if not isinstance(job, dict):
|
|
raise KeyError(f"workflow job {job_name!r} is not a mapping")
|
|
return job
|
|
|
|
|
|
def get_matrix_references(value: object) -> set[str]:
|
|
"""Extracts top-level matrix keys referenced by a workflow YAML value.
|
|
|
|
For a workflow value with:
|
|
with:
|
|
python_version: ${{ matrix.python_version }}
|
|
package_url: >-
|
|
${{
|
|
format('{0}/{1}/index.html',
|
|
needs.build.outputs.package_find_links_url,
|
|
matrix.amdgpu_family)
|
|
}}
|
|
|
|
get_matrix_references(value) returns:
|
|
{"python_version", "amdgpu_family"}
|
|
|
|
Nested matrix objects like matrix.family_info.amdgpu_family return only the
|
|
top-level matrix key, {"family_info"}.
|
|
"""
|
|
if isinstance(value, str):
|
|
return set(_MATRIX_REFERENCE_RE.findall(value))
|
|
|
|
if isinstance(value, dict):
|
|
references = set()
|
|
for child_value in value.values():
|
|
references.update(get_matrix_references(child_value))
|
|
return references
|
|
|
|
if isinstance(value, list):
|
|
references = set()
|
|
for child_value in value:
|
|
references.update(get_matrix_references(child_value))
|
|
return references
|
|
|
|
return set()
|
|
|
|
|
|
def _get_workflow_dispatch_block(workflow: dict) -> dict | None:
|
|
"""Returns the workflow_dispatch block, or None."""
|
|
# PyYAML parses the unquoted YAML key `on:` as boolean True.
|
|
on_block = workflow.get("on") or workflow.get(True)
|
|
if not isinstance(on_block, dict):
|
|
return None
|
|
dispatch = on_block.get("workflow_dispatch")
|
|
if not isinstance(dispatch, dict):
|
|
return None
|
|
return dispatch
|
|
|
|
|
|
def _get_dispatch_inputs(workflow: dict) -> dict:
|
|
"""Returns the workflow_dispatch inputs dict, or empty dict."""
|
|
dispatch = _get_workflow_dispatch_block(workflow)
|
|
if dispatch is None:
|
|
return {}
|
|
inputs = dispatch.get("inputs")
|
|
if not isinstance(inputs, dict):
|
|
return {}
|
|
return inputs
|
|
|
|
|
|
def get_workflow_dispatch_inputs(workflow: dict) -> set:
|
|
"""Extracts input names from a workflow's on.workflow_dispatch.inputs section.
|
|
|
|
For a workflow with:
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
amdgpu_family: ...
|
|
release_type: ...
|
|
|
|
Returns: {"amdgpu_family", "release_type"}
|
|
"""
|
|
return set(_get_dispatch_inputs(workflow).keys())
|
|
|
|
|
|
def get_required_workflow_dispatch_inputs(workflow: dict) -> set:
|
|
"""Extracts required input names (no default) from workflow_dispatch.
|
|
|
|
For a workflow with:
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
amdgpu_family:
|
|
required: true
|
|
release_type:
|
|
required: true
|
|
default: dev
|
|
|
|
Returns: {"amdgpu_family"} (release_type has a default)
|
|
"""
|
|
required = set()
|
|
for name, props in _get_dispatch_inputs(workflow).items():
|
|
if isinstance(props, dict):
|
|
if props.get("required", False) and "default" not in props:
|
|
required.add(name)
|
|
return required
|
|
|
|
|
|
def get_transitive_workflow_uses(root_filenames: list[str]) -> set[str]:
|
|
"""Returns all workflow filenames transitively referenced via reusable workflow calls.
|
|
|
|
Starting from the given root workflow filenames, follows all
|
|
``uses: ./.github/workflows/<name>.yml`` references in job definitions
|
|
and returns the complete set of workflow filenames (including the roots).
|
|
"""
|
|
visited: set[str] = set()
|
|
queue = list(root_filenames)
|
|
while queue:
|
|
filename = queue.pop()
|
|
if filename in visited:
|
|
continue
|
|
visited.add(filename)
|
|
workflow_path = WORKFLOWS_DIR / filename
|
|
if not workflow_path.exists():
|
|
continue
|
|
workflow = load_workflow(workflow_path)
|
|
if not isinstance(workflow, dict):
|
|
continue
|
|
jobs = workflow.get("jobs")
|
|
if not isinstance(jobs, dict):
|
|
continue
|
|
for job_def in jobs.values():
|
|
if not isinstance(job_def, dict):
|
|
continue
|
|
uses = job_def.get("uses")
|
|
if isinstance(uses, str) and uses.startswith("./.github/workflows/"):
|
|
ref_filename = uses.removeprefix("./.github/workflows/")
|
|
queue.append(ref_filename)
|
|
return visited
|
|
|
|
|
|
def get_choice_options(workflow: dict, input_name: str) -> list | None:
|
|
"""Extracts the options list for a type: choice workflow_dispatch input.
|
|
|
|
For a workflow with:
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
amdgpu_family:
|
|
type: choice
|
|
options:
|
|
- gfx94X-dcgpu
|
|
- gfx110X-all
|
|
|
|
get_choice_options(workflow, "amdgpu_family") returns:
|
|
["gfx94X-dcgpu", "gfx110X-all"]
|
|
|
|
Returns None if the input doesn't exist or isn't type: choice.
|
|
"""
|
|
input_def = _get_dispatch_inputs(workflow).get(input_name)
|
|
if not isinstance(input_def, dict):
|
|
return None
|
|
if input_def.get("type") != "choice":
|
|
return None
|
|
options = input_def.get("options")
|
|
if not isinstance(options, list):
|
|
return None
|
|
return options
|