Mastodon

Introducing: Anvil-KSP

After more than a year of work and contributions from a number of developers in the community, I'm pleased to share a functionally-complete KSP implementation of Anvil code gen.

A cartoon Anvil with the letters "KSP" imprinted onto it.
GitHub - ZacSweers/anvil: A Kotlin compiler plugin to make dependency injection with Dagger 2 easier.
A Kotlin compiler plugin to make dependency injection with Dagger 2 easier. - ZacSweers/anvil

Usage is easy and you can find instructions here: https://github.com/ZacSweers/anvil/blob/main/FORK.md

Why a fork?

Firstly, it's important to acknowledge the elephant in the room: it's a fork!

Much of the phase 1 work for KSP was implemented in the upstream square/anvil, but contribution merging wasn't implemented yet. As the folks at Square are focused on the existing Anvil implementation for the time being and in the interest of finishing this work to make it available to folks that want it, I continued in this fork. This is not "the" Anvil KSP or Anvil K2 implementation, just "an" implementation.

Motivations

At the point of divergence (~2.0.0-beta09), square/anvil ("upstream") had some notable caveats:

KSP Benefits

A KSP implementation functionally addresses all of the above.

KSP Costs

As with any solution, KSP isn't perfect.

I've tried to documented all the known rough edges here.

Long Term

I plan to maintain this for the foreseeable future, until Anvil either supports K2 or upstreams this implementation.

Stats

TL;DR At the time of writing, the optimal scenario is to use KSP contribution merging merging + KAPT for dagger-compiler in large project. dagger-ksp performance may be fine for your needs in a smaller project. You should measure!

I've tested with three primary modes

  1. KAPT merging - KSP contribution gen, KAPT for everything else (IR contribution merging, dagger-compiler)
  2. Hybrid - KSP contribution gen and merging, KAPT for dagger-compiler
  3. KSP only - KSP for contribution gen, merging, and dagger-compiler

In the Slack app on Kotlin 1.9.25 + Dagger 2.52, I measured around a ~12% improvement switching from mode 1 (our starting baseline) to mode 2. Mode 3 was a non-starter as it appears that dagger-ksp struggles in larger projects and runs significantly slower (possibly some pathological case with a large number of modules).

Slack Profiling Notes

Both configurations 2 and 3 enable component merging in their build like so.

anvil {
  useKsp(
    contributesAndFactoryGeneration = true,
    componentMerging = true,
  )
}

Configurations examples for the rest are below

Hybrid Mode

// Connect KSP outputs to KAPT inputs
afterEvaluate {
  // Example config for a "release" build variant in an android project
  val buildType = "Release
  val kspTaskName = "ksp${buildType}Kotlin"
  val useKSP2 = providers.gradleProperty("ksp.useKSP2").getOrElse("false").toBoolean()
  val generatedKspKotlinFiles =
    if (useKSP2) {
      val kspReleaseTask = tasks.named<KspAATask>(kspTaskName)
      kspReleaseTask.flatMap { it.kspConfig.kotlinOutputDir }
    } else {
      val kspReleaseTask = tasks.named<KspTaskJvm>(kspTaskName)
      kspReleaseTask.flatMap { it.destination }
    }
  tasks.named<KotlinCompile>("kaptGenerateStubs${buildType}Kotlin${target}").configure {
    source(generatedKspKotlinFiles)
  }
}

dependencies {
  kapt(libs.dagger.compiler)
}

KSP-only Mode

dependencies {
  ksp(libs.dagger.compiler)
}