Skip to content

Fix cross-module @ComponentScan hints generation without @Configuration#25

Open
wjz2001 wants to merge 2 commits intoInsertKoinIO:mainfrom
wjz2001:fix/componentscan-hints-without-configuration
Open

Fix cross-module @ComponentScan hints generation without @Configuration#25
wjz2001 wants to merge 2 commits intoInsertKoinIO:mainfrom
wjz2001:fix/componentscan-hints-without-configuration

Conversation

@wjz2001
Copy link
Copy Markdown

@wjz2001 wjz2001 commented Apr 18, 2026

Summary

This PR fixes a cross-module failure mode in the Koin Compiler Plugin where compileSafety (call-site validation) reports Missing definition for dependencies that are actually defined in a library module using @Module + @ComponentScan, unless the module is also annotated with @Configuration. The fix aligns the plugin’s behavior with the documented usage of @ComponentScan on plain @Module classes and prevents a “hint black hole” that breaks downstream visibility.


Problem Statement

In a multi-module Gradle build, a library module may declare a Koin module using annotations:

@Module
@ComponentScan("com.example.lib")
class LibModule

and define components in the scanned packages:

@Single class Repo

A consuming app module may inject those types:

val repo: Repo by inject()

When compileSafety = true is enabled, the consumer compilation can fail with errors similar to:

  • [Koin] Missing definition: com.example.lib.Repo
  • resolved by: inject<Repo>()
  • No matching definition found in any declared module

even though the definition exists and is properly annotated.


Root Cause Analysis (Why it happens)

This failure is caused by an interaction between two hint-generation paths and their exclusion conditions:

  1. “Orphan definition” hints (e.g., definition_*, definitionfunc_*) are generated only for definitions that are not “covered” by a local @ComponentScan.

    • If a definition is discovered through a module’s @ComponentScan, it may be excluded from orphan hint generation (by design, to avoid redundancy).
  2. Module-scoped @ComponentScan hints (e.g., componentscan_*, componentscanfunc_*) are intended to export what a module’s scan discovered so that downstream compilations can resolve scanned definitions across Gradle modules.

However, the existing implementation only generates module-scoped scan hints for modules that satisfy:

  • @Module + @ComponentScan + @Configuration

If a module has @Module + @ComponentScan but does not have @Configuration, then:

  • The scanned definitions can be skipped by the orphan hint path (because they are scan-covered), and
  • The module-scoped scan hints are not generated (because the module is filtered out due to missing @Configuration).

This produces a “black hole” where the provider JAR contains no usable hints under org/koin/plugin/hints for those scanned definitions, so downstream compileSafety validation cannot “see” them and fails.

This was verified empirically by inspecting the provider artifact:

  • Before adding @Configuration, jar tf <provider>.jar showed no org/koin/plugin/hints entries.
  • After adding @Configuration, the JAR contained org/koin/plugin/hints/...Koin_hints_... classes and the Missing definition errors disappeared.

Why This PR Is Correct / Alignment With Documentation

The documented behavior of Koin annotations shows @ComponentScan being used directly on @Module classes; @Configuration is described as a grouping/selection concept, not as a prerequisite for component scanning or cross-module discovery.

Therefore, requiring @Configuration for module-scoped scan hint generation is inconsistent with the expected usage pattern and causes valid projects to fail compilation under compileSafety.


What This PR Changes

Behavioral change: module-scoped scan hints are generated for all @Module classes that have @ComponentScan, not only for modules that also have @Configuration.

Concretely, in KoinAnnotationProcessor.generateModuleScanHints(...), the module selection changes from:

  • “only @Configuration + @ComponentScan modules”

to:

  • “all @ComponentScan modules”

@Configuration is still recognized and can be logged/observed, but it no longer gates scan hint generation.


Reproduction Steps (Minimal Example)

  1. Create a library module :lib with:

    @Module
    @ComponentScan("com.example.lib")
    class LibModule
  2. In the scanned package:

    @Single class Repo
  3. In an app module :app that depends on :lib, enable:

    koinCompiler {
      compileSafety = true
    }

    and inject:

    val repo: Repo by inject()
  4. Build the app:

    • Before this PR: compilation can fail with Missing definition for Repo.
    • After this PR: compilation succeeds; provider JAR contains scan hint classes in org/koin/plugin/hints.

Verification / Evidence

  • Dependency substitution confirmed that the build was using the local plugin project on the Kotlin compiler plugin classpath (i.e., io.insert-koin:koin-compiler-plugin -> project ...).
  • Provider JAR inspection (jar tf) was used to confirm presence/absence of org/koin/plugin/hints entries.
  • A clean rebuild (clean, --rerun-tasks, --no-build-cache) was used to avoid incremental compilation artifacts during validation.

Impact and Trade-offs

Pros

  • Fixes cross-module compileSafety visibility for valid @Module + @ComponentScan modules without requiring @Configuration.
  • Aligns implementation with documented usage of @ComponentScan.
  • Prevents scan-covered definitions from becoming invisible to downstream validation due to missing hints.

Potential trade-offs

  • More modules may now generate scan hints, which can slightly increase the number of generated hint classes and metadata size. The implementation already batches hints per module, which mitigates overhead.
  • Some projects may observe stricter/more accurate compileSafety validation as previously hidden definitions become visible.

Notes

  • This PR focuses on correctness and documented behavior: @Configuration remains useful for grouping/conditional module loading but should not be required to export scan results across modules.
  • If desired, follow-up work could add targeted tests to cover multiple scenarios (e.g., multiple scan modules, mixed @Configuration and non-@Configuration scan modules) to prevent regressions.

wjz2001 added 2 commits April 18, 2026 13:06
…to match documented behavior and fix cross-module compileSafety resolution.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant