diff --git a/.github/workflows/lightsaber-ci.yml b/.github/workflows/lightsaber-ci.yml index 7a5ed50..9916aed 100644 --- a/.github/workflows/lightsaber-ci.yml +++ b/.github/workflows/lightsaber-ci.yml @@ -19,8 +19,7 @@ jobs: - uses: ./.github/actions/setup-lightsaber-ci - name: Build Shared - # TODO Understand why compileIosMainKotlinMetadata fails every time - run: ./gradlew shared:assemble -x compileIosMainKotlinMetadata + run: ./gradlew shared:assemble build-androidApp: runs-on: macos-14 @@ -74,6 +73,13 @@ jobs: mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + # Generate dummy framework to ensure that compose-resources are copied. + # See https://github.com/JetBrains/compose-multiplatform/issues/5011 + - name: Generate dummy framework + run: | + mkdir -p ./shared/build/compose/cocoapods/compose-resources + ./gradlew generateDummyFramework + - name: Shared Pod Install run: ./gradlew podInstall @@ -188,4 +194,15 @@ jobs: - name: Run Maestro tests run: | export MAESTRO_DRIVER_STARTUP_TIMEOUT=60000 - maestro test .maestro \ No newline at end of file + maestro test .maestro + + - name: Collect logs + if: failure() + run: xcrun simctl spawn "${{ steps.ios-simulator.outputs.udid }}" log collect --output ${{ github.workspace }}/${{ steps.ios-simulator.outputs.udid }}.logarchive + + - name: Archive the device logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: device-logs + path: ${{ github.workspace }}/*.logarchive \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68a0371..722200a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ androidx-appcompat = "1.7.0" androidx-core-ktx = "1.13.1" datastore-version = "1.1.1" compose-ui-test = "1.6.7" +kermit = "2.0.4" [libraries] androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "core-splashscreen" } @@ -26,6 +27,7 @@ androidx-datastore-core-okio = { group = "androidx.datastore", name = "datastore androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "datastore-version" } androidx-compose-ui-test-junit4-android = { group = "androidx.compose.ui" , name = "ui-test-junit4-android", version.ref = "compose-ui-test" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui" , name = "ui-test-manifest", version.ref = "compose-ui-test" } +kermit = { group = "co.touchlab" , name = "kermit", version.ref = "kermit" } [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 64e7056..936e9ef 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -16,6 +16,3 @@ struct ContentView: View { .ignoresSafeArea(.all, edges: .bottom) // Compose has own keyboard handler } } - - - diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index bcd4e8b..c9eb79e 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -55,6 +55,7 @@ kotlin { implementation(libs.circuitx.gesture.navigation) implementation(libs.androidx.datastore.preferences.core) implementation(libs.androidx.datastore.core.okio) + api(libs.kermit) } } commonTest { diff --git a/shared/src/iosMain/kotlin/xyz/alaniz/aaron/lightsaber/main.ios.kt b/shared/src/iosMain/kotlin/xyz/alaniz/aaron/lightsaber/main.ios.kt index 3e6a296..c343397 100644 --- a/shared/src/iosMain/kotlin/xyz/alaniz/aaron/lightsaber/main.ios.kt +++ b/shared/src/iosMain/kotlin/xyz/alaniz/aaron/lightsaber/main.ios.kt @@ -3,6 +3,10 @@ package xyz.alaniz.aaron.lightsaber import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.ui.platform.AccessibilitySyncOptions import androidx.compose.ui.window.ComposeUIViewController +import co.touchlab.kermit.DefaultFormatter +import co.touchlab.kermit.Logger +import co.touchlab.kermit.NSLogWriter +import co.touchlab.kermit.OSLogWriter import kotlinx.cinterop.ExperimentalForeignApi import platform.Foundation.NSDocumentDirectory import platform.Foundation.NSFileManager @@ -12,8 +16,9 @@ import xyz.alaniz.aaron.lightsaber.di.IosApplicationComponent import xyz.alaniz.aaron.lightsaber.di.create import xyz.alaniz.aaron.lightsaber.di.dataStoreFileName import xyz.alaniz.aaron.lightsaber.ui.lightsaber.IosLightsaberScreen +import kotlin.experimental.ExperimentalNativeApi -@OptIn(ExperimentalForeignApi::class, ExperimentalComposeApi::class) +@OptIn(ExperimentalForeignApi::class, ExperimentalComposeApi::class, ExperimentalNativeApi::class) fun MainViewController() = ComposeUIViewController(configure = { /** * TODO Update this to only sync the accessibility tree for debug builds @@ -36,6 +41,20 @@ fun MainViewController() = ComposeUIViewController(configure = { error = null, ) val dataStorePath = requireNotNull(documentDirectory).path + "/$dataStoreFileName" + + /** + * Use NSLogWriter so that logs are available to pull off the simulator in CI. + * + * TODO Inject log writers based on debug or release builds. + */ + Logger.setLogWriters(listOf(NSLogWriter(messageStringFormatter = DefaultFormatter))) + + /** + * Log unhandled exceptions. + */ + setUnhandledExceptionHook { + Logger.e(throwable = it) { "Unhandled exception: cause = ${it.cause} message = ${it.message}" } + } App(initialScreen = IosLightsaberScreen) { scope, navigator -> IosApplicationComponent.create( navigator = navigator,