recly

Developing Recly

Build · test

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.

Values filled in locally

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).