diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 09af414dca6c..57532636eae8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,10 +31,10 @@ on: branches-ignore: # SapMachine 2020-11-04: Ignore sapmachine branch - sapmachine21 - # SapMachine 2020-11-04: Trigger on pull request - pull_request: - branches: - - sapmachine21 + # SapMachine 2026-06-01: do not trigger github actions for docs files + paths-ignore: + - '**/*.md' + - 'doc/**' workflow_dispatch: inputs: platforms: @@ -60,8 +60,6 @@ jobs: prepare: name: 'Prepare the run' - # SapMachine 2022-06-23: On 'pull_request' we only want to run GHA if the PR comes from a remote repo. Otherwise we have the run on 'push' already as a check. - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} runs-on: ubuntu-22.04 outputs: linux-x64: ${{ steps.include.outputs.linux-x64 }} @@ -99,8 +97,7 @@ jobs: function check_platform() { if [[ $GITHUB_EVENT_NAME == workflow_dispatch ]]; then input='${{ github.event.inputs.platforms }}' - # SapMachine 2022-06-24: Also handle 'pull_request' event. - elif [[ $GITHUB_EVENT_NAME == push ]] || [[ $GITHUB_EVENT_NAME == pull_request ]]; then + elif [[ $GITHUB_EVENT_NAME == push ]]; then if [[ '${{ !secrets.JDK_SUBMIT_FILTER || startsWith(github.ref, 'refs/heads/submit/') }}' == 'false' ]]; then # If JDK_SUBMIT_FILTER is set, and this is not a "submit/" branch, don't run anything >&2 echo 'JDK_SUBMIT_FILTER is set and not a "submit/" branch' diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 000000000000..8b8e3b184478 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,243 @@ +# +# Copyright (c) 2026 SAP SE. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +# Fires in the upstream repo context when a fork PR is opened or updated. +# Polls the fork repo's Actions API for the "SapMachine GHA Sanity Checks" +# run on the PR head commit, then mirrors every individual job as a native +# Check Run on the upstream PR — matching the appearance of the fork's own +# checks tab. No submit branches are created; no CI is re-run upstream. +name: 'Pre-submit tests' + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + +permissions: + checks: write + pull-requests: read + +jobs: + mirror: + name: 'Mirror fork CI result' + runs-on: ubuntu-24.04 + # Only act on fork PRs — same-repo PRs get CI directly from main.yml. + if: github.event.pull_request.head.repo.full_name != github.repository + steps: + - name: 'Poll fork CI and mirror per-job results' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + FORK_REPO: ${{ github.event.pull_request.head.repo.full_name }} + UPSTREAM_REPO: ${{ github.repository }} + run: | + # ── Phase 1: Wait for the fork workflow run to appear ─────────────── + # The fork may not have triggered its CI yet immediately after push. + MAX_WAIT=20 + ATTEMPT=0 + RUN_ID="" + + while [[ $ATTEMPT -lt $MAX_WAIT ]]; do + ATTEMPT=$((ATTEMPT + 1)) + echo "Waiting for fork workflow run (attempt ${ATTEMPT}/${MAX_WAIT})..." + + RUN_ID=$(gh api \ + "repos/${FORK_REPO}/actions/runs?head_sha=${HEAD_SHA}&per_page=10" \ + --jq '.workflow_runs[] | select(.name == "SapMachine GHA Sanity Checks") | .id' \ + 2>/dev/null | head -1) + + if [[ -n "$RUN_ID" && "$RUN_ID" != "null" ]]; then + echo "Found fork workflow run: ${RUN_ID}" + break + fi + + sleep 60 + done + + if [[ -z "$RUN_ID" || "$RUN_ID" == "null" ]]; then + echo "No fork workflow run found within ${MAX_WAIT} minutes — giving up." + exit 1 + fi + + # ── Phase 2: Mirror each fork job as an upstream Check Run ────────── + # job name → upstream check run ID + declare -A JOB_CHECK_RUN_IDS + # job name → '1' once the check run has been finalized + declare -A JOB_DONE + + MAX_POLL=50 + POLL=0 + OVERALL_CONCLUSION="" + + while [[ $POLL -lt $MAX_POLL ]]; do + POLL=$((POLL + 1)) + echo "Poll ${POLL}/${MAX_POLL} — syncing fork job statuses..." + + JOBS_JSON=$(gh api \ + "repos/${FORK_REPO}/actions/runs/${RUN_ID}/jobs?per_page=100" \ + 2>/dev/null) + + if [[ -z "$JOBS_JSON" ]]; then + sleep 60 + continue + fi + + JOB_COUNT=$(echo "$JOBS_JSON" | jq '.jobs | length') + + for i in $(seq 0 $((JOB_COUNT - 1))); do + JOB_NAME=$(echo "$JOBS_JSON" | jq -r ".jobs[$i].name") + JOB_STATUS=$(echo "$JOBS_JSON" | jq -r ".jobs[$i].status") + JOB_CONCLUSION=$(echo "$JOBS_JSON" | jq -r ".jobs[$i].conclusion") + JOB_URL=$(echo "$JOBS_JSON" | jq -r ".jobs[$i].html_url") + + # Create a Check Run for this job the first time we see it. + if [[ -z "${JOB_CHECK_RUN_IDS[$JOB_NAME]}" ]]; then + echo " Creating check run: ${JOB_NAME}" + CR_ID=$(gh api \ + -X POST \ + "repos/${UPSTREAM_REPO}/check-runs" \ + -f name="${JOB_NAME}" \ + -f head_sha="${HEAD_SHA}" \ + -f status="in_progress" \ + -f output[title]="Waiting for fork job: ${JOB_NAME}" \ + -f output[summary]="Polling fork repository for job '${JOB_NAME}' on commit ${HEAD_SHA}. [View job](${JOB_URL})" \ + --jq '.id') + JOB_CHECK_RUN_IDS[$JOB_NAME]=$CR_ID + echo " → check run ID: ${CR_ID}" + fi + + # Finalize any job that has completed since the last poll. + if [[ "$JOB_STATUS" == "completed" && -z "${JOB_DONE[$JOB_NAME]}" ]]; then + CR_ID="${JOB_CHECK_RUN_IDS[$JOB_NAME]}" + # Guard against null conclusion (e.g. cancelled mid-queue). + [[ -z "$JOB_CONCLUSION" || "$JOB_CONCLUSION" == "null" ]] && JOB_CONCLUSION="cancelled" + + if [[ "$JOB_CONCLUSION" == "success" ]]; then + TITLE="Passed: ${JOB_NAME}" + SUMMARY="Job **${JOB_NAME}** passed on the fork. [View job](${JOB_URL})" + else + TITLE="${JOB_CONCLUSION^}: ${JOB_NAME}" + SUMMARY="Job **${JOB_NAME}** reported **${JOB_CONCLUSION}** on the fork. [View job](${JOB_URL})" + fi + + gh api \ + -X PATCH \ + "repos/${UPSTREAM_REPO}/check-runs/${CR_ID}" \ + -f status="completed" \ + -f conclusion="${JOB_CONCLUSION}" \ + -f output[title]="${TITLE}" \ + -f output[summary]="${SUMMARY}" \ + -f details_url="${JOB_URL}" + + JOB_DONE[$JOB_NAME]=1 + echo " ✓ Finalized ${JOB_NAME}: ${JOB_CONCLUSION}" + fi + done + + # Check whether the overall run has finished. + RUN_JSON=$(gh api \ + "repos/${FORK_REPO}/actions/runs/${RUN_ID}" \ + --jq '{status: .status, conclusion: .conclusion}' \ + 2>/dev/null) + + RUN_STATUS=$(echo "$RUN_JSON" | jq -r '.status') + OVERALL_CONCLUSION=$(echo "$RUN_JSON" | jq -r '.conclusion') + + if [[ "$RUN_STATUS" == "completed" ]]; then + echo "Fork workflow run completed with conclusion: ${OVERALL_CONCLUSION}" + # Do one final job sync to catch any jobs that finished in this last window. + JOBS_JSON=$(gh api \ + "repos/${FORK_REPO}/actions/runs/${RUN_ID}/jobs?per_page=100" \ + 2>/dev/null) + JOB_COUNT=$(echo "$JOBS_JSON" | jq '.jobs | length') + for i in $(seq 0 $((JOB_COUNT - 1))); do + JOB_NAME=$(echo "$JOBS_JSON" | jq -r ".jobs[$i].name") + JOB_STATUS=$(echo "$JOBS_JSON" | jq -r ".jobs[$i].status") + JOB_CONCLUSION=$(echo "$JOBS_JSON" | jq -r ".jobs[$i].conclusion") + JOB_URL=$(echo "$JOBS_JSON" | jq -r ".jobs[$i].html_url") + if [[ -z "${JOB_CHECK_RUN_IDS[$JOB_NAME]}" ]]; then + CR_ID=$(gh api \ + -X POST \ + "repos/${UPSTREAM_REPO}/check-runs" \ + -f name="${JOB_NAME}" \ + -f head_sha="${HEAD_SHA}" \ + -f status="in_progress" \ + -f output[title]="Waiting for fork job: ${JOB_NAME}" \ + -f output[summary]="Polling fork repository for job '${JOB_NAME}' on commit ${HEAD_SHA}. [View job](${JOB_URL})" \ + --jq '.id') + JOB_CHECK_RUN_IDS[$JOB_NAME]=$CR_ID + fi + if [[ "$JOB_STATUS" == "completed" && -z "${JOB_DONE[$JOB_NAME]}" ]]; then + CR_ID="${JOB_CHECK_RUN_IDS[$JOB_NAME]}" + [[ -z "$JOB_CONCLUSION" || "$JOB_CONCLUSION" == "null" ]] && JOB_CONCLUSION="cancelled" + if [[ "$JOB_CONCLUSION" == "success" ]]; then + TITLE="Passed: ${JOB_NAME}" + SUMMARY="Job **${JOB_NAME}** passed on the fork. [View job](${JOB_URL})" + else + TITLE="${JOB_CONCLUSION^}: ${JOB_NAME}" + SUMMARY="Job **${JOB_NAME}** reported **${JOB_CONCLUSION}** on the fork. [View job](${JOB_URL})" + fi + gh api \ + -X PATCH \ + "repos/${UPSTREAM_REPO}/check-runs/${CR_ID}" \ + -f status="completed" \ + -f conclusion="${JOB_CONCLUSION}" \ + -f output[title]="${TITLE}" \ + -f output[summary]="${SUMMARY}" \ + -f details_url="${JOB_URL}" + JOB_DONE[$JOB_NAME]=1 + echo " ✓ Finalized ${JOB_NAME}: ${JOB_CONCLUSION}" + fi + done + break + fi + + sleep 60 + done + + # ── Phase 3: Handle timeout ───────────────────────────────────────── + if [[ -z "$OVERALL_CONCLUSION" || "$OVERALL_CONCLUSION" == "null" ]]; then + OVERALL_CONCLUSION="timed_out" + echo "Timed out — marking remaining open check runs." + for JOB_NAME in "${!JOB_CHECK_RUN_IDS[@]}"; do + if [[ -z "${JOB_DONE[$JOB_NAME]}" ]]; then + CR_ID="${JOB_CHECK_RUN_IDS[$JOB_NAME]}" + gh api \ + -X PATCH \ + "repos/${UPSTREAM_REPO}/check-runs/${CR_ID}" \ + -f status="completed" \ + -f conclusion="timed_out" \ + -f output[title]="Timed out: ${JOB_NAME}" \ + -f output[summary]="The polling window expired before job '${JOB_NAME}' completed." + fi + done + fi + + # Exit non-zero so the upstream PR shows a red check if CI did not pass. + if [[ "$OVERALL_CONCLUSION" != "success" ]]; then + exit 1 + fi