Skip to content
Closed
68 changes: 54 additions & 14 deletions .github/workflows/manifest-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
start_ref:
description: 'Start commit SHA or workflow run ID (required when using workflow mode or commit SHA mode without find-last-successful)'
description: 'Start commit SHA or workflow run ID (required when using workflow mode or commit SHA mode without find-last-run / pr-base-ref)'
required: false
type: string
end_ref:
Expand All @@ -16,10 +16,24 @@ on:
required: false
type: boolean
default: false
find_last_successful:
description: 'Workflow file to find last successful run (e.g., ci_nightly.yml)'
find_last_run:
description: 'Workflow file to find last run on the branch (e.g., ci_nightly.yml). Run status filter is set via accepted_statuses.'
required: false
type: string
accepted_statuses:
description: 'Comma-separated workflow run statuses accepted by --find-last-run (default: success).'
required: false
type: string
default: 'success'
pr_base_ref:
description: 'PR base branch name. When set, start_ref is resolved as the merge-base between end_ref and origin/<pr_base_ref> via the GitHub Compare API.'
required: false
type: string
branch:
description: 'Branch to scope --find-last-run lookups against (default: main).'
required: false
type: string
default: 'main'
workflow_call:
inputs:
start_ref:
Expand All @@ -32,46 +46,72 @@ on:
required: false
type: boolean
default: false
find_last_successful:
find_last_run:
required: false
type: string
accepted_statuses:
required: false
type: string
default: 'success'
pr_base_ref:
required: false
type: string
branch:
required: false
type: string
default: 'main'

permissions:
contents: read

jobs:
generate-report:
name: Generate Manifest Diff Report
runs-on: ubuntu-24.04
runs-on: azure-linux-scale-rocm
# Informational job: never fail the parent caller.
continue-on-error: true
permissions:
id-token: write
contents: read
container:
image: ghcr.io/rocm/therock_build_manylinux_x86_64@sha256:702a5133851e6d1daf1207d2c9fbb01c2667914a5b6dc5a01faeb3ce66ea6421
options: -v /runner/config:/home/awsconfig/
env:
AWS_SHARED_CREDENTIALS_FILE: /home/awsconfig/credentials.ini
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- name: Adjust git config
run: |
git config --global --add safe.directory $PWD
git config fetch.parallel 10

- name: Install python deps
run: pip install -r requirements.txt

- name: Generate manifest diff report
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
python3 build_tools/generate_manifest_diff_report.py \
--end "${{ inputs.end_ref }}" \
--start "${{ inputs.start_ref || '' }}" \
--find-last-successful "${{ inputs.find_last_successful || '' }}" \
--start "${{ inputs.start_ref }}" \
--find-last-run "${{ inputs.find_last_run }}" \
--accepted-statuses "${{ inputs.accepted_statuses }}" \
--pr-base-ref "${{ inputs.pr_base_ref }}" \
--branch "${{ inputs.branch }}" \
${{ inputs.workflow_mode && '--workflow-mode' || '' }} \
--output-dir reports

- name: Configure AWS Credentials
if: ${{ github.repository == 'ROCm/TheRock' }}
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
with:
aws-region: us-east-2
role-to-assume: arn:aws:iam::692859939525:role/therock-ci
- name: Configure AWS credentials for artifact uploads
if: ${{ always() }}
uses: ./.github/actions/configure_aws_artifacts_credentials

- name: Upload Report to S3
if: ${{ github.repository == 'ROCm/TheRock' }}
if: ${{ always() }}
run: |
python3 build_tools/github_actions/upload_test_report_script.py \
--run-id ${{ github.run_id }} \
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/multi_arch_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ jobs:
contents: read
id-token: write

manifest_diff:
name: Manifest Diff
if: github.repository == 'ROCm/TheRock'
uses: ./.github/workflows/manifest-diff.yml
permissions:
contents: read
id-token: write
with:
end_ref: >-
${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
start_ref: >-
${{ github.event_name == 'push' && github.event.before || '' }}
pr_base_ref: >-
${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || '' }}
branch: ${{ github.event.pull_request.base.ref || github.ref_name }}

ci_summary:
name: CI Summary
if: always()
Expand Down
134 changes: 99 additions & 35 deletions build_tools/generate_manifest_diff_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,32 @@
for each component between builds.

