Skip to content

Commit

Permalink
Add support for kotlin-inject-anvil (#1635)
Browse files Browse the repository at this point in the history
This introduces support for kotlin-inject + kotlin-inject-anvil. Note
that kotlin-inject needs another release with
evant/kotlin-inject#414 to work, so I'm game
putting this in the release now but mentioning this caveat. Will remove
the snapshots use and just disable the new sample project for now.

kotlin-inject-anvil has support for options coming too, but in the
meantime I think it's actually fine to check in an anvil hint property
too as it doesn't require any runtime anvil APIs and can just be
compileOnly.

**TODO in the future**

- [x] Need a new kotlin-inject release for
evant/kotlin-inject#414
- [x] Need anvil support for annotation options:
amzn/kotlin-inject-anvil#24
- [ ] Migrate STAR sample (can be a later PR if need be)
  • Loading branch information
ZacSweers authored Sep 16, 2024
1 parent 5451256 commit ce398b3
Show file tree
Hide file tree
Showing 24 changed files with 752 additions and 279 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
**Unreleased**
--------------

- **New**: Add code gen support for [kotlin-inject](https://github.com/evant/kotlin-inject) + [kotlin-inject-anvil](https://github.com/amzn/kotlin-inject-anvil). See the [code gen docs](https://slackhq.github.io/circuit/code-gen/) for usage instructions. We've also added a sample project.
- Update to Kotlin `2.0.20`.
- **Change**: Switch to stdlib's implementation of `Uuid`. This release now requires Kotlin `2.0.20` or later.
- **Behavior change**: `presenterTestOf` and `Presenter.test` have a new optional `moleculeFlowTransformer` parameter that allows for advanced filtering of the `Flow` returned out of the underlying `moleculeFlow`. By default, this now runs a `distinctUntilChanged` operator.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ subprojects {
}

tasks.withType<Detekt>().configureEach {
jvmTarget = "11"
jvmTarget = jvmTargetProject.get()
reports {
html.required.set(true)
xml.required.set(true)
Expand Down
63 changes: 54 additions & 9 deletions circuit-codegen-annotations/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Copyright (C) 2022 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl

plugins {
alias(libs.plugins.agp.library)
alias(libs.plugins.kotlin.multiplatform)
Expand All @@ -11,25 +14,67 @@ kotlin {
androidTarget { publishLibraryVariants("release") }
jvm()
// Anvil/Dagger does not support iOS targets
iosX64()
iosArm64()
iosSimulatorArm64()
watchosArm32()
watchosArm64()
watchosX64()
watchosSimulatorArm64()
tvosArm64()
tvosX64()
tvosSimulatorArm64()
macosX64()
macosArm64()
linuxArm64()
linuxX64()
// TODO https://github.com/evant/kotlin-inject/pull/440
// mingwX64()
js(IR) {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

applyDefaultHierarchyTemplate()
@OptIn(ExperimentalKotlinGradlePluginApi::class)
applyDefaultHierarchyTemplate {
common {
group("commonJs") {
withJs()
withWasmJs()
}
group("commonJvm") {
withJvm()
withAndroidTarget()
}
}
}

sourceSets {
commonMain {
dependencies {
// Only here for docs linking
compileOnly(projects.circuitFoundation)
compileOnly(libs.kotlinInject.anvil.runtime)
api(projects.circuitRuntimeScreen)
}
}
val commonJvm =
maybeCreate("commonJvm").apply {
dependsOn(commonMain.get())
dependencies { compileOnly(libs.hilt) }
named("commonJvmMain") { dependencies { compileOnly(libs.hilt) } }
nativeMain {
dependencies {
compileOnly(libs.kotlinInject.anvil.runtime)
api(libs.kotlinInject.anvil.runtime)
}
}
named("commonJsMain") {
dependencies {
compileOnly(libs.kotlinInject.anvil.runtime)
api(libs.kotlinInject.anvil.runtime)
}
androidMain { dependsOn(commonJvm) }
jvmMain { dependsOn(commonJvm) }
}
}

targets.configureEach {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// Copyright (C) 2022 Slack Technologies, LLC
// Copyright (C) 2024 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
package com.slack.circuit.codegen.annotations

import com.slack.circuit.runtime.screen.Screen
import dagger.hilt.GeneratesRootInput
import kotlin.reflect.KClass
import software.amazon.lastmile.kotlin.inject.anvil.extend.ContributingAnnotation

/**
* Android-specific [CircuitInject], applying the Hilt [GeneratesRootInput] annotation.
* JS-specific [CircuitInject].
*
* For more general information about this annotation, see
* [com.slack.circuit.codegen.annotations.CircuitInject]
*/
@GeneratesRootInput
@ContributingAnnotation
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
public actual annotation class CircuitInject(
actual val screen: KClass<out Screen>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ package com.slack.circuit.codegen.annotations
import com.slack.circuit.runtime.screen.Screen
import dagger.hilt.GeneratesRootInput
import kotlin.reflect.KClass
import software.amazon.lastmile.kotlin.inject.anvil.extend.ContributingAnnotation

/**
* JVM-specific [CircuitInject], applying the Hilt [GeneratesRootInput] annotation.
*
* For more general information about this annotation, see
* [com.slack.circuit.codegen.annotations.CircuitInject]
*/
@ContributingAnnotation
@GeneratesRootInput
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
public actual annotation class CircuitInject(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
package com.slack.circuit.codegen.annotations

import androidx.compose.runtime.Composable
import com.slack.circuit.foundation.Circuit
import com.slack.circuit.runtime.CircuitUiState
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuit.runtime.screen.Screen
import com.slack.circuit.runtime.ui.Ui
import kotlin.reflect.KClass
import software.amazon.lastmile.kotlin.inject.anvil.extend.ContributingAnnotation

/**
* This annotation is used to mark a UI or presenter class or function for code generation. When
Expand Down Expand Up @@ -123,5 +119,6 @@ import kotlin.reflect.KClass
* }
* ```
*/
@ContributingAnnotation
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
public expect annotation class CircuitInject(val screen: KClass<out Screen>, val scope: KClass<*>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (C) 2024 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
@file:Suppress("PackageDirectoryMismatch")

package amazon.lastmile.inject

import com.slack.circuit.codegen.annotations.CircuitInject
import kotlin.reflect.KClass
import software.amazon.lastmile.kotlin.inject.anvil.`internal`.Origin

// TODO temporary until https://github.com/amzn/kotlin-inject-anvil/issues/24
@Origin(value = CircuitInject::class)
internal val comSlackCircuitCodegenAnnotationsCircuitInject: KClass<CircuitInject> =
CircuitInject::class
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (C) 2024 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
package com.slack.circuit.codegen.annotations

import com.slack.circuit.runtime.screen.Screen
import kotlin.reflect.KClass
import software.amazon.lastmile.kotlin.inject.anvil.extend.ContributingAnnotation

/**
* Native-specific [CircuitInject].
*
* For more general information about this annotation, see
* [com.slack.circuit.codegen.annotations.CircuitInject]
*/
@ContributingAnnotation
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
public actual annotation class CircuitInject(
actual val screen: KClass<out Screen>,
actual val scope: KClass<*>,
)
2 changes: 2 additions & 0 deletions circuit-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ plugins {
dependencies {
compileOnly(libs.ksp.api)
compileOnly(libs.anvil.annotations)
compileOnly(libs.kotlinInject.runtime)
compileOnly(libs.kotlinInject.anvil.runtime)
ksp(libs.autoService.ksp)
implementation(libs.autoService.annotations)
implementation(libs.dagger)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2024 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
package com.slack.circuit.codegen

import com.squareup.kotlinpoet.ClassName

internal object CircuitNames {
val ASSISTED = ClassName("dagger.assisted", "Assisted")
val ASSISTED_FACTORY = ClassName("dagger.assisted", "AssistedFactory")
val ASSISTED_INJECT = ClassName("dagger.assisted", "AssistedInject")
val INJECT = ClassName("javax.inject", "Inject")
val PROVIDER = ClassName("javax.inject", "Provider")
const val CIRCUIT_RUNTIME_BASE_PACKAGE = "com.slack.circuit.runtime"
const val DAGGER_PACKAGE = "dagger"
const val DAGGER_HILT_PACKAGE = "$DAGGER_PACKAGE.hilt"
const val DAGGER_HILT_CODEGEN_PACKAGE = "$DAGGER_HILT_PACKAGE.codegen"
const val DAGGER_MULTIBINDINGS_PACKAGE = "$DAGGER_PACKAGE.multibindings"
const val CIRCUIT_RUNTIME_UI_PACKAGE = "$CIRCUIT_RUNTIME_BASE_PACKAGE.ui"
const val CIRCUIT_RUNTIME_SCREEN_PACKAGE = "$CIRCUIT_RUNTIME_BASE_PACKAGE.screen"
const val CIRCUIT_RUNTIME_PRESENTER_PACKAGE = "$CIRCUIT_RUNTIME_BASE_PACKAGE.presenter"
val MODIFIER = ClassName("androidx.compose.ui", "Modifier")
val CIRCUIT_INJECT_ANNOTATION =
ClassName("com.slack.circuit.codegen.annotations", "CircuitInject")
val CIRCUIT_PRESENTER = ClassName(CIRCUIT_RUNTIME_PRESENTER_PACKAGE, "Presenter")
val CIRCUIT_PRESENTER_FACTORY = CIRCUIT_PRESENTER.nestedClass("Factory")
val CIRCUIT_UI = ClassName(CIRCUIT_RUNTIME_UI_PACKAGE, "Ui")
val CIRCUIT_UI_FACTORY = CIRCUIT_UI.nestedClass("Factory")
val CIRCUIT_UI_STATE = ClassName(CIRCUIT_RUNTIME_BASE_PACKAGE, "CircuitUiState")
val SCREEN = ClassName(CIRCUIT_RUNTIME_SCREEN_PACKAGE, "Screen")
val NAVIGATOR = ClassName(CIRCUIT_RUNTIME_BASE_PACKAGE, "Navigator")
val CIRCUIT_CONTEXT = ClassName(CIRCUIT_RUNTIME_BASE_PACKAGE, "CircuitContext")
val DAGGER_MODULE = ClassName(DAGGER_PACKAGE, "Module")
val DAGGER_BINDS = ClassName(DAGGER_PACKAGE, "Binds")
val DAGGER_INSTALL_IN = ClassName(DAGGER_HILT_PACKAGE, "InstallIn")
val DAGGER_ORIGINATING_ELEMENT = ClassName(DAGGER_HILT_CODEGEN_PACKAGE, "OriginatingElement")
val DAGGER_INTO_SET = ClassName(DAGGER_MULTIBINDINGS_PACKAGE, "IntoSet")
const val MODULE = "Module"
const val FACTORY = "Factory"
const val CIRCUIT_CODEGEN_MODE = "circuit.codegen.mode"

object KotlinInject {
private const val ANNOTATIONS_PACKAGE = "me.tatarka.inject.annotations"
val INJECT = ClassName(ANNOTATIONS_PACKAGE, "Inject")
val ASSISTED = ClassName(ANNOTATIONS_PACKAGE, "Assisted")

object Anvil {
private const val RUNTIME_PACKAGE = "software.amazon.lastmile.kotlin.inject.anvil"
internal val CONTRIBUTES_BINDING = ClassName(RUNTIME_PACKAGE, "ContributesBinding")
}
}
}
Loading

0 comments on commit ce398b3

Please sign in to comment.