Repository navigation
The host deals a client's player's tripmine going off under its vehicle #255
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
| name: Build | |
| # Debug and release builds of the native ports (Linux, Windows, Android) and | |
| # the web port for every pushed commit, uploaded as artifacts. | |
| # tools/ci_build.py does each build; see it for the options. | |
| # | |
| # Kept cheap: | |
| # - one job per platform builds both configurations, so its set-up (tools, | |
| # downloads, caches) is paid once; | |
| # - compiles go through ccache, whose cache carries over from run to run, so | |
| # a commit only recompiles what it changed; downloads (SDL, musl, Gradle, | |
| # the Linux build's system root) are cached too; | |
| # - a newer push to the same branch cancels a run still going; | |
| # - pushes that only change documentation do not build; | |
| # - Windows, whose minutes cost twice Linux's, builds only Windows; | |
| # - artifacts are kept for three days. | |
| # | |
| # A successful build of main is also published as a GitHub release, the | |
| # newest marked latest, so that | |
| # https://github.com/<repository>/releases/latest/download/<file> always | |
| # gives the latest build; the last five are kept. Its web build is then the | |
| # site on GitHub Pages (https://halocombatevolved.com/, port/web/README.md). | |
| on: | |
| push: | |
| paths-ignore: | |
| - "**.md" | |
| workflow_dispatch: | |
| inputs: | |
| profile: | |
| description: "Also build the profiling flavour (configure.py --profile) on Linux" | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: build-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| CI_COMPILER_LAUNCHER: ccache | |
| CCACHE_DIR: ${{ github.workspace }}/.ccache | |
| CCACHE_BASEDIR: ${{ github.workspace }} | |
| CCACHE_MAXSIZE: 1G | |
| jobs: | |
| linux: | |
| # Arch Linux has what the build needs as packages: clang 22 (for the | |
| # profile-guided optimisation in pgo/), and CMake, pkg-config and | |
| # wayland-scanner for the SDL 3 that the portable build brings. The build | |
| # links against Debian 11's 32-bit glibc (tools/linux_sysroot.py), not | |
| # Arch's, so that it starts on SteamOS and older distributions; Arch's | |
| # 32-bit glibc is for the tests. | |
| runs-on: ubuntu-24.04 | |
| container: archlinux:latest | |
| timeout-minutes: 40 | |
| steps: | |
| - name: Install the toolchain | |
| run: | | |
| printf '\n[multilib]\nInclude = /etc/pacman.d/mirrorlist\n' >> /etc/pacman.conf | |
| pacman -Syu --noconfirm --needed git zstd python python-pytest ninja ccache \ | |
| clang lld gcc lib32-glibc lib32-gcc-libs cmake pkgconf wayland | |
| - uses: actions/checkout@v4 | |
| - uses: actions/cache@v4 | |
| with: | |
| path: .ccache | |
| key: ccache-linux-${{ github.sha }} | |
| restore-keys: ccache-linux- | |
| # the system root and SDL's source, which the build downloads | |
| - uses: actions/cache@v4 | |
| with: | |
| path: build/linux/third_party | |
| key: linux-third-party-${{ hashFiles('tools/linux_sysroot.py') }} | |
| - name: Debug | |
| run: python tools/ci_build.py linux debug | |
| # (the profiling build, configure.py --profile, which no other step | |
| # compiles: only when a run started by hand asks for it, as it costs a | |
| # build of its own) | |
| - name: Profile | |
| if: github.event_name == 'workflow_dispatch' && inputs.profile | |
| run: python tools/ci_build.py linux profile | |
| - name: Release | |
| run: python tools/ci_build.py linux release | |
| - name: Test | |
| run: | | |
| python -m pytest -q tools/test_linux_port.py tools/test_android_asm_convert.py tools/test_android_mirror_uploads.py tools/test_android_ce_texture_cache.py tools/test_host_watch_hash.py tools/test_touch_menu.py tools/harness tools/test_profile.py tools/stagekit/tests tools/test_net_compare.py | |
| python tools/test_light_storage.py | |
| - name: Compile cache | |
| run: ccache --show-stats | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-linux-debug | |
| path: dist/halo-linux-debug | |
| retention-days: 3 | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-linux-release | |
| path: dist/halo-linux-release | |
| retention-days: 3 | |
| android: | |
| # The runner image has the Android SDK and NDK, a JDK and CMake. | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 40 | |
| steps: | |
| - name: Install the toolchain | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/tools" | |
| # (each download checked against the hash it had when it was pinned) | |
| curl -fsSL -o ninja.zip https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip | |
| curl -fsSL -o ccache.tar.xz https://github.com/ccache/ccache/releases/download/v4.10.2/ccache-4.10.2-linux-x86_64.tar.xz | |
| sha256sum -c - <<'EOF' | |
| 6f98805688d19672bd699fbbfa2c2cf0fc054ac3df1f0e6a47664d963d530255 ninja.zip | |
| 80cab87bd510eca796467aee8e663c398239e0df1c4800a0b5dff11dca0b4f18 ccache.tar.xz | |
| EOF | |
| unzip -q ninja.zip -d "$RUNNER_TEMP/tools" && rm ninja.zip | |
| tar -xJf ccache.tar.xz -C "$RUNNER_TEMP/tools" --strip-components=1 ccache-4.10.2-linux-x86_64/ccache && rm ccache.tar.xz | |
| echo "$RUNNER_TEMP/tools" >> "$GITHUB_PATH" | |
| - uses: actions/checkout@v4 | |
| - uses: actions/cache@v4 | |
| with: | |
| path: .ccache | |
| key: ccache-android-${{ github.sha }} | |
| restore-keys: ccache-android- | |
| # musl and SDL, which configure.py fetches | |
| - uses: actions/cache@v4 | |
| with: | |
| path: build/android/third_party | |
| key: android-third-party-${{ hashFiles('tools/android_build.py', 'port/android/patches/**') }} | |
| - uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: gradle-${{ hashFiles('port/android/**/*.gradle*', 'port/android/gradle/wrapper/gradle-wrapper.properties') }} | |
| # the app's signing key, from the ANDROID_KEYSTORE_BASE64 secret (a | |
| # keystore, base64) and ANDROID_KEYSTORE_PASSWORD (its password, and | |
| # the key's, alias halo): every build signed with it installs over the | |
| # one before, as the app's self-updater (Updater.java) needs. Without | |
| # them each build is signed with the runner's own debug key. | |
| - name: Signing key | |
| env: | |
| KEYSTORE: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| run: | | |
| if [ -n "$KEYSTORE" ]; then | |
| printf '%s' "$KEYSTORE" | base64 -d > "$RUNNER_TEMP/halo.keystore" | |
| echo "HALO_ANDROID_KEYSTORE=$RUNNER_TEMP/halo.keystore" >> "$GITHUB_ENV" | |
| else | |
| echo "::warning::no ANDROID_KEYSTORE_BASE64 secret: the Android builds cannot update over each other" | |
| fi | |
| - name: Debug | |
| env: | |
| HALO_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| run: python3 tools/ci_build.py android debug | |
| - name: Release | |
| env: | |
| HALO_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| run: python3 tools/ci_build.py android release | |
| # the touch controls' layout and gyroscope maths (the runner's JDK) | |
| - name: Test the touch layout | |
| run: python3 tools/test_touch_layout.py | |
| - name: Compile cache | |
| run: ccache --show-stats | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-android-debug | |
| path: dist/halo-android-debug | |
| retention-days: 3 | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-android-release | |
| path: dist/halo-android-release | |
| retention-days: 3 | |
| windows: | |
| # The runner image has LLVM, Python and Visual Studio's x86 libraries and | |
| # Windows SDK. | |
| runs-on: windows-2025 | |
| timeout-minutes: 40 | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Install the toolchain | |
| shell: pwsh | |
| run: | | |
| Invoke-WebRequest -OutFile ninja.zip https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-win.zip | |
| Invoke-WebRequest -OutFile ccache.zip https://github.com/ccache/ccache/releases/download/v4.10.2/ccache-4.10.2-windows-x86_64.zip | |
| # (each download checked against the hash it had when it was pinned) | |
| if ((Get-FileHash ninja.zip -Algorithm SHA256).Hash -ne "F550FEC705B6D6FF58F2DB3C374C2277A37691678D6ABA463ADCBB129108467A") { throw "ninja.zip: unexpected SHA-256" } | |
| if ((Get-FileHash ccache.zip -Algorithm SHA256).Hash -ne "6252F081876A9A9F700FAE13A5AEC5D0D486B28261D7F1F72AC11C7AD9DF4DA9") { throw "ccache.zip: unexpected SHA-256" } | |
| Expand-Archive ninja.zip -DestinationPath "$env:RUNNER_TEMP\tools" | |
| Expand-Archive ccache.zip -DestinationPath $env:RUNNER_TEMP | |
| Copy-Item "$env:RUNNER_TEMP\ccache-4.10.2-windows-x86_64\ccache.exe" "$env:RUNNER_TEMP\tools" | |
| Remove-Item ninja.zip, ccache.zip | |
| "$env:RUNNER_TEMP\tools" | Out-File -Append -Encoding utf8 $env:GITHUB_PATH | |
| - uses: actions/checkout@v4 | |
| - uses: actions/cache@v4 | |
| with: | |
| path: .ccache | |
| key: ccache-windows-${{ github.sha }} | |
| restore-keys: ccache-windows- | |
| # SDL's development package, which configure.py fetches | |
| - uses: actions/cache@v4 | |
| with: | |
| path: build/windows/third_party | |
| key: windows-third-party-${{ hashFiles('tools/windows_build.py') }} | |
| - name: Debug | |
| run: python tools/ci_build.py windows debug | |
| - name: Release | |
| run: python tools/ci_build.py windows release | |
| - name: Test light storage | |
| run: python tools/test_light_storage.py | |
| - name: Compile cache | |
| run: ccache --show-stats | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-windows-debug | |
| path: dist/halo-windows-debug | |
| retention-days: 3 | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-windows-release | |
| path: dist/halo-windows-release | |
| retention-days: 3 | |
| # the PDBs, for crash reports' backtraces (tools/ci_build.py) | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-windows-debug-symbols | |
| path: dist/halo-windows-debug-symbols | |
| retention-days: 3 | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-windows-release-symbols | |
| path: dist/halo-windows-release-symbols | |
| retention-days: 3 | |
| web: | |
| # Emscripten (emsdk, the version below, cached) builds the web port's | |
| # site (port/web/README.md); the tests check its graph, the page's | |
| # importer and service worker, its sockets, that it starts in a | |
| # headless Chromium (with no game data, as far as the menus' map), and | |
| # that two browsers join a room through a local MQTT broker. | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 40 | |
| env: | |
| EMSDK_VERSION: 6.0.10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/emsdk | |
| key: emsdk-${{ env.EMSDK_VERSION }}-${{ hashFiles('port/web/halo_sdl3.py') }} | |
| - name: Install the toolchain | |
| run: | | |
| if [ ! -x ~/emsdk/upstream/emscripten/emcc ]; then | |
| rm -rf ~/emsdk | |
| git clone --depth 1 https://github.com/emscripten-core/emsdk.git ~/emsdk | |
| ~/emsdk/emsdk install "$EMSDK_VERSION" | |
| fi | |
| ~/emsdk/emsdk activate "$EMSDK_VERSION" | |
| sudo apt-get update -q | |
| sudo apt-get install -y -q ninja-build ccache python3-pytest mosquitto | |
| - uses: actions/cache@v4 | |
| with: | |
| path: .ccache | |
| key: ccache-web-${{ github.sha }} | |
| restore-keys: ccache-web- | |
| - name: Debug | |
| run: | | |
| source ~/emsdk/emsdk_env.sh | |
| python3 tools/ci_build.py web debug | |
| - name: Release | |
| run: | | |
| source ~/emsdk/emsdk_env.sh | |
| python3 tools/ci_build.py web release | |
| - name: Test | |
| run: | | |
| source ~/emsdk/emsdk_env.sh | |
| node --test "port/web/tests/*.test.js" | |
| python3 -m pytest -q tools/test_web_build.py | |
| - name: Test in Chromium and Firefox | |
| run: | | |
| npm install --no-save --prefix "$RUNNER_TEMP/playwright" playwright@1.58 | |
| "$RUNNER_TEMP/playwright/node_modules/.bin/playwright" install --with-deps chromium firefox | |
| NODE_PATH="$RUNNER_TEMP/playwright/node_modules" node port/web/tests/smoke.mjs dist/halo-web-release | |
| NODE_PATH="$RUNNER_TEMP/playwright/node_modules" node port/web/tests/rooms.mjs dist/halo-web-release | |
| # (the native builds' WebRTC, port/linux/src/p2p_webrtc.c, with both) | |
| NODE_PATH="$RUNNER_TEMP/playwright/node_modules" node port/web/tests/webrtc_native.mjs chromium firefox | |
| - name: Compile cache | |
| run: ccache --show-stats | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-web-debug | |
| path: dist/halo-web-debug | |
| include-hidden-files: true | |
| retention-days: 3 | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: halo-web-release | |
| path: dist/halo-web-release | |
| include-hidden-files: true | |
| retention-days: 3 | |
| release: | |
| # The eight builds (and the Windows builds' PDBs) as a release, tagged | |
| # build-<run number> and marked latest | |
| # (every file keeps its name from release to release, for the README's | |
| # latest/download links); then all but the newest five build-* releases | |
| # go, tags and all. Only for main, and only when every platform built. | |
| needs: [linux, android, windows, web] | |
| if: ${{ github.ref == 'refs/heads/main' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write | |
| outputs: | |
| tag: ${{ steps.publish.outputs.tag }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: halo-* | |
| path: artifacts | |
| - name: Package | |
| run: | | |
| mkdir release | |
| for build in artifacts/*/; do | |
| name="$(basename "$build")" | |
| (cd "$build" && zip -qr "$GITHUB_WORKSPACE/release/$name.zip" .) | |
| done | |
| ls -l release | |
| - name: Publish | |
| id: publish | |
| env: | |
| MESSAGE: ${{ github.event.head_commit.message }} | |
| COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| tag="build-$GITHUB_RUN_NUMBER" | |
| subject="$(printf '%s' "${MESSAGE:-manual run}" | head -n 1)" | |
| printf '%s\n\nCommit: %s\nBuild: %s\n\nThe release builds are for playing; the debug builds stop at the first failed assertion and log it.\n' \ | |
| "$subject" "$COMMIT_URL" "$RUN_URL" > notes.md | |
| gh release create "$tag" release/*.zip --target "$GITHUB_SHA" --latest \ | |
| --title "Build $GITHUB_RUN_NUMBER (${GITHUB_SHA:0:7})" --notes-file notes.md | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| # The Windows builds' executables and PDBs to Sentry, which turns the | |
| # crash reports of these builds (port/windows/src/win32_crash.c) into | |
| # backtraces with function names and lines. Needs the SENTRY_AUTH_TOKEN | |
| # secret: an organization auth token of the Sentry organization, which | |
| # names the organization and its region itself. The SENTRY_PROJECT | |
| # variable can name the project (its slug), else the id in the DSN of | |
| # win32_crash.c; SENTRY_ORG and SENTRY_URL (https://de.sentry.io) are | |
| # for a personal token instead. A failed upload does not stop the | |
| # release. | |
| - name: Upload Windows symbols to Sentry | |
| continue-on-error: true | |
| env: | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT || '4512207917744208' }} | |
| ORG: ${{ vars.SENTRY_ORG }} | |
| URL: ${{ vars.SENTRY_URL }} | |
| SENTRY_CLI_VERSION: 2.46.0 | |
| run: | | |
| if [ -z "$SENTRY_AUTH_TOKEN" ]; then | |
| echo "::warning::no SENTRY_AUTH_TOKEN secret: crash reports of this build will have no function names" | |
| exit 0 | |
| fi | |
| if [ -n "$ORG" ]; then export SENTRY_ORG="$ORG"; fi | |
| if [ -n "$URL" ]; then export SENTRY_URL="$URL"; fi | |
| curl -fsSL -o sentry-cli "https://github.com/getsentry/sentry-cli/releases/download/$SENTRY_CLI_VERSION/sentry-cli-Linux-x86_64" | |
| chmod +x sentry-cli | |
| ./sentry-cli debug-files upload --wait \ | |
| artifacts/halo-windows-release artifacts/halo-windows-release-symbols \ | |
| artifacts/halo-windows-debug artifacts/halo-windows-debug-symbols | |
| - name: Keep the last five | |
| run: | | |
| gh release list --limit 100 --json tagName,createdAt \ | |
| --jq '[.[] | select(.tagName | startswith("build-"))] | sort_by(.createdAt) | reverse | .[5:] | .[].tagName' | | |
| while read -r tag; do | |
| gh release delete "$tag" --cleanup-tag --yes | |
| done | |
| pages: | |
| # The release's web build to GitHub Pages, which serves it at | |
| # https://halocombatevolved.com/ (port/web/README.md, "Hosting"). Only | |
| # main, only once every platform built and the release is out (the site | |
| # and the release are one build), and a deployment is never cancelled | |
| # halfway. The repository's Pages source must be GitHub Actions. | |
| needs: [release] | |
| if: ${{ github.ref == 'refs/heads/main' }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| pages: write | |
| id-token: write | |
| concurrency: | |
| group: pages | |
| cancel-in-progress: false | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deploy.outputs.page_url }} | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: halo-web-release | |
| path: site | |
| # The game's wasm, most of a build, from jsDelivr rather than GitHub | |
| # Pages, which serves 100 GB a month: the wasm alone in a commit of the | |
| # web-wasm branch (pushed over the last, so the repository keeps one), | |
| # which jsDelivr serves by its hash (with CORS; GitHub's release | |
| # downloads have none, so a page cannot fetch them), and version.json | |
| # names that copy and its SHA-256. The site's service worker takes the | |
| # wasm from there, checked, and from the site only when it cannot | |
| # (port/web/site/sw.js). A failure here leaves the site serving it. | |
| - name: The wasm on jsDelivr | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| sha256="$(sha256sum site/halo.wasm | cut -d' ' -f1)" | |
| mkdir wasm | |
| cp site/halo.wasm wasm/ | |
| cd wasm | |
| git init -q -b web-wasm | |
| git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \ | |
| add halo.wasm | |
| git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \ | |
| commit -q -m "The web build's wasm (build $GITHUB_RUN_NUMBER, $GITHUB_SHA)" | |
| git push -q --force "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:refs/heads/web-wasm | |
| url="https://cdn.jsdelivr.net/gh/${GITHUB_REPOSITORY}@$(git rev-parse HEAD)/halo.wasm" | |
| # (jsDelivr fetches it now, and it must be the same file: it serves | |
| # files of at most 20 MB from GitHub) | |
| curl -fsSL --retry 6 --retry-delay 10 --retry-all-errors -o served.wasm "$url" | |
| echo "$sha256 served.wasm" | sha256sum -c - | |
| cd .. | |
| python3 - "$url" "$sha256" <<'EOF' | |
| import json, sys | |
| with open("site/version.json") as file: | |
| stamp = json.load(file) | |
| stamp["wasm"] = {"url": sys.argv[1], "sha256": sys.argv[2]} | |
| with open("site/version.json", "w") as file: | |
| json.dump(stamp, file, indent=1) | |
| EOF | |
| echo "the wasm from $url" | |
| - uses: actions/configure-pages@v5 | |
| - uses: actions/upload-pages-artifact@v3 | |
| with: | |
| path: site | |
| - id: deploy | |
| uses: actions/deploy-pages@v4 | |
| notify: | |
| # A Discord embed for the run, to the DISCORD_WEBHOOK secret (nothing | |
| # without it): green when every platform built, red when one did not, | |
| # with each platform's result, this run's release (main only), and links | |
| # to the latest release's builds and the site. | |
| needs: [linux, android, windows, web, release, pages] | |
| if: ${{ !cancelled() }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Post to Discord | |
| env: | |
| WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} | |
| LINUX: ${{ needs.linux.result }} | |
| ANDROID: ${{ needs.android.result }} | |
| WINDOWS: ${{ needs.windows.result }} | |
| WEB: ${{ needs.web.result }} | |
| PAGES: ${{ needs.pages.result }} | |
| SITE_URL: https://halocombatevolved.com/ | |
| RELEASE_TAG: ${{ needs.release.outputs.tag }} | |
| SERVER: ${{ github.server_url }} | |
| REPOSITORY: ${{ github.repository }} | |
| BRANCH: ${{ github.ref_name }} | |
| SHA: ${{ github.sha }} | |
| ACTOR: ${{ github.actor }} | |
| MESSAGE: ${{ github.event.head_commit.message }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} | |
| run: | | |
| if [ -z "$WEBHOOK" ]; then | |
| echo "no DISCORD_WEBHOOK secret: nothing to post" | |
| exit 0 | |
| fi | |
| status() { | |
| case "$1" in | |
| success) echo "✅ Built" ;; | |
| failure) echo "❌ Failed" ;; | |
| cancelled) echo "⏹️ Cancelled" ;; | |
| *) echo "➖ Skipped" ;; | |
| esac | |
| } | |
| if [ "$LINUX" = success ] && [ "$ANDROID" = success ] && [ "$WINDOWS" = success ] && [ "$WEB" = success ]; then | |
| title="Build succeeded"; color=5763719 | |
| else | |
| title="Build failed"; color=15548997 | |
| fi | |
| latest="$SERVER/$REPOSITORY/releases/latest" | |
| downloads="[Linux]($latest/download/halo-linux-release.zip) · [Windows]($latest/download/halo-windows-release.zip) · [Android]($latest/download/halo-android-release.zip) · [Web]($SITE_URL) ([all builds]($latest))" | |
| if [ -n "$RELEASE_TAG" ]; then | |
| release="[$RELEASE_TAG]($SERVER/$REPOSITORY/releases/tag/$RELEASE_TAG)" | |
| if [ "$PAGES" = success ]; then | |
| release="$release, on [the site]($SITE_URL)" | |
| fi | |
| else | |
| release="None (only a successful build of main makes one)" | |
| fi | |
| subject="$(printf '%s' "${MESSAGE:-manual run}" | head -n 1 | cut -c1-200)" | |
| jq -n \ | |
| --arg title "$title" --argjson color "$color" --arg url "$RUN_URL" \ | |
| --arg description "[\`${SHA:0:7}\`]($COMMIT_URL) $subject — $ACTOR ($BRANCH)" \ | |
| --arg release "$release" --arg downloads "$downloads" \ | |
| --arg linux "$(status "$LINUX")" --arg windows "$(status "$WINDOWS")" --arg android "$(status "$ANDROID")" \ | |
| --arg web "$(status "$WEB")" \ | |
| --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ | |
| '{embeds: [{ | |
| title: $title, url: $url, color: $color, description: $description, timestamp: $timestamp, | |
| fields: [ | |
| {name: "Linux", value: $linux, inline: true}, | |
| {name: "Windows", value: $windows, inline: true}, | |
| {name: "Android", value: $android, inline: true}, | |
| {name: "Web", value: $web, inline: true}, | |
| {name: "Release", value: $release}, | |
| {name: "Latest release builds", value: $downloads} | |
| ] | |
| }]}' > payload.json | |
| # Discord rate limits webhooks (HTTP 429, with how many seconds to | |
| # wait in retry_after): wait and try again, a few times. A post | |
| # still limited, or a network or Discord error, is a warning rather | |
| # than a failed run; any other answer (a deleted webhook) fails. (At | |
| # most 5 tries of 15 seconds and 4 waits of 30: inside the job's 5 | |
| # minutes.) | |
| for attempt in 1 2 3 4 5; do | |
| code=$(curl -sS --max-time 15 -o response.json -w '%{http_code}' \ | |
| -H "Content-Type: application/json" -d @payload.json "$WEBHOOK") || code=000 | |
| case "$code" in | |
| 2??) | |
| exit 0 | |
| ;; | |
| 429) | |
| wait=$(jq -r '.retry_after // 5' response.json 2>/dev/null || echo 5) | |
| wait=$(awk -v w="$wait" 'BEGIN { w += 0; if (w < 1) w = 1; if (w > 30) w = 30; printf "%d", w + 0.999 }') | |
| echo "Discord is rate limiting the webhook; trying again in ${wait}s (attempt $attempt of 5)" | |
| ;; | |
| 000|5??) | |
| wait=5 | |
| echo "Discord answered HTTP $code; trying again in ${wait}s (attempt $attempt of 5)" | |
| ;; | |
| *) | |
| echo "::error::Discord answered HTTP $code: $(cat response.json 2>/dev/null)" | |
| exit 1 | |
| ;; | |
| esac | |
| [ "$attempt" -lt 5 ] && sleep "$wait" | |
| done | |
| echo "::warning::The Discord post did not go through after 5 attempts (HTTP $code)" |