diff --git a/.github/workflows/manifest-diff.yml b/.github/workflows/manifest-diff.yml index 515f86cf199..66657e696aa 100644 --- a/.github/workflows/manifest-diff.yml +++ b/.github/workflows/manifest-diff.yml @@ -4,37 +4,72 @@ 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. Used when neither pr_base_ref nor find_last_run is set (start resolution precedence: pr_base_ref > find_last_run > start_ref).' required: false type: string end_ref: - description: 'End commit SHA or workflow run ID' + description: 'End commit SHA (or, with workflow_mode=true, a workflow run ID).' required: true type: string workflow_mode: - description: 'Treat inputs as workflow run IDs instead of commit SHAs' + description: 'Interpret start_ref and end_ref as workflow run IDs instead of commit SHAs (pr_base_ref and find_last_run are unaffected).' 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 filename (e.g. multi_arch_ci.yml). When set, start_ref is resolved as the head SHA of that workflow''s most recent run on `branch` that concluded with success or failure (cancelled / skipped / in-progress runs are ignored).' required: false type: string + pr_base_ref: + description: 'PR base branch name (e.g. main). When set, start_ref is resolved as the merge-base between end_ref and the named branch via the GitHub Compare API — rebase-safe, works on rewritten PR histories.' + required: false + type: string + branch: + description: 'Branch to scope find_last_run lookups against (default: main). Only consulted when find_last_run is set; ignored otherwise.' + required: false + type: string + default: 'main' + repository: + description: 'Repository to check out (default: the repository hosting this workflow run).' + required: false + type: string + default: '' + ref: + description: 'Git ref to check out (default: the ref hosting this workflow run).' + required: false + type: string + default: '' workflow_call: inputs: start_ref: required: false type: string end_ref: - required: true + required: false type: string + default: '' workflow_mode: required: false type: boolean default: false - find_last_successful: + find_last_run: + required: false + type: string + pr_base_ref: + required: false + type: string + branch: required: false type: string + default: 'main' + repository: + required: false + type: string + default: '' + ref: + required: false + type: string + default: '' permissions: contents: read @@ -42,36 +77,61 @@ permissions: jobs: generate-report: name: Generate Manifest Diff Report - runs-on: ubuntu-24.04 + runs-on: ${{ github.repository_owner == 'ROCm' && 'azure-linux-scale-rocm' || 'ubuntu-24.04' }} + continue-on-error: true permissions: id-token: write contents: read + container: + image: ubuntu:24.04 + options: -v /runner/config:/home/awsconfig/ + env: + AWS_SHARED_CREDENTIALS_FILE: /home/awsconfig/credentials.ini steps: + - name: Install bootstrap packages + run: | + apt-get update + apt-get install -y git ca-certificates curl unzip + - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + repository: ${{ inputs.repository || github.repository }} + ref: ${{ inputs.ref || '' }} fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.12' + + - name: Install AWS CLI + run: | + ./dockerfiles/install_awscli.sh + + - name: Adjust git config + run: | + git config --global --add safe.directory $PWD + - 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 || '' }}" \ + --end "${{ inputs.end_ref || (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha }}" \ + --start "${{ inputs.start_ref || (github.event_name == 'push' && github.event.before) || '' }}" \ + --find-last-run "${{ inputs.find_last_run }}" \ + --pr-base-ref "${{ inputs.pr_base_ref || (github.event_name == 'pull_request' && github.event.pull_request.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 }} \ diff --git a/.github/workflows/multi_arch_ci.yml b/.github/workflows/multi_arch_ci.yml index f191268b69c..5a43ac94cff 100644 --- a/.github/workflows/multi_arch_ci.yml +++ b/.github/workflows/multi_arch_ci.yml @@ -115,6 +115,13 @@ jobs: contents: read id-token: write + manifest_diff: + name: Manifest Diff + uses: ./.github/workflows/manifest-diff.yml + permissions: + contents: read + id-token: write + ci_summary: name: CI Summary if: always() diff --git a/build_tools/generate_manifest_diff_report.py b/build_tools/generate_manifest_diff_report.py index 36fa2102567..6e852d85518 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 filename (e.g., 'multi_arch_ci.yml'). When set, + --start is resolved as the head SHA of that workflow's + most recent run on --branch that concluded with + 'success' or 'failure' (cancelled / skipped / + in-progress runs are ignored). + --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'). Only consulted when --find-last-run + is set. + --output-dir Directory to write the HTML report into. If unset, + falls back to 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 multi_arch_ci.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,21 @@ 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 filename (e.g. 'multi_arch_ci.yml'). When set, --start " + "is resolved as the head SHA of that workflow's most recent run " + "on --branch that concluded with 'success' or 'failure' " + "(cancelled / skipped / in-progress runs are ignored)." + ), + ) + 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 +223,10 @@ 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 scope --find-last-run lookups against (default: main). " + "Only consulted when --find-last-run is set; ignored otherwise." + ), ) parser.add_argument( "--output-dir", @@ -214,42 +248,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_repo_full = f"{ROCM_ORG}/{THEROCK_REPO}" + therock = f"{ROCM_ORG}/{THEROCK_REPO}" - # 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 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, 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: + # Hardcoded to terminal-status: any run that ran to completion (success + # or failure) is acceptable — devs comparing to "the last run that + # actually ran" don't care whether it was green or red, only that it + # wasn't cancelled / skipped / in-progress. + accepted = {"success", "failure"} + 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 +1483,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 941c4520f55..3e2ac7ad667 100644 --- a/build_tools/github_actions/configure_ci_path_filters.py +++ b/build_tools/github_actions/configure_ci_path_filters.py @@ -193,6 +193,7 @@ def is_ci_run_required(paths: Optional[Iterable[str]]) -> bool: "multi_arch_build_windows_artifacts.yml", "multi_arch_build_wsl_rocdxg_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..4ea47198626 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,89 @@ 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_uses_terminal_statuses(self): + """--find-last-run hardcodes accepted statuses to {success, failure}.""" + args = parse_args(["--end", "def456", "--find-last-run", "ci.yml"]) + + 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_pr_base_ref_takes_precedence_over_find_last_run(self): + """When both --pr-base-ref and --find-last-run are set, Compare wins. + + This pins the precedence ladder documented in resolve_commits(): + pr_base_ref > find_last_run > workflow_mode/start_ref. If a future + refactor reorders the branches, this test catches it. + """ + args = parse_args( + [ + "--end", + "deadbeef", + "--pr-base-ref", + "main", + "--find-last-run", + "ci.yml", + ] + ) + + with mock.patch( + "generate_manifest_diff_report.gha_send_request" + ) as mock_compare, mock.patch( + "generate_manifest_diff_report.gha_query_last_workflow_run" + ) as mock_last_run: + mock_compare.return_value = {"merge_base_commit": {"sha": "merge_base"}} + start_sha, end_sha = resolve_commits(args) + + self.assertEqual(start_sha, "merge_base") + self.assertEqual(end_sha, "deadbeef") + mock_compare.assert_called_once() + mock_last_run.assert_not_called() + def test_direct_commit_shas_no_api_calls(self): """Direct commit SHAs don't require API calls.""" args = parse_args(["--start", "abc123", "--end", "def456"]) diff --git a/docs/development/ci_overview.md b/docs/development/ci_overview.md index 5c1a547d8c9..06fa9a8c241 100644 --- a/docs/development/ci_overview.md +++ b/docs/development/ci_overview.md @@ -167,6 +167,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..30f058ecf43 --- /dev/null +++ b/docs/development/manifest_diff.md @@ -0,0 +1,76 @@ +# 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 for `pull_request` and `push` events, and can also be invoked on-demand via direct `workflow_dispatch` of [`manifest-diff.yml`](../../.github/workflows/manifest-diff.yml) 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 ([`multi_arch_ci.yml`](../../.github/workflows/multi_arch_ci.yml)) hosts the `manifest_diff` job as a top-level sibling. The job has no `needs:`, runs in parallel with `linux_build_and_test` / `windows_build_and_test`, and calls `manifest-diff.yml` with no `with:` block — the reusable workflow derives the **start** and **end** refs from the caller's `github.event` itself, choosing differently depending on what triggered the run. + +```mermaid +flowchart TD + A[multi_arch_ci.yml fires] --> B{event_name} + B -- pull_request --> P[pr_base_ref = pull_request.base.ref
end_ref = pull_request.head.sha] + B -- push --> U[start_ref = github.event.before
end_ref = github.sha] + P --> G[generate_manifest_diff_report.py] + U --> 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` — the tip of the PR's source branch. | +| `push` | `github.event.before` — the branch tip before the push. | `github.sha` — the new tip the branch was just moved to. | + +To run the report manually with explicit refs, dispatch [`manifest-diff.yml`](../../.github/workflows/manifest-diff.yml) directly — see [Running it manually](#running-it-manually). `multi_arch_ci.yml`'s own `workflow_dispatch` surface intentionally does not expose manifest-diff inputs, so dispatching `multi_arch_ci.yml` is not a supported way to produce a report; the auto-fired `manifest_diff` job will fail (yellow via `continue-on-error`, never red). + +The `manifest_diff` job in `multi_arch_ci.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 is reported as a non-blocking warning on the run summary instead of 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 (base-repo runs): + +``` +s3://therock-ci-artifacts/{run_id}-linux/logs/manifest-diff/index.html +``` + +On downstream forks the bucket and prefix shift to `therock-ci-artifacts-external` and `-/{run_id}-linux/...` per `WorkflowOutputRoot`. The upload step is non-fatal if the runner has no AWS credentials, so on a fork without the creds mount the report simply isn't uploaded. 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). `end_ref` is required; pick exactly one of `pr_base_ref`, `find_last_run`, or `start_ref` to resolve the start (precedence: `pr_base_ref` > `find_last_run` > `start_ref`). See the `description:` fields on the inputs in [`manifest-diff.yml`](../../.github/workflows/manifest-diff.yml) for the authoritative reference. + +### Local CLI + +See [`generate_manifest_diff_report.py`](../../build_tools/generate_manifest_diff_report.py) and run with `--help` for usage. Set `GITHUB_TOKEN` (any token with `public_repo` read scope) before running to avoid GitHub's 60 req/hr unauthenticated rate limit. + +## Out of scope + +External orchestrator workflows in `rocm-libraries` / `rocm-systems` that drive TheRock's reusable workflows via `setup_multi_arch.yml`'s `external_repo` input, and the rockrel release-driver flow (via `multi_arch_release.yml`), currently produce no manifest-diff; extending the report to those callers is tracked in #5219. + +## Code map + +| File | Role | +| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| [`.github/workflows/manifest-diff.yml`](../../.github/workflows/manifest-diff.yml) | Reusable workflow: derives refs from caller event, runs script, uploads. | +| [`.github/workflows/multi_arch_ci.yml`](../../.github/workflows/multi_arch_ci.yml) | Hosts the `manifest_diff` sibling job that calls `manifest-diff.yml`. | +| [`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.