Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to KMP project structure #76

Merged
merged 38 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cf428be
Raise snapshots
ZacSweers Jun 5, 2024
d2dded1
Pull eithernet artifact into its own dir
ZacSweers Jun 5, 2024
6fad3a8
Extract test-fixtures artifact
ZacSweers Jun 6, 2024
9f92805
Set up multiplatform structure
ZacSweers Jun 6, 2024
2398379
Spotless
ZacSweers Jun 6, 2024
cf4df1b
Pull up as much as possible into common
ZacSweers Jun 6, 2024
929d6df
Rename .java to .kt
ZacSweers Jun 6, 2024
e04694a
Use kotlin impls of types/util
ZacSweers Jun 6, 2024
bcff3c8
No more java here
ZacSweers Jun 6, 2024
e12faa8
More tweaks
ZacSweers Jun 6, 2024
b7bd556
Pull ApiValidator into commonMain
ZacSweers Jun 6, 2024
b116eb6
Start shifting toward KType
ZacSweers Jun 6, 2024
4b69235
Remove remaining java.* usages in commonMain
ZacSweers Jun 6, 2024
388fb67
Spotless
ZacSweers Jun 6, 2024
58ec603
Pull some tests into commonTest and lean more on KType
ZacSweers Jun 6, 2024
40254f2
Spotless
ZacSweers Jun 6, 2024
289fa57
Regenerate APIs
ZacSweers Jun 6, 2024
d875bb5
Extract Retrofit/okhttp integration artifact
ZacSweers Jun 6, 2024
28f4b28
Spotless and API dumps
ZacSweers Jun 6, 2024
40e2c9a
changelog
ZacSweers Jun 6, 2024
0e5b0a1
Update README
ZacSweers Jun 6, 2024
5144c3d
Fix gradle properties location
ZacSweers Jun 6, 2024
486fe74
Set up KMP targets
ZacSweers Jun 6, 2024
787c648
Remove JvmOverloads in commonMain
ZacSweers Jun 6, 2024
cd5ae92
Multiplatform KTypes support
ZacSweers Jun 6, 2024
69f9f40
Add missing contract opt-ins
ZacSweers Jun 6, 2024
4300f78
Move EndpointKey and ParameterKey to commonMain
ZacSweers Jun 6, 2024
40f219e
Tweak EndpointKey creation extension point
ZacSweers Jun 6, 2024
f77b519
Promote much of test fixtures to common
ZacSweers Jun 6, 2024
8a3ab3c
Spotless
ZacSweers Jun 6, 2024
9d79fd6
Update APIs + add klib
ZacSweers Jun 6, 2024
17999a4
Dedupe APIs
ZacSweers Jun 6, 2024
16cf12f
Fix tests
ZacSweers Jun 6, 2024
e8fabad
Final fixes
ZacSweers Jun 6, 2024
fb5f196
Exclude internal APIs from API dump
ZacSweers Jun 6, 2024
7a14718
Gradle 8.8 + check in yarn lock
ZacSweers Jun 6, 2024
8df8bb7
More API reduction
ZacSweers Jun 6, 2024
39c715d
Update CI
ZacSweers Jun 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@ concurrency:

jobs:
build:
name: "Build / KSP2 = ${{ matrix.useKSP2 }}"
name: "Build"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
useKSP2: [true, false]

steps:
- name: Checkout
Expand All @@ -42,8 +38,8 @@ jobs:
uses: gradle/actions/setup-gradle@v3

- name: Test
run: ./gradlew check -Pksp.useKSP2=${{ matrix.useKSP2 }}
run: ./gradlew check

- name: Publish (default branch only)
if: github.repository == 'slackhq/EitherNet' && github.ref == 'refs/heads/main' && matrix.useKSP2 == 'false'
if: github.repository == 'slackhq/EitherNet' && github.ref == 'refs/heads/main'
run: ./gradlew publish -PmavenCentralUsername=${{ secrets.SONATYPEUSERNAME }} -PmavenCentralPassword=${{ secrets.SONATYPEPASSWORD }}
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Changelog
=========

**Unreleased**
--------------

