The Makefile wraps every command below with the flags that matter (JDK 21 and the Android SDK
path for Gradle, ARCHS=arm64 for simulators, -collect-test-diagnostics never for xctest):
make test # core · android · windows unit tests (JVM)
make core # build the XCFramework and stage it into apple/RecKit (do this first on a Mac)
make mac # build Recly Mac make mac-test # RecKit tests on macOS
make ios # Recly on the iOS simulator make watch # Recly Watch on the watch simulator
make apk # phone debug APK make spec # validate spec/examples
make help # the full list — IOS_SIM / WATCH_SIM override the simulator names
What the targets run, if you need the commands themselves. Gradle needs JDK 21 and the Android SDK path:
export JAVA_HOME=/opt/homebrew/opt/openjdk@21
export ANDROID_HOME=/opt/homebrew/share/android-commandlinetools
Core · Android · Windows (JVM) — the unit tests, in one go:
./gradlew :core:jvmTest :android:app:testDebugUnitTest :android:wear:testDebugUnitTest \
:android:recording:testDebugUnitTest :android:datalayer:testDebugUnitTest :windows:app:test
./gradlew :android:app:assembleDebug # phone APK
./gradlew :windows:app:run # run the Windows shell on the dev host
Apple (requires macOS) — build the XCFramework and stage it into RecKit first:
./apple/scripts/build-core.sh # :core:assembleXCFramework → apple/RecKit/Frameworks/
./apple/scripts/setup-local-signing.sh # once per Mac; keeps Keychain grants across rebuilds
xcodebuild -workspace apple/Rec.xcworkspace -scheme RecKit -destination 'platform=macOS' -collect-test-diagnostics never test
xcodebuild -workspace apple/Rec.xcworkspace -scheme 'Recly Mac' -destination 'platform=macOS' build
./apple/scripts/build-sim.sh Recly "iOS Simulator" "iPhone 17 Pro" build
./apple/scripts/build-sim.sh "Recly Watch" "watchOS Simulator" "Apple Watch Series 11 (46mm)" build
Simulator builds go through build-sim.sh, which pins ARCHS=arm64 on the command line: the
core ships arm64-only simulator slices, and a command-line build setting is the only thing that
reaches SwiftPM package targets (project-level ARCHS/EXCLUDED_ARCHS and arch-qualified
destinations do not). Calling xcodebuild on a simulator scheme without it fails inside RecKit
with “cannot find type … in scope” for ReclyCore types — the x86_64 half of the build.
Windows capture helper (Rust):
cd windows/capture-helper && cargo test # rules, boundaries, sha256, drift harness
cargo build --release # the real capture binary, on Windows
Spec validation · local webhook receiver (Node):
cd spec && npm ci && npm run validate # validate the examples against the JSON Schemas
node scripts/webhook-receiver.mjs --port 8787 --secret whsec_… # a receiver that checks signature & schema
To cut a release: make apk and ./gradlew :android:wear:assembleDebug, then
gh release create v0.1.0 <phone.apk> <watch.apk> --target main --prerelease.
Release signing (Android): Play App Signing holds the app signing key; this tree only ever
sees the upload key. Create it once, outside the repository (*.jks is gitignored anyway):
keytool -genkeypair -v -keystore ~/.recly/upload.jks -alias upload -keyalg RSA -keysize 2048 -validity 10000
Then point the build at it, in local.properties or the environment (REC_UPLOAD_STORE_FILE,
REC_UPLOAD_STORE_PASSWORD, REC_UPLOAD_KEY_ALIAS, REC_UPLOAD_KEY_PASSWORD):
upload.storeFile=/Users/you/.recly/upload.jks
upload.storePassword=…
upload.keyAlias=upload
upload.keyPassword=…
make aab builds the phone and watch bundles (android/*/build/outputs/bundle/release/), both
signed with that key — Play pairs the two only when their signatures match. Without the key the
release bundles are unsigned and Play refuses them. After the first upload, Play Console → Setup →
App signing shows the app signing key’s SHA-1: register an Android OAuth client with it in the
GCP project, next to the debug one, or sign-in fails in every Play-installed build.
Releases: macOS via apple/scripts/release-mac.sh (Developer ID + notarization + DMG);
the Windows MSI via ./gradlew :windows:app:packageMsi (Windows hosts only — see
windows/README.md).
Icons, when regenerating (macOS only): swift scripts/render-icons.swift, then
python3 scripts/make-ico.py --check windows/app/src/main/icons/recly.ico.
Client files (google-services.json, GoogleService-Info.plist, client_secret*.json) and
OAuth client IDs are never committed. While either Apple app’s Info.plist GIDClientID is a
placeholder, its sign-in button is disabled and stopped recordings park their jobs as
NEEDS_AUTH.
| App | Info.plist | Client type | Bundle ID |
|---|---|---|---|
| RecMac | apple/RecMac/RecMac/Info.plist |
iOS | app.recly.mac |
| RecPhone | apple/RecPhone/RecPhone/Info.plist |
iOS | app.recly |
Create a client of that type and bundle ID in the GCP console, then copy
apple/Config/Local.xcconfig.example to apple/Config/Local.xcconfig (gitignored) and fill in
the four values — each app’s issued ID and its reversed client ID
(com.googleusercontent.apps.{number}-{hash}). Both Info.plist files read them as build
settings, so nothing you fill in shows up in the tracked tree. The consent screen must carry
exactly one scope:
drive.file (recly.md §6).