Arguments:
--start Start commit SHA or workflow run ID (required unless using --find-last-successful)
--end End commit SHA or workflow run ID (required)
--find-last-successful Workflow file to find last successful run (e.g., 'ci_nightly.yml')
--workflow-mode Treat --start and --end as workflow run IDs instead of commit SHAs
--start Start commit SHA or workflow run ID (required unless using
--find-last-run or --pr-base-ref).
--end End commit SHA or workflow run ID (required).
--find-last-run Workflow file to find the most recent prior run on the
branch whose status is in --accepted-statuses
(e.g., 'ci_nightly.yml').
--accepted-statuses Comma-separated workflow run conclusions accepted by
--find-last-run (default: 'success'). Other examples:
'success,failure'.
--pr-base-ref PR base branch name. When set, --start is resolved as
the merge-base between --end and the named branch via
the GitHub Compare API. Rebase-safe.
--workflow-mode Treat --start and --end as workflow run IDs instead of
commit SHAs.
--branch Branch to scope --find-last-run lookups against
(default: 'main').
--output-dir Directory to write the HTML report into (default: the
TheRock root directory).

If no usable start ref can be derived, the script logs the reason and
exits 0 without writing a report.

Example usage:
python build_tools/generate_manifest_diff_report.py --start abc123 --end def456
python build_tools/generate_manifest_diff_report.py --end def456 --find-last-successful ci_nightly.yml
python build_tools/generate_manifest_diff_report.py --end def456 --find-last-run ci_nightly.yml
python build_tools/generate_manifest_diff_report.py --end def456 --pr-base-ref main
python build_tools/generate_manifest_diff_report.py --start 12345 --end 67890 --workflow-mode
"""

Expand All @@ -34,7 +52,7 @@
from generate_therock_manifest import build_manifest_schema
from github_actions.github_actions_api import (
gha_append_step_summary,
gha_query_last_successful_workflow_run,
gha_query_last_workflow_run,
gha_query_workflow_run_by_id,
gha_send_request,
)
Expand Down Expand Up @@ -181,8 +199,28 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
)
parser.add_argument("--end", required=True, help="End workflow ID or commit SHA")
parser.add_argument(
"--find-last-successful",
help="Workflow name to find last successful run (e.g., 'ci_nightly.yml')",
"--find-last-run",
help=(
"Workflow name to find the most recent prior run on the branch "
"whose status is in --accepted-statuses (e.g., 'ci_nightly.yml')."
),
)
parser.add_argument(
"--accepted-statuses",
default="success",
help=(
"Comma-separated workflow run conclusions accepted by "
"--find-last-run (default: 'success'). Examples: 'success', "
"'success,failure'."
),
)
parser.add_argument(
"--pr-base-ref",
help=(
"PR base branch name. When set, --start is resolved as the "
"merge-base between --end and this branch via the GitHub Compare "
"API. Rebase-safe."
),
)
parser.add_argument(
"--workflow-mode",
Expand All @@ -192,7 +230,7 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
parser.add_argument(
"--branch",
default="main",
help="Branch to search for last successful workflow run (default: main)",
help="Branch to search for last workflow run (default: main)",
)
parser.add_argument(
"--output-dir",
Expand All @@ -214,42 +252,68 @@ def _optional_str(val: str | None) -> str | None:
return s if s else None


def resolve_commits(args: argparse.Namespace) -> tuple[str, str]:
"""Resolve start and end commit SHAs from arguments."""
def resolve_commits(
args: argparse.Namespace,
) -> tuple[str | None, str | None]:
"""Resolve start and end commit SHAs from arguments.