- Migrate to Kotlin Multiplatform. Structurally, the core EitherNet APIs now live in `common` code and are _implemented_ by integration modules.
- Move Retrofit/OkHttp integration to separate `eithernet-integration-retrofit` artifact.
- Move test fixtures to new `eithernet-test-fixtures` artifact. Most of its implementation is still JVM-only for now.
- Remove deprecated APIs.
- Update Okio to `3.9.0`.

1.9.0
-----

Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# EitherNet

A pluggable sealed API result type for modeling [Retrofit](https://github.com/square/retrofit) responses.
A multiplatform, pluggable, and sealed API result type for modeling network API responses. Currently, this is
only implemented for [Retrofit](https://github.com/square/retrofit) on the JVM, but the core API is defined in
common code and can be implemented for other platforms.

The rest of the README below focuses on the Retrofit implementation.

## Usage

Expand Down Expand Up @@ -239,6 +243,7 @@ add your own validations on top of this, you can provide implementations of `Api
```gradle
dependencies {
implementation("com.slack.eithernet:eithernet:<version>")
implementation("com.slack.eithernet:eithernet-integration-retrofit:<version>")

// Test fixtures
testImplementation(testFixtures("com.slack.eithernet:eithernet:<version>"))
Expand Down
198 changes: 116 additions & 82 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,53 +13,118 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.vanniktech.maven.publish.MavenPublishBaseExtension
import io.gitlab.arturbosch.detekt.Detekt
import java.net.URI
import kotlinx.validation.ExperimentalBCVApi
import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.dokka.gradle.DokkaTaskPartial
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask

plugins {
alias(libs.plugins.kotlin.jvm)
`java-test-fixtures`
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.dokka)
alias(libs.plugins.ksp)
alias(libs.plugins.ksp) apply false
alias(libs.plugins.spotless)
alias(libs.plugins.mavenPublish)
alias(libs.plugins.detekt)
alias(libs.plugins.mavenPublish) apply false
alias(libs.plugins.detekt) apply false
alias(libs.plugins.binaryCompatibilityValidator)
}

repositories { mavenCentral() }
apiValidation {
@OptIn(ExperimentalBCVApi::class)
klib.enabled = true
nonPublicMarkers += "com.slack.eithernet.InternalEitherNetApi"
}

tasks.dokkaHtmlMultiModule {
outputDirectory.set(rootDir.resolve("docs/api/2.x"))
includes.from(project.layout.projectDirectory.file("README.md"))
}

val tomlJvmTarget = libs.versions.jvmTarget.get()

pluginManager.withPlugin("java") {
configure<JavaPluginExtension> {
toolchain { languageVersion.set(libs.versions.jdk.map(JavaLanguageVersion::of)) }
subprojects {
pluginManager.withPlugin("java") {
configure<JavaPluginExtension> {
toolchain { languageVersion.set(libs.versions.jdk.map(JavaLanguageVersion::of)) }
}

project.tasks.withType<JavaCompile>().configureEach {
options.release.set(tomlJvmTarget.toInt())
}
}

project.tasks.withType<JavaCompile>().configureEach { options.release.set(tomlJvmTarget.toInt()) }
}
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
configure<KotlinJvmProjectExtension> {
explicitApi()
compilerOptions {
progressiveMode.set(true)
jvmTarget.set(libs.versions.jvmTarget.map(JvmTarget::fromTarget))
}
}
}

kotlin {
explicitApi()
compilerOptions {
progressiveMode.set(true)
jvmTarget.set(libs.versions.jvmTarget.map(JvmTarget::fromTarget))
pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform") {
configure<KotlinMultiplatformExtension> {
explicitApi()
@OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { progressiveMode.set(true) }
jvmToolchain { languageVersion.set(libs.versions.jvmTarget.map(JavaLanguageVersion::of)) }
}
tasks.withType<KotlinCompilationTask<*>>().configureEach {
compilerOptions {
if (this is KotlinJvmCompilerOptions) {
jvmTarget.set(libs.versions.jvmTarget.map(JvmTarget::fromTarget))
// Enable new JvmDefault behavior
// https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-m3-generating-default-methods-in-interfaces/
freeCompilerArgs.add("-Xjvm-default=all")
}
}
}
}
}

tasks.compileTestKotlin {
compilerOptions {
optIn.addAll("kotlin.ExperimentalStdlibApi", "kotlinx.coroutines.ExperimentalCoroutinesApi")
// Enable new JvmDefault behavior
// https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-m3-generating-default-methods-in-interfaces/
freeCompilerArgs.add("-Xjvm-default=all")
apply(plugin = "io.gitlab.arturbosch.detekt")
tasks.withType<Detekt>().configureEach { jvmTarget = tomlJvmTarget }

pluginManager.withPlugin("com.vanniktech.maven.publish") {
apply(plugin = "org.jetbrains.dokka")
tasks.withType<DokkaTaskPartial>().configureEach {
outputDirectory.set(layout.buildDirectory.dir("docs/partial"))
dokkaSourceSets.configureEach {
val readMeProvider = project.layout.projectDirectory.file("README.md")
if (readMeProvider.asFile.exists()) {
includes.from(readMeProvider)
}
skipDeprecated.set(true)
sourceLink {
localDirectory.set(layout.projectDirectory.dir("src").asFile)
val relPath = rootProject.projectDir.toPath().relativize(projectDir.toPath())
remoteUrl.set(
providers.gradleProperty("POM_SCM_URL").map { scmUrl ->
URI("$scmUrl/tree/main/$relPath/src").toURL()
}
)
remoteLineSuffix.set("#L")
}
}
}

configure<MavenPublishBaseExtension> {
publishToMavenCentral(automaticRelease = true)
signAllPublications()
}
// Ref: https://github.com/slackhq/EitherNet/issues/58
project.group = project.property("GROUP").toString()
project.version = project.property("VERSION_NAME").toString()
}
}

tasks.withType<Detekt>().configureEach { jvmTarget = tomlJvmTarget }

tasks.named<DokkaTask>("dokkaHtml") {
outputDirectory.set(layout.projectDirectory.dir("docs/1.x"))
dokkaSourceSets.configureEach {
Expand All @@ -76,62 +141,31 @@ tasks.named<DokkaTask>("dokkaHtml") {

val ktfmtVersion = libs.versions.ktfmt.get()

spotless {
format("misc") {
target("*.md", ".gitignore")
trimTrailingWhitespace()
endWithNewline()
}
kotlin {
target("**/*.kt")
ktfmt(ktfmtVersion).googleStyle()
trimTrailingWhitespace()
endWithNewline()
licenseHeaderFile("spotless/spotless.kt")
targetExclude("**/spotless.kt")
}
kotlinGradle {
ktfmt(ktfmtVersion).googleStyle()
trimTrailingWhitespace()
endWithNewline()
licenseHeaderFile(
"spotless/spotless.kt",
"(import|plugins|buildscript|dependencies|pluginManagement|rootProject)",
)
}
}
allprojects {
apply(plugin = "com.diffplug.spotless")

mavenPublishing {
publishToMavenCentral(automaticRelease = true)
signAllPublications()
}

// Ref: https://github.com/slackhq/EitherNet/issues/58
project.group = project.property("GROUP").toString()

project.version = project.property("VERSION_NAME").toString()

dependencies {
implementation(libs.retrofit)
implementation(libs.coroutines.core)

testImplementation(libs.coroutines.core)
testImplementation(libs.coroutines.test)
testImplementation(libs.retrofit.converterScalars)
testImplementation(libs.okhttp)
testImplementation(libs.okhttp.mockwebserver)
testImplementation(libs.moshi)
testImplementation(libs.moshi.kotlin)
testImplementation(libs.junit)
testImplementation(libs.truth)
testImplementation(libs.kotlin.test)
testImplementation(libs.autoService.annotations)
kspTest(libs.autoService.ksp)

// Android APIs access, gated at runtime
testFixturesCompileOnly(libs.androidProcessingApi)
testFixturesImplementation(libs.coroutines.core)
// For access to Types
testFixturesImplementation(libs.moshi)
testFixturesApi(libs.kotlin.reflect)
spotless {
format("misc") {
target("*.md", ".gitignore")
trimTrailingWhitespace()
endWithNewline()
}
kotlin {
target("**/*.kt")
ktfmt(ktfmtVersion).googleStyle()
trimTrailingWhitespace()
endWithNewline()
licenseHeaderFile(rootProject.layout.projectDirectory.file("spotless/spotless.kt"))
targetExclude("**/spotless.kt")
}
kotlinGradle {
ktfmt(ktfmtVersion).googleStyle()
trimTrailingWhitespace()
endWithNewline()
licenseHeaderFile(
rootProject.layout.projectDirectory.file("spotless/spotless.kt"),
"(import|plugins|buildscript|dependencies|pluginManagement|rootProject)",
)
}
}
}
28 changes: 10 additions & 18 deletions api/eithernet.api → eithernet/api/eithernet.api
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
public final class com/slack/eithernet/AnnotationsKt {
public static final fun errorType ([Ljava/lang/annotation/Annotation;)Lkotlin/Pair;
public static final fun statusCode ([Ljava/lang/annotation/Annotation;)Lkotlin/Pair;
public static final fun toKType (Lcom/slack/eithernet/ResultType;)Lkotlin/reflect/KType;
}

public final class com/slack/eithernet/Annotations_jvmKt {
public static final fun toType (Lcom/slack/eithernet/ResultType;)Ljava/lang/reflect/Type;
}

Expand Down Expand Up @@ -54,16 +58,6 @@ public final class com/slack/eithernet/ApiResult$Success : com/slack/eithernet/A
public final fun withTags (Ljava/util/Map;)Lcom/slack/eithernet/ApiResult$Success;
}

public final class com/slack/eithernet/ApiResultCallAdapterFactory : retrofit2/CallAdapter$Factory {
public static final field INSTANCE Lcom/slack/eithernet/ApiResultCallAdapterFactory;
public fun get (Ljava/lang/reflect/Type;[Ljava/lang/annotation/Annotation;Lretrofit2/Retrofit;)Lretrofit2/CallAdapter;
}

public final class com/slack/eithernet/ApiResultConverterFactory : retrofit2/Converter$Factory {
public static final field INSTANCE Lcom/slack/eithernet/ApiResultConverterFactory;
public fun responseBodyConverter (Ljava/lang/reflect/Type;[Ljava/lang/annotation/Annotation;Lretrofit2/Retrofit;)Lretrofit2/Converter;
}

public abstract interface annotation class com/slack/eithernet/DecodeErrorBody : java/lang/annotation/Annotation {
}

Expand All @@ -72,10 +66,8 @@ public abstract interface annotation class com/slack/eithernet/ExperimentalEithe

public final class com/slack/eithernet/ExtensionsKt {
public static final fun exceptionOrNull (Lcom/slack/eithernet/ApiResult$Failure;)Ljava/lang/Throwable;
public static final synthetic fun fold (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final synthetic fun fold (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun foldWithValue (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun foldWithValue (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun fold (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun fold (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun onApiFailure (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;)Lcom/slack/eithernet/ApiResult;
public static final fun onFailure (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;)Lcom/slack/eithernet/ApiResult;
public static final fun onHttpFailure (Lcom/slack/eithernet/ApiResult;Lkotlin/jvm/functions/Function1;)Lcom/slack/eithernet/ApiResult;
Expand All @@ -98,8 +90,6 @@ public abstract interface annotation class com/slack/eithernet/ResultType : java
}

public final class com/slack/eithernet/RetriesKt {
public static final synthetic fun retryWithExponentialBackoff-3FA4DCs (IJDJDLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun retryWithExponentialBackoff-3FA4DCs$default (IJDJDLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun retryWithExponentialBackoff-3c68mSE (IJDJDLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun retryWithExponentialBackoff-3c68mSE$default (IJDJDLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}
Expand All @@ -109,8 +99,10 @@ public abstract interface annotation class com/slack/eithernet/StatusCode : java
}

public final class com/slack/eithernet/TagsKt {
public static final fun request (Lcom/slack/eithernet/ApiResult;)Lokhttp3/Request;
public static final fun response (Lcom/slack/eithernet/ApiResult;)Lokhttp3/Response;
public static final fun tag (Lcom/slack/eithernet/ApiResult;Lkotlin/reflect/KClass;)Ljava/lang/Object;
}

public final class com/slack/eithernet/Util {
public static final fun resolve (Ljava/lang/reflect/Type;Ljava/lang/reflect/Type;Ljava/lang/Class;)Ljava/lang/reflect/Type;
}

Loading