From 219d11877bb9008ac107d7d11e01bccef5ecbb8c Mon Sep 17 00:00:00 2001 From: MalTeeez <41554385+MalTeeez@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:36:18 +0200 Subject: [PATCH] Full Pack tests against latest daily w. dependency-aware actions (#66) Co-authored-by: UltraProdigy <187078471+UltraProdigy@users.noreply.github.com> --- .github/workflows/build-and-test.yml | 36 ++++ .github/workflows/gate-by-required.yml | 22 +++ .../latest-daily-integration-test.yml | 172 ++++++++++++++++++ .../workflows/trigger-rebuild-with-deps.yml | 104 +++++++++++ scripts/server.sh | 119 ++++++++++++ scripts/upload-deps-to-localmaven.sh | 96 ++++++++++ scripts/verify_server.sh | 87 +++++++++ templates/build-and-test.yml | 1 + templates/gate-by-required.yml | 10 + templates/latest-daily-integration-test.yml | 19 ++ templates/trigger-rebuild-with-deps.yml | 23 +++ 11 files changed, 689 insertions(+) create mode 100644 .github/workflows/gate-by-required.yml create mode 100644 .github/workflows/latest-daily-integration-test.yml create mode 100644 .github/workflows/trigger-rebuild-with-deps.yml create mode 100644 scripts/server.sh create mode 100644 scripts/upload-deps-to-localmaven.sh create mode 100644 scripts/verify_server.sh create mode 100644 templates/gate-by-required.yml create mode 100644 templates/latest-daily-integration-test.yml create mode 100644 templates/trigger-rebuild-with-deps.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b74accd..6d5b5df 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -46,6 +46,11 @@ on: required: false default: false type: boolean + external-deps-inclusion: + description: 'Enable usage of external dependency artifacts that might have been provided by trigger-rebuild-with-deps (if installed)' + required: false + default: false + type: boolean concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -72,6 +77,37 @@ jobs: path: .gtnh-workflows fetch-depth: 0 + - name: Find external-deps artifact for this run + id: find-artifact + if: ${{ inputs.external-deps-inclusion }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # The artifact is named after this run's ID because try-rebuild-with-deps runs in the + # master context; filtering by head_sha would never match the PR's SHA (I tried). + # Reruns keep the same run_id, so this name is stable across attempts. + RUN_ID=$(gh api \ + "repos/${{ github.repository }}/actions/artifacts?name=external-deps-${{ github.run_id }}" \ + --jq ".artifacts[0].workflow_run.id // empty") + echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT + + - name: Download external deps + if: ${{ inputs.external-deps-inclusion && steps.find-artifact.outputs.run_id != '' }} + uses: actions/download-artifact@v8 + with: + name: external-deps-${{ github.run_id }} + path: ./.packscripts/ + run-id: ${{ steps.find-artifact.outputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install external deps to local maven + if: ${{ inputs.external-deps-inclusion && hashFiles('./.packscripts/**') != '' }} + run: | + echo "Aquired build artifacts from rebuild job: " && ls -lahR ./.packscripts/ + bash .gtnh-workflows/scripts/upload-deps-to-localmaven.sh ./.packscripts/required_prs.json + echo "Overriding dependencies of project with versions from local maven:" && cat ~/.gradle/init.gradle + echo "Contents of local maven:" && ls -lahR ~/.m2/repository/com/github + - name: Determine JDK versions id: list-jdk-versions shell: bash diff --git a/.github/workflows/gate-by-required.yml b/.github/workflows/gate-by-required.yml new file mode 100644 index 0000000..9ff6ae2 --- /dev/null +++ b/.github/workflows/gate-by-required.yml @@ -0,0 +1,22 @@ +name: Check state of other required PRs + +on: + workflow_call: + +jobs: + check-required-prs: + runs-on: ubuntu-latest + steps: + - name: Check required PRs + uses: MalTeeez/packscripts@a89fe398d047ac9e2e165465a51096be4f9853cb + env: + NO_COLOR: 1 + with: + command: >- + pr gate ${{ github.event.pull_request.html_url }} + --build_job "Build and test" + --other_allowed_owner "GTNewHorizons" + --allow_all_merged + github_token: ${{ secrets.GITHUB_TOKEN }} + config: https://raw.githubusercontent.com/MalTeeez/packscripts-auto-builds/refs/heads/gtnh-daily/packscripts.json + annotated_file: https://raw.githubusercontent.com/MalTeeez/packscripts-auto-builds/refs/heads/gtnh-daily/annotated_mods.json \ No newline at end of file diff --git a/.github/workflows/latest-daily-integration-test.yml b/.github/workflows/latest-daily-integration-test.yml new file mode 100644 index 0000000..6b3315c --- /dev/null +++ b/.github/workflows/latest-daily-integration-test.yml @@ -0,0 +1,172 @@ +name: Test integration in latest daily + +# Note: this reusable workflow must called from a context that is triggered by a "Build and test" run (which then propagates the event) +# That context requires workflow_run, which runs in the context of the default branch, +# therefore we use the statuses api to place it on the source commit manually here +on: + workflow_call: + +permissions: + actions: read + contents: read + statuses: write + +# Key on the triggering build's head_sha so each PR head is isolated, and a +# repaired rerun (same run_id, same head_sha, new attempt) cancels the now-stale +# in-flight run for that head instead of racing it to post a status +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_sha }} + cancel-in-progress: true + +env: + STATUS_CONTEXT: "Test in latest daily" + +jobs: + get-pr-url: + runs-on: ubuntu-latest + # Run on ANY pull_request build completion. We branch on conclusion below + if: github.event.workflow_run.event == 'pull_request' + outputs: + pr_url: ${{ steps.lookup.outputs.url }} + conclusion: ${{ github.event.workflow_run.conclusion }} + steps: + - name: Look up PR url + id: lookup + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_URL=$(gh api \ + "repos/${{ github.repository }}/commits/${{ github.event.workflow_run.head_sha }}/pulls" \ + --jq "[.[] | select(.head.sha == \"${{ github.event.workflow_run.head_sha }}\" and .state == \"open\")] | first | .html_url // empty") + + if [ -z "$PR_URL" ]; then + echo "Failed to find a PR url" + exit 1 + fi + echo "url=$PR_URL" >> "$GITHUB_OUTPUT" + + # Pending only on the path where the real test is about to run + - name: Mark status pending on PR commit + if: github.event.workflow_run.conclusion == 'success' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api -X POST \ + "repos/${{ github.repository }}/statuses/${{ github.event.workflow_run.head_sha }}" \ + -f state="pending" \ + -f context="$STATUS_CONTEXT" \ + -f description="Running integration test in latest daily…" \ + -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + full-pack-test: + permissions: + actions: read + contents: read + needs: get-pr-url + # Real test work runs ONLY on a successful build + if: needs.get-pr-url.outputs.conclusion == 'success' + name: Test in latest daily + runs-on: ubuntu-latest + container: + image: ghcr.io/malteeez/gtnh-daily:latest + env: + PR_URL: ${{ needs.get-pr-url.outputs.pr_url }} + SERVER_DIR: /home/gtnh + RUN_DIR: /home/gtnh + SERVER_EXIT_FLAG: /home/gtnh/server.exit + steps: + - name: Checkout workflows repo + uses: actions/checkout@v7 + with: + repository: GTNewHorizons/GTNH-Actions-Workflows + ref: ${{ job.workflow_sha }} + sparse-checkout: + scripts/ + path: .gtnh-workflows + fetch-depth: 0 + + - name: Install libc so we can run our binary on alpine + run: apk add --no-cache libstdc++ libgcc + + - name: Check out PR build artifact + uses: MalTeeez/packscripts@a89fe398d047ac9e2e165465a51096be4f9853cb + env: + NO_COLOR: 1 + with: + command: >- + pr apply $PR_URL + --build_job "Build and test" + --artifact_name "build-libs" + --other_allowed_owner "GTNewHorizons" + --pack_variant "server" + github_token: ${{ secrets.GITHUB_TOKEN }} + working_directory: /home/gtnh + musl: true + + - name: Run server + timeout-minutes: 30 + run: bash .gtnh-workflows/scripts/server.sh + + - name: Verify server + if: success() || failure() + run: bash .gtnh-workflows/scripts/verify_server.sh + + report: + needs: [get-pr-url, full-pack-test] + # Post a terminal status for every pull_request build, independent of get-pr-url's + # result, so a superseding run always clears a stale pending on the source commit + if: always() && github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + steps: + # Backstop for the cancel-in-progress race on a repaired rerun. Attempt 1 and + # attempt 2 of the triggering build share one run_id but differ in run_attempt, + # and both target the SAME commit's status slot. If this run is processing an + # earlier attempt than the build's current one, it's stale — bail so a late + # attempt-1 result can't clobber the attempt-2 result on the context. + - name: Bail if a newer attempt of the triggering build exists + id: stalecheck + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TRIGGER_RUN_ID="${{ github.event.workflow_run.id }}" + MY_ATTEMPT="${{ github.event.workflow_run.run_attempt }}" + LATEST_ATTEMPT=$(gh api \ + "repos/${{ github.repository }}/actions/runs/${TRIGGER_RUN_ID}" \ + --jq '.run_attempt' 2>/dev/null || true) + echo "::notice::trigger run ${TRIGGER_RUN_ID} — mine=${MY_ATTEMPT} latest=${LATEST_ATTEMPT}" + if [ -z "$LATEST_ATTEMPT" ]; then + # Couldn't read the triggering run — fail open, let the post proceed. + echo "stale=false" >> "$GITHUB_OUTPUT" + elif [ "$MY_ATTEMPT" -lt "$LATEST_ATTEMPT" ]; then + echo "::notice::Attempt ${MY_ATTEMPT} superseded by attempt ${LATEST_ATTEMPT}; not posting status." + echo "stale=true" >> "$GITHUB_OUTPUT" + else + echo "stale=false" >> "$GITHUB_OUTPUT" + fi + + - name: Report final status to PR commit + if: steps.stalecheck.outputs.stale == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + CONCL="${{ github.event.workflow_run.conclusion }}" + TEST_RESULT="${{ needs.full-pack-test.result }}" + if [ "$CONCL" != "success" ]; then + STATE="failure" + DESC="Build failed; integration test not run" + elif [ "$TEST_RESULT" = "success" ]; then + STATE="success" + DESC="Integration test complete" + elif [ "$TEST_RESULT" = "skipped" ]; then + STATE="error" + DESC="Could not resolve PR; integration test not run" + else + STATE="failure" + DESC="Integration test failed" + fi + gh api -X POST \ + "repos/${{ github.repository }}/statuses/${{ github.event.workflow_run.head_sha }}" \ + -f state="$STATE" \ + -f context="$STATUS_CONTEXT" \ + -f description="$DESC" \ + -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ No newline at end of file diff --git a/.github/workflows/trigger-rebuild-with-deps.yml b/.github/workflows/trigger-rebuild-with-deps.yml new file mode 100644 index 0000000..c77b24b --- /dev/null +++ b/.github/workflows/trigger-rebuild-with-deps.yml @@ -0,0 +1,104 @@ +name: Trigger a rebuild with externally required PRs + +# Note: this reusable workflow must called from a context that is triggered by a "Build and test" run (which then propagates the event) +# That context requires workflow_run, which runs in the context of the default branch, +# therefore we use the statuses api to place it on the source commit manually here +on: + workflow_call: + +permissions: + actions: write + contents: read + statuses: write + +jobs: + trigger-rebuild-with-deps: + name: Apply required PRs from external repos + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'failure' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.run_attempt == 1 + steps: + - name: Look up PR url + id: lookup + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_URL=$(gh api \ + "repos/${{ github.repository }}/commits/${{ github.event.workflow_run.head_sha }}/pulls" \ + --jq "[.[] | select(.head.sha == \"${{ github.event.workflow_run.head_sha }}\" and .state == \"open\")] | first | .html_url // empty") + + if [ -z "$PR_URL" ]; then + echo "Failed to find a PR url" + exit 1 + fi + echo "url=$PR_URL" >> "$GITHUB_OUTPUT" + + - name: Collect required PRs + uses: MalTeeez/packscripts@a89fe398d047ac9e2e165465a51096be4f9853cb + env: + NO_COLOR: 1 + with: + command: >- + pr deps ${{ steps.lookup.outputs.url }} + --target_dir ./.packscripts/ + --jar_suffix -dev.jar + --artifact_name -dev.jar + --artifact_name build-libs + --build_job "Build and test" + --other_allowed_owner "GTNewHorizons" + github_token: ${{ secrets.GITHUB_TOKEN }} + config: https://raw.githubusercontent.com/MalTeeez/packscripts-auto-builds/refs/heads/gtnh-daily/packscripts.json + annotated_file: https://raw.githubusercontent.com/MalTeeez/packscripts-auto-builds/refs/heads/gtnh-daily/annotated_mods.json + + - name: Check if deps were collected + id: check-deps + run: | + if [ -n "$(ls -A ./.packscripts/ 2>/dev/null)" ]; then + echo "found=true" >> "$GITHUB_OUTPUT" + else + echo "found=false" >> "$GITHUB_OUTPUT" + fi + + - name: Mark status pending on PR commit + if: steps.check-deps.outputs.found == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api -X POST \ + repos/${{ github.repository }}/statuses/${{ github.event.workflow_run.head_sha }} \ + -f state="pending" \ + -f context="Trigger rebuild with deps" \ + -f description="Applying external PR deps and rebuilding…" \ + -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + - name: Upload external deps artifact + if: steps.check-deps.outputs.found == 'true' + uses: actions/upload-artifact@v7 + with: + # Named after the triggering run ID so the rerun can find it by its own run_id. + # workflow_run always runs in the default branch context, so artifacts would otherwise + # be filed under master's head_sha instead of the PR's, making them invisible to the rerun. + name: external-deps-${{ github.event.workflow_run.id }} + path: ./.packscripts/ + include-hidden-files: true + + - name: Rerun failed build + if: steps.check-deps.outputs.found == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api -X POST \ + repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/rerun-failed-jobs + + - name: Report final status to PR commit + if: always() && steps.check-deps.outputs.found == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api -X POST \ + repos/${{ github.repository }}/statuses/${{ github.event.workflow_run.head_sha }} \ + -f state="${{ job.status }}" \ + -f context="Trigger rebuild with deps" \ + -f description="External PR deps applied; rebuild triggered" \ + -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ No newline at end of file diff --git a/scripts/server.sh b/scripts/server.sh new file mode 100644 index 0000000..2fa244a --- /dev/null +++ b/scripts/server.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# Server side of the integration run: starts the server, lets it settle, then +# stops it cleanly and records the exit code for later verification. +# +# This script does not decide whether a run was good. That is verified later by +# verify_server.sh, which reads the outputs produced here (server.log and the +# exit-code flag). +# +# Base logic moved here from https://github.com/MalTeeez/packscripts-auto-builds/blob/gtnh-daily/packaging/scripts/entrypoint.sh + +set -euo pipefail + +SERVER_DIR="${SERVER_DIR:-/home/gtnh}" +RUN_DIR="${RUN_DIR:-$SERVER_DIR}" + +SERVER_LOG="$RUN_DIR/server.log" +SERVER_EXIT_FLAG="${SERVER_EXIT_FLAG:-$RUN_DIR/server.exit}" +SERVER_READY_FLAG="${SERVER_READY_FLAG:-$RUN_DIR/server.ready}" + +RCON_HOST="${RCON_HOST:-localhost}" +RCON_PORT="${RCON_PORT:-25575}" +RCON_PASSWORD="${RCON_PASSWORD:-whoahaplaintextpassword}" + +SERVER_JAVA_ARGS="${SERVER_JAVA_ARGS:--Xms1G -Xmx2G -Dfml.readTimeout=5 @java9args.txt -jar lwjgl3ify-forgePatches.jar nogui}" + +START_TIMEOUT="${SERVER_START_TIMEOUT:-240}" +SETTLE_DURATION="${SERVER_SETTLE_DURATION:-30}" +STOP_TIMEOUT="${SERVER_STOP_TIMEOUT:-20}" + +SERVER_PID="" +TAIL_PID="" + +cleanup() { + [ -n "$TAIL_PID" ] && kill "$TAIL_PID" 2>/dev/null + # If we still hold the JVM (e.g. an early failure), don't leak it into the runner. + [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null && kill "$SERVER_PID" 2>/dev/null + return 0 +} + +stop_and_reap() { + ( sleep "$STOP_TIMEOUT"; kill -9 "$SERVER_PID" 2>/dev/null ) & + local killer=$! + local code=0 + wait "$SERVER_PID" || code=$? + kill "$killer" 2>/dev/null + SERVER_PID="" + echo "$code" > "$SERVER_EXIT_FLAG" + echo "server exited with code $code" +} + +trap cleanup EXIT INT TERM + +run() { + # SERVER_JAVA_ARGS can arrive as a multi-line string. Split it on whitespace + # into a proper argv array so the launch below stays happy. + local java_args=() + local arg + for arg in $SERVER_JAVA_ARGS; do + java_args+=("$arg") + done + + mkdir -p "$RUN_DIR" + rm -f "$SERVER_EXIT_FLAG" "$SERVER_READY_FLAG" + + cd "$SERVER_DIR" + sed -i 's|eula=false|eula=true|g' eula.txt + sed -i "s|enable-rcon=false|enable-rcon=true\nrcon.password=${RCON_PASSWORD}\nrcon.port=${RCON_PORT}|" server.properties + sed -i 's|online-mode=true|online-mode=false|' server.properties + + # Backgrounded and logged to a file so we can watch startup while the JVM runs. + java "${java_args[@]}" > "$SERVER_LOG" 2>&1 & + SERVER_PID=$! + + tail -n +1 -F "$SERVER_LOG" 2>/dev/null & + TAIL_PID=$! + + local waited=0 + while [ "$waited" -lt "$START_TIMEOUT" ]; do + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + echo "server exited unexpectedly during startup" + return + fi + if grep -q 'Done.*For help, type' "$SERVER_LOG" 2>/dev/null; then + echo "server started after ${waited}s" + : > "$SERVER_READY_FLAG" + break + fi + sleep 5 + waited=$((waited + 5)) + done + + if [ ! -e "$SERVER_READY_FLAG" ]; then + echo "server did not become ready within ${START_TIMEOUT}s" + return + fi + + echo "waiting ${SETTLE_DURATION}s for server to settle..." + sleep "$SETTLE_DURATION" + + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + echo "server exited during settling" + return + fi + + echo "stopping server via rcon" + rcon-cli --host "$RCON_HOST" --port "$RCON_PORT" --password "$RCON_PASSWORD" stop + + stop_and_reap +} + +# Run the lifecycle without aborting on a server-side failure - capturing the +# exit code is the whole point, so verify_server.sh can judge the run. +run || true + +# run bailed early with the JVM still up so stop it too +if [ -n "$SERVER_PID" ]; then + kill "$SERVER_PID" 2>/dev/null || true + stop_and_reap +fi \ No newline at end of file diff --git a/scripts/upload-deps-to-localmaven.sh b/scripts/upload-deps-to-localmaven.sh new file mode 100644 index 0000000..7e2e83c --- /dev/null +++ b/scripts/upload-deps-to-localmaven.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -euo pipefail + +JSON_FILE=$1 +GRADLE_DIR=~/.gradle +INIT_GRADLE=$GRADLE_DIR/init.gradle + +# Exit early if there are no dependencies +DEP_COUNT=$(jq '.dependencies | length' "$JSON_FILE") +if [ "$DEP_COUNT" -eq 0 ]; then + echo "No dependencies found, failure was probably not due to a required dependency. Failing to propagate initial failure." + exit 1 +fi + +# Write init.gradle header to user gradle dir +# (first as a temp file since it will parsed w. just this) +mkdir -p $GRADLE_DIR +cat > $INIT_GRADLE.tmp << 'EOF' +allprojects { + repositories { + mavenLocal() + } + configurations.all { + resolutionStrategy.eachDependency { details -> +EOF + +# Process each dependency +jq -c '.dependencies[]' $JSON_FILE | while read -r dep; do + JAR_PATH=$(echo $dep | jq -r '.jar_path') + REPO_URL=$(echo $dep | jq -r '.repo_url') + COMMIT_SHA=$(echo $dep | jq -r '.commit_sha') + + PREV_DIR=$(pwd) + REPO_NAME=$(basename $REPO_URL .git) + WORK_DIR=$(mktemp -d)/$REPO_NAME + mkdir -p $WORK_DIR + echo "Processing $REPO_URL @ $COMMIT_SHA (in $WORK_DIR)" + cd $WORK_DIR + + # Shallow clone and checkout + git init . + git remote add origin $REPO_URL + git fetch --depth 1 origin $COMMIT_SHA + git fetch --tags --depth 1 + git checkout $COMMIT_SHA + + # Get coordinates + PROPS=$(./gradlew -q :properties 2>/dev/null < /dev/null) + GROUP=$(echo "$PROPS" | grep '^group:' | awk '{print $2}') + # Gradle does not actually seem to have project name, + # it just takes the project folder name (in this case, the repo) + PROJECT=$REPO_NAME + # We don't actually care about the version, we just want to override it (this just returns a short sha rn) + # If we ever do care about it, we need to figure out how to get it with just a shallow clone + VERSION=$(echo "$PROPS" | grep '^version:' | awk '{print $2}')-local + + echo "Coordinates of local maven result: $GROUP:$PROJECT:$VERSION" + + # Generate a pom for local maven (need to do this, so we have deps included) + VERSION=$VERSION ./gradlew generatePomFileForMavenPublication < /dev/null + + # Setup local maven + MAVEN_PATH="${GROUP//.//}/$PROJECT/$VERSION" + mkdir -p ~/.m2/repository/$MAVEN_PATH + + # Copy in pom and create fake normal jar so gradle is happy + mv ./build/publications/maven/pom-default.xml ~/.m2/repository/$MAVEN_PATH/$PROJECT-$VERSION.pom + touch ~/.m2/repository/$MAVEN_PATH/$PROJECT-$VERSION.jar + + # Go back to original dir & clean up workdir + cd $PREV_DIR + rm -rf $WORK_DIR + + # Move actual jar to local maven + cp $JAR_PATH ~/.m2/repository/$MAVEN_PATH/$PROJECT-$VERSION-dev.jar + + # Append override to init.gradle + cat >> $INIT_GRADLE.tmp << EOF + if (details.requested.module.toString() == '${GROUP}:${PROJECT}') { + details.useVersion '${VERSION}' + details.because 'PR dependency override' + } +EOF + + rm -rf $WORK_DIR +done + +# Close init.gradle +cat >> $INIT_GRADLE.tmp << 'EOF' + } + } +} +EOF + +# Rename to actual now that its parseable +mv $INIT_GRADLE.tmp $INIT_GRADLE \ No newline at end of file diff --git a/scripts/verify_server.sh b/scripts/verify_server.sh new file mode 100644 index 0000000..6f0a33a --- /dev/null +++ b/scripts/verify_server.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Checks whether the server side of the integration run passed +# +# adjusted from the full DAXXL version +# +# No `set -e`: greps that legitimately find nothing must not be mistaken for +# failures. Each check feeds the explicit status instead, so every problem of +# the run gets reported, not just the first one. +set -uo pipefail +shopt -s nullglob + +RUN_DIR="${RUN_DIR:?RUN_DIR must be set}" +SERVER_DIR="${SERVER_DIR:?SERVER_DIR must be set}" + +SERVER_LOG="$RUN_DIR/server.log" +SERVER_EXIT_FLAG="${SERVER_EXIT_FLAG:-$RUN_DIR/server.exit}" + +rc=0 + +# Sets a non-zero exit code, with the provided reason +fail() { + echo "FAIL: $*" + rc=1 +} + +# Exit code recorded by server.sh when the JVM stopped +if [ -e "$SERVER_EXIT_FLAG" ]; then + exit_code=$(cat "$SERVER_EXIT_FLAG") +else + exit_code=missing +fi +echo "server exit code: $exit_code" +[ "$exit_code" = "0" ] || fail "server exited with non-zero code: $exit_code" + +# Crash reports +crash_reports=("$SERVER_DIR/crash-reports/crash"*.txt) +if [ "${#crash_reports[@]}" -gt 0 ]; then + latest_crash_report="${crash_reports[-1]}" + fail "found server crash report at ${latest_crash_report##*/}:" + cat "$latest_crash_report" +fi + +# JVM fatal error logs +hs_err_logs=("$SERVER_DIR/hs_err_pid"*.log) +if [ "${#hs_err_logs[@]}" -gt 0 ]; then + latest_hs_err="${hs_err_logs[-1]}" + fail "JVM fatal error log detected ${latest_hs_err##*/}:" + cat "$latest_hs_err" +fi + +if [ -r "$SERVER_LOG" ]; then + if grep --quiet --fixed-strings 'Fatal errors were detected' "$SERVER_LOG"; then + fail "fatal errors detected:" + grep -n --fixed-strings 'Fatal errors were detected' "$SERVER_LOG" + fi + + if grep --quiet --fixed-strings 'The state engine was in incorrect state ERRORED and forced into state SERVER_STOPPED' \ + "$SERVER_LOG"; then + fail "server force stopped:" + grep -n --fixed-strings 'The state engine was in incorrect state ERRORED and forced into state SERVER_STOPPED' "$SERVER_LOG" + fi + + # Duplicate mods only count when the two jar names in the line differ; + # a mod colliding with itself is just a jar being seen twice. + if grep --quiet --fixed-strings 'Duplicate mod found:' "$SERVER_LOG"; then + dupes=$(grep --fixed-strings 'Duplicate mod found:' "$SERVER_LOG" \ + | while read -r line || [ -n "$line" ]; do + a=$(echo "$line" | grep -oP '(?<=\()[^)]+\.jar(?=\))' | head -1) + b=$(echo "$line" | grep -oP '(?<=\()[^)]+\.jar(?=\))' | tail -1) + [ "$a" != "$b" ] && echo "$line" || true + done) + if [ -n "$dupes" ]; then + fail "server had duplicate files, environment cant be guaranteed to contain correct versions:" + echo "$dupes" + fi + fi + + if grep --quiet --fixed-strings 'Exception stopping the server' "$SERVER_LOG"; then + fail "server didn't shut down cleanly:" + grep -n --fixed-strings 'Exception stopping the server' "$SERVER_LOG" + fi +else + fail "server log missing or unreadable: $SERVER_LOG" +fi + +[ "$rc" -eq 0 ] && echo "SERVER: pass" || echo "SERVER: fail" +exit "$rc" \ No newline at end of file diff --git a/templates/build-and-test.yml b/templates/build-and-test.yml index 044d731..1b392a8 100644 --- a/templates/build-and-test.yml +++ b/templates/build-and-test.yml @@ -18,3 +18,4 @@ jobs: # horizonqa: true # horizonqa-tests: '' # horizonqa-allow-no-tests: false +# external-deps-inclusion: true diff --git a/templates/gate-by-required.yml b/templates/gate-by-required.yml new file mode 100644 index 0000000..5044479 --- /dev/null +++ b/templates/gate-by-required.yml @@ -0,0 +1,10 @@ +name: Check state of other required PRs + +on: + pull_request: + branches: [ master, main, release/** ] + +jobs: + check-required-prs: + uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/gate-by-required.yml@master + secrets: inherit diff --git a/templates/latest-daily-integration-test.yml b/templates/latest-daily-integration-test.yml new file mode 100644 index 0000000..54bd2c2 --- /dev/null +++ b/templates/latest-daily-integration-test.yml @@ -0,0 +1,19 @@ +name: Test integration in latest daily + +# Shim to forward the "Build and test" completion to the full pack test wf, whose +# jobs inherit this workflow_run event context (github.event.workflow_run.*) +on: + workflow_run: + workflows: ["Build and test"] + types: [completed] + +permissions: + actions: read + contents: read + statuses: write + +jobs: + latest-daily-integration-test: + if: github.event.workflow_run.event == 'pull_request' + uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/latest-daily-integration-test.yml@master + secrets: inherit diff --git a/templates/trigger-rebuild-with-deps.yml b/templates/trigger-rebuild-with-deps.yml new file mode 100644 index 0000000..8659c96 --- /dev/null +++ b/templates/trigger-rebuild-with-deps.yml @@ -0,0 +1,23 @@ +name: Trigger a rebuild with externally required PRs + +# Shim to forward the "Build and test" completion to the rebuild with deps wf, whose +# job inherits this workflow_run event context (github.event.workflow_run.*) +on: + workflow_run: + workflows: ["Build and test"] + types: [completed] + +permissions: + actions: write + contents: read + statuses: write + +jobs: + trigger-rebuild-with-deps: + # Only act on a failed, first-attempt PR build + if: >- + github.event.workflow_run.conclusion == 'failure' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.run_attempt == 1 + uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/trigger-rebuild-with-deps.yml@master + secrets: inherit