# Runs on version tags (e.g. `v1.0.0`): builds runnable executables for # Linux and Windows plus a WebAssembly build, uploads them as workflow # artifacts, and publishes them to a Gitea release. # # macOS: a native macOS binary needs a macOS runner (Odin cannot cross-link # to Mach-O from Linux). Add a `build-macos` job mirroring `build-linux` # with `runs-on: ` and the `odin-macos-amd64-...` tarball when # such a runner is available. name: Release on: push: tags: - 'v*' concurrency: group: desi-explorer-release-${{ github.ref }} cancel-in-progress: true permissions: contents: read env: # Odin release to use (matches `dev-2026-07`, the version used locally). ODIN_VERSION: dev-2026-07 jobs: build-linux: name: Linux executable runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Install Odin ${{ env.ODIN_VERSION }} run: scripts/install_odin.sh - name: Install clang run: | if command -v sudo >/dev/null 2>&1; then sudo apt-get update sudo apt-get install -y clang else apt-get update apt-get install -y clang fi - name: Build run: | mkdir -p lib/local build/linux odin build src -o:speed -collection:lib=lib/local -out:build/linux/desi_explorer - name: Package run: | mkdir -p dist ARCH="$(uname -m)" case "$ARCH" in x86_64 | amd64) OUT="amd64" ;; aarch64 | arm64) OUT="arm64" ;; *) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; esac tar -C build/linux -czf "dist/desi-explorer-linux-${OUT}.tar.gz" desi_explorer - uses: christopherHX/gitea-upload-artifact@v4 with: name: linux path: dist/desi-explorer-linux-*.tar.gz build-windows: name: Windows executable (cross-compiled) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Install Odin ${{ env.ODIN_VERSION }} run: scripts/install_odin.sh # The Linux Odin tarball ships prebuilt raylib libs for Linux, macOS # and wasm but not Windows, so grab the Windows bundle for the vendored # raylib DLL + import lib. - name: Fetch Odin Windows bundle (vendored raylib Windows libs) run: | curl -fL -o /tmp/odin-win.zip \ "https://github.com/odin-lang/Odin/releases/download/${{ env.ODIN_VERSION }}/odin-windows-amd64-${{ env.ODIN_VERSION }}.zip" python3 - <<'EOF' import zipfile zf = zipfile.ZipFile('/tmp/odin-win.zip') for name in zf.namelist(): if name.startswith('dist/vendor/raylib/windows/') and not name.endswith('/'): zf.extract(name, '/tmp/odin-win') EOF - name: Install MinGW cross-compiler run: | if command -v sudo >/dev/null 2>&1; then sudo apt-get update sudo apt-get install -y gcc-mingw-w64-x86-64 else apt-get update apt-get install -y gcc-mingw-w64-x86-64 fi # Odin emits a Windows object; MinGW links it against the vendored # raylib DLL (RAYLIB_SHARED=true keeps the DLL's own ABI intact instead # of fighting the MSVC-built static lib). raylib.dll must be shipped # next to desi_explorer.exe. - name: Build run: | mkdir -p lib/local build/windows odin build src -target:windows_amd64 -build-mode:obj \ -define:RAYLIB_SHARED=true -o:speed \ -collection:lib=lib/local -out:build/windows/desi_explorer x86_64-w64-mingw32-gcc -O2 -o build/windows/desi_explorer.exe \ build/windows/desi_explorer.obj \ /tmp/odin-win/dist/vendor/raylib/windows/raylibdll.lib \ -lwinmm -lgdi32 -luser32 -lshell32 -lbcrypt \ -Wl,--defsym=__chkstk=___chkstk_ms cp /tmp/odin-win/dist/vendor/raylib/windows/raylib.dll build/windows/ - name: Package run: | mkdir -p dist python3 -c "import zipfile; z=zipfile.ZipFile('dist/desi-explorer-windows-amd64.zip','w',zipfile.ZIP_DEFLATED); [z.write('build/windows/'+f,f) for f in ['desi_explorer.exe','raylib.dll']]" - uses: christopherHX/gitea-upload-artifact@v4 with: name: windows path: dist/desi-explorer-windows-amd64.zip build-web: name: Web (WebAssembly) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Install Odin ${{ env.ODIN_VERSION }} run: scripts/install_odin.sh - name: Install Emscripten run: | git clone --depth 1 https://github.com/emscripten-core/emsdk.git /tmp/emsdk /tmp/emsdk/emsdk install latest /tmp/emsdk/emsdk activate latest - name: Build WebAssembly run: | source /tmp/emsdk/emsdk_env.sh scripts/build_web.sh - name: Package run: | mkdir -p dist cd build/web python3 - <<'EOF' import glob, zipfile z = zipfile.ZipFile('../../dist/desi-explorer-web.zip', 'w', zipfile.ZIP_DEFLATED) for f in glob.glob('*'): z.write(f) EOF - uses: christopherHX/gitea-upload-artifact@v4 with: name: web path: dist/desi-explorer-web.zip release: name: Publish Gitea release needs: [build-linux, build-windows, build-web] runs-on: ubuntu-latest permissions: contents: write steps: - uses: christopherHX/gitea-download-artifact@v4 with: path: artifacts merge-multiple: true - name: Create release run: | API="${{ gitea.api_url }}" REPO="${{ gitea.repository }}" TAG="${{ gitea.ref_name }}" TOKEN="${{ github.token }}" body="$(curl -sS -H "Authorization: token ${TOKEN}" \ "${API}/repos/${REPO}/releases/tags/${TAG}")" release_id="$(printf '%s' "$body" | python3 -c \ "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)" if [ -z "$release_id" ]; then release_id="$(curl -fsS -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\"}" \ "${API}/repos/${REPO}/releases" | python3 -c \ "import sys,json; print(json.load(sys.stdin)['id'])")" fi echo "release_id=${release_id}" >> "$GITHUB_ENV" echo "Release ${TAG} -> id ${release_id}" - name: Upload release assets run: | API="${{ gitea.api_url }}" REPO="${{ gitea.repository }}" TOKEN="${{ github.token }}" for file in artifacts/*; do [ -f "$file" ] || continue name="$(basename "$file")" echo "Uploading ${name}..." curl -fsS -X POST \ -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/octet-stream" \ --data-binary "@${file}" \ "${API}/repos/${REPO}/releases/${release_id}/assets?name=${name}" >/dev/null done