Verify compatibility with latest library versions #548
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Scheduled upstream-version tracker (every 6 hours): discovers newer versions, tests each across | |
| # the matrix, records passing ones via addTestedVersion (§TCK-test-harness.5) and files issues. | |
| # §CI-verify-new-library-version-compatibility implements §FS-library-version-update-automation. | |
| name: "Verify compatibility with latest library versions" | |
| on: | |
| schedule: | |
| - cron: '0 */6 * * *' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: "workflow=${{ github.workflow }},ref=${{ github.event.ref }},pr=${{ github.event.pull_request.id }}" | |
| cancel-in-progress: true | |
| jobs: | |
| get-all-libraries: | |
| name: "📋 Get list of all supported libraries with newer versions" | |
| # Opens PRs, with labels and assignees. Works only on the main repo. | |
| if: github.repository == 'oracle/graalvm-reachability-metadata' | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: write | |
| issues: read | |
| outputs: | |
| matrix: ${{ steps.set-matrix.outputs.matrix }} | |
| none-found: ${{ steps.set-matrix.outputs.none-found }} | |
| branch: ${{ steps.set-branch-name.outputs.branch }} | |
| steps: | |
| - name: "☁️ Checkout repository" | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: "🔧 Setup java" | |
| uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 | |
| with: | |
| distribution: 'graalvm' | |
| java-version: '25' | |
| - name: "📅 Set branch name" | |
| id: set-branch-name | |
| run: | | |
| BRANCH_NAME="check-new-library-versions/$(date '+%Y-%m-%dT%H-%M')" | |
| echo "branch=$BRANCH_NAME" >> "$GITHUB_OUTPUT" | |
| - name: "🕸️ Populate matrix" | |
| id: set-matrix | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -o pipefail | |
| MATRIX_OUTPUT_FILE="$(mktemp)" | |
| RAW_OUTPUT="$(./gradlew fetchExistingLibrariesWithNewerVersions --quiet | sed -n '/\[/,$p')" | |
| JSON_PAYLOAD="$(printf '%s' "$RAW_OUTPUT" | jq -c '.')" | |
| OPEN_ISSUES_JSON="$( | |
| gh issue list \ | |
| --repo "${{ github.repository }}" \ | |
| --state open \ | |
| --limit 1000 \ | |
| --json title | |
| )" | |
| FILTERED_JSON_PAYLOAD="$( | |
| printf '%s' "$JSON_PAYLOAD" | jq -c \ | |
| --arg issueTitlePrefix "[Automation] " \ | |
| --arg issueTitleMiddle " fails for " \ | |
| --argjson issues "$OPEN_ISSUES_JSON" ' | |
| [ | |
| .[] | |
| | . as $library | |
| | ($library.versions[0] // null) as $firstVersion | |
| | if $firstVersion == null then | |
| $library | |
| else | |
| ($library.name + ":" + $firstVersion) as $gavCoordinates | |
| | ($issueTitleMiddle + $gavCoordinates) as $titleEnd | |
| | select( | |
| any( | |
| $issues[]?; | |
| .title | (startswith($issueTitlePrefix) and endswith($titleEnd)) | |
| ) | not | |
| ) | |
| end | |
| ] | |
| ' | |
| )" | |
| TOTAL_CANDIDATES="$(printf '%s' "$JSON_PAYLOAD" | jq 'length')" | |
| FILTERED_CANDIDATES="$(printf '%s' "$FILTERED_JSON_PAYLOAD" | jq 'length')" | |
| SKIPPED_CANDIDATES="$((TOTAL_CANDIDATES - FILTERED_CANDIDATES))" | |
| { | |
| echo "Compatibility candidates discovered: $TOTAL_CANDIDATES" | |
| echo "Filtered because an open automation issue already exists for the first candidate version: $SKIPPED_CANDIDATES" | |
| echo "Libraries scheduled for testing: $FILTERED_CANDIDATES" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| GITHUB_OUTPUT="$MATRIX_OUTPUT_FILE" ./gradlew generateNewLibraryVersionCompatibilityMatrix -PnewLibraryVersionCandidates="$FILTERED_JSON_PAYLOAD" | |
| { | |
| echo "matrix<<EOF" | |
| cat "$MATRIX_OUTPUT_FILE" | sed -n 's/^matrix=//p' | |
| echo "EOF" | |
| echo "none-found=$(sed -n 's/^none-found=//p' "$MATRIX_OUTPUT_FILE")" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: "🔨 Create branch" | |
| if: steps.set-matrix.outputs.none-found != 'true' | |
| run: | | |
| git config --local user.email "actions@github.com" | |
| git config --local user.name "Github Actions" | |
| git checkout -b "${{ steps.set-branch-name.outputs.branch }}" | |
| git push origin "${{ steps.set-branch-name.outputs.branch }}" | |
| test-all-metadata: | |
| name: "🧪 ${{ matrix.name }} [${{ matrix.nativeImageMode }}] (GraalVM for JDK ${{ matrix.version }} @ ${{ matrix.os }})" | |
| if: needs.get-all-libraries.outputs.none-found != 'true' | |
| runs-on: ${{ matrix.os }} | |
| needs: get-all-libraries | |
| permissions: | |
| contents: read | |
| env: | |
| GVM_TCK_NATIVE_IMAGE_MODE: ${{ matrix.nativeImageMode }} | |
| LOG_LINES: "300" | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.get-all-libraries.outputs.matrix) }} | |
| steps: | |
| - name: "☁️ Checkout repository" | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: "🛠️ Setup latest native-build-tools snapshot" | |
| uses: ./.github/actions/setup-native-build-tools | |
| - name: "🔧 Download GraalVM for metadata testing" | |
| uses: graalvm/setup-graalvm@60c26726de13f8b90771df4bc1641a52a3159994 # v1 | |
| with: | |
| distribution: 'graalvm' | |
| java-version: ${{ matrix.version }} | |
| set-java-home: 'true' | |
| native-image-job-reports: 'true' | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: "Extract test path and library version" | |
| id: extract-params | |
| run: ./gradlew extractLibraryTestParams -Pcoordinates="${{ matrix.name }}" | |
| - name: "Pull allowed docker images" | |
| run: ./gradlew pullAllowedDockerImages -Pcoordinates="${{ env.TEST_COORDINATES }}" | |
| - name: "Disable docker networking" | |
| run: bash ./.github/workflows/scripts/disable-docker.sh | |
| - name: "🧪 Run '${{ env.TEST_COORDINATES }}' tests" | |
| id: runtests | |
| run: | | |
| bash ./.github/workflows/scripts/run-consecutive-tests.sh "${{ env.TEST_COORDINATES }}" '${{ toJson(matrix.versions) }}' 2>&1 | tee test_results.txt || true | |
| grep -a "^PASSED:" test_results.txt | sed 's/PASSED://g' > successful_versions.txt || true | |
| FAILED_LINE="$(grep -a "^FAILED" test_results.txt | head -n 1 || true)" | |
| FAILURE_TYPE="" | |
| FAILED_VERSION="" | |
| FAILED_COMMAND="" | |
| if [[ -n "$FAILED_LINE" ]]; then | |
| FAILURE_TYPE="$(printf '%s\n' "$FAILED_LINE" | awk -F'[][]' '{print $2}')" | |
| FAILED_VERSION="$(printf '%s\n' "$FAILED_LINE" | awk -F'[][]' '{print $4}')" | |
| FAILED_COMMAND="$(printf '%s\n' "$FAILED_LINE" | awk -F'[][]' '{print $6}')" | |
| fi | |
| RUNNER_LOG_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| SUCCESSFUL_VERSIONS_JSON="$(jq -R -s 'split("\n") | map(select(length > 0))' successful_versions.txt)" | |
| jq -n \ | |
| --arg name "${{ matrix.name }}" \ | |
| --arg os "${{ matrix.os }}" \ | |
| --arg jdk "${{ matrix.version }}" \ | |
| --arg nativeImageMode "${{ matrix.nativeImageMode }}" \ | |
| --arg latestVersion "${{ env.LATEST_VERSION }}" \ | |
| --arg runnerLogUrl "$RUNNER_LOG_URL" \ | |
| --arg failureType "$FAILURE_TYPE" \ | |
| --arg failedVersion "$FAILED_VERSION" \ | |
| --arg failedCommand "$FAILED_COMMAND" \ | |
| --arg logLines "${{ env.LOG_LINES }}" \ | |
| --argjson successfulVersions "$SUCCESSFUL_VERSIONS_JSON" \ | |
| --rawfile logTail test_results.txt \ | |
| '{ | |
| name: $name, | |
| os: $os, | |
| version: $jdk, | |
| nativeImageMode: $nativeImageMode, | |
| latestVersion: $latestVersion, | |
| runnerLogUrl: $runnerLogUrl, | |
| successfulVersions: $successfulVersions, | |
| failure: (if $failedVersion == "" then null else { | |
| failureType: $failureType, | |
| failedVersion: $failedVersion, | |
| failedCommand: $failedCommand, | |
| logLines: $logLines, | |
| logTail: ($logTail | split("\n") | .[-($logLines | tonumber):] | join("\n")) | |
| } end) | |
| }' > result.json | |
| - name: "📝 Compute artifact names" | |
| id: artifact-name | |
| run: | | |
| SAFE_NAME="$(printf '%s' '${{ matrix.name }}' | tr ':' '-')" | |
| SAFE_MODE="$(printf '%s' '${{ matrix.nativeImageMode }}' | tr ':' '-')" | |
| echo "artifact_name=compatibility-result-$SAFE_NAME-${{ matrix.version }}-${{ matrix.os }}-$SAFE_MODE" >> "$GITHUB_OUTPUT" | |
| echo "result_file=result-$SAFE_NAME-${{ matrix.version }}-${{ matrix.os }}-$SAFE_MODE.json" >> "$GITHUB_OUTPUT" | |
| - name: "📝 Rename result file for merged download" | |
| run: mv result.json "${{ steps.artifact-name.outputs.result_file }}" | |
| - name: "📦 Upload result artifact" | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: ${{ steps.artifact-name.outputs.artifact_name }} | |
| path: ${{ steps.artifact-name.outputs.result_file }} | |
| if-no-files-found: error | |
| process-results: | |
| name: "🧪 Process results" | |
| runs-on: ubuntu-22.04 | |
| permissions: | |
| actions: read | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| env: | |
| ISSUE_TITLE_PREFIX: "[Automation] " | |
| ISSUE_TITLE_MIDDLE: " fails for " | |
| ISSUE_BODY_CHAR_LIMIT: "60000" | |
| LOG_LINES: "300" | |
| needs: | |
| - get-all-libraries | |
| - test-all-metadata | |
| if: needs.get-all-libraries.outputs.none-found != 'true' | |
| steps: | |
| - name: "☁️ Checkout repository" | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: "🔧 Setup java" | |
| uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 | |
| with: | |
| distribution: 'graalvm' | |
| java-version: '25' | |
| - name: "📥 Download matrix results" | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| path: compatibility-results | |
| pattern: compatibility-result-* | |
| merge-multiple: true | |
| - name: "🧮 Aggregate compatibility results" | |
| id: aggregate | |
| run: | | |
| mapfile -t RESULT_FILES < <(find compatibility-results -type f -name 'result-*.json' | sort) | |
| if [[ "${#RESULT_FILES[@]}" -eq 0 ]]; then | |
| echo "No result.json artifacts were found under compatibility-results" | |
| exit 1 | |
| fi | |
| jq -s ' | |
| def failures_by_library: | |
| map(select(.failure != null)) | |
| | group_by(.name) | |
| | map({ | |
| name: .[0].name, | |
| latestVersion: .[0].latestVersion, | |
| failures: ( | |
| map(.failure + { | |
| os: .os, | |
| jdk: .version, | |
| nativeImageMode: .nativeImageMode, | |
| runnerLogUrl: .runnerLogUrl | |
| }) | |
| | group_by(.failedVersion) | |
| | map({ | |
| failedVersion: .[0].failedVersion, | |
| failureTypes: (map(.failureType) | unique), | |
| environments: map({ | |
| os: .os, | |
| jdk: .jdk, | |
| nativeImageMode: .nativeImageMode, | |
| failureType: .failureType, | |
| failedCommand: .failedCommand, | |
| runnerLogUrl: .runnerLogUrl, | |
| logLines: .logLines, | |
| logTail: .logTail | |
| }) | |
| }) | |
| ) | |
| }); | |
| def unique_preserve: | |
| reduce .[] as $item ([]; if index($item) then . else . + [$item] end); | |
| def successes_by_library: | |
| group_by(.name) | |
| | map( | |
| (. | map(select(.skipped != true))) as $active | |
| | if ($active | length) == 0 then empty else { | |
| name: $active[0].name, | |
| latestVersion: $active[0].latestVersion, | |
| successfulVersions: ( | |
| ($active | map(.successfulVersions | unique_preserve)) as $lists | |
| | if ($lists | length) == 0 then [] | |
| else reduce $lists[1:][] as $candidate ($lists[0]; [ .[] | select($candidate | index(.)) ]) | |
| end | |
| ), | |
| consideredVersions: ($active | map(.successfulVersions + (if .failure == null then [] else [.failure.failedVersion] end)) | add | unique) | |
| } | |
| end | |
| ); | |
| { | |
| successes: successes_by_library, | |
| failures: failures_by_library | |
| } | |
| ' "${RESULT_FILES[@]}" > aggregate-results.json | |
| jq -r ' | |
| .successes[] | |
| | .name as $name | |
| | .latestVersion as $latest | |
| | .successfulVersions as $versions | |
| | range(0; $versions | length) as $i | |
| | ($versions[$i]) as $version | |
| | (if $i == 0 then $latest else $versions[$i - 1] end) as $lastSupported | |
| | "\($name):\($version)|\($lastSupported)" | |
| ' aggregate-results.json > successful_updates.txt | |
| if [[ -s successful_updates.txt ]]; then | |
| echo "has-successful-updates=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has-successful-updates=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| if jq -e '.failures | length > 0' aggregate-results.json > /dev/null; then | |
| echo "has-failures=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "has-failures=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: "✔️ New library versions are supported (with locks, waiting and retries)" | |
| if: steps.aggregate.outputs.has-successful-updates == 'true' | |
| env: | |
| BRANCH: ${{ needs.get-all-libraries.outputs.branch }} | |
| run: | | |
| set -euo pipefail | |
| git config --local user.email "actions@github.com" | |
| git config --local user.name "Github Actions" | |
| update_new_versions() { | |
| while IFS='|' read -r coordinates last_supported_version; do | |
| if [[ -n "$coordinates" && -n "$last_supported_version" ]]; then | |
| ./gradlew addTestedVersion -Pcoordinates="$coordinates" --lastSupportedVersion="$last_supported_version" | |
| fi | |
| done < successful_updates.txt | |
| } | |
| update_root_coverage_markdown() { | |
| local coverage_dir="${RUNNER_TEMP}/root-coverage" | |
| rm -rf "$coverage_dir" | |
| mkdir -p "$coverage_dir" | |
| ./gradlew generateReadmeBadgeSummary -PreadmeMetricsOutputRoot="$coverage_dir" --stacktrace | |
| cp "$coverage_dir/COVERAGE.md" COVERAGE.md | |
| } | |
| sleep $((RANDOM % 5 + 1)) | |
| for i in {1..10}; do | |
| git fetch origin "$BRANCH" | |
| git checkout -B "$BRANCH" "origin/$BRANCH" | |
| update_new_versions | |
| git restore --source=HEAD --staged --worktree gradle/libs.versions.toml || true | |
| update_root_coverage_markdown | |
| if git status --porcelain | grep -q .; then | |
| git add -A -- metadata stats tests/src COVERAGE.md | |
| COMMIT_BODY_FILE="$(mktemp)" | |
| { | |
| printf 'Update tested library versions\n\n' | |
| printf 'Added tested versions:\n' | |
| awk -F'[|]' ' | |
| { | |
| split($1, coordinatesParts, ":") | |
| library = coordinatesParts[1] ":" coordinatesParts[2] | |
| testedVersion = coordinatesParts[3] | |
| updates[library] = updates[library] "\n- `" testedVersion "`" | |
| } | |
| END { | |
| n = asorti(updates, libraries) | |
| for (i = 1; i <= n; i++) { | |
| library = libraries[i] | |
| printf "#### `%s`%s\n", library, updates[library] | |
| } | |
| } | |
| ' successful_updates.txt | |
| } > "$COMMIT_BODY_FILE" | |
| git commit -F "$COMMIT_BODY_FILE" | |
| rm "$COMMIT_BODY_FILE" | |
| else | |
| echo "No tested version updates to commit." | |
| exit 0 | |
| fi | |
| if git push origin "$BRANCH"; then | |
| echo "Pushed successfully." | |
| exit 0 | |
| fi | |
| echo "Push failed (likely concurrent update). Waiting and retrying..." | |
| sleep 5 | |
| done | |
| echo "Timed out waiting to push updates after concurrent jobs." | |
| exit 1 | |
| - name: "❗ Create aggregated issues for unsupported library versions" | |
| if: steps.aggregate.outputs.has-failures == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GRAALBOT_PR_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --no-tags --depth=1 origin +master:refs/remotes/origin/master | |
| latest_master_supports_version() { | |
| local name="$1" | |
| local failed_version="$2" | |
| local group="${name%%:*}" | |
| local artifact="${name#*:}" | |
| local index_path="metadata/$group/$artifact/index.json" | |
| if ! git cat-file -e "origin/master:$index_path" 2>/dev/null; then | |
| return 1 | |
| fi | |
| git show "origin/master:$index_path" | jq -e --arg version "$failed_version" ' | |
| any(.[]?; (."metadata-version" == $version) or ((."tested-versions" // []) | index($version) != null)) | |
| ' >/dev/null | |
| } | |
| jq -c '.failures[]' aggregate-results.json | while read -r library; do | |
| NAME="$(printf '%s' "$library" | jq -r '.name')" | |
| printf '%s' "$library" | jq -c '.failures[]' | while read -r failure; do | |
| FAILED_VERSION="$(printf '%s' "$failure" | jq -r '.failedVersion')" | |
| GAV_COORDINATES="$NAME:$FAILED_VERSION" | |
| TITLE_END="${{ env.ISSUE_TITLE_MIDDLE }}$GAV_COORDINATES" | |
| FAILURE_TYPE="$(printf '%s' "$failure" | jq -r '.failureTypes | sort | join(", ")')" | |
| PRIMARY_FAILURE_TYPE="$(printf '%s' "$failure" | jq -r '.failureTypes | sort | .[0]')" | |
| TITLE="${{ env.ISSUE_TITLE_PREFIX }}$PRIMARY_FAILURE_TYPE$TITLE_END" | |
| FAILURE_LABEL="fails-$(printf '%s' "$PRIMARY_FAILURE_TYPE" | sed 's/ /-/g')" | |
| if latest_master_supports_version "$NAME" "$FAILED_VERSION"; then | |
| echo "Latest origin/master already supports $GAV_COORDINATES. Skipping stale failure issue." | |
| continue | |
| fi | |
| ISSUE_NUMBER=$( | |
| gh issue list \ | |
| --repo "${{ github.repository }}" \ | |
| --state open \ | |
| --search "\"$TITLE_END\" in:title" \ | |
| --json number,title \ | |
| --jq ".[] | select(.title | (startswith(\"${{ env.ISSUE_TITLE_PREFIX }}\") and endswith(\"$TITLE_END\"))) | .number" | |
| ) | |
| if [[ -n "$ISSUE_NUMBER" ]]; then | |
| echo "Issue already exists: $ISSUE_NUMBER. Skipping creation." | |
| continue | |
| fi | |
| BODY_FILE="$(mktemp)" | |
| BODY_CHAR_LIMIT="${{ env.ISSUE_BODY_CHAR_LIMIT }}" | |
| CODE_BLOCK_PREFIX=$'\n```console\n' | |
| CODE_BLOCK_SUFFIX=$'\n```\n\n' | |
| mapfile -t ENVIRONMENTS < <(printf '%s\n' "$failure" | jq -c '.environments[]') | |
| ENVIRONMENT_COUNT="${#ENVIRONMENTS[@]}" | |
| declare -a NORMAL_PREFIXES | |
| declare -a TRUNCATED_PREFIXES | |
| declare -a LOG_TAILS | |
| declare -a LOG_LENGTHS | |
| FULL_BODY_LENGTH=0 | |
| FIXED_TRUNCATED_LENGTH=0 | |
| TOTAL_LOG_LENGTH=0 | |
| for environment in "${ENVIRONMENTS[@]}"; do | |
| OS="$(printf '%s' "$environment" | jq -r '.os')" | |
| JDK="$(printf '%s' "$environment" | jq -r '.jdk')" | |
| NATIVE_IMAGE_MODE="$(printf '%s' "$environment" | jq -r '.nativeImageMode')" | |
| COMMAND="$(printf '%s' "$environment" | jq -r '.failedCommand')" | |
| RUNNER_LOG_URL="$(printf '%s' "$environment" | jq -r '.runnerLogUrl')" | |
| ENV_FAILURE_TYPE="$(printf '%s' "$environment" | jq -r '.failureType')" | |
| ENV_LOG_LINES="$(printf '%s' "$environment" | jq -r '.logLines')" | |
| LOG_TAIL="$(printf '%s' "$environment" | jq -r '.logTail')" | |
| NORMAL_PREFIX="$( | |
| printf '## %s on %s / JDK %s / mode %s\n\n' "$ENV_FAILURE_TYPE" "$OS" "$JDK" "$NATIVE_IMAGE_MODE" | |
| printf 'Failure kind: %s\n' "$ENV_FAILURE_TYPE" | |
| printf 'OS: %s\n' "$OS" | |
| printf 'JDK: %s\n' "$JDK" | |
| printf 'Native-image mode: %s\n' "$NATIVE_IMAGE_MODE" | |
| printf 'Reproducer: `%s`\n' "$COMMAND" | |
| printf 'Runner log: %s\n' "$RUNNER_LOG_URL" | |
| printf 'Last %s lines of the log:\n' "$ENV_LOG_LINES" | |
| )" | |
| TRUNCATED_PREFIX="$( | |
| printf '## %s on %s / JDK %s / mode %s\n\n' "$ENV_FAILURE_TYPE" "$OS" "$JDK" "$NATIVE_IMAGE_MODE" | |
| printf 'Failure kind: %s\n' "$ENV_FAILURE_TYPE" | |
| printf 'OS: %s\n' "$OS" | |
| printf 'JDK: %s\n' "$JDK" | |
| printf 'Native-image mode: %s\n' "$NATIVE_IMAGE_MODE" | |
| printf 'Reproducer: `%s`\n' "$COMMAND" | |
| printf 'Runner log: %s\n' "$RUNNER_LOG_URL" | |
| printf 'Last %s lines of the log (excerpt truncated to fit the GitHub issue size limit):\n' "$ENV_LOG_LINES" | |
| )" | |
| LOG_LENGTH="$(printf '%s' "$LOG_TAIL" | wc -c)" | |
| NORMAL_PREFIXES+=("$NORMAL_PREFIX") | |
| TRUNCATED_PREFIXES+=("$TRUNCATED_PREFIX") | |
| LOG_TAILS+=("$LOG_TAIL") | |
| LOG_LENGTHS+=("$LOG_LENGTH") | |
| FULL_BODY_LENGTH=$((FULL_BODY_LENGTH + $(printf '%s' "$NORMAL_PREFIX" | wc -c) + $(printf '%s' "$CODE_BLOCK_PREFIX" | wc -c) + LOG_LENGTH + $(printf '%s' "$CODE_BLOCK_SUFFIX" | wc -c))) | |
| FIXED_TRUNCATED_LENGTH=$((FIXED_TRUNCATED_LENGTH + $(printf '%s' "$TRUNCATED_PREFIX" | wc -c) + $(printf '%s' "$CODE_BLOCK_PREFIX" | wc -c) + $(printf '%s' "$CODE_BLOCK_SUFFIX" | wc -c))) | |
| TOTAL_LOG_LENGTH=$((TOTAL_LOG_LENGTH + LOG_LENGTH)) | |
| done | |
| : > "$BODY_FILE" | |
| if (( FULL_BODY_LENGTH <= BODY_CHAR_LIMIT )); then | |
| for i in "${!ENVIRONMENTS[@]}"; do | |
| printf '%s' "${NORMAL_PREFIXES[$i]}" >> "$BODY_FILE" | |
| printf '%s' "$CODE_BLOCK_PREFIX" >> "$BODY_FILE" | |
| printf '%s' "${LOG_TAILS[$i]}" >> "$BODY_FILE" | |
| printf '%s' "$CODE_BLOCK_SUFFIX" >> "$BODY_FILE" | |
| done | |
| else | |
| REMAINING_LOG_BUDGET=$((BODY_CHAR_LIMIT - FIXED_TRUNCATED_LENGTH)) | |
| if (( REMAINING_LOG_BUDGET < 0 )); then | |
| echo "Issue metadata exceeds the configured issue body size limit." | |
| exit 1 | |
| fi | |
| declare -a LOG_BUDGETS | |
| ASSIGNED_LOG_BUDGET=0 | |
| for i in "${!ENVIRONMENTS[@]}"; do | |
| if (( TOTAL_LOG_LENGTH == 0 )); then | |
| LOG_BUDGET=0 | |
| else | |
| LOG_BUDGET=$((LOG_LENGTHS[$i] * REMAINING_LOG_BUDGET / TOTAL_LOG_LENGTH)) | |
| fi | |
| LOG_BUDGETS+=("$LOG_BUDGET") | |
| ASSIGNED_LOG_BUDGET=$((ASSIGNED_LOG_BUDGET + LOG_BUDGET)) | |
| done | |
| EXTRA_BYTES=$((REMAINING_LOG_BUDGET - ASSIGNED_LOG_BUDGET)) | |
| NEXT_INDEX=0 | |
| while (( EXTRA_BYTES > 0 && ENVIRONMENT_COUNT > 0 )); do | |
| LOG_BUDGETS[$NEXT_INDEX]=$((LOG_BUDGETS[$NEXT_INDEX] + 1)) | |
| EXTRA_BYTES=$((EXTRA_BYTES - 1)) | |
| NEXT_INDEX=$(((NEXT_INDEX + 1) % ENVIRONMENT_COUNT)) | |
| done | |
| for i in "${!ENVIRONMENTS[@]}"; do | |
| printf '%s' "${TRUNCATED_PREFIXES[$i]}" >> "$BODY_FILE" | |
| printf '%s' "$CODE_BLOCK_PREFIX" >> "$BODY_FILE" | |
| if (( LOG_BUDGETS[$i] > 0 )); then | |
| if (( LOG_BUDGETS[$i] >= LOG_LENGTHS[$i] )); then | |
| printf '%s' "${LOG_TAILS[$i]}" >> "$BODY_FILE" | |
| else | |
| printf '%s' "${LOG_TAILS[$i]}" | tail -c "${LOG_BUDGETS[$i]}" >> "$BODY_FILE" | |
| fi | |
| fi | |
| printf '%s' "$CODE_BLOCK_SUFFIX" >> "$BODY_FILE" | |
| done | |
| fi | |
| ASSIGNEE_ARGS=() | |
| if [[ "$FAILURE_LABEL" == "fails-native-image-build" ]]; then | |
| ASSIGNEE_ARGS=(--assignee "vjovanov") | |
| fi | |
| gh issue create \ | |
| --repo "${{ github.repository }}" \ | |
| --title "$TITLE" \ | |
| --body-file "$BODY_FILE" \ | |
| --label "library-unsupported-version" \ | |
| --label "$FAILURE_LABEL" \ | |
| "${ASSIGNEE_ARGS[@]}" | |
| rm "$BODY_FILE" | |
| done | |
| done | |
| - name: "🧹 Close old pre-release issues for tested versions" | |
| if: steps.aggregate.outputs.has-successful-updates == 'true' || steps.aggregate.outputs.has-failures == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GRAALBOT_PR_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| PRE_RELEASE_SUFFIX: "(alpha[0-9]*|beta[0-9]*|rc[0-9]*|cr[0-9]*|m[0-9]+|ea[0-9]*|b[0-9]+|[0-9]+|preview)" | |
| run: | | |
| set -euo pipefail | |
| LIBRARY_VERSIONS_FILE="$(mktemp)" | |
| jq -r '.successes[]? | .name as $name | .successfulVersions[]? | "\($name)|\(.)"' aggregate-results.json >> "$LIBRARY_VERSIONS_FILE" | |
| jq -r '.failures[]? | .name as $name | .failures[]?.failedVersion | "\($name)|\(.)"' aggregate-results.json >> "$LIBRARY_VERSIONS_FILE" | |
| sort -u "$LIBRARY_VERSIONS_FILE" -o "$LIBRARY_VERSIONS_FILE" | |
| while IFS='|' read -r LIBRARY_NAME VERSION; do | |
| if [[ -z "$LIBRARY_NAME" || -z "$VERSION" ]]; then | |
| continue | |
| fi | |
| VERSION_REGEX="^([0-9]+(\.[0-9]+)*)(\.Final|\.RELEASE)?([-.]$PRE_RELEASE_SUFFIX([-.].*)?)?$" | |
| if [[ "$VERSION" =~ $VERSION_REGEX ]]; then | |
| BASE_VERSION="${BASH_REMATCH[1]}" | |
| PRE_RELEASE_TAG="${BASH_REMATCH[4]-}" | |
| else | |
| echo "Skipping invalid version: $VERSION" | |
| continue | |
| fi | |
| if [[ -n "$PRE_RELEASE_TAG" ]]; then | |
| echo "$VERSION is a pre-release, skipping issue closing." | |
| continue | |
| fi | |
| PRE_RELEASE_REGEX="$BASE_VERSION[-.]$PRE_RELEASE_SUFFIX" | |
| GROUP_ID="$(echo "$LIBRARY_NAME" | cut -d: -f1)" | |
| ARTIFACT_ID="$(echo "$LIBRARY_NAME" | cut -d: -f2)" | |
| GAV_COORDINATES_VERSIONLESS="$GROUP_ID:$ARTIFACT_ID" | |
| TITLE_END_VERSIONLESS="${{ env.ISSUE_TITLE_MIDDLE }}$GAV_COORDINATES_VERSIONLESS" | |
| PRE_RELEASE_ISSUES=$(gh issue list \ | |
| --repo "$REPO" \ | |
| --state open \ | |
| --search "\"$TITLE_END_VERSIONLESS\" in:title" \ | |
| --json number,title \ | |
| --jq ".[] | select(.title | startswith(\"${{ env.ISSUE_TITLE_PREFIX }}\") and test(\"$TITLE_END_VERSIONLESS:$PRE_RELEASE_REGEX\"; \"i\")) | .number") | |
| if [[ -n "$PRE_RELEASE_ISSUES" ]]; then | |
| echo "Closing pre-release issues for $LIBRARY_NAME version $BASE_VERSION:" | |
| for ISSUE in $PRE_RELEASE_ISSUES; do | |
| echo "Closing issue #$ISSUE" | |
| gh issue close "$ISSUE" --repo "$REPO" \ | |
| -c "Closing this pre-release issue because a full release is now available and/or builds successfully." \ | |
| -r "not planned" | |
| done | |
| else | |
| echo "No pre-release issues found for $LIBRARY_NAME version $BASE_VERSION" | |
| fi | |
| done < "$LIBRARY_VERSIONS_FILE" | |
| rm "$LIBRARY_VERSIONS_FILE" | |
| - name: "✏️ PR for supported versions" | |
| if: steps.aggregate.outputs.has-successful-updates == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GRAALBOT_PR_TOKEN }} | |
| run: | | |
| git fetch origin "${{ needs.get-all-libraries.outputs.branch }}" | |
| git checkout "${{ needs.get-all-libraries.outputs.branch }}" | |
| if git rev-parse --verify HEAD >/dev/null 2>&1 && ! git diff --quiet "origin/master...HEAD"; then | |
| PR_BODY_FILE="$(mktemp)" | |
| { | |
| printf 'This pull request updates supported versions of the existing libraries in the repo.\n\n' | |
| printf 'Added tested versions:\n' | |
| awk -F'[|]' ' | |
| { | |
| split($1, coordinatesParts, ":") | |
| library = coordinatesParts[1] ":" coordinatesParts[2] | |
| testedVersion = coordinatesParts[3] | |
| updates[library] = updates[library] "\n- `" testedVersion "`" | |
| } | |
| END { | |
| n = asorti(updates, libraries) | |
| for (i = 1; i <= n; i++) { | |
| library = libraries[i] | |
| printf "#### `%s`%s\n", library, updates[library] | |
| } | |
| } | |
| ' successful_updates.txt | |
| } > "$PR_BODY_FILE" | |
| gh pr create \ | |
| --title "${{ env.ISSUE_TITLE_PREFIX }}Update supported library versions $(date '+%Y-%m-%dT%H:%M')" \ | |
| --body-file "$PR_BODY_FILE" \ | |
| --reviewer "kimeta,vjovanov,jormundur00" \ | |
| --assignee "vjovanov" \ | |
| --label "library-bulk-update" | |
| rm "$PR_BODY_FILE" | |
| else | |
| echo "No branch changes available for PR creation." | |
| fi |