Returns ``(None, None)`` when --find-last-run finds no prior matching
run (graceful empty); caller should exit 0. Other API errors (e.g.
Compare API 404 on --pr-base-ref) propagate as GitHubAPIError; the
workflow's continue-on-error gate handles those.
"""
start = _optional_str(args.start)
find_last = _optional_str(args.find_last_successful)
find_last = _optional_str(args.find_last_run)
pr_base = _optional_str(args.pr_base_ref)
end = _optional_str(args.end)

if start is None and find_last is None:
raise ValueError(
"--start is required unless --find-last-successful is provided"
)
if end is None:
raise ValueError("--end is required")
if start is None and find_last is None and pr_base is None:
raise ValueError(
"--start is required unless --find-last-run or --pr-base-ref is provided"
)

therock = f"{ROCM_ORG}/{THEROCK_REPO}"

therock_repo_full = f"{ROCM_ORG}/{THEROCK_REPO}"
# Resolve end first; --pr-base-ref needs end_sha to compute the merge-base.
if args.workflow_mode:
end_sha = gha_query_workflow_run_by_id(therock, end).get("head_sha")
else:
end_sha = end

# Resolve start commit
if find_last is not None:
last_run = gha_query_last_successful_workflow_run(
therock_repo_full, find_last, branch=args.branch
# Resolve start, in priority order: --pr-base-ref, --find-last-run,
# --workflow-mode, then plain --start.
if pr_base is not None:
compare_url = (
f"https://api.github.com/repos/{therock}/compare/{pr_base}...{end_sha}"
)
compare = gha_send_request(compare_url)
start_sha = compare.get("merge_base_commit", {}).get("sha")
elif find_last is not None:
accepted = {
tok.strip().lower()
for tok in (args.accepted_statuses or "").split(",")
if tok.strip()
} or {"success"}
last_run = gha_query_last_workflow_run(
therock,
find_last,
branch=args.branch,
accepted_statuses=accepted,
)
if not last_run:
raise ValueError(f"No previous successful run found for {find_last}")
if last_run is None:
print(
f" [empty] No prior run of {find_last} on {args.branch} with "
f"conclusion in {sorted(accepted)} (likely a first-ever run)."
)
return None, None
start_sha = last_run["head_sha"]
elif args.workflow_mode:
workflow_info = gha_query_workflow_run_by_id(therock_repo_full, start)
start_sha = workflow_info.get("head_sha")
start_sha = gha_query_workflow_run_by_id(therock, start).get("head_sha")
else:
start_sha = start

# Resolve end commit
if args.workflow_mode:
workflow_info = gha_query_workflow_run_by_id(therock_repo_full, end)
end_sha = workflow_info.get("head_sha")
else:
end_sha = end

return start_sha, end_sha


Expand Down Expand Up @@ -1423,11 +1487,11 @@ def main(argv: list[str] | None = None) -> int:
"""Main entry point."""
args = parse_args(argv)
start_commit, end_commit = resolve_commits(args)
if start_commit is None:
return 0

diff = compare_manifests(start_commit, end_commit)

output_dir = args.output_dir
generate_html_report(diff, output_dir)
generate_html_report(diff, args.output_dir)

print("\n=== Generating Step Summary ===")
generate_step_summary(diff)
Expand Down
1 change: 1 addition & 0 deletions build_tools/github_actions/configure_ci_path_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ def is_ci_run_required(paths: Optional[Iterable[str]]) -> bool:
"multi_arch_build_windows.yml",
"multi_arch_build_windows_artifacts.yml",
"setup_multi_arch.yml",
"manifest-diff.yml",
"test_artifacts_structure.yml",
"test_native_linux_packages_install.yml",
# both
Expand Down
33 changes: 18 additions & 15 deletions build_tools/github_actions/github_actions_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,29 +474,32 @@ def gha_query_workflow_runs_for_commit(
return runs


def gha_query_last_successful_workflow_run(
def gha_query_last_workflow_run(
github_repository: str = "ROCm/TheRock",
workflow_name: str = "multi_arch_ci.yml",
branch: str = "main",
accepted_statuses: set[str] | None = None,
) -> dict | None:
"""Find the last successful run of a specific workflow on the specified branch.
"""Find the most recent run of a workflow on ``branch`` whose conclusion
is in ``accepted_statuses`` (default ``{"success"}``).

Args:
github_repository: Repository in format "owner/repo"
workflow_name: Name of the workflow file (e.g., "ci_nightly.yml")
branch: Branch to filter by (defaults to "main")
Filters client-side from the most-recent 100 runs because the
workflow-runs endpoint accepts at most one ``status=`` filter.

Returns:
The full workflow run object of the most recent successful run on the specified branch,
or None if no successful runs are found.
Returns the matching run dict, or ``None`` if none of the last ~100
runs on ``branch`` has an accepted conclusion.
"""
# Use GitHub API query parameters to pre-filter for successful runs on the specified branch
url = f"https://api.github.com/repos/{github_repository}/actions/workflows/{workflow_name}/runs?status=success&branch={branch}&per_page=100&sort=created&direction=desc"
if accepted_statuses is None:
accepted_statuses = {"success"}
url = (
f"https://api.github.com/repos/{github_repository}"
f"/actions/workflows/{workflow_name}/runs"
f"?branch={branch}&per_page=100&sort=created&direction=desc"
)
response = gha_send_request(url)

# Return the first (most recent) successful run
if response and response.get("workflow_runs"):
return response["workflow_runs"][0]
for run in response.get("workflow_runs", []) if response else []:
if run.get("conclusion") in accepted_statuses:
return run
return None


Expand Down
Loading
Loading