Skip to content

Generating Metro Code

Java annotation processing and KSP both support multiple rounds of processing, allowing custom processors to generate new code with injection annotations that can be processed in later rounds. Anvil supported custom CodeGenerator implementations in K1 and anvil-ksp and kotlin-inject-anvil support specifying custom contributing annotations to allow them to intelligently defer processing to later rounds.

Since Metro is implemented as a compiler plugin, asking users to write compiler plugins to interact with it would be a bit unwieldy. However, KSP processors that generate metro-annotated code work out of the box with it since they run before Metro’s plugin does.

If you have an existing KSP processor for a different framework, you could leverage it + custom annotations interop support described above to make them work out of the box with Metro.

Origin Annotations

When code generators create Metro-annotated types, they can use the @Origin annotation to link the generated type back to its source. This is particularly useful for contribution merging - when a source type is excluded or replaced in a @DependencyGraph, any generated types with @Origin pointing to it will also be excluded or replaced automatically.

// Source type
@GenerateSomething
class UserRepository

// Generated by your KSP processor
@ContributesBinding(AppScope::class)
@Origin(UserRepository::class)  // Links back to the source
class UserRepository_Impl : UserRepository

Now if UserRepository is excluded in a graph:

@DependencyGraph(
  scope = AppScope::class,
  excludes = [UserRepository::class]  // This also excludes UserRepository_Impl
)
interface AppGraph

Custom Origin Annotations

You can configure Metro to recognize custom origin annotations through the Gradle plugin:

metro {
  interop {
    origin.add("com.example.GeneratedFrom")
  }
}

The annotation must have a KClass parameter at index 0.

If using Anvil interop, kotlin-inject-anvil’s @Origin annotation is automatically recognized.

metro {
  interop {
    includeAnvil(includeKotlinInjectAnvil = true)
  }
}