feat: integrate dual-flavor CI, fix keystore heredoc, ABI split control, and backend startup order
- Add embedded job to android-build.yml and release.yml with Go build step - Fix keystore.properties generation using printf instead of heredoc to avoid whitespace issues - Control ABI split via -PenableAbiSplit Gradle property (disabled by default, enabled only in CI embedded builds) - Wire EmbeddedBackendLifecycle in FlavorModule DI, replacing NoopBackendLifecycle - Fix WebSocket connect race by combining backendLifecycle.state with settings flow, waiting for BackendState.RUNNING before connecting - Add per-architecture Makefile targets (build-android-arm64, build-android-x86_64, build-android-arm) and clean-android target - Fix termux APK filename consistency in release workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
19333f6685
commit
db2373b40d
6 changed files with 223 additions and 37 deletions
74
.github/workflows/android-build.yml
vendored
74
.github/workflows/android-build.yml
vendored
|
|
@ -5,15 +5,25 @@ on:
|
|||
branches: ["main"]
|
||||
paths:
|
||||
- "android/**"
|
||||
- "cmd/**"
|
||||
- "pkg/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "Makefile"
|
||||
- ".github/workflows/android-build.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "android/**"
|
||||
- "cmd/**"
|
||||
- "pkg/**"
|
||||
- "go.mod"
|
||||
- "go.sum"
|
||||
- "Makefile"
|
||||
- ".github/workflows/android-build.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
termux:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
|
|
@ -43,12 +53,8 @@ jobs:
|
|||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: |
|
||||
cat > keystore.properties <<EOF
|
||||
storeFile=release.keystore
|
||||
storePassword=$STORE_PASSWORD
|
||||
keyAlias=$KEY_ALIAS
|
||||
keyPassword=$KEY_PASSWORD
|
||||
EOF
|
||||
printf 'storeFile=release.keystore\nstorePassword=%s\nkeyAlias=%s\nkeyPassword=%s\n' \
|
||||
"$STORE_PASSWORD" "$KEY_ALIAS" "$KEY_PASSWORD" > keystore.properties
|
||||
|
||||
- name: Build release APK
|
||||
run: ./gradlew assembleTermuxRelease
|
||||
|
|
@ -56,5 +62,55 @@ jobs:
|
|||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: clawdroid-release-apk
|
||||
path: android/app/build/outputs/apk/termuxRelease/*.apk
|
||||
name: clawdroid-termux-release-apk
|
||||
path: android/app/build/outputs/apk/termux/release/*.apk
|
||||
|
||||
embedded:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build Go binaries for Android
|
||||
run: make build-android
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Decode keystore
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
working-directory: android
|
||||
run: |
|
||||
echo "$KEYSTORE_BASE64" | base64 -d > release.keystore
|
||||
|
||||
- name: Create keystore.properties
|
||||
env:
|
||||
STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
working-directory: android
|
||||
run: |
|
||||
printf 'storeFile=release.keystore\nstorePassword=%s\nkeyAlias=%s\nkeyPassword=%s\n' \
|
||||
"$STORE_PASSWORD" "$KEY_ALIAS" "$KEY_PASSWORD" > keystore.properties
|
||||
|
||||
- name: Build release APKs
|
||||
working-directory: android
|
||||
run: ./gradlew assembleEmbeddedRelease -PenableAbiSplit
|
||||
|
||||
- name: Upload APKs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: clawdroid-embedded-release-apks
|
||||
path: android/app/build/outputs/apk/embedded/release/*.apk
|
||||
|
|
|
|||
94
.github/workflows/release.yml
vendored
94
.github/workflows/release.yml
vendored
|
|
@ -77,8 +77,8 @@ jobs:
|
|||
--draft=${{ inputs.draft }} \
|
||||
--prerelease=${{ inputs.prerelease }}
|
||||
|
||||
android:
|
||||
name: Android Release APK
|
||||
android-termux:
|
||||
name: Android Termux APK
|
||||
needs: create-tag
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
|
|
@ -112,24 +112,94 @@ jobs:
|
|||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: |
|
||||
cat > keystore.properties <<EOF
|
||||
storeFile=release.keystore
|
||||
storePassword=$STORE_PASSWORD
|
||||
keyAlias=$KEY_ALIAS
|
||||
keyPassword=$KEY_PASSWORD
|
||||
EOF
|
||||
printf 'storeFile=release.keystore\nstorePassword=%s\nkeyAlias=%s\nkeyPassword=%s\n' \
|
||||
"$STORE_PASSWORD" "$KEY_ALIAS" "$KEY_PASSWORD" > keystore.properties
|
||||
|
||||
- name: Build release APK
|
||||
run: ./gradlew assembleTermuxRelease
|
||||
|
||||
- name: Rename APK with version
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
mv app/build/outputs/apk/termuxRelease/app-termux-release.apk \
|
||||
app/build/outputs/apk/termuxRelease/clawdroid-${{ inputs.tag }}.apk
|
||||
mv app/build/outputs/apk/termux/release/app-termux-release.apk \
|
||||
app/build/outputs/apk/termux/release/clawdroid-termux-"$RELEASE_TAG".apk
|
||||
|
||||
- name: Upload APK to release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
gh release upload "${{ inputs.tag }}" \
|
||||
app/build/outputs/apk/termuxRelease/clawdroid-${{ inputs.tag }}.apk
|
||||
gh release upload "$RELEASE_TAG" \
|
||||
app/build/outputs/apk/termux/release/clawdroid-termux-"$RELEASE_TAG".apk
|
||||
|
||||
android-embedded:
|
||||
name: Android Embedded APKs
|
||||
needs: create-tag
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout tag
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build Go binaries for Android
|
||||
run: make build-android
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Decode keystore
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
working-directory: android
|
||||
run: echo "$KEYSTORE_BASE64" | base64 -d > release.keystore
|
||||
|
||||
- name: Create keystore.properties
|
||||
env:
|
||||
STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
working-directory: android
|
||||
run: |
|
||||
printf 'storeFile=release.keystore\nstorePassword=%s\nkeyAlias=%s\nkeyPassword=%s\n' \
|
||||
"$STORE_PASSWORD" "$KEY_ALIAS" "$KEY_PASSWORD" > keystore.properties
|
||||
|
||||
- name: Build release APKs
|
||||
working-directory: android
|
||||
run: ./gradlew assembleEmbeddedRelease -PenableAbiSplit
|
||||
|
||||
- name: Rename APKs with version
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
working-directory: android/app/build/outputs/apk/embedded/release
|
||||
run: |
|
||||
mv app-embedded-arm64-v8a-release.apk clawdroid-"$RELEASE_TAG"-arm64-v8a.apk
|
||||
mv app-embedded-armeabi-v7a-release.apk clawdroid-"$RELEASE_TAG"-armeabi-v7a.apk
|
||||
mv app-embedded-x86_64-release.apk clawdroid-"$RELEASE_TAG"-x86_64.apk
|
||||
mv app-embedded-universal-release.apk clawdroid-"$RELEASE_TAG"-universal.apk
|
||||
|
||||
- name: Upload APKs to release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
working-directory: android/app/build/outputs/apk/embedded/release
|
||||
run: |
|
||||
gh release upload "$RELEASE_TAG" \
|
||||
clawdroid-"$RELEASE_TAG"-arm64-v8a.apk \
|
||||
clawdroid-"$RELEASE_TAG"-armeabi-v7a.apk \
|
||||
clawdroid-"$RELEASE_TAG"-x86_64.apk \
|
||||
clawdroid-"$RELEASE_TAG"-universal.apk
|
||||
|
|
|
|||
53
Makefile
53
Makefile
|
|
@ -1,4 +1,4 @@
|
|||
.PHONY: all build install uninstall clean help test build-android
|
||||
.PHONY: all build install uninstall clean help test build-android build-android-arm64 build-android-x86_64 build-android-arm clean-android
|
||||
|
||||
# Build variables
|
||||
BINARY_NAME=clawdroid
|
||||
|
|
@ -23,6 +23,9 @@ INSTALL_PREFIX?=$(HOME)/.local
|
|||
INSTALL_BIN_DIR=$(INSTALL_PREFIX)/bin
|
||||
INSTALL_MAN_DIR=$(INSTALL_PREFIX)/share/man/man1
|
||||
|
||||
# Android
|
||||
ANDROID_JNILIBS_DIR=android/app/src/embedded/jniLibs
|
||||
|
||||
# Workspace and Skills
|
||||
CLAWDROID_HOME?=$(HOME)/.clawdroid
|
||||
WORKSPACE_DIR?=$(CLAWDROID_HOME)/workspace
|
||||
|
|
@ -72,19 +75,49 @@ build-all: generate
|
|||
GOOS=linux GOARCH=arm $(GO) build $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm ./$(CMD_DIR)
|
||||
@echo "All builds complete"
|
||||
|
||||
## build-android: Build clawdroid for Android (embedded flavor jniLibs)
|
||||
## build-android: Build clawdroid for Android (all architectures)
|
||||
build-android: generate
|
||||
@echo "Building $(BINARY_NAME) for Android..."
|
||||
@mkdir -p android/app/src/embedded/jniLibs/arm64-v8a
|
||||
@mkdir -p android/app/src/embedded/jniLibs/x86_64
|
||||
@mkdir -p android/app/src/embedded/jniLibs/armeabi-v7a
|
||||
@echo "Building $(BINARY_NAME) for Android (all architectures)..."
|
||||
@mkdir -p $(ANDROID_JNILIBS_DIR)/arm64-v8a
|
||||
@mkdir -p $(ANDROID_JNILIBS_DIR)/x86_64
|
||||
@mkdir -p $(ANDROID_JNILIBS_DIR)/armeabi-v7a
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -trimpath $(LDFLAGS) \
|
||||
-o android/app/src/embedded/jniLibs/arm64-v8a/libclawdroid.so ./$(CMD_DIR)
|
||||
-o $(ANDROID_JNILIBS_DIR)/arm64-v8a/libclawdroid.so ./$(CMD_DIR)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -trimpath $(LDFLAGS) \
|
||||
-o android/app/src/embedded/jniLibs/x86_64/libclawdroid.so ./$(CMD_DIR)
|
||||
-o $(ANDROID_JNILIBS_DIR)/x86_64/libclawdroid.so ./$(CMD_DIR)
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 $(GO) build -trimpath $(LDFLAGS) \
|
||||
-o android/app/src/embedded/jniLibs/armeabi-v7a/libclawdroid.so ./$(CMD_DIR)
|
||||
@echo "Android build complete"
|
||||
-o $(ANDROID_JNILIBS_DIR)/armeabi-v7a/libclawdroid.so ./$(CMD_DIR)
|
||||
@echo "Android build complete (all architectures)"
|
||||
|
||||
## build-android-arm64: Build clawdroid for Android (arm64-v8a only)
|
||||
build-android-arm64: generate
|
||||
@echo "Building $(BINARY_NAME) for Android (arm64-v8a)..."
|
||||
@mkdir -p $(ANDROID_JNILIBS_DIR)/arm64-v8a
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -trimpath $(LDFLAGS) \
|
||||
-o $(ANDROID_JNILIBS_DIR)/arm64-v8a/libclawdroid.so ./$(CMD_DIR)
|
||||
@echo "Android build complete (arm64-v8a)"
|
||||
|
||||
## build-android-x86_64: Build clawdroid for Android (x86_64 only)
|
||||
build-android-x86_64: generate
|
||||
@echo "Building $(BINARY_NAME) for Android (x86_64)..."
|
||||
@mkdir -p $(ANDROID_JNILIBS_DIR)/x86_64
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -trimpath $(LDFLAGS) \
|
||||
-o $(ANDROID_JNILIBS_DIR)/x86_64/libclawdroid.so ./$(CMD_DIR)
|
||||
@echo "Android build complete (x86_64)"
|
||||
|
||||
## build-android-arm: Build clawdroid for Android (armeabi-v7a only)
|
||||
build-android-arm: generate
|
||||
@echo "Building $(BINARY_NAME) for Android (armeabi-v7a)..."
|
||||
@mkdir -p $(ANDROID_JNILIBS_DIR)/armeabi-v7a
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 $(GO) build -trimpath $(LDFLAGS) \
|
||||
-o $(ANDROID_JNILIBS_DIR)/armeabi-v7a/libclawdroid.so ./$(CMD_DIR)
|
||||
@echo "Android build complete (armeabi-v7a)"
|
||||
|
||||
## clean-android: Remove Android jniLibs build artifacts
|
||||
clean-android:
|
||||
@echo "Cleaning Android build artifacts..."
|
||||
@rm -rf $(ANDROID_JNILIBS_DIR)
|
||||
@echo "Android clean complete"
|
||||
|
||||
## install: Install clawdroid to system and copy builtin skills
|
||||
install: build
|
||||
|
|
|
|||
|
|
@ -60,6 +60,19 @@ android {
|
|||
buildConfig = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs.useLegacyPackaging = true
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = project.hasProperty("enableAbiSplit")
|
||||
reset()
|
||||
include("arm64-v8a", "armeabi-v7a", "x86_64")
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("termux") { java.srcDirs("src/termux/java") }
|
||||
getByName("embedded") { java.srcDirs("src/embedded/java") }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package io.clawdroid.di
|
||||
|
||||
import io.clawdroid.backend.api.BackendLifecycle
|
||||
import io.clawdroid.backend.api.NoopBackendLifecycle
|
||||
import io.clawdroid.backend.loader.EmbeddedBackendLifecycle
|
||||
import io.clawdroid.backend.loader.GatewayProcessManager
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val flavorModule = module {
|
||||
// Step 13 で EmbeddedBackendLifecycle に差し替え予定
|
||||
single<BackendLifecycle> { NoopBackendLifecycle() }
|
||||
single { GatewayProcessManager(androidContext(), get()) }
|
||||
single<BackendLifecycle> { EmbeddedBackendLifecycle(androidContext(), get()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package io.clawdroid
|
|||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import io.clawdroid.backend.api.BackendLifecycle
|
||||
import io.clawdroid.backend.api.BackendState
|
||||
import io.clawdroid.backend.api.GatewaySettingsStore
|
||||
import io.clawdroid.backend.config.ConfigApiClient
|
||||
import io.clawdroid.backend.config.configModule
|
||||
|
|
@ -10,7 +12,9 @@ import io.clawdroid.di.appModule
|
|||
import io.clawdroid.di.flavorModule
|
||||
import io.clawdroid.receiver.NotificationHelper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
|
|
@ -29,14 +33,22 @@ class ClawDroidApp : Application() {
|
|||
NotificationHelper.createNotificationChannel(this)
|
||||
|
||||
val koin = koinApp.koin
|
||||
val backendLifecycle: BackendLifecycle = koin.get()
|
||||
val settingsStore: GatewaySettingsStore = koin.get()
|
||||
val wsClient: WebSocketClient = koin.get()
|
||||
val configApiClient: ConfigApiClient = koin.get()
|
||||
val scope: CoroutineScope = koin.get()
|
||||
scope.launch { backendLifecycle.start() }
|
||||
scope.launch {
|
||||
// Only react when httpPort or apiKey actually change
|
||||
settingsStore.settings
|
||||
.map { it.httpPort to it.apiKey }
|
||||
// Wait for backend to be running before connecting WebSocket
|
||||
combine(
|
||||
settingsStore.settings
|
||||
.map { it.httpPort to it.apiKey }
|
||||
.distinctUntilChanged(),
|
||||
backendLifecycle.state,
|
||||
) { settings, state -> settings to state }
|
||||
.filter { (_, state) -> state == BackendState.RUNNING }
|
||||
.map { (settings, _) -> settings }
|
||||
.distinctUntilChanged()
|
||||
.collect {
|
||||
// Fetch WS connection info from config API
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue