mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 10:29:58 +02:00
Added a comprehensive collection of Compose development guidelines, best practices, and skill definitions in the .codex directory. Co-authored-by: Junie <junie@jetbrains.com>
291 lines
7.4 KiB
Markdown
291 lines
7.4 KiB
Markdown
# CI/CD & Distribution
|
|
|
|
CI/CD and native distribution for Compose Multiplatform: Android, Desktop (JVM), and iOS.
|
|
|
|
## 1. Distribution Overview
|
|
|
|
| Platform | Output | Gradle Task | Notes |
|
|
|----------|--------|-------------|-------|
|
|
| Android | APK/AAB | `assembleRelease`/`bundleRelease` | Standard distribution |
|
|
| Desktop macOS | DMG | `packageDmg` | Needs signing for Gatekeeper |
|
|
| Desktop Windows | MSI | `packageMsi` | Optional signing |
|
|
| Desktop Linux | DEB | `packageDeb` | Package manager format |
|
|
| iOS | .app/.ipa | Xcode Archive | Gradle builds framework only |
|
|
|
|
## 2. GitHub Actions — Android
|
|
|
|
```yaml
|
|
name: Android Build
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-java@v4
|
|
with:
|
|
java-version: '21'
|
|
distribution: 'temurin'
|
|
- uses: gradle/actions/setup-gradle@v4
|
|
|
|
- run: ./gradlew :androidApp:assembleRelease
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: android-apk
|
|
path: androidApp/build/outputs/apk/release/*.apk
|
|
```
|
|
|
|
### With Signing
|
|
|
|
```yaml
|
|
- name: Decode Keystore
|
|
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > release.keystore
|
|
|
|
- run: ./gradlew :androidApp:assembleRelease
|
|
env:
|
|
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
|
|
```
|
|
|
|
## 3. GitHub Actions — Desktop Multi-Platform
|
|
|
|
```yaml
|
|
name: Desktop Build
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
build_macos: { type: boolean, default: true }
|
|
build_windows: { type: boolean, default: true }
|
|
build_linux: { type: boolean, default: true }
|
|
|
|
jobs:
|
|
build-macos:
|
|
if: ${{ inputs.build_macos }}
|
|
runs-on: macos-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-java@v4
|
|
with: { java-version: '21', distribution: 'temurin' }
|
|
- uses: gradle/actions/setup-gradle@v4
|
|
- run: ./gradlew :desktopApp:packageDmg
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: macos-dmg
|
|
path: desktopApp/build/compose/binaries/main/dmg/*.dmg
|
|
|
|
build-windows:
|
|
if: ${{ inputs.build_windows }}
|
|
runs-on: windows-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-java@v4
|
|
with: { java-version: '21', distribution: 'temurin' }
|
|
- uses: gradle/actions/setup-gradle@v4
|
|
- run: ./gradlew :desktopApp:packageMsi
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: windows-msi
|
|
path: desktopApp/build/compose/binaries/main/msi/*.msi
|
|
|
|
build-linux:
|
|
if: ${{ inputs.build_linux }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-java@v4
|
|
with: { java-version: '21', distribution: 'temurin' }
|
|
- uses: gradle/actions/setup-gradle@v4
|
|
- run: ./gradlew :desktopApp:packageDeb
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: linux-deb
|
|
path: desktopApp/build/compose/binaries/main/deb/*.deb
|
|
```
|
|
|
|
## 4. Desktop App Module
|
|
|
|
```kotlin
|
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
|
|
|
plugins {
|
|
alias(libs.plugins.kotlin.multiplatform)
|
|
alias(libs.plugins.compose.multiplatform)
|
|
alias(libs.plugins.compose.compiler)
|
|
}
|
|
|
|
kotlin {
|
|
jvm()
|
|
sourceSets {
|
|
jvmMain.dependencies {
|
|
implementation(compose.desktop.currentOs)
|
|
implementation(projects.composeApp)
|
|
}
|
|
}
|
|
}
|
|
|
|
compose.desktop {
|
|
application {
|
|
mainClass = "com.example.MainKt"
|
|
|
|
// Required for DataStore/serialization
|
|
jvmArgs += listOf(
|
|
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
|
|
"--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED"
|
|
)
|
|
|
|
nativeDistributions {
|
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
|
packageName = "MyApp"
|
|
packageVersion = "1.0.0"
|
|
modules("jdk.unsupported")
|
|
|
|
macOS {
|
|
bundleID = "com.example.myapp"
|
|
iconFile.set(project.file("icons/icon.icns"))
|
|
// signing { sign.set(true); identity.set("Developer ID Application: ...") }
|
|
}
|
|
windows {
|
|
iconFile.set(project.file("icons/icon.ico"))
|
|
upgradeUuid = "YOUR-UUID" // Keep constant across versions
|
|
}
|
|
linux {
|
|
iconFile.set(project.file("icons/icon.png"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 5. iOS Xcode Integration
|
|
|
|
iOS uses Xcode, not Gradle. Gradle builds the shared framework; Xcode embeds it.
|
|
|
|
### Framework in `composeApp`
|
|
|
|
```kotlin
|
|
kotlin {
|
|
listOf(iosArm64(), iosSimulatorArm64()).forEach {
|
|
it.binaries.framework {
|
|
baseName = "ComposeApp"
|
|
isStatic = true // Required for App Store
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Xcode Build Phase Script
|
|
|
|
Add "Run Script" before "Compile Sources":
|
|
|
|
```bash
|
|
cd "$SRCROOT/.."
|
|
./gradlew :composeApp:embedAndSignAppleFrameworkForXcode
|
|
```
|
|
|
|
### Swift Entry Point
|
|
|
|
```swift
|
|
import SwiftUI
|
|
import ComposeApp
|
|
|
|
@main
|
|
struct iOSApp: App {
|
|
init() { AppKt.doInitKoin() }
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
ComposeViewControllerRepresentable().ignoresSafeArea()
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ComposeViewControllerRepresentable: UIViewControllerRepresentable {
|
|
func makeUIViewController(context: Context) -> UIViewController {
|
|
MainViewControllerKt.MainViewController()
|
|
}
|
|
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
|
|
}
|
|
```
|
|
|
|
## 6. Signing
|
|
|
|
### Android
|
|
|
|
```kotlin
|
|
android {
|
|
signingConfigs {
|
|
create("release") {
|
|
storeFile = file("release.keystore")
|
|
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
|
keyAlias = System.getenv("KEY_ALIAS")
|
|
keyPassword = System.getenv("KEY_PASSWORD")
|
|
}
|
|
}
|
|
buildTypes {
|
|
release { signingConfig = signingConfigs.getByName("release") }
|
|
}
|
|
}
|
|
```
|
|
|
|
### macOS (Direct Distribution)
|
|
|
|
```kotlin
|
|
macOS {
|
|
signing {
|
|
sign.set(true)
|
|
identity.set("Developer ID Application: Your Name (TEAM_ID)")
|
|
}
|
|
notarization {
|
|
appleID.set("your-email@example.com")
|
|
password.set("@keychain:AC_PASSWORD")
|
|
teamID.set("YOUR_TEAM_ID")
|
|
}
|
|
}
|
|
```
|
|
|
|
### iOS
|
|
|
|
Handled by Xcode via `CODE_SIGN_STYLE = Automatic` and `DEVELOPMENT_TEAM`.
|
|
|
|
## 7. Adding Desktop to Existing CMP Project
|
|
|
|
1. Add `jvm()` target in `composeApp`:
|
|
```kotlin
|
|
kotlin {
|
|
jvm()
|
|
sourceSets {
|
|
jvmMain.dependencies { /* desktop deps */ }
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Add KSP for JVM: `add("kspJvm", libs.room.compiler)`
|
|
|
|
3. Create `desktopApp` module with `compose.desktop {}` config
|
|
|
|
4. Add `include(":desktopApp")` to `settings.gradle.kts`
|
|
|
|
## 8. Gradle Tasks
|
|
|
|
| Platform | Build | Package | Run |
|
|
|----------|-------|---------|-----|
|
|
| Android | `assembleRelease` | `bundleRelease` | — |
|
|
| Desktop | `jvmJar` | `packageDmg`/`packageMsi`/`packageDeb` | `run` |
|
|
| iOS | `compileKotlinIosArm64` | Xcode Archive | Xcode |
|
|
|
|
## 9. Troubleshooting
|
|
|
|
| Issue | Solution |
|
|
|-------|----------|
|
|
| `InaccessibleObjectException` | Add `--add-opens` JVM args |
|
|
| "App is damaged" on macOS | Enable code signing |
|
|
| Framework not found in Xcode | Check `FRAMEWORK_SEARCH_PATHS` |
|
|
| Windows MSI won't upgrade | Keep `upgradeUuid` constant |
|