diff --git a/.github/workflows/mu-pr-validation-pending.yml b/.github/workflows/mu-pr-validation-pending.yml new file mode 100644 index 0000000000..d5a7c389f0 --- /dev/null +++ b/.github/workflows/mu-pr-validation-pending.yml @@ -0,0 +1,100 @@ +# Posts an immediate "pending" notification on a pull request +# indicating that QEMU validation is waiting for CI to complete. +# +# Note: This is triggered by pull_request_target so the comment +# appears immediately when a PR is pushed, rather than waiting +# for CI to complete. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## +name: Mu QEMU PR Validation Pending + +on: + pull_request_target: + types: + - opened + - reopened + - synchronize + branches: + - release/202511 + +permissions: + contents: read + +concurrency: + group: mu-qemu-pending-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + notify-pending: + name: Post Pending Notification + runs-on: ubuntu-latest + steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ vars.MU_ACCESS_APP_ID }} + private-key: ${{ secrets.MU_ACCESS_APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + + - name: Update PR Comment to Pending + uses: actions/github-script@v8 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const issue_number = context.issue.number; + const hiddenMarker = ''; + const marker = '*This comment was automatically generated by the Mu QEMU PR Validation workflow.*'; + + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + }); + + const existingComment = comments.data.find((c) => + c.body.includes(hiddenMarker) + ); + + const body = [ + hiddenMarker, + '## :hourglass: QEMU Validation Pending', + '', + 'QEMU validation is pending on successful CI completion.', + '', + '> **Note:** Any previous results are available in this comment\'s edit history.', + '', + marker, + ].join('\n'); + + if (existingComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + body, + }); + } + + - name: Set Pending Commit Status + uses: actions/github-script@v8 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + await github.rest.repos.createCommitStatus({ + owner: context.repo.owner, + repo: context.repo.repo, + sha: '${{ github.event.pull_request.head.sha }}', + state: 'pending', + description: 'Waiting for CI to complete', + context: 'Mu QEMU PR Validation', + }); diff --git a/.github/workflows/mu-pr-validation-post.yml b/.github/workflows/mu-pr-validation-post.yml new file mode 100644 index 0000000000..7d563b09de --- /dev/null +++ b/.github/workflows/mu-pr-validation-post.yml @@ -0,0 +1,30 @@ +# Posts final results after the Mu QEMU PR Validation +# workflow completes. Downloads metadata and updates +# the PR comment and commit status with the final +# outcome. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## +name: Mu QEMU PR Validation Post + +on: + workflow_run: + workflows: ["Mu QEMU PR Validation"] + types: ["completed"] + +permissions: + contents: read + actions: read + +jobs: + post-results: + name: Post Validation Results + uses: microsoft/mu_devops/.github/workflows/MuPrValidationPost.yml@add_mu_pr_val_workflow_e2e_val + with: + app-id: ${{ vars.MU_ACCESS_APP_ID }} + conclusion: ${{ github.event.workflow_run.conclusion }} + head-sha: ${{ github.event.workflow_run.head_sha }} + triggering-run-id: ${{ github.event.workflow_run.id }} + secrets: + private-key: ${{ secrets.MU_ACCESS_APP_PRIVATE_KEY }} diff --git a/.github/workflows/mu-pr-validation.yml b/.github/workflows/mu-pr-validation.yml new file mode 100644 index 0000000000..9cff89b172 --- /dev/null +++ b/.github/workflows/mu-pr-validation.yml @@ -0,0 +1,132 @@ +# Runs QEMU-based platform validation on pull requests. +# +# Triggered by the completion of the "CLANGPDB Package CI" workflow. +# +# Gathers PR metadata and calls the reusable MuPrValidation workflow in +# mu_devops to build and boot mu_basecore changes on QEMU Q35 and SBSA. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## +name: Mu QEMU PR Validation + +on: + workflow_run: + workflows: ["CLANGPDB Package CI"] + types: ["completed"] + +concurrency: + group: mu-qemu-pr-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true + +permissions: + actions: read + contents: read + pull-requests: read + +jobs: + prepare: + runs-on: ubuntu-latest + name: Gather PR Metadata + if: | + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'pull_request' + outputs: + pr_number: ${{ steps.pr-info.outputs.pr_number }} + skip: ${{ steps.pr-info.outputs.skip }} + skip_reason: ${{ steps.pr-info.outputs.skip_reason }} + steps: + - name: Get PR Information + id: pr-info + shell: bash + env: + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + HEAD_REF: ${{ github.event.workflow_run.head_branch }} + HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }} + run: | + owner="${HEAD_REPO%%/*}" + pr_number=$(gh api "repos/${GITHUB_REPOSITORY}/pulls" \ + --method GET -f state=open -f head="${owner}:${HEAD_REF}" \ + --jq '.[0].number') + + if [[ -z "$pr_number" || "$pr_number" == "null" ]]; then + echo "::notice::No open PR found for head $owner:$HEAD_REF." + echo "skip=true" >> "$GITHUB_OUTPUT" + + closed_pr_json=$(gh api "repos/${GITHUB_REPOSITORY}/pulls" \ + --method GET -f state=closed -f head="${owner}:${HEAD_REF}" \ + -f sort=updated -f direction=desc -f per_page=1 \ + --jq '.[0] | select(. != null) | {number, merged: (.merged_at != null)}') + + if [[ -n "$closed_pr_json" ]]; then + closed_pr_number=$(echo "$closed_pr_json" | jq -r '.number') + closed_pr_merged=$(echo "$closed_pr_json" | jq -r '.merged') + + echo "pr_number=$closed_pr_number" >> "$GITHUB_OUTPUT" + + if [[ "$closed_pr_merged" == "true" ]]; then + echo "::notice::PR #$closed_pr_number was merged before validation started." + echo "skip_reason=merged" >> "$GITHUB_OUTPUT" + else + echo "::notice::PR #$closed_pr_number was closed before validation started." + echo "skip_reason=closed" >> "$GITHUB_OUTPUT" + fi + else + echo "::notice::No PR (open or closed) found for head $owner:$HEAD_REF." + echo "skip_reason=branch_unavailable" >> "$GITHUB_OUTPUT" + fi + + exit 0 + fi + + echo "skip=false" >> "$GITHUB_OUTPUT" + + echo "::group::PR Information" + echo " - PR Number: $pr_number" + echo " - Head SHA: $HEAD_SHA" + echo " - Head Ref: $HEAD_REF" + echo " - Source: $HEAD_REPO" + echo "::endgroup::" + + echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT" + + upload-skip-metadata: + name: Upload Skip Metadata + runs-on: ubuntu-latest + needs: [prepare] + if: needs.prepare.outputs.skip == 'true' && needs.prepare.outputs.pr_number != '' + steps: + - name: Create Skip Metadata + shell: bash + run: | + mkdir -p metadata + cat > metadata/pr-metadata.json <