From 93efa135e41c138dd8fae07d559dcd2cef7da7b7 Mon Sep 17 00:00:00 2001 From: amd-hsivasun Date: Tue, 12 May 2026 17:48:07 +0000 Subject: [PATCH 1/6] [ci] Auto-generate manifest-diff report from multi-arch CI on PR/push/schedule --- .github/workflows/manifest-diff.yml | 34 ++++- .github/workflows/setup_multi_arch.yml | 33 +++++ build_tools/generate_manifest_diff_report.py | 134 +++++++++++++----- .../configure_ci_path_filters.py | 1 + .../github_actions/github_actions_api.py | 33 +++-- .../tests/github_actions_api_test.py | 24 ++-- .../generate_manifest_diff_report_test.py | 63 ++++++-- 7 files changed, 249 insertions(+), 73 deletions(-) diff --git a/.github/workflows/manifest-diff.yml b/.github/workflows/manifest-diff.yml index a9edfad85da..369ba2df8bd 100644 --- a/.github/workflows/manifest-diff.yml +++ b/.github/workflows/manifest-diff.yml @@ -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: @@ -16,8 +16,17 @@ 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/ via the GitHub Compare API.' required: false type: string workflow_call: @@ -32,7 +41,14 @@ 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 @@ -43,6 +59,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 @@ -58,8 +76,10 @@ 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 }}" \ ${{ inputs.workflow_mode && '--workflow-mode' || '' }} \ --output-dir reports @@ -70,6 +90,8 @@ jobs: aws-region: us-east-2 role-to-assume: arn:aws:iam::692859939525:role/therock-ci + # upload_test_report_script.py appends the S3 report link to + # $GITHUB_STEP_SUMMARY itself, so no extra link step is needed. - name: Upload Report to S3 if: ${{ github.repository == 'ROCm/TheRock' }} run: | diff --git a/.github/workflows/setup_multi_arch.yml b/.github/workflows/setup_multi_arch.yml index cdbcf31e8fd..60151f618a5 100644 --- a/.github/workflows/setup_multi_arch.yml +++ b/.github/workflows/setup_multi_arch.yml @@ -159,3 +159,36 @@ 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 + 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 + permissions: + contents: read + id-token: write + 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' || '' }} diff --git a/build_tools/generate_manifest_diff_report.py b/build_tools/generate_manifest_diff_report.py index 36fa2102567..a68aeecdd10 100644 --- a/build_tools/generate_manifest_diff_report.py +++ b/build_tools/generate_manifest_diff_report.py @@ -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 """ @@ -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, ) @@ -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", @@ -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", @@ -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 @@ -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) diff --git a/build_tools/github_actions/configure_ci_path_filters.py b/build_tools/github_actions/configure_ci_path_filters.py index a63f00fd7fd..42473482c24 100644 --- a/build_tools/github_actions/configure_ci_path_filters.py +++ b/build_tools/github_actions/configure_ci_path_filters.py @@ -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 diff --git a/build_tools/github_actions/github_actions_api.py b/build_tools/github_actions/github_actions_api.py index 4f6aaa6c978..2964bccc7f4 100644 --- a/build_tools/github_actions/github_actions_api.py +++ b/build_tools/github_actions/github_actions_api.py @@ -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 diff --git a/build_tools/github_actions/tests/github_actions_api_test.py b/build_tools/github_actions/tests/github_actions_api_test.py index 348e563204a..595d6446535 100644 --- a/build_tools/github_actions/tests/github_actions_api_test.py +++ b/build_tools/github_actions/tests/github_actions_api_test.py @@ -16,7 +16,7 @@ GitHubAPI, GitHubAPIError, gha_load_github_event, - gha_query_last_successful_workflow_run, + gha_query_last_workflow_run, gha_query_recent_branch_commits, gha_query_workflow_run_by_id, gha_query_workflow_runs_for_commit, @@ -556,26 +556,34 @@ def test_gha_query_workflow_runs_for_commit_sorts_by_created_at(self): self.assertEqual(runs[1]["id"], 1, "Older run should be second") @_skip_unless_authenticated_github_api_is_available - def test_gha_query_last_successful_workflow_run(self): - """Test querying for the last successful workflow run on a branch.""" + def test_gha_query_last_workflow_run(self): + """Test querying for the last workflow run on a branch.""" # Test successful run found on main branch - result = gha_query_last_successful_workflow_run( - "ROCm/TheRock", "ci_nightly.yml", "main" - ) + result = gha_query_last_workflow_run("ROCm/TheRock", "ci_nightly.yml", "main") self.assertIsNotNone(result) self.assertEqual(result["head_branch"], "main") self.assertEqual(result["conclusion"], "success") self.assertIn("id", result) + # Test multi-status set: accept success or failure + result = gha_query_last_workflow_run( + "ROCm/TheRock", + "ci_nightly.yml", + "main", + accepted_statuses={"success", "failure"}, + ) + self.assertIsNotNone(result) + self.assertIn(result["conclusion"], {"success", "failure"}) + # Test no matching branch - should return None - result = gha_query_last_successful_workflow_run( + result = gha_query_last_workflow_run( "ROCm/TheRock", "ci_nightly.yml", "nonexistent-branch-12345" ) self.assertIsNone(result) # Test non-existent workflow - should raise an exception with self.assertRaises(Exception): - gha_query_last_successful_workflow_run( + gha_query_last_workflow_run( "ROCm/TheRock", "nonexistent_workflow_12345.yml", "main" ) diff --git a/build_tools/tests/generate_manifest_diff_report_test.py b/build_tools/tests/generate_manifest_diff_report_test.py index 4d725844f42..0c8dfac51a4 100644 --- a/build_tools/tests/generate_manifest_diff_report_test.py +++ b/build_tools/tests/generate_manifest_diff_report_test.py @@ -221,8 +221,8 @@ def test_workflow_mode_resolves_both_commits(self): "generate_manifest_diff_report.gha_query_workflow_run_by_id" ) as mock_query: mock_query.side_effect = [ + {"head_sha": "789xyz000111"}, # end workflow (resolved first) {"head_sha": "abc123def456"}, # start workflow - {"head_sha": "789xyz000111"}, # end workflow ] start_sha, end_sha = resolve_commits(args) @@ -230,22 +230,67 @@ def test_workflow_mode_resolves_both_commits(self): self.assertEqual(end_sha, "789xyz000111") self.assertEqual(mock_query.call_count, 2) - def test_find_last_successful_resolves_start(self): - """--find-last-successful finds last successful run for start commit.""" - args = parse_args( - ["--end", "def456", "--find-last-successful", "multi_arch_ci.yml"] - ) + def test_find_last_run_resolves_start(self): + """--find-last-run finds the most recent matching run for start commit.""" + args = parse_args(["--end", "def456", "--find-last-run", "multi_arch_ci.yml"]) with mock.patch( - "generate_manifest_diff_report.gha_query_last_successful_workflow_run" + "generate_manifest_diff_report.gha_query_last_workflow_run" ) as mock_query: - mock_query.return_value = {"head_sha": "last_successful_sha"} + mock_query.return_value = {"head_sha": "last_matching_sha"} start_sha, end_sha = resolve_commits(args) - self.assertEqual(start_sha, "last_successful_sha") + self.assertEqual(start_sha, "last_matching_sha") self.assertEqual(end_sha, "def456") mock_query.assert_called_once() + def test_find_last_run_passes_accepted_statuses(self): + """--accepted-statuses is parsed and forwarded to gha_query_last_workflow_run.""" + args = parse_args( + [ + "--end", + "def456", + "--find-last-run", + "ci.yml", + "--accepted-statuses", + "success,failure", + ] + ) + + with mock.patch( + "generate_manifest_diff_report.gha_query_last_workflow_run" + ) as mock_query: + mock_query.return_value = {"head_sha": "abc"} + resolve_commits(args) + + _, kwargs = mock_query.call_args + self.assertEqual(kwargs["accepted_statuses"], {"success", "failure"}) + + def test_pr_base_ref_resolves_start_via_compare(self): + """--pr-base-ref resolves start as the merge-base via the Compare API.""" + args = parse_args(["--end", "deadbeef", "--pr-base-ref", "main"]) + + with mock.patch( + "generate_manifest_diff_report.gha_send_request" + ) as mock_request: + mock_request.return_value = {"merge_base_commit": {"sha": "base_sha_xyz"}} + start_sha, end_sha = resolve_commits(args) + + self.assertEqual(start_sha, "base_sha_xyz") + self.assertEqual(end_sha, "deadbeef") + # Verify the URL hit Compare API with base...end ordering. + called_url = mock_request.call_args.args[0] + self.assertIn("/compare/main...deadbeef", called_url) + + def test_find_last_run_no_match_returns_none(self): + """--find-last-run with no matching prior run → (None, None).""" + args = parse_args(["--end", "def456", "--find-last-run", "ci.yml"]) + with mock.patch( + "generate_manifest_diff_report.gha_query_last_workflow_run", + return_value=None, + ): + self.assertEqual(resolve_commits(args), (None, None)) + def test_direct_commit_shas_no_api_calls(self): """Direct commit SHAs don't require API calls.""" args = parse_args(["--start", "abc123", "--end", "def456"]) From d9d724c54401bf2efa2fe6a552a3d3e6f199126c Mon Sep 17 00:00:00 2001 From: amd-hsivasun Date: Tue, 12 May 2026 19:08:12 +0000 Subject: [PATCH 2/6] [docs] Document manifest-diff report tool and multi-arch CI integration --- docs/development/ci_overview.md | 1 + docs/development/manifest_diff.md | 151 ++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 docs/development/manifest_diff.md diff --git a/docs/development/ci_overview.md b/docs/development/ci_overview.md index c624c2e3e64..14bed27d84a 100644 --- a/docs/development/ci_overview.md +++ b/docs/development/ci_overview.md @@ -162,6 +162,7 @@ See [workflow_outputs.md](workflow_outputs.md) for the S3 layout structure and [ - [workflow_outputs.md](workflow_outputs.md) - CI output directory structure - [github_actions_debugging.md](github_actions_debugging.md) - Debugging GitHub Actions - [ci_behavior_manipulation.md](ci_behavior_manipulation.md) - Controlling CI behavior with labels and inputs +- [manifest_diff.md](manifest_diff.md) - Manifest diff report (submodule SHA changes between two commits) ## Getting Help diff --git a/docs/development/manifest_diff.md b/docs/development/manifest_diff.md new file mode 100644 index 00000000000..c7c5ae8ecf0 --- /dev/null +++ b/docs/development/manifest_diff.md @@ -0,0 +1,151 @@ +# Manifest Diff Report + +This document describes the **manifest diff report** — a CI tool that +summarizes which TheRock submodule SHAs changed between two commits. It runs +automatically on every multi-arch CI run (PR / push / schedule) and can also +be invoked on-demand via `workflow_dispatch` or locally on the command line. + +## Summary + +TheRock is a CMake super-project pinned to a small set of top-level git +submodules (`.gitmodules`), two of which (`rocm-libraries`, `rocm-systems`) +are themselves superrepos containing further ROCm components under +`projects/` and `shared/`. When a change in TheRock or in any of those +upstream repos lands, it can be non-obvious which pointer(s) actually +moved. The manifest diff report answers that question: for two TheRock +commits (a **start** and an **end**), it walks the manifest produced by +`generate_therock_manifest.py` — top-level submodules plus superrepo +components — and produces an HTML page listing the new commit range on +each one, with links back to the upstream repos. + +The report is generated by +[`build_tools/generate_manifest_diff_report.py`](../../build_tools/generate_manifest_diff_report.py) +and uploaded to S3 by the +[`manifest-diff.yml`](../../.github/workflows/manifest-diff.yml) reusable +workflow. + +## How it runs in CI + +The multi-arch CI top-level workflow +([`setup_multi_arch.yml`](../../.github/workflows/setup_multi_arch.yml)) +gates the `manifest_diff` job to `ROCm/TheRock` only and skips it for +external-repo runs. It then calls `manifest-diff.yml` with per-event inputs +that derive the **start** ref differently depending on what triggered the +run: + +```mermaid +flowchart TD + A[Event triggers multi_arch_ci] --> B{event_name} + B -- pull_request --> P[pr_base_ref = base branch
end_ref = pull_request.head.sha
script computes merge-base via Compare API] + B -- push --> U[start_ref = github.event.before
end_ref = setup.outputs.ref] + B -- schedule --> S[find_last_run = github.workflow
accepted_statuses = success,failure
script queries last matching run on the branch] + P --> G[generate_manifest_diff_report.py] + U --> G + S --> G + G --> R[reports/index.html] + R --> O[upload_test_report_script.py] + O --> H[S3 index.html + step-summary link] +``` + +| Event | Start ref source | End ref source | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| `pull_request` | `pr_base_ref` (the PR's base branch). The script calls the GitHub Compare API to get the merge-base, which is rebase-safe and works on rewritten PRs. | `pull_request.head.sha` (not `setup.outputs.ref`, which is the synthetic `refs/pull/N/merge` SHA). | +| `push` | `github.event.before` — the branch tip before the push. | `setup.outputs.ref` — the commit the build is running against. | +| `schedule` | `find_last_run` — the script queries the last run of the current workflow whose conclusion is in `accepted_statuses`. Scheduled runs accept `success,failure` so a single broken nightly still produces a baseline. | `setup.outputs.ref`. | + +The `manifest_diff` job in `setup_multi_arch.yml` is **purely +informational** — it runs in parallel with the build/test jobs, never +gates them, and is marked `continue-on-error: true` inside +`manifest-diff.yml` itself so an API hiccup turns the job card red without +turning the whole CI run red. + +### Where the report lives + +`manifest-diff.yml`'s upload step calls +[`upload_test_report_script.py`](../../build_tools/github_actions/upload_test_report_script.py), +which pushes `reports/index.html` under the run's S3 prefix using +`manifest-diff` as the "amdgpu family" segment. The same upload script +appends a link to `$GITHUB_STEP_SUMMARY`, so the report shows up in the +**Summary** tab of the workflow run. + +S3 path: + +``` +s3://therock-ci-artifacts/{run_id}-linux/test/manifest-diff/index.html +``` + +See [`workflow_outputs.md`](workflow_outputs.md) for the full S3 layout. + +## Running it manually + +### `workflow_dispatch` + +Trigger `TheRock Manifest Diff Report` on the +[Actions page](https://github.com/ROCm/TheRock/actions/workflows/manifest-diff.yml). +The dispatch inputs are: + +| Input | Required | Description | +| ------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `end_ref` | yes | End commit SHA, or a workflow run ID when `workflow_mode=true`. | +| `start_ref` | one of these three | Start commit SHA (or run ID with `workflow_mode=true`). | +| `find_last_run` | one of these three | Workflow filename (e.g. `multi_arch_ci.yml`). The script queries the last run on the branch whose status is in `accepted_statuses`. | +| `pr_base_ref` | one of these three | PR base branch name. The script resolves the merge-base between `end_ref` and `origin/` via the GitHub Compare API. | +| `accepted_statuses` | no (default `success`) | Comma-separated workflow run conclusions accepted by `--find-last-run`. | +| `workflow_mode` | no (default `false`) | Treat `start_ref` / `end_ref` as workflow run IDs instead of commit SHAs (resolves each to its `head_sha`). | + +Exactly one of `start_ref`, `find_last_run`, or `pr_base_ref` is required. + +### Local CLI + +```bash +python3 build_tools/generate_manifest_diff_report.py \ + --start \ + --end \ + --output-dir reports +``` + +Other supported flags: + +| Flag | Purpose | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `--start ` | Explicit start ref. With `--workflow-mode`, treated as a workflow run ID. | +| `--end ` | Explicit end ref. With `--workflow-mode`, treated as a workflow run ID. | +| `--find-last-run ` | Resolve `start` as the most recent run of `` on `--branch` whose conclusion is in `--accepted-statuses`. | +| `--accepted-statuses success,failure` | Comma-separated set of accepted run conclusions for `--find-last-run`. Default: `success`. | +| `--pr-base-ref ` | Resolve `start` as the merge-base between `end` and `origin/` via the Compare API. | +| `--workflow-mode` | Treat `--start` and `--end` as workflow run IDs and resolve each to its `head_sha`. | +| `--branch ` | Branch to scope `--find-last-run` to. Default: `main`. | +| `--output-dir ` | Output directory for `index.html`. Default: `reports`. | + +`GITHUB_TOKEN` (or any token with `public_repo` read scope) must be set in +the environment for the GitHub API calls used by `--find-last-run`, +`--pr-base-ref`, and `--workflow-mode`. + +### Graceful empty + +`--find-last-run` returns `(None, None)` and exits successfully when there +is no prior matching run on the branch (e.g. a first-ever nightly). In that +case the script prints an `[empty]` line and does not write a report; the +downstream upload step gracefully no-ops on the missing directory and the +job card stays green. + +Other API failures (e.g. a 404 from the Compare API on a bad +`--pr-base-ref`) propagate as `GitHubAPIError` and are absorbed by the +job-level `continue-on-error: true`, so the job card goes red but the +overall CI run stays green. + +## Code map + +| File | Role | +| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| [`.github/workflows/manifest-diff.yml`](../../.github/workflows/manifest-diff.yml) | Reusable workflow: checkout, run script, upload to S3. | +| [`.github/workflows/setup_multi_arch.yml`](../../.github/workflows/setup_multi_arch.yml) | `manifest_diff` job: per-event input derivation. | +| [`build_tools/generate_manifest_diff_report.py`](../../build_tools/generate_manifest_diff_report.py) | Resolves start/end SHAs, walks submodules, renders the HTML report. | +| [`build_tools/github_actions/github_actions_api.py`](../../build_tools/github_actions/github_actions_api.py) | `gha_query_last_workflow_run()` shared helper used by `--find-last-run`. | +| [`build_tools/github_actions/upload_test_report_script.py`](../../build_tools/github_actions/upload_test_report_script.py) | S3 upload + step-summary link (shared with test reports). | + +## Related + +- [`ci_overview.md`](ci_overview.md) — overall multi-arch CI architecture. +- [`workflow_outputs.md`](workflow_outputs.md) — S3 layout used by the upload step. +- [`github_actions_debugging.md`](github_actions_debugging.md) — debugging GitHub Actions runs. From 63eb988097612e1befc17f8f8d4ba06acb2f7c32 Mon Sep 17 00:00:00 2001 From: amd-hsivasun Date: Tue, 12 May 2026 22:29:58 +0000 Subject: [PATCH 3/6] [CI] Add --branch parameter into manifest-diff workflow --- .github/workflows/manifest-diff.yml | 12 ++++++++++-- .github/workflows/setup_multi_arch.yml | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/manifest-diff.yml b/.github/workflows/manifest-diff.yml index 369ba2df8bd..c380b2af140 100644 --- a/.github/workflows/manifest-diff.yml +++ b/.github/workflows/manifest-diff.yml @@ -29,6 +29,11 @@ on: description: 'PR base branch name. When set, start_ref is resolved as the merge-base between end_ref and origin/ 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: @@ -51,6 +56,10 @@ on: pr_base_ref: required: false type: string + branch: + required: false + type: string + default: 'main' permissions: contents: read @@ -80,6 +89,7 @@ jobs: --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 @@ -90,8 +100,6 @@ jobs: aws-region: us-east-2 role-to-assume: arn:aws:iam::692859939525:role/therock-ci - # upload_test_report_script.py appends the S3 report link to - # $GITHUB_STEP_SUMMARY itself, so no extra link step is needed. - name: Upload Report to S3 if: ${{ github.repository == 'ROCm/TheRock' }} run: | diff --git a/.github/workflows/setup_multi_arch.yml b/.github/workflows/setup_multi_arch.yml index 60151f618a5..26d8312e6d2 100644 --- a/.github/workflows/setup_multi_arch.yml +++ b/.github/workflows/setup_multi_arch.yml @@ -192,3 +192,4 @@ jobs: ${{ github.event_name == 'schedule' && github.workflow || '' }} accepted_statuses: >- ${{ github.event_name == 'schedule' && 'success,failure' || '' }} + branch: ${{ github.ref_name }} From c327453df9a1fbddaea1b073809f7a4c0e92206d Mon Sep 17 00:00:00 2001 From: amd-hsivasun Date: Tue, 12 May 2026 22:54:02 +0000 Subject: [PATCH 4/6] [Docs] Link manifest-diff doc to new issue created --- docs/development/manifest_diff.md | 66 ++++++------------------------- 1 file changed, 12 insertions(+), 54 deletions(-) diff --git a/docs/development/manifest_diff.md b/docs/development/manifest_diff.md index c7c5ae8ecf0..21b15b03a34 100644 --- a/docs/development/manifest_diff.md +++ b/docs/development/manifest_diff.md @@ -1,37 +1,16 @@ # Manifest Diff Report -This document describes the **manifest diff report** — a CI tool that -summarizes which TheRock submodule SHAs changed between two commits. It runs -automatically on every multi-arch CI run (PR / push / schedule) and can also -be invoked on-demand via `workflow_dispatch` or locally on the command line. +This document describes the **manifest diff report** — a CI tool that summarizes which TheRock submodule SHAs changed between two commits. It runs automatically on every multi-arch CI run (PR / push / schedule) and can also be invoked on-demand via `workflow_dispatch` or locally on the command line. ## Summary -TheRock is a CMake super-project pinned to a small set of top-level git -submodules (`.gitmodules`), two of which (`rocm-libraries`, `rocm-systems`) -are themselves superrepos containing further ROCm components under -`projects/` and `shared/`. When a change in TheRock or in any of those -upstream repos lands, it can be non-obvious which pointer(s) actually -moved. The manifest diff report answers that question: for two TheRock -commits (a **start** and an **end**), it walks the manifest produced by -`generate_therock_manifest.py` — top-level submodules plus superrepo -components — and produces an HTML page listing the new commit range on -each one, with links back to the upstream repos. - -The report is generated by -[`build_tools/generate_manifest_diff_report.py`](../../build_tools/generate_manifest_diff_report.py) -and uploaded to S3 by the -[`manifest-diff.yml`](../../.github/workflows/manifest-diff.yml) reusable -workflow. +TheRock is a CMake super-project pinned to a small set of top-level git submodules (`.gitmodules`), two of which (`rocm-libraries`, `rocm-systems`) are themselves superrepos containing further ROCm components under `projects/` and `shared/`. When a change in TheRock or in any of those upstream repos lands, it can be non-obvious which pointer(s) actually moved. The manifest diff report answers that question: for two TheRock commits (a **start** and an **end**), it walks the manifest produced by `generate_therock_manifest.py` — top-level submodules plus superrepo components — and produces an HTML page listing the new commit range on each one, with links back to the upstream repos. + +The report is generated by [`build_tools/generate_manifest_diff_report.py`](../../build_tools/generate_manifest_diff_report.py) and uploaded to S3 by the [`manifest-diff.yml`](../../.github/workflows/manifest-diff.yml) reusable workflow. ## How it runs in CI -The multi-arch CI top-level workflow -([`setup_multi_arch.yml`](../../.github/workflows/setup_multi_arch.yml)) -gates the `manifest_diff` job to `ROCm/TheRock` only and skips it for -external-repo runs. It then calls `manifest-diff.yml` with per-event inputs -that derive the **start** ref differently depending on what triggered the -run: +The multi-arch CI top-level workflow ([`setup_multi_arch.yml`](../../.github/workflows/setup_multi_arch.yml)) gates the `manifest_diff` job to `ROCm/TheRock` only and skips it for external-repo runs. It then calls `manifest-diff.yml` with per-event inputs that derive the **start** ref differently depending on what triggered the run: ```mermaid flowchart TD @@ -53,20 +32,11 @@ flowchart TD | `push` | `github.event.before` — the branch tip before the push. | `setup.outputs.ref` — the commit the build is running against. | | `schedule` | `find_last_run` — the script queries the last run of the current workflow whose conclusion is in `accepted_statuses`. Scheduled runs accept `success,failure` so a single broken nightly still produces a baseline. | `setup.outputs.ref`. | -The `manifest_diff` job in `setup_multi_arch.yml` is **purely -informational** — it runs in parallel with the build/test jobs, never -gates them, and is marked `continue-on-error: true` inside -`manifest-diff.yml` itself so an API hiccup turns the job card red without -turning the whole CI run red. +The `manifest_diff` job in `setup_multi_arch.yml` is **purely informational** — it runs in parallel with the build/test jobs, never gates them, and is marked `continue-on-error: true` inside `manifest-diff.yml` itself so an API hiccup turns the job card red without turning the whole CI run red. ### Where the report lives -`manifest-diff.yml`'s upload step calls -[`upload_test_report_script.py`](../../build_tools/github_actions/upload_test_report_script.py), -which pushes `reports/index.html` under the run's S3 prefix using -`manifest-diff` as the "amdgpu family" segment. The same upload script -appends a link to `$GITHUB_STEP_SUMMARY`, so the report shows up in the -**Summary** tab of the workflow run. +`manifest-diff.yml`'s upload step calls [`upload_test_report_script.py`](../../build_tools/github_actions/upload_test_report_script.py), which pushes `reports/index.html` under the run's S3 prefix using `manifest-diff` as the "amdgpu family" segment. The same upload script appends a link to `$GITHUB_STEP_SUMMARY`, so the report shows up in the **Summary** tab of the workflow run. S3 path: @@ -80,9 +50,7 @@ See [`workflow_outputs.md`](workflow_outputs.md) for the full S3 layout. ### `workflow_dispatch` -Trigger `TheRock Manifest Diff Report` on the -[Actions page](https://github.com/ROCm/TheRock/actions/workflows/manifest-diff.yml). -The dispatch inputs are: +Trigger `TheRock Manifest Diff Report` on the [Actions page](https://github.com/ROCm/TheRock/actions/workflows/manifest-diff.yml). The dispatch inputs are: | Input | Required | Description | | ------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | @@ -91,6 +59,7 @@ The dispatch inputs are: | `find_last_run` | one of these three | Workflow filename (e.g. `multi_arch_ci.yml`). The script queries the last run on the branch whose status is in `accepted_statuses`. | | `pr_base_ref` | one of these three | PR base branch name. The script resolves the merge-base between `end_ref` and `origin/` via the GitHub Compare API. | | `accepted_statuses` | no (default `success`) | Comma-separated workflow run conclusions accepted by `--find-last-run`. | +| `branch` | no (default `main`) | Branch to scope `--find-last-run` lookups against. | | `workflow_mode` | no (default `false`) | Treat `start_ref` / `end_ref` as workflow run IDs instead of commit SHAs (resolves each to its `head_sha`). | Exactly one of `start_ref`, `find_last_run`, or `pr_base_ref` is required. @@ -117,22 +86,11 @@ Other supported flags: | `--branch ` | Branch to scope `--find-last-run` to. Default: `main`. | | `--output-dir ` | Output directory for `index.html`. Default: `reports`. | -`GITHUB_TOKEN` (or any token with `public_repo` read scope) must be set in -the environment for the GitHub API calls used by `--find-last-run`, -`--pr-base-ref`, and `--workflow-mode`. - -### Graceful empty +`GITHUB_TOKEN` (or any token with `public_repo` read scope) must be set in the environment for the GitHub API calls used by `--find-last-run`, `--pr-base-ref`, and `--workflow-mode`. -`--find-last-run` returns `(None, None)` and exits successfully when there -is no prior matching run on the branch (e.g. a first-ever nightly). In that -case the script prints an `[empty]` line and does not write a report; the -downstream upload step gracefully no-ops on the missing directory and the -job card stays green. +## Scope -Other API failures (e.g. a 404 from the Compare API on a bad -`--pr-base-ref`) propagate as `GitHubAPIError` and are absorbed by the -job-level `continue-on-error: true`, so the job card goes red but the -overall CI run stays green. +The job is currently gated to `ROCm/TheRock` `pull_request` / `push` / `schedule` events with `external_repo_config == ''`. External-repo CI runs (`rocm-libraries`, `rocm-systems`) and the rockrel release-driver flow are deliberately excluded for now; extending the report to those callers is tracked in #5219. ## Code map From 1d9f400eb8c8e64c49223bd59bd1405d77ce8dc8 Mon Sep 17 00:00:00 2001 From: amd-hsivasun Date: Thu, 14 May 2026 22:05:08 +0000 Subject: [PATCH 5/6] [CI] Move manifest_diff to multi_arch_ci.yml; convert manifest-diff.yml --- .github/workflows/manifest-diff.yml | 23 ++++++++++------- .github/workflows/multi_arch_ci.yml | 16 ++++++++++++ .github/workflows/setup_multi_arch.yml | 34 -------------------------- 3 files changed, 30 insertions(+), 43 deletions(-) diff --git a/.github/workflows/manifest-diff.yml b/.github/workflows/manifest-diff.yml index c380b2af140..5a5f6c0579b 100644 --- a/.github/workflows/manifest-diff.yml +++ b/.github/workflows/manifest-diff.yml @@ -67,18 +67,26 @@ permissions: jobs: generate-report: name: Generate Manifest Diff Report - runs-on: ubuntu-24.04 - # Informational job: never block the build/test jobs that needs: setup. + 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: Install python deps + run: pip install -r requirements.txt + - name: Generate manifest diff report env: GITHUB_TOKEN: ${{ github.token }} @@ -93,15 +101,12 @@ jobs: ${{ inputs.workflow_mode && '--workflow-mode' || '' }} \ --output-dir reports - - name: Configure AWS Credentials - if: ${{ github.repository == 'ROCm/TheRock' }} - uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0 - 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 }} \ diff --git a/.github/workflows/multi_arch_ci.yml b/.github/workflows/multi_arch_ci.yml index f191268b69c..d33a07cd15e 100644 --- a/.github/workflows/multi_arch_ci.yml +++ b/.github/workflows/multi_arch_ci.yml @@ -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() diff --git a/.github/workflows/setup_multi_arch.yml b/.github/workflows/setup_multi_arch.yml index 26d8312e6d2..cdbcf31e8fd 100644 --- a/.github/workflows/setup_multi_arch.yml +++ b/.github/workflows/setup_multi_arch.yml @@ -159,37 +159,3 @@ 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 - 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 - permissions: - contents: read - id-token: write - 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 }} From ec0fd6821363fb47f22eccde523828d814b6ade4 Mon Sep 17 00:00:00 2001 From: amd-hsivasun Date: Fri, 15 May 2026 02:12:59 +0000 Subject: [PATCH 6/6] [CI] Add safe.directory step to manifest-diff for container-based git access --- .github/workflows/manifest-diff.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/manifest-diff.yml b/.github/workflows/manifest-diff.yml index 5a5f6c0579b..54942640d3a 100644 --- a/.github/workflows/manifest-diff.yml +++ b/.github/workflows/manifest-diff.yml @@ -84,6 +84,11 @@ jobs: 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