diff --git a/.github/ci-gradle.properties b/.github/ci-gradle.properties index b517966..12980cc 100644 --- a/.github/ci-gradle.properties +++ b/.github/ci-gradle.properties @@ -6,7 +6,8 @@ kotlin.code.style=official org.gradle.caching=true org.gradle.parallel=true android.enableR8.fullMode=true -org.gradle.configureondemand=true +# TODO: Not compatible with wasmJS +org.gradle.configureondemand=false android.enableJetifier=false kotlin.incremental.usePreciseJavaTracking=true org.gradle.configuration-cache.problems=warn @@ -32,7 +33,6 @@ kotlin.native.binary.appStateTracking=enabled kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none # Native incremental compilation kotlin.incremental.native=true -android.experimental.additionalArtifactsInModel=true kotlin.apple.xcodeCompatibility.nowarn=true # Enable new k/n GC kotlin.native.binary.gc=cms diff --git a/.gitignore b/.gitignore index 29e7cd8..ce93832 100644 --- a/.gitignore +++ b/.gitignore @@ -160,6 +160,7 @@ hs_err_pid* /app/debug/output-metadata.json /.idea/deploymentTargetDropDown.xml /.idea/other.xml +/.kotlin/ ### Custom rules .firebase-service-account.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7a53d67 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,28 @@ +# Repository Guidelines + +## Project Structure & Module Organization + +- `core/` is the Kotlin Multiplatform library. Shared code lives in `core/src/commonMain/kotlin/pro/respawn/apiresult/`. +- JVM tests live in `core/src/jvmTest/kotlin/pro/respawn/apiresult/test/`. +- `app/` is a sample Android app. Kotlin sources are under `app/src/main/kotlin/…` and resources in `app/src/main/res/`. +- `buildSrc/` contains Gradle convention plugins and shared build logic. +- `docs/` holds documentation sources; `detekt.yml` defines lint rules. + +## Build, Test, and Development Commands + +- `./gradlew :core:jvmTest` — runs JVM unit tests for the library. +- `./gradlew :app:assembleDebug` — builds the sample app APK. +- `./gradlew detektAll` — runs Detekt across the project. +- `./gradlew detektFormat` — applies Detekt auto-corrections. + +## Testing Guidelines + +- Tests use Kotest with the JUnit 5 platform. +- Place JVM tests in `core/src/jvmTest/...` and name files `*Tests.kt`. +- Add tests for new operators or error-handling behaviors; keep operator coverage high. + +## Commit & Pull Request Guidelines + +- Commit subjects are short, imperative, and lowercase (for example, `fix publish workflow`). +- Release/version commits use a plain version string, optionally with the PR number (for example, `2.1.0 (#14)`). +- PRs should include: a concise description, rationale for changes, and tests run. Update docs/KDoc when the public API changes. diff --git a/README.md b/README.md index ce82506..79998a2 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Ready to try? Start with reading the [Quickstart Guide](https://opensource.respa ## License ``` - Copyright 2022-2025 Respawn Team and contributors + Copyright 2022-2026 Respawn Team and contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a679462..d1058dc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + plugins { id("com.android.application") kotlin("android") @@ -5,6 +7,13 @@ plugins { alias(libs.plugins.compose.compiler) } +kotlin { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(Config.jvmTarget) + } +} + android { configureAndroid() @@ -22,9 +31,6 @@ android { buildConfig = true compose = true } - kotlinOptions { - jvmTarget = Config.jvmTarget.target - } } dependencies { diff --git a/build.gradle.kts b/build.gradle.kts index eea104b..7349f65 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,6 @@ import com.vanniktech.maven.publish.JavadocJar import com.vanniktech.maven.publish.KotlinMultiplatform import com.vanniktech.maven.publish.MavenPublishBaseExtension import com.vanniktech.maven.publish.MavenPublishBasePlugin -import com.vanniktech.maven.publish.SonatypeHost import nl.littlerobots.vcu.plugin.versionCatalogUpdate import nl.littlerobots.vcu.plugin.versionSelector import org.gradle.kotlin.dsl.withType @@ -48,14 +47,8 @@ subprojects { plugins.withType { the().apply { val isReleaseBuild = properties["release"]?.toString().toBoolean() - configure( - KotlinMultiplatform( - javadocJar = JavadocJar.Empty(), - sourcesJar = true, - androidVariantsToPublish = listOf("release"), - ) - ) - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, false) + configure(KotlinMultiplatform(javadocJar = JavadocJar.Empty(), sourcesJar = true)) + publishToMavenCentral() if (isReleaseBuild) signAllPublications() coordinates(Config.artifactId, name, Config.version(isReleaseBuild)) pom { @@ -87,7 +80,12 @@ subprojects { tasks { withType().configureEach { useJUnitPlatform() - filter { isFailOnNoMatchingTests = true } + val isAndroidUnitTest = name.contains("UnitTest") + // Android unit test tasks can exist without sources; don't fail those on empty discovery. + filter { isFailOnNoMatchingTests = !isAndroidUnitTest } + if (isAndroidUnitTest) { + failOnNoDiscoveredTests = false + } } } } @@ -111,8 +109,6 @@ versionCatalogUpdate { keep { keepUnusedVersions = true - keepUnusedLibraries = true - keepUnusedPlugins = true } } diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 0b897e7..37e5dd3 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -16,7 +16,7 @@ object Config { const val artifactId = "$group.$artifact" const val majorRelease = 2 - const val minorRelease = 1 + const val minorRelease = 2 const val patch = 0 const val postfix = "" // include dash const val versionName = "$majorRelease.$minorRelease.$patch$postfix" @@ -37,9 +37,9 @@ object Config { val jvmTarget = JvmTarget.JVM_11 val javaVersion = JavaVersion.VERSION_11 - const val compileSdk = 35 + const val compileSdk = 36 const val targetSdk = compileSdk - const val minSdk = 21 + const val minSdk = 23 const val appMinSdk = 26 const val publishingVariant = "release" @@ -54,16 +54,12 @@ object Config { val compilerArgs = listOf( "-Xbackend-threads=0", // parallel IR compilation "-Xexpect-actual-classes", - "-Xwasm-use-new-exception-proposal", "-Xconsistent-data-class-copy-visibility", - "-Xsuppress-warning=NOTHING_TO_INLINE", - "-Xsuppress-warning=UNUSED_ANONYMOUS_PARAMETER", - "-Xwasm-debugger-custom-formatters" + "-Xwarning-level=NOTHING_TO_INLINE:disabled", + "-Xwarning-level=UNUSED_ANONYMOUS_PARAMETER:disabled" ) val jvmCompilerArgs = buildList { - addAll(compilerArgs) - add("-Xjvm-default=all") // enable all jvm optimizations - add("-Xcontext-receivers") + add("-jvm-default=no-compatibility") // enable all jvm optimizations add("-Xstring-concat=inline") add("-Xlambdas=indy") add("-Xjdk-release=${jvmTarget.target}") diff --git a/buildSrc/src/main/kotlin/ConfigureAndroid.kt b/buildSrc/src/main/kotlin/ConfigureAndroid.kt index f99ada6..869247d 100644 --- a/buildSrc/src/main/kotlin/ConfigureAndroid.kt +++ b/buildSrc/src/main/kotlin/ConfigureAndroid.kt @@ -76,15 +76,6 @@ fun Project.configureAndroidLibrary(variant: LibraryExtension) = variant.apply { consumerProguardFiles(file(Config.consumerProguardFile)) } - buildTypes { - release { - setProperty( - "archivesBaseName", - project.name - ) - } - } - libraryVariants.all { sourceSets { getByName(name) { diff --git a/buildSrc/src/main/kotlin/ConfigureMultiplatform.kt b/buildSrc/src/main/kotlin/ConfigureMultiplatform.kt index 51de264..81af4bb 100644 --- a/buildSrc/src/main/kotlin/ConfigureMultiplatform.kt +++ b/buildSrc/src/main/kotlin/ConfigureMultiplatform.kt @@ -50,7 +50,7 @@ fun Project.configureMultiplatform( } if (wasmJs) wasmJs { - moduleName = this@configureMultiplatform.name + outputModuleName.set(this@configureMultiplatform.name) nodejs() browser() binaries.library() @@ -60,6 +60,7 @@ fun Project.configureMultiplatform( nodejs() } + @Suppress("DEPRECATION") if (android) androidTarget { publishLibraryVariants(Config.publishingVariant) compilerOptions { diff --git a/buildSrc/src/main/kotlin/pro.respawn.android-library.gradle.kts b/buildSrc/src/main/kotlin/pro.respawn.android-library.gradle.kts index 36d0e87..5c6b512 100644 --- a/buildSrc/src/main/kotlin/pro.respawn.android-library.gradle.kts +++ b/buildSrc/src/main/kotlin/pro.respawn.android-library.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + plugins { kotlin("android") id("com.android.library") @@ -5,12 +7,12 @@ plugins { kotlin { explicitApi() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(Config.jvmTarget) + } } android { configureAndroidLibrary(this) - - kotlinOptions { - jvmTarget = Config.jvmTarget.target - } } diff --git a/gradle.properties b/gradle.properties index b183749..3b90d82 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,8 @@ kotlin.code.style=official org.gradle.caching=true org.gradle.parallel=true android.enableR8.fullMode=true -org.gradle.configureondemand=true +# TODO: Not compatible with wasmJS +# org.gradle.configureondemand=true android.enableJetifier=false kotlin.incremental.usePreciseJavaTracking=true org.gradle.configuration-cache.problems=warn @@ -32,7 +33,6 @@ kotlin.native.binary.appStateTracking=enabled kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none # Native incremental compilation kotlin.incremental.native=true -android.experimental.additionalArtifactsInModel=true kotlin.apple.xcodeCompatibility.nowarn=true # Enable new k/n GC kotlin.native.binary.gc=cms diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cd8763a..1f28e80 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,23 +1,23 @@ [versions] -atomicfu = "0.26.0" -compose = "1.7.6" -compose-activity = "1.10.0-rc01" -compose-material3 = "1.3.1" +atomicfu = "0.30.0-beta" +compose = "1.10.0" +compose-activity = "1.12.2" +compose-material3 = "1.4.0" composeDetektPlugin = "1.4.0" -core-ktx = "1.15.0" -coroutines = "1.10.1" -dependencyAnalysisPlugin = "2.6.1" -detekt = "1.23.7" -detektFormattingPlugin = "1.23.7" -dokka = "2.0.0" -gradleAndroid = "8.7.3" -gradleDoctorPlugin = "0.10.0" -kotest = "6.0.0.M1" -kotlin = "2.1.0" -lifecycle = "2.8.7" -maven-publish-plugin = "0.30.0" +core-ktx = "1.17.0" +coroutines = "1.10.2" +dependencyAnalysisPlugin = "3.5.1" +detekt = "1.23.8" +detektFormattingPlugin = "1.23.8" +dokka = "2.1.0" +gradleAndroid = "8.13.0" +gradleDoctorPlugin = "0.12.1" +kotest = "6.0.7" +kotlin = "2.3.0" +lifecycle = "2.10.0" +maven-publish-plugin = "0.35.0" turbine = "1.0.0" -versionCatalogUpdatePlugin = "0.8.4" +versionCatalogUpdatePlugin = "1.0.1" [libraries] android-gradle = { module = "com.android.tools.build:gradle", version.ref = "gradleAndroid" } @@ -35,7 +35,9 @@ detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", detekt-gradle = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } detekt-libraries = { module = "io.gitlab.arturbosch.detekt:detekt-rules-libraries", version.ref = "detekt" } dokka-android = { module = "org.jetbrains.dokka:android-documentation-plugin", version.ref = "dokka" } +dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } +kotest-assertions-table = { module = "io.kotest:kotest-assertions-table", version.ref = "kotest" } kotest-framework = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" } kotest-junit = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" } @@ -46,11 +48,11 @@ kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version. kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test-common", version.ref = "kotlin" } lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } -dokka-gradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } [bundles] unittest = [ "kotest-assertions", + "kotest-assertions-table", "kotest-framework", "kotest-property", "kotlin-coroutines-test", diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9..f8e1ee3 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a79..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f3b75f3..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -205,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/scripts/update_deps.sh b/scripts/update_deps.sh new file mode 100755 index 0000000..daef667 --- /dev/null +++ b/scripts/update_deps.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +GRADLE_VER="$1" + +if [ -z "$GRADLE_VER" ]; then + GRADLE_VER="latest" +fi + +./gradlew wrapper --gradle-version "$GRADLE_VER" --distribution-type bin +./gradlew wrapper --gradle-version "$GRADLE_VER" --distribution-type bin +./gradlew versionCatalogUpdate --no-configuration-cache