Skip to content

Nightly Builds

Nightly Builds #70

name: Nightly Builds
on:
schedule:
- cron: '0 6 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
metadata:
name: Nightly Metadata
runs-on: ubuntu-latest
outputs:
base_version: ${{ steps.version.outputs.base_version }}
version_iteration: ${{ steps.version.outputs.iteration }}
version: ${{ steps.version.outputs.version }}
version_tag: ${{ steps.version.outputs.version_tag }}
release_tag: ${{ steps.version.outputs.release_tag }}
release_name: ${{ steps.version.outputs.release_name }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Compute nightly version
id: version
shell: bash
run: |
set -euo pipefail
nightly_date="$(date -u +%Y%m%d)"
run_id="${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}"
python tools/build/openq4_version.py \
--source-root . \
--track nightly \
--iteration "${nightly_date}.${run_id}" >> "$GITHUB_OUTPUT"
version_tag="$(grep '^version_tag=' "$GITHUB_OUTPUT" | tail -n 1 | cut -d= -f2-)"
release_tag="nightly-${version_tag}"
release_name="OpenQ4 Nightly ${version_tag}"
echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT"
echo "release_name=${release_name}" >> "$GITHUB_OUTPUT"
nightly:
name: ${{ matrix.label }}
needs: metadata
runs-on: ${{ matrix.os }}
env:
OPENQ4_GAMELIBS_REPO: ${{ github.workspace }}/OpenQ4-GameLibs
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
platform: windows
label: Windows
binary_arch: x64
platform_backend: sdl3
archive_format: zip
archive_ext: .zip
- os: ubuntu-latest
platform: linux
label: Linux
binary_arch: x64
platform_backend: sdl3
archive_format: tar.xz
archive_ext: .tar.xz
- os: macos-latest
platform: macos
label: macOS
binary_arch: arm64
platform_backend: sdl3
archive_format: tar.gz
archive_ext: .tar.gz
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Fetch OpenQ4-GameLibs
shell: bash
run: |
set -euo pipefail
git clone --depth 1 https://github.com/themuffinator/OpenQ4-GameLibs.git OpenQ4-GameLibs
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install Meson and Ninja
run: |
python -m pip install --upgrade pip
python -m pip install meson ninja
- name: Compute nightly version
id: version_echo
shell: bash
run: |
set -euo pipefail
echo "Nightly version: ${{ needs.metadata.outputs.version }}"
echo "Nightly version tag: ${{ needs.metadata.outputs.version_tag }}"
- name: Install Linux native dependencies
if: matrix.platform == 'linux'
shell: bash
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y \
libglew-dev \
libopenal-dev \
libx11-dev \
libxext-dev \
libxxf86vm-dev
- name: Install macOS native dependencies
if: matrix.platform == 'macos'
shell: bash
run: |
set -euo pipefail
brew install glew
- name: Build and install (Windows)
if: matrix.platform == 'windows'
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$env:OPENQ4_SKIP_GAMELIBS_SYNC = "1"
powershell -ExecutionPolicy Bypass -File tools/build/meson_setup.ps1 setup --wipe builddir . --backend ninja --buildtype=release --wrap-mode=forcefallback "-Dplatform_backend=${{ matrix.platform_backend }}" "-Dversion_track=nightly" "-Dversion_iteration=${{ needs.metadata.outputs.version_iteration }}"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
powershell -ExecutionPolicy Bypass -File tools/build/meson_setup.ps1 compile -C builddir
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
powershell -ExecutionPolicy Bypass -File tools/build/meson_setup.ps1 install -C builddir --no-rebuild --skip-subprojects
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Build and install (Linux/macOS)
if: matrix.platform != 'windows'
shell: bash
run: |
set -euo pipefail
export OPENQ4_SKIP_GAMELIBS_SYNC=1
bash tools/build/meson_setup.sh setup --wipe builddir . --backend ninja --buildtype=release --wrap-mode=default -Dplatform_backend=${{ matrix.platform_backend }} -Dversion_track=nightly -Dversion_iteration=${{ needs.metadata.outputs.version_iteration }}
bash tools/build/meson_setup.sh compile -C builddir
bash tools/build/meson_setup.sh install -C builddir --no-rebuild --skip-subprojects
- name: Validate staged payload
shell: bash
run: |
set -euo pipefail
if [ ! -d ".install/openq4" ]; then
echo "Staged openq4 payload directory is missing."
exit 1
fi
has_payload_files="$(find ".install/openq4" -type f -print -quit 2>/dev/null || true)"
if [ -z "${has_payload_files}" ]; then
echo "Staged openq4 payload directory has no files."
exit 1
fi
if [ "${{ matrix.platform }}" = "linux" ]; then
if [ ! -f ".install/share/applications/openq4.desktop" ]; then
echo "Missing Linux desktop entry in staged payload."
exit 1
fi
if [ ! -f ".install/share/icons/hicolor/256x256/apps/openq4.png" ]; then
echo "Missing Linux 256x256 icon in staged payload."
exit 1
fi
fi
- name: Prepare package
id: package
shell: bash
run: |
set -euo pipefail
python tools/build/package_nightly.py \
--platform "${{ matrix.platform }}" \
--arch "${{ matrix.binary_arch }}" \
--version "${{ needs.metadata.outputs.version }}" \
--version-tag "${{ needs.metadata.outputs.version_tag }}" \
--archive-format "${{ matrix.archive_format }}" \
--source-root "${GITHUB_WORKSPACE}" \
--output-dir "${RUNNER_TEMP}"
archive_path="${RUNNER_TEMP}/openq4-${{ needs.metadata.outputs.version_tag }}-${{ matrix.platform }}${{ matrix.archive_ext }}"
package_dir="${RUNNER_TEMP}/openq4-${{ needs.metadata.outputs.version_tag }}-${{ matrix.platform }}"
if [ ! -f "${archive_path}" ]; then
echo "Expected package archive not found: ${archive_path}"
exit 1
fi
if [ ! -d "${package_dir}" ]; then
echo "Expected package directory not found: ${package_dir}"
exit 1
fi
echo "archive_path=${archive_path}" >> "$GITHUB_OUTPUT"
echo "package_dir=${package_dir}" >> "$GITHUB_OUTPUT"
- name: Validate macOS app bundle
if: matrix.platform == 'macos'
shell: bash
run: |
set -euo pipefail
app_root="${{ steps.package.outputs.package_dir }}/OpenQ4.app"
app_plist="${app_root}/Contents/Info.plist"
app_exec="${app_root}/Contents/MacOS/OpenQ4"
app_icon="${app_root}/Contents/Resources/OpenQ4.icns"
if [ ! -f "${app_plist}" ]; then
echo "Missing macOS app Info.plist: ${app_plist}"
exit 1
fi
if [ ! -x "${app_exec}" ]; then
echo "Missing or non-executable macOS app launcher: ${app_exec}"
exit 1
fi
if [ ! -f "${app_icon}" ]; then
echo "Missing macOS app icon: ${app_icon}"
exit 1
fi
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: openq4-nightly-${{ needs.metadata.outputs.version_tag }}-${{ matrix.platform }}
path: ${{ steps.package.outputs.archive_path }}
if-no-files-found: error
release:
name: Publish Nightly Release
needs: [metadata, nightly]
if: ${{ needs.nightly.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Fetch tags
shell: bash
run: |
set -euo pipefail
git fetch --force --tags origin
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Download packaged artifacts
uses: actions/download-artifact@v4
with:
pattern: openq4-nightly-${{ needs.metadata.outputs.version_tag }}-*
path: ${{ runner.temp }}/nightly-artifacts
merge-multiple: true
- name: Verify platform archives
shell: bash
run: |
set -euo pipefail
artifact_dir="${RUNNER_TEMP}/nightly-artifacts"
if [ ! -d "${artifact_dir}" ]; then
echo "Artifact directory is missing: ${artifact_dir}"
exit 1
fi
ls -lah "${artifact_dir}"
expected=(
"openq4-${{ needs.metadata.outputs.version_tag }}-windows.zip"
"openq4-${{ needs.metadata.outputs.version_tag }}-linux.tar.xz"
"openq4-${{ needs.metadata.outputs.version_tag }}-macos.tar.gz"
)
missing=0
for package_file in "${expected[@]}"; do
if [ ! -f "${artifact_dir}/${package_file}" ]; then
echo "Missing expected package archive: ${package_file}"
missing=1
fi
done
if [ "${missing}" -ne 0 ]; then
exit 1
fi
- name: Generate changelog
shell: bash
run: |
set -euo pipefail
python tools/build/generate_nightly_changelog.py \
--version "${{ needs.metadata.outputs.version }}" \
--version-tag "${{ needs.metadata.outputs.version_tag }}" \
--release-tag "${{ needs.metadata.outputs.release_tag }}" \
--repo "${GITHUB_REPOSITORY}" \
--run-id "${GITHUB_RUN_ID}" \
--run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
--output "${RUNNER_TEMP}/nightly-changelog.md"
- name: Create or update nightly release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail
tag="${{ needs.metadata.outputs.release_tag }}"
name="${{ needs.metadata.outputs.release_name }}"
notes="${RUNNER_TEMP}/nightly-changelog.md"
assets_dir="${RUNNER_TEMP}/nightly-artifacts"
mapfile -t assets < <(find "${assets_dir}" -maxdepth 1 -type f | sort)
if [ "${#assets[@]}" -eq 0 ]; then
echo "No release assets were downloaded."
exit 1
fi
if gh release view "${tag}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
gh release edit "${tag}" \
--repo "${GITHUB_REPOSITORY}" \
--title "${name}" \
--notes-file "${notes}" \
--latest=false
gh release upload "${tag}" "${assets[@]}" --repo "${GITHUB_REPOSITORY}" --clobber
else
gh release create "${tag}" "${assets[@]}" \
--repo "${GITHUB_REPOSITORY}" \
--title "${name}" \
--notes-file "${notes}" \
--latest=false
fi
release_id="$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" --jq .id)"
gh api \
--method PATCH \
"repos/${GITHUB_REPOSITORY}/releases/${release_id}" \
-f prerelease=false \
-f draft=false >/dev/null