Tristan
05/28/2025, 4:09 AMproduct:
type: lib
platforms: [android, iosArm64, iosSimulatorArm64, iosX64]
dependencies:
- io.insert-koin:koin-core:4.0.2
- io.insert-koin:koin-annotations:2.0.0
- me.tatarka.inject:kotlin-inject-runtime:0.8.0
settings:
kotlin:
ksp:
processors:
- io.insert-koin:koin-ksp-compiler:2.0.0
- me.tatarka.inject:kotlin-inject-compiler-ksp:0.8.0
Tristan
05/28/2025, 4:11 AMpackage domain.di
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Inject
import me.tatarka.inject.annotations.Provides
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
@Module
@ComponentScan
internal class SignalCollectorModule
// ---
@Component
abstract class AppComponent {
abstract val repo: Repository
protected val RealHttp.bind: Http
@Provides get() = this
}
interface Http
@Inject
class RealHttp : Http
@Inject
class Api(private val http: Http)
@Inject
class Repository(private val api: Api)
fun test() {
SignalCollectorModule().module
AppComponent::class.create()
}
.module
is unresolved (and nothing generated about it. And .create()
is not accessible in common. But is on android
package domain.di
import kotlin.reflect.KClass
public fun KClass<AppComponent>.create(): AppComponent = InjectAppComponent()
public class InjectAppComponent : AppComponent() {
override val repo: Repository
get() = Repository(
api = Api(
http = RealHttp().bind
)
)
}
joffrey
06/12/2025, 11:09 AMiosArm64
, the compiler expects `common`+`native`+`apple`+`ios`+`iosArm64` sources. Etc. Each of these invocations produce target-specific code (JVM bytecode, native framework, etc.).
In the KSP world it is very much mirrored. For each of those target compilations, KSP is called to process all sources. Any code generated by KSP at this point can only be placed in the most specific target fragment (e.g. jvm
or iosArm64
in the examples above), and thus cannot be available to common sources.
In Gradle, people usually hack their way around this by adding an artificial dependency on common metadata compilation. This compilation is a special one that processes common code from the common
fragment. So when we run a "common" KSP processing, it only gets common sources. If there is any codegen, it's ok to place the generated code in common
as well, and thus it would make your generated declarations available. It would also create duplicate classes, because now we generate the same classes for common
and for each target-specific compilation, which is why this is a hack.
The best way will come with the new Kotlin compilation model where each fragment is compiled separately and we use the resulting klib of each fragment as a dependency of more specific fragments. This way KSP could run on each of those fragments, and the generated code, if any, can be safely placed in the corresponding fragment, and the natural visibility rules will work as expected.
We're not there yet, though. As far as I understood, the "proper" way at the moment suggested by @laszio is to create expect
declarations in common
and make KSP generate the `actual`s in target compilations. This is really not ideal, though, and I can't really describe right now how it would look. Maybe Ting-Yuan can help here 🙂
I would love to implement a workaround for this in Amper, but I don't see a proper way to do so right now.