Skip to content

Build & test

Prerequisites

  • JDK 17 (Gradle's embedded Kotlin DSL crashes on newer JVMs — see README).
  • Android SDK with platforms;android-35 and build-tools;34.0.0.
  • The Gradle wrapper (never use a system Gradle).
  • Node.js only when editing the injected JS (web/src/*.ts) — the TypeScript sources are compiled to app/src/main/assets/*.js on the host; the Docker container has no Node.

A concrete JDK export that works:

export JAVA_HOME="$HOME/.jdks/temurin-17"

If the SDK isn't wired through local.properties, export it for the session:

export ANDROID_HOME=/path/to/android-sdk

Commands

All commands run via the wrapper (./gradlew):

Task Command
Debug APK ./gradlew assembleDebug
Release APK ./gradlew assembleRelease
Play-Store bundle ./gradlew bundleRelease
Lint (primary check) ./gradlew lint
Kotlin style ./gradlew ktlintCheck
Static analysis ./gradlew detekt
Unit tests ./gradlew testDebugUnitTest
Single test ./gradlew testDebugUnitTest --tests "com.spooty.app.SomeTest"
Auto-format ./gradlew ktlintFormat

Desktop companion (JVM)

The desktop/ module is a small Compose Desktop companion. jpackage distribution is configured via compose.desktop.application.nativeDistributions (EXE/MSI/ZIP targets):

Task Command
Run locally ./gradlew :desktop:run
Compile ./gradlew :desktop:build
Windows installer ./gradlew :desktop:packageExe (Windows host + WiX)

packageExe/packageMsi must run on a Windows host with the WiX Toolset installed (the on-demand build passes the tag version via -PspootyDesktopVersion=X.Y.Z). On Linux/macOS these tasks are unavailable. CI packages the installer on a windows-latest runner via the Desktop (Windows EXE) workflow — on v* tags it attaches the installer to the GitHub Release, and it can be triggered on demand with workflow_dispatch.

The local verification sweep

Before opening a PR, run the full sweep and wait for it to pass:

./gradlew ktlintCheck detekt lint testDebugUnitTest assembleDebug

If you changed the injected JS (web/src/*.ts), regenerate the committed assets on the host first — CI recompiles them and fails if they diverge:

npm ci --prefix web
npm run build --prefix web

The TypeScript build is idempotent: it only rewrites app/src/main/assets/*.js when the source actually changed.

Get the APK

You can sideload the app straight from CI — no local Android Studio needed:

  1. Open the Actions tab on GitHub.
  2. Run the CI workflow (or reuse the latest green run from main).
  3. Download the spooty-debug-apk artifact from the build job.
  4. Install on your phone with adb install --no-streaming app-debug.apk or by tapping the file once your device allows unknown sources.

On-demand builds

The CI workflow supports workflow_dispatch, so you can Run workflow → ⚙️ → Branch: main anytime to build a fresh APK without committing.

Release & signing

  • Signing is optional and driven by keystore.properties (gitignored). CI injects it from repository secrets when present; otherwise the release build is unsigned (debug-key) — fine for sideloading a personal app.
  • Tagging vX.Y.Z triggers the release job: assembleRelease + bundleRelease and a GitHub Release with both artifacts attached.

CI overview

  • CI (.github/workflows/ci.yml) — on pushes to main and testing, on PRs, and on demand:
  • compiles the TypeScript assets (web/src/ → app/src/main/assets/),
  • fails if the committed assets are stale (git diff --exit-code),
  • runs lint, ktlint, detekt, unit tests, assembleDebug,
  • uploads the debug APK — spooty-debug.apk on main/PRs, spooty-testing.apk when the push came from the testing branch.
  • Cloudflare Pages (.github/workflows/cloudflare-pages.yml) — on pushes touching docs/ or mkdocs.yml: builds the MkDocs site and direct-uploads it with Wrangler to https://spooty.pages.dev.
  • The release job runs on v* tags, recompiles the web assets first, and publishes APK
  • AAB from a GitHub Release.