name: Build WDTT Release on: push: branches: - "**" workflow_dispatch: jobs: build-release: name: Build release APK runs-on: ubuntu-latest permissions: contents: read env: ANDROID_MIN_API: "29" ANDROID_COMPILE_SDK: "35" ANDROID_BUILD_TOOLS: "35.0.0" ANDROID_NDK_VERSION: "27.2.12479018" SERVER_BINARY_NAME: "server" steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Java 17 uses: actions/setup-java@v4 with: distribution: temurin java-version: "17" - name: Set up Android SDK uses: android-actions/setup-android@v3 - name: Install Android SDK packages shell: bash run: | set -euo pipefail sdkmanager \ "platforms;android-${ANDROID_COMPILE_SDK}" \ "build-tools;${ANDROID_BUILD_TOOLS}" \ "ndk;${ANDROID_NDK_VERSION}" echo "ANDROID_NDK_HOME=${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}" >> "$GITHUB_ENV" - name: Set up Go uses: actions/setup-go@v5 with: go-version: "1.26.x" cache: true cache-dependency-path: | go.mod go_client/go.mod - name: Generate Go sums shell: bash run: | set -euo pipefail echo "== Root Go module ==" go mod tidy echo "== Go client module ==" cd go_client go mod tidy - name: Build Linux server binaries shell: bash run: | set -euo pipefail mkdir -p build/server echo "== Build server linux/amd64 ==" CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build \ -trimpath \ -ldflags="-s -w -checklinkname=0" \ -o build/server/wdtt-server-linux-amd64 \ ./server.go echo "== Build server linux/arm64 ==" CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ go build \ -trimpath \ -ldflags="-s -w -checklinkname=0" \ -o build/server/wdtt-server-linux-arm64 \ ./server.go chmod +x build/server/wdtt-server-linux-amd64 chmod +x build/server/wdtt-server-linux-arm64 - name: Put server binary into Android assets shell: bash run: | set -euo pipefail mkdir -p app/src/main/assets # Android deploy code expects this exact asset name. cp build/server/wdtt-server-linux-amd64 app/src/main/assets/${SERVER_BINARY_NAME} chmod +x app/src/main/assets/${SERVER_BINARY_NAME} echo "Server asset:" ls -lh app/src/main/assets/${SERVER_BINARY_NAME} - name: Build Android Go client libraries shell: bash run: | set -euo pipefail NDK_ROOT="${ANDROID_NDK_HOME}" TOOLCHAIN="$NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin" if [ ! -d "$TOOLCHAIN" ]; then echo "Android NDK toolchain not found: $TOOLCHAIN" exit 1 fi echo "Using NDK: $NDK_ROOT" echo "Using toolchain: $TOOLCHAIN" mkdir -p app/src/main/jniLibs/arm64-v8a mkdir -p app/src/main/jniLibs/armeabi-v7a mkdir -p app/src/main/jniLibs/x86_64 mkdir -p build/go-client cd go_client echo "== Build libclient.so for arm64-v8a ==" CGO_ENABLED=1 \ GOOS=android \ GOARCH=arm64 \ CC="$TOOLCHAIN/aarch64-linux-android${ANDROID_MIN_API}-clang" \ go build \ -buildmode=c-shared \ -trimpath \ -ldflags="-s -w -checklinkname=0" \ -o ../app/src/main/jniLibs/arm64-v8a/libclient.so \ . cp ../app/src/main/jniLibs/arm64-v8a/libclient.so \ ../build/go-client/libclient-arm64-v8a.so echo "== Build libclient.so for armeabi-v7a ==" CGO_ENABLED=1 \ GOOS=android \ GOARCH=arm \ GOARM=7 \ CC="$TOOLCHAIN/armv7a-linux-androideabi${ANDROID_MIN_API}-clang" \ go build \ -buildmode=c-shared \ -trimpath \ -ldflags="-s -w -checklinkname=0" \ -o ../app/src/main/jniLibs/armeabi-v7a/libclient.so \ . cp ../app/src/main/jniLibs/armeabi-v7a/libclient.so \ ../build/go-client/libclient-armeabi-v7a.so echo "== Build libclient.so for x86_64 ==" CGO_ENABLED=1 \ GOOS=android \ GOARCH=amd64 \ CC="$TOOLCHAIN/x86_64-linux-android${ANDROID_MIN_API}-clang" \ go build \ -buildmode=c-shared \ -trimpath \ -ldflags="-s -w -checklinkname=0" \ -o ../app/src/main/jniLibs/x86_64/libclient.so \ . cp ../app/src/main/jniLibs/x86_64/libclient.so \ ../build/go-client/libclient-x86_64.so cd .. echo "Built JNI libraries:" find app/src/main/jniLibs -type f -name "libclient.so" -exec ls -lh {} \; - name: Make Gradle wrapper executable shell: bash run: chmod +x ./gradlew - name: Generate temporary release keystore shell: bash run: | set -euo pipefail KEYSTORE_PASSWORD="wdtt_temp_release_password" KEY_PASSWORD="$KEYSTORE_PASSWORD" KEY_ALIAS="wdtt-release" rm -f release.keystore local.properties signing.env keytool -genkeypair -v -keystore release.keystore -storetype PKCS12 -storepass "$KEYSTORE_PASSWORD" -keypass "$KEY_PASSWORD" -alias "$KEY_ALIAS" -keyalg RSA -keysize 4096 -validity 10000 -dname "CN=WDTT,O=WDTT,C=LV" { echo "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" echo "KEY_PASSWORD=$KEY_PASSWORD" echo "KEY_ALIAS=$KEY_ALIAS" } > signing.env echo "Generated temporary release keystore" ls -lh release.keystore - name: Build Android release APK shell: bash run: | set -euo pipefail # No local.properties is created here, so Gradle produces unsigned release APKs. # The workflow signs them manually in the next step using apksigner. ./gradlew clean :app:assembleRelease --stacktrace - name: Sign release APKs manually shell: bash run: | set -euo pipefail source signing.env BUILD_TOOLS="$(find "$ANDROID_HOME/build-tools" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n 1)" ZIPALIGN="$BUILD_TOOLS/zipalign" APKSIGNER="$BUILD_TOOLS/apksigner" echo "Using zipalign: $ZIPALIGN" echo "Using apksigner: $APKSIGNER" mkdir -p build/signed-apk shopt -s nullglob unsigned_apks=(app/build/outputs/apk/release/*-unsigned.apk) if [ ${#unsigned_apks[@]} -eq 0 ]; then echo "No unsigned release APKs found" echo "Available APK outputs:" find app/build/outputs/apk -type f -name "*.apk" -print || true exit 1 fi for apk in "${unsigned_apks[@]}"; do name="$(basename "$apk" -unsigned.apk)" aligned="build/signed-apk/${name}-aligned.apk" signed="build/signed-apk/${name}-signed.apk" echo "== Align $apk ==" "$ZIPALIGN" -p -f 4 "$apk" "$aligned" echo "== Sign $aligned ==" "$APKSIGNER" sign --ks release.keystore --ks-key-alias "$KEY_ALIAS" --ks-pass "pass:$KEYSTORE_PASSWORD" --key-pass "pass:$KEY_PASSWORD" --v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled false --out "$signed" "$aligned" echo "== Verify $signed ==" "$APKSIGNER" verify --verbose --print-certs "$signed" done echo "Signed APKs:" ls -lh build/signed-apk/*-signed.apk - name: Collect build outputs shell: bash run: | set -euo pipefail mkdir -p build/artifacts/apk mkdir -p build/artifacts/server mkdir -p build/artifacts/go-client echo "== Signed APK outputs ==" find build/signed-apk -type f -name "*-signed.apk" -print -exec ls -lh {} \; cp build/signed-apk/*-signed.apk build/artifacts/apk/ cp build/server/* build/artifacts/server/ cp build/go-client/* build/artifacts/go-client/ echo "== Final artifacts ==" find build/artifacts -type f -exec ls -lh {} \; - name: Upload release APK artifact uses: actions/upload-artifact@v4 with: name: WDTT-release-apk path: build/artifacts/apk/*.apk if-no-files-found: error retention-days: 14 - name: Upload server binaries artifact uses: actions/upload-artifact@v4 with: name: WDTT-server-binaries path: build/artifacts/server/* if-no-files-found: error retention-days: 14 - name: Upload Go client libraries artifact uses: actions/upload-artifact@v4 with: name: WDTT-go-client-libraries path: build/artifacts/go-client/* if-no-files-found: error retention-days: 14