diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ad8b977 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,123 @@ +name: release + +# GitHub's build. The Gitea build is .gitea/workflows/build.yml. +# +# Pushing a tag vX.Y.Z builds the Linux and Windows packages and the Quest APK +# inside the build-image and attaches them to a GitHub release. A manual run +# builds the same files and keeps them as a workflow artifact without +# releasing them. +# +# The build-image (build-image/Dockerfile) is published to GHCR, tagged with a +# hash of the files that define it, and only built when that tag does not +# exist yet. +on: + push: + tags: + - "v*" + workflow_dispatch: + +permissions: + contents: read + +jobs: + image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + name: ${{ steps.name.outputs.name }} + steps: + - uses: actions/checkout@v4 + + - name: Name the image after the files that define it + id: name + run: | + set -euo pipefail + hash="$(cat build-image/Dockerfile build-image/docker-entrypoint.sh build-image/build-engine.sh | sha256sum | cut -c1-16)" + echo "name=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/questshock-builder:${hash}" >> "$GITHUB_OUTPUT" + + - name: Build and push the image if it does not exist yet + env: + IMAGE: ${{ steps.name.outputs.name }} + REGISTRY_USER: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + echo "$REGISTRY_PASSWORD" | docker login ghcr.io -u "$REGISTRY_USER" --password-stdin + if docker manifest inspect "$IMAGE" >/dev/null 2>&1; then + echo "$IMAGE already exists" + exit 0 + fi + # The image is large and the runner's disk is small. + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL + # The label links the package to this repository, so the build job + # can pull it with the workflow's token. + docker build -f build-image/Dockerfile \ + --label "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}" \ + -t "$IMAGE" . + docker push "$IMAGE" + + build: + needs: image + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + defaults: + run: + shell: bash + # The job runs inside the build-image, so `make` compiles directly instead + # of starting the image itself (QUESTSHOCK_BUILD_IMAGE is set in it). + container: + image: ${{ needs.image.outputs.name }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + # The version comes from `git describe --tags`. + fetch-depth: 0 + + - name: Build the Linux and Windows packages + run: make package + + # Needs network access for Gradle's own dependency resolution. + - name: Build the Quest APK + run: make apk + + - uses: actions/upload-artifact@v4 + with: + name: questshock + path: | + dist/shockolate-*-linux-*.tar.gz + dist/shockolate-*-windows-*.zip + dist/questshock-*-android-*.apk + + release: + if: startsWith(github.ref, 'refs/tags/') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + steps: + - uses: actions/download-artifact@v4 + with: + name: questshock + path: dist + + - name: Create the release, or update its files if it exists + run: | + set -euo pipefail + if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then + gh release upload "$GITHUB_REF_NAME" dist/* --clobber + else + gh release create "$GITHUB_REF_NAME" dist/* \ + --title "$GITHUB_REF_NAME" \ + --generate-notes \ + --notes "Sideload the APK onto a Meta Quest (see README section 5.1). The game data is not included: you need your own copy of System Shock: Enhanced Edition." + fi diff --git a/AGENTS.md b/AGENTS.md index e9e0dd7..c139621 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,7 +74,8 @@ These come from the maintainer. Follow them without being asked again. | `android/app/build.gradle` | Gradle project; the `stageEngine` task stages the patched engine. | | `android/force-rebuild.sh` | Stops Gradle daemons and wipes staged/build output for a fully fresh native build. | | `build-image/` | Docker build environment (`Dockerfile`, `VERSION`, `prepare-android-project.sh`, `build-apk.sh`, engine build scripts). | -| `res/` | Bundled resources: `questshock.config.default.json`, `controller_profiles/` (JSON + PNG per controller), `assets/` (where the user's own game data goes, git-ignored), `run.sh`/`run.bat`. | +| `res/` | Bundled resources: `questshock.config.default.json`, `controller_profiles/` (JSON + PNG per controller), `splash.png` (startup splash), `logo.png` (1024x1024 logo, master of the launcher icons in `android/app/src/main/res/mipmap-*/`), `logo_small.png` (the README header), `assets/` (where the user's own game data goes, git-ignored), `run.sh`/`run.bat`. | +| `.gitea/workflows/build.yml`, `.github/workflows/release.yml` | CI. Gitea is the primary repository and builds on every push; GitHub is a mirror whose workflow builds and publishes a release when a `vX.Y.Z` tag is pushed. Each platform has its own file. | | `Makefile` | `engine`, `dist`, `package`, `apk`, `android-studio`, `clean`, ... (see README section 2). | | `README.md`, `CONFIG.md`, `NOTICE.txt` | User docs, config reference, license notice. | diff --git a/README.md b/README.md index 1e8409c..cfed678 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,11 @@ sideloaded Android app, and needs your own copy of the game data. (see [CONFIG.md](CONFIG.md)); `res/controller_profiles/` - the per-controller illustrations and labels shown in the Controller Options tab (see [CONFIG.md](CONFIG.md)). Both are copied into the APK, as is - `res/splash.png`, the startup splash image. + `res/splash.png`, the startup splash image. `res/logo.png` is the 1024x1024 + logo, the master of the launcher icon + (`android/app/src/main/res/mipmap-*/ic_launcher.png` are scaled copies of it) + and the image to use for store listings; `res/logo_small.png` is the copy + shown at the top of this file. - `Makefile` - targets: - `all` (default) - alias for `dist`. - `build-image` - builds the Docker build-image (`./build-image.sh`). @@ -211,6 +215,25 @@ package on every push, using the build-image as its container (so no extra setup is needed in CI beyond the image itself), and publishes the resulting tarball to dl.ladkau.de. +### 4.3. GitHub releases + +The repository is developed on Gitea, and the GitHub repository is a mirror of +it. `.github/workflows/release.yml` is GitHub's own workflow (the Gitea build +is `.gitea/workflows/build.yml`): + +- Pushing a tag `vX.Y.Z` builds the Linux and Windows packages and the Quest + APK inside the build-image and attaches them to a GitHub release with the + same name. A manual run ("Run workflow") builds the same files without + releasing them and keeps them as a downloadable workflow artifact. +- The build-image is pushed to GitHub's container registry (`ghcr.io`) when it + does not exist there yet. Its tag is a hash of `build-image/Dockerfile`, + `docker-entrypoint.sh` and `build-engine.sh`, so a change to any of them + builds a new image (`build-image/VERSION` is not used). The first run is slow + because it builds the image. +- The APK is built with `assembleDebug`, so it is signed with the debug key of + the machine that built it. Android only updates an installed app from an APK + with the same signing key. + ## 5. Android / Quest build **Prerequisites:** diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index c618787..d5ad605 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index e6e0e7a..a27e243 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 27757bb..c3953a2 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index f2d9724..21321db 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 0f5e0f9..f9ea7cc 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/res/logo.png b/res/logo.png index dc20bca..9a17cf0 100644 Binary files a/res/logo.png and b/res/logo.png differ diff --git a/res/logo_small.png b/res/logo_small.png index d5c1a7d..ff7fcc2 100644 Binary files a/res/logo_small.png and b/res/logo_small.png differ