Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 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,9 +46,20 @@ 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
Expand All @@ -43,6 +68,8 @@ jobs:
generate-report:
name: Generate Manifest Diff Report
runs-on: ubuntu-24.04
# Informational job: never block the build/test jobs that needs: setup.
continue-on-error: true
permissions:
id-token: write
contents: read
Expand All @@ -58,8 +85,11 @@ jobs:
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

Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/setup_multi_arch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,37 @@ jobs:
--release-type=${{ inputs.release_type || 'dev' }} \
--prerelease-version=${{ inputs.prerelease_version }} \
--override-git-sha=${{ steps.checkout.outputs.commit }}

# Per-event start-ref derivation:
# pull_request → pr_base_ref (merge-base via Compare API, rebase-safe);
# end_ref is the PR head SHA, not setup.outputs.ref
# (which is the synthetic refs/pull/N/merge SHA).
# push → start_ref = github.event.before (previous tip).
# schedule → find_last_run scoped to github.workflow;
# accepted_statuses="success,failure" so a failed
# nightly still has a baseline.
manifest_diff:
name: Manifest Diff
needs: setup
Comment thread
amd-hsivasun marked this conversation as resolved.
Outdated
if: >-
github.repository == 'ROCm/TheRock' &&
inputs.external_repo_config == '' &&
(github.event_name == 'pull_request' ||
github.event_name == 'push' ||
github.event_name == 'schedule')
uses: ./.github/workflows/manifest-diff.yml
Comment thread
amd-hsivasun marked this conversation as resolved.
Outdated
permissions:
contents: read
id-token: write
Comment thread
amd-hsivasun marked this conversation as resolved.
Outdated
with:
end_ref: >-
${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || needs.setup.outputs.ref }}
start_ref: >-
${{ github.event_name == 'push' && github.event.before || '' }}
pr_base_ref: >-
${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || '' }}
find_last_run: >-
${{ github.event_name == 'schedule' && github.workflow || '' }}
accepted_statuses: >-
${{ github.event_name == 'schedule' && 'success,failure' || '' }}
branch: ${{ github.ref_name }}
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