This commit is contained in:
@@ -69,7 +69,25 @@ jobs:
|
|||||||
dist/shockolate-*-linux-*.tar.gz
|
dist/shockolate-*-linux-*.tar.gz
|
||||||
dist/shockolate-*-windows-*.zip
|
dist/shockolate-*-windows-*.zip
|
||||||
|
|
||||||
|
# The release key is stored base64-encoded in ANDROID_KEYSTORE_BASE64
|
||||||
|
# (see README section 4.3). Without it the APK is a debug build.
|
||||||
|
- name: Prepare release signing
|
||||||
|
env:
|
||||||
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "$KEYSTORE_BASE64" ]; then
|
||||||
|
echo "No signing key: the APK will be a debug build"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "$KEYSTORE_BASE64" | base64 -d > "${RUNNER_TEMP:-/tmp}/questshock-release.jks"
|
||||||
|
echo "QUESTSHOCK_KEYSTORE=${RUNNER_TEMP:-/tmp}/questshock-release.jks" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build Quest APK
|
- name: Build Quest APK
|
||||||
|
env:
|
||||||
|
QUESTSHOCK_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
QUESTSHOCK_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
QUESTSHOCK_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
# QUESTSHOCK_BUILD_IMAGE is already set (see Preflight above), so
|
# QUESTSHOCK_BUILD_IMAGE is already set (see Preflight above), so
|
||||||
# this runs build-apk.sh directly instead of trying to docker-run
|
# this runs build-apk.sh directly instead of trying to docker-run
|
||||||
# the image again - same reasoning as `make package` above.
|
# the image again - same reasoning as `make package` above.
|
||||||
|
|||||||
@@ -83,8 +83,30 @@ jobs:
|
|||||||
- name: Build the Linux and Windows packages
|
- name: Build the Linux and Windows packages
|
||||||
run: make package
|
run: make package
|
||||||
|
|
||||||
|
# The release key is stored base64-encoded in ANDROID_KEYSTORE_BASE64; see
|
||||||
|
# README section 4.3. A tag build must be signed with it.
|
||||||
|
- name: Prepare release signing
|
||||||
|
env:
|
||||||
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ -z "$KEYSTORE_BASE64" ]; then
|
||||||
|
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
||||||
|
echo "::error::ANDROID_KEYSTORE_BASE64 is not set: a release APK must be signed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "No signing key: the APK will be a debug build"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "$KEYSTORE_BASE64" | base64 -d > "${RUNNER_TEMP:-/tmp}/questshock-release.jks"
|
||||||
|
echo "QUESTSHOCK_KEYSTORE=${RUNNER_TEMP:-/tmp}/questshock-release.jks" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
# Needs network access for Gradle's own dependency resolution.
|
# Needs network access for Gradle's own dependency resolution.
|
||||||
- name: Build the Quest APK
|
- name: Build the Quest APK
|
||||||
|
env:
|
||||||
|
QUESTSHOCK_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
QUESTSHOCK_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
QUESTSHOCK_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
run: make apk
|
run: make apk
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -38,3 +38,7 @@
|
|||||||
|
|
||||||
# Local registry credentials (see registry.env.example)
|
# Local registry credentials (see registry.env.example)
|
||||||
/registry.env
|
/registry.env
|
||||||
|
|
||||||
|
# Signing keys
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ These come from the maintainer. Follow them without being asked again.
|
|||||||
`make apk` and `make android-studio` overwrite `android/engine.properties`
|
`make apk` and `make android-studio` overwrite `android/engine.properties`
|
||||||
with container-only paths and break an open Android Studio project. What you
|
with container-only paths and break an open Android Studio project. What you
|
||||||
can do is dry-run patches (section 6) and run static consistency checks
|
can do is dry-run patches (section 6) and run static consistency checks
|
||||||
(section 12). Say plainly in your report that the change was not built or
|
(section 13). Say plainly in your report that the change was not built or
|
||||||
tested.
|
tested.
|
||||||
3. **`engine/` and `android/gl4es-src/` are never edited.** They are vendored
|
3. **`engine/` and `android/gl4es-src/` are never edited.** They are vendored
|
||||||
snapshots. Every change to them is a numbered patch under
|
snapshots. Every change to them is a numbered patch under
|
||||||
@@ -141,7 +141,7 @@ not print it or commit it.
|
|||||||
`qs_config_frame_tick()`) is called from `xr_input.c`.
|
`qs_config_frame_tick()`) is called from `xr_input.c`.
|
||||||
- JNI symbol names must match the Java `native` declarations exactly
|
- JNI symbol names must match the Java `native` declarations exactly
|
||||||
(`Java_de_ladkau_questshock_<Class>_<method>`). A mismatch only fails at
|
(`Java_de_ladkau_questshock_<Class>_<method>`). A mismatch only fails at
|
||||||
runtime, so check it (section 12).
|
runtime, so check it (section 13).
|
||||||
|
|
||||||
### 4.5 Off-screen views (`OverlayPanel`, `MenuOverlay`, `KeyboardOverlay`)
|
### 4.5 Off-screen views (`OverlayPanel`, `MenuOverlay`, `KeyboardOverlay`)
|
||||||
|
|
||||||
@@ -337,7 +337,19 @@ or directory must also be declared as `inputs.file(...)`/`inputs.dir(...)` of
|
|||||||
The proprietary game data (`res/data/`, `res/sound/`) is never bundled or
|
The proprietary game data (`res/data/`, `res/sound/`) is never bundled or
|
||||||
committed. The app refuses to start the engine until it is present.
|
committed. The app refuses to start the engine until it is present.
|
||||||
|
|
||||||
## 11. Code and documentation style
|
## 11. Signing
|
||||||
|
|
||||||
|
Distributed APKs are release builds signed with one fixed key, so users can
|
||||||
|
update in place. `build-image/build-apk.sh` reads the key from
|
||||||
|
`QUESTSHOCK_KEYSTORE`, `QUESTSHOCK_KEYSTORE_PASSWORD`, `QUESTSHOCK_KEY_ALIAS`
|
||||||
|
and `QUESTSHOCK_KEY_PASSWORD`; `android/app/build.gradle` applies it to the
|
||||||
|
`release` build type only when `QUESTSHOCK_KEYSTORE` is set. Both CI workflows
|
||||||
|
decode it from the `ANDROID_KEYSTORE_BASE64` secret. Without a key the build is
|
||||||
|
a debug APK (`...-debug.apk`), which is only for testing. Never commit a
|
||||||
|
keystore or a password, print them, or put them in a workflow file (README
|
||||||
|
section 4.3 lists the secrets).
|
||||||
|
|
||||||
|
## 12. Code and documentation style
|
||||||
|
|
||||||
- Match the surrounding code: naming, comment density, idiom. C is plain C with
|
- Match the surrounding code: naming, comment density, idiom. C is plain C with
|
||||||
`static` helpers and `//` comments; Java targets Android API 34 and uses only the SDK plus
|
`static` helpers and `//` comments; Java targets Android API 34 and uses only the SDK plus
|
||||||
@@ -353,7 +365,7 @@ committed. The app refuses to start the engine until it is present.
|
|||||||
- English, French and German strings live in `Strings.java`; keep the three
|
- English, French and German strings live in `Strings.java`; keep the three
|
||||||
entries of each key on the same `put(...)`.
|
entries of each key on the same `put(...)`.
|
||||||
|
|
||||||
## 12. Verifying a change without building
|
## 13. Verifying a change without building
|
||||||
|
|
||||||
Because you may not build (rule 2), check statically and say so in the report:
|
Because you may not build (rule 2), check statically and say so in the report:
|
||||||
|
|
||||||
@@ -375,7 +387,7 @@ Then report what was checked, what was not (build, on-device behaviour), and
|
|||||||
any device check the maintainer should do (for example a new patch that may
|
any device check the maintainer should do (for example a new patch that may
|
||||||
need `android/force-rebuild.sh`).
|
need `android/force-rebuild.sh`).
|
||||||
|
|
||||||
## 13. Commit messages
|
## 14. Commit messages
|
||||||
|
|
||||||
Draft only (rule 1). Use a short imperative subject (about 70 characters), a
|
Draft only (rule 1). Use a short imperative subject (about 70 characters), a
|
||||||
blank line, then a body that explains what the change does and why: the
|
blank line, then a body that explains what the change does and why: the
|
||||||
|
|||||||
@@ -69,9 +69,11 @@ sideloaded Android app, and needs your own copy of the game data.
|
|||||||
proprietary game assets (ships `res/GET_ASSETS.txt` in their place).
|
proprietary game assets (ships `res/GET_ASSETS.txt` in their place).
|
||||||
See "4.1. Packaging a distributable build" below.
|
See "4.1. Packaging a distributable build" below.
|
||||||
- `apk` - builds `dist/questshock-<version>-android-arm64.apk` for
|
- `apk` - builds `dist/questshock-<version>-android-arm64.apk` for
|
||||||
Android-based OpenXR headsets. Unlike every other target, this needs
|
Android-based OpenXR headsets (a debug build, `...-arm64-debug.apk`,
|
||||||
network access at build time (Gradle/AGP's own dependency
|
when there is no signing key - see "4.3. Releases and signing" below).
|
||||||
resolution). See "5. Android / Quest build" below.
|
Unlike every other target, this needs network access at build time
|
||||||
|
(Gradle/AGP's own dependency resolution). See "5. Android / Quest
|
||||||
|
build" below.
|
||||||
- `android-studio` - stages `engine/`/`android/gl4es-src/` (scratch,
|
- `android-studio` - stages `engine/`/`android/gl4es-src/` (scratch,
|
||||||
patched copies) and the Android prebuilts with host-resolvable paths,
|
patched copies) and the Android prebuilts with host-resolvable paths,
|
||||||
so Android Studio can compile and deploy `android/` natively instead
|
so Android Studio can compile and deploy `android/` natively instead
|
||||||
@@ -215,7 +217,7 @@ 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
|
extra setup is needed in CI beyond the image itself), and publishes the
|
||||||
resulting tarball to dl.ladkau.de.
|
resulting tarball to dl.ladkau.de.
|
||||||
|
|
||||||
### 4.3. GitHub releases
|
### 4.3. Releases and signing
|
||||||
|
|
||||||
The repository is developed on Gitea, and the GitHub repository is a mirror of
|
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
|
it. `.github/workflows/release.yml` is GitHub's own workflow (the Gitea build
|
||||||
@@ -230,9 +232,33 @@ is `.gitea/workflows/build.yml`):
|
|||||||
`docker-entrypoint.sh` and `build-engine.sh`, so a change to any of them
|
`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
|
builds a new image (`build-image/VERSION` is not used). The first run is slow
|
||||||
because it builds the image.
|
because it builds the image.
|
||||||
- The APK is built with `assembleDebug`, so it is signed with the debug key of
|
- The APK is signed with the release key described below.
|
||||||
the machine that built it. Android only updates an installed app from an APK
|
|
||||||
with the same signing key.
|
**Signing.** Android only updates an installed app from an APK signed with the
|
||||||
|
same key, so every distributed APK is signed with one fixed key. `make apk`
|
||||||
|
(`build-image/build-apk.sh`) builds and signs the release APK when
|
||||||
|
`QUESTSHOCK_KEYSTORE` is set, together with `QUESTSHOCK_KEYSTORE_PASSWORD` and
|
||||||
|
optionally `QUESTSHOCK_KEY_ALIAS` (default `questshock`) and
|
||||||
|
`QUESTSHOCK_KEY_PASSWORD` (default: the keystore password). Without a keystore
|
||||||
|
it builds a debug APK, `questshock-<version>-android-arm64-debug.apk`, signed
|
||||||
|
with a per-machine debug key. That is what Android Studio's Run uses too; an
|
||||||
|
installed build has to be uninstalled before one signed with another key can be
|
||||||
|
installed.
|
||||||
|
|
||||||
|
Create the key once and keep the file and its password safe: without them,
|
||||||
|
users have to uninstall the app to get an update.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
keytool -genkeypair -v -storetype PKCS12 -keystore questshock-release.jks \
|
||||||
|
-alias questshock -keyalg RSA -keysize 4096 -validity 10000
|
||||||
|
base64 -w0 questshock-release.jks # the value of ANDROID_KEYSTORE_BASE64
|
||||||
|
```
|
||||||
|
|
||||||
|
Both CI systems read the key from these secrets: `ANDROID_KEYSTORE_BASE64`,
|
||||||
|
`ANDROID_KEYSTORE_PASSWORD`, and optionally `ANDROID_KEY_ALIAS` and
|
||||||
|
`ANDROID_KEY_PASSWORD`. Without `ANDROID_KEYSTORE_BASE64`, the Gitea build
|
||||||
|
produces a debug APK and the GitHub build fails on a tag. `*.jks` and
|
||||||
|
`*.keystore` are git-ignored.
|
||||||
|
|
||||||
## 5. Android / Quest build
|
## 5. Android / Quest build
|
||||||
|
|
||||||
@@ -248,7 +274,8 @@ is `.gitea/workflows/build.yml`):
|
|||||||
apk` needs its own separate toolchain - see "5.2. Building natively in
|
apk` needs its own separate toolchain - see "5.2. Building natively in
|
||||||
Android Studio" below.
|
Android Studio" below.
|
||||||
|
|
||||||
`make apk` builds `dist/questshock-<version>-android-arm64.apk` - an
|
`make apk` builds `dist/questshock-<version>-android-arm64.apk` (or
|
||||||
|
`...-arm64-debug.apk`, see "4.3. Releases and signing" above) - an
|
||||||
immersive OpenXR app (see `android/app/src/main/cpp/xr_session.c`) that
|
immersive OpenXR app (see `android/app/src/main/cpp/xr_session.c`) that
|
||||||
can be sideloaded onto any Android-based VR headset with OpenXR support
|
can be sideloaded onto any Android-based VR headset with OpenXR support
|
||||||
(Meta Quest, Pico, etc. - see "1. Design principles" above), not just one
|
(Meta Quest, Pico, etc. - see "1. Design principles" above), not just one
|
||||||
|
|||||||
@@ -212,9 +212,28 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The release key comes from the environment (build-image/build-apk.sh,
|
||||||
|
// CI secrets). Without it there is no release signing, and the release
|
||||||
|
// build type is left unsigned.
|
||||||
|
def releaseKeystore = System.getenv("QUESTSHOCK_KEYSTORE")
|
||||||
|
def releaseStorePassword = System.getenv("QUESTSHOCK_KEYSTORE_PASSWORD")
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
if (releaseKeystore) {
|
||||||
|
storeFile file(releaseKeystore)
|
||||||
|
storePassword releaseStorePassword
|
||||||
|
keyAlias System.getenv("QUESTSHOCK_KEY_ALIAS") ?: "questshock"
|
||||||
|
keyPassword System.getenv("QUESTSHOCK_KEY_PASSWORD") ?: releaseStorePassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
|
if (releaseKeystore) {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,16 +24,33 @@ VERSION="$("$REPO_ROOT/build-image/version.sh")"
|
|||||||
VERSION_CODE="$(git -C "$REPO_ROOT" rev-list --count HEAD)"
|
VERSION_CODE="$(git -C "$REPO_ROOT" rev-list --count HEAD)"
|
||||||
echo "Version: $VERSION (versionCode $VERSION_CODE)"
|
echo "Version: $VERSION (versionCode $VERSION_CODE)"
|
||||||
|
|
||||||
echo "== Building the APK (gradlew assembleDebug) =="
|
# A release APK is built and signed when QUESTSHOCK_KEYSTORE is set (with
|
||||||
|
# QUESTSHOCK_KEYSTORE_PASSWORD, and optionally QUESTSHOCK_KEY_ALIAS,
|
||||||
|
# default "questshock", and QUESTSHOCK_KEY_PASSWORD, default the keystore
|
||||||
|
# password). Without it the APK is a debug build, signed with a per-machine
|
||||||
|
# debug key.
|
||||||
|
if [ -n "${QUESTSHOCK_KEYSTORE:-}" ]; then
|
||||||
|
[ -f "$QUESTSHOCK_KEYSTORE" ] || { echo "BUILD FAIL: QUESTSHOCK_KEYSTORE ($QUESTSHOCK_KEYSTORE) does not exist" >&2; exit 1; }
|
||||||
|
[ -n "${QUESTSHOCK_KEYSTORE_PASSWORD:-}" ] || { echo "BUILD FAIL: QUESTSHOCK_KEYSTORE_PASSWORD is not set" >&2; exit 1; }
|
||||||
|
GRADLE_TASK=assembleRelease
|
||||||
|
APK="$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk"
|
||||||
|
PKG_SUFFIX=""
|
||||||
|
else
|
||||||
|
echo "== QUESTSHOCK_KEYSTORE is not set: building a debug APK, not for distribution =="
|
||||||
|
GRADLE_TASK=assembleDebug
|
||||||
|
APK="$ANDROID_DIR/app/build/outputs/apk/debug/app-debug.apk"
|
||||||
|
PKG_SUFFIX="-debug"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== Building the APK (gradlew $GRADLE_TASK) =="
|
||||||
cd "$ANDROID_DIR"
|
cd "$ANDROID_DIR"
|
||||||
./gradlew --no-daemon assembleDebug \
|
./gradlew --no-daemon "$GRADLE_TASK" \
|
||||||
"-PquestshockVersionName=$VERSION" "-PquestshockVersionCode=$VERSION_CODE"
|
"-PquestshockVersionName=$VERSION" "-PquestshockVersionCode=$VERSION_CODE"
|
||||||
|
|
||||||
APK="$ANDROID_DIR/app/build/outputs/apk/debug/app-debug.apk"
|
[ -f "$APK" ] || { echo "BUILD FAIL: $APK not found after $GRADLE_TASK" >&2; exit 1; }
|
||||||
[ -f "$APK" ] || { echo "BUILD FAIL: $APK not found after assembleDebug" >&2; exit 1; }
|
|
||||||
|
|
||||||
mkdir -p "$REPO_ROOT/dist"
|
mkdir -p "$REPO_ROOT/dist"
|
||||||
PKG_NAME="questshock-$VERSION-android-arm64.apk"
|
PKG_NAME="questshock-$VERSION-android-arm64$PKG_SUFFIX.apk"
|
||||||
cp "$APK" "$REPO_ROOT/dist/$PKG_NAME"
|
cp "$APK" "$REPO_ROOT/dist/$PKG_NAME"
|
||||||
|
|
||||||
echo "== Done =="
|
echo "== Done =="
|
||||||
|
|||||||
Reference in New Issue
Block a user