evant / kotlin-inject

Dependency injection lib for kotlin
Apache License 2.0
1.22k stars 53 forks source link

KmpComponentCreate not generated for IosSimulatorArm64 #422

Open AlexBurdu opened 4 weeks ago

AlexBurdu commented 4 weeks ago

I have the @KmpComponentCreate annotation set up on an extension in the shared code and it all works fine for Android and jvm, but I can't seem to get it to generate the KmpComponentCreate for the iOS target. The InjectApplicationComponent is generated just fine:

image

Stripped out package id from the screenshot and logs

@KmpComponentCreate
expect fun createApplicationComponent(): ApplicationComponent
> Task :my:app:wiring:kspReleaseKotlinAndroid
w: [ksp] Unable to process:me.tatarka.inject.compiler.ksp.InjectProcessor:   ApplicationComponent

> Task :my:app:wiring:kspKotlinIosSimulatorArm64 FAILED
i: [ksp] loaded provider(s): [me.tatarka.inject.compiler.ksp.InjectProcessorProvider]
e: [ksp] @Provides with scope: @SingletonScope cannot be provided in an unscoped component
e: [ksp] @Provides with scope: @SingletonScope cannot be provided in an unscoped component
e: Error occurred in KSP, check log for detail

FAILURE: Build failed with an exception.

The directory structure that I'm using is a bit customized, as in I'm not using the default src directories. Unsure if that matters for this error.

import org.gradle.configurationcache.extensions.capitalized
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType

plugins {
  kotlin("multiplatform")
  kotlin("native.cocoapods")
  id("com.google.devtools.ksp") version "2.0.0-1.0.22"
  id("com.android.library")
}

kotlin {
  // Add Android target platform
  androidTarget()

  // Add iOS target platforms
  listOf(
    iosX64(),
    iosArm64(),
    iosSimulatorArm64()
  ).forEach { iosTarget ->
    iosTarget.binaries.framework {
      baseName = "wiring"
      isStatic = true
    }
  }

  // Add web target platform
  js(IR).browser()

  // Add JVM target platform
  jvmToolchain(17)
  jvm("jvm")

  cocoapods {
    version = "1"
    summary = "Some description for the Shared Module"
    homepage = "Link to the Shared Module homepage"
    ios.deploymentTarget = "14.1"
  }

  sourceSets {
    val commonMain by getting {
      kotlin.apply {
        srcDir("src/commonMain")
        resources.srcDir("src/commonMain/composeResources")
      }

      dependencies {
        // other missing deps
        implementation("me.tatarka.inject:kotlin-inject-runtime-kmp:0.7.1")
      }
    }

    // Add Android source sets
    val androidMain by getting {
      kotlin.apply {
        srcDir("androidMain")
      }
    }

    // Add Android instrumentation tests source sets
    val androidInstrumentedTest by getting {
      kotlin.apply {
        srcDir("androidTest")
      }
    }

    // Add iOS source sets
    val iosX64Main by getting
    val iosArm64Main by getting
    val iosSimulatorArm64Main by getting
    val iosMain by creating {
      dependsOn(commonMain)
      iosX64Main.dependsOn(this)
      iosArm64Main.dependsOn(this)
      iosSimulatorArm64Main.dependsOn(this)
      kotlin.apply {
        srcDir("iosMain")
      }
    }

    // Add web source sets
    val jsMain by getting {
      kotlin.apply {
        srcDir("jsMain")
      }
    }

    // Add JVM source sets
    val jvmMain by getting {
      kotlin.apply {
        srcDir("jvmMain")
      }
    }
  }

}

// Add Android extension block
android {
  sourceSets["main"].apply {
    kotlin.srcDirs("androidMain")
    manifest.srcFile("androidMain/AndroidManifest.xml")
    res.srcDirs("androidMain/res")
    resources.srcDirs("commonMain/resources")
  }

  namespace = "my.app"
  defaultConfig {
    minSdk = 28
  }
  compileSdk = 34

  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
  }

  kotlin {
  }
}

ksp {
  arg("me.tatarka.inject.generateCompanionExtensions", "true")
}
dependencies {
  // Configure code generation into the common source set
   kspCommonMainMetadata("me.tatarka.inject:kotlin-inject-runtime-kmp:0.7.1")

  add("kspAndroid", "me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.1")
  add("kspJs", "me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.1")
  add("kspJvm", "me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.1")
  add("kspIosX64", "me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.1")
  add("kspIosArm64", "me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.1")
  add("kspIosSimulatorArm64", "me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.1")
}

tasks.withType<Copy> { duplicatesStrategy = DuplicatesStrategy.WARN }
AlexBurdu commented 4 weeks ago

As far as I can tell by exploring the generated sources, the problem is with what is being generated in the Inject component class.

On the left we can see the one generated for iosSimulatorArm64 and on the right the jvm target one.

image

AlexBurdu commented 4 weeks ago

Ok, by removing the scope from my coroutine scope providers it builds:

  @Provides
//  @SingletonScope
  fun providesBackgroundCoroutineScope(): BackgroundCoroutineScope {

I don't understand why it doesn't build for the iOS target with @Singleton scope for the provider and is fine for the other targets. Why does is say that the iOS target is not scoped?

evant commented 4 weeks ago

Seems like it's not seeing the scope annotation on the component for some reason? How are you declaring it, directly on ApplicationComponent?

AlexBurdu commented 4 weeks ago

Ah, that was it! I didn't have it on the ApplicationComponent. Thank you! How come the other targets build fine without it?

evant commented 4 weeks ago

That's a good question... they should've reported the same